    001// Copyright 2011 The Apache Software Foundation
    002//
    003// Licensed under the Apache License, Version 2.0 (the "License");
    004// you may not use this file except in compliance with the License.
    005// You may obtain a copy of the License at
    006//
    007// http://www.apache.org/licenses/LICENSE-2.0
    008//
    009// Unless required by applicable law or agreed to in writing, software
    010// distributed under the License is distributed on an "AS IS" BASIS,
    011// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    012// See the License for the specific language governing permissions and
    013// limitations under the License.
    014
    015package org.apache.tapestry5.internal.plastic;
    016
    017import java.util.function.Function;
    018import java.util.function.Predicate;
    019
    020import org.apache.tapestry5.plastic.PlasticUtils;
    021
    022public class PlasticClassLoader extends ClassLoader
    023{
    024    static
    025    {
    026        // TAP5-2546
    027        ClassLoader.registerAsParallelCapable();
    028    }
    029
    030    private final ClassLoaderDelegate delegate;
    031
    032    private Predicate<String> filter;
    033
    034    private Function<String, Class<?>> alternativeClassloading;
    035
    036    private String tag;
    037
    038    public PlasticClassLoader(ClassLoader parent, ClassLoaderDelegate delegate)
    039    {
    040        super(parent);
    041        this.delegate = delegate;
    042    }
    043
    044    @Override
    045    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
    046    {
    047        synchronized(getClassLoadingLock(name))
    048        {
    049            Class<?> loadedClass = findLoadedClass(name);
    050
    051            if (loadedClass != null)
    052                return loadedClass;
    053
    054            if (shouldInterceptClassLoading(name))
    055            {
    056                Class<?> c = null;
    057                if ((filter != null && filter.test(name)) || (filter == null && delegate.shouldInterceptClassLoading(name)))
    058                {
    059                    c = delegate.loadAndTransformClass(name);
    060                }
    061                else if (alternativeClassloading != null)
    062                {
    063                    c = alternativeClassloading.apply(name);
    064                }
    065
    066                if (c == null)
    067                {
    068                    return super.loadClass(name, resolve);
    069                }
    070
    071                if (resolve)
    072                    resolveClass(c);
    073
    074                return c;
    075            } else
    076            {
    077                return super.loadClass(name, resolve);
    078            }
    079        }
    080    }
    081
    082    private boolean shouldInterceptClassLoading(String name) {
    083        return delegate.shouldInterceptClassLoading(
    084                PlasticUtils.getEnclosingClassName(name));
    085    }
    086
    087    public synchronized Class<?> defineClassWithBytecode(String className, byte[] bytecode)
    088    {
    089        synchronized(getClassLoadingLock(className))
    090        {
    091            return defineClass(className, bytecode, 0, bytecode.length);
    092        }
    093    }
    094
    095    /**
    096     * When alternatingClassloader is set, this classloader delegates to it the
    097     * call to {@linkplain ClassLoader#loadClass(String)}. If it returns a non-null object,
    098     * it's returned by <code>loadClass(String)</code>. Otherwise, it returns
    099     * <code>super.loadClass(name)</code>.
    100     * @since 5.8.3
    101     */
    102    public void setAlternativeClassloading(Function<String, Class<?>> alternateClassloading)
    103    {
    104        this.alternativeClassloading = alternateClassloading;
    105    }
    106
    107    /**
    108     * @since 5.8.3
    109     */
    110    public void setTag(String tag)
    111    {
    112        this.tag = tag;
    113    }
    114
    115    /**
    116     * When a filter is set, only classes accepted by it will be loaded by this classloader.
    117     * Instead, it will be delegated to alternate classloading first and the parent classloader
    118     * in case the alternate doesn't handle it.
    119     * @since 5.8.3
    120     */
    121    public void setFilter(Predicate<String> filter)
    122    {
    123        this.filter = filter;
    124    }
    125
    126    @Override
    127    public String toString()
    128    {
    129        final String superToString = super.toString();
    130        final String id = superToString.substring(superToString.indexOf('@')).trim();
    131        return String.format("PlasticClassLoader[%s, tag=%s, parent=%s]", id, tag, getParent());
    132    }
    133
    134}