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}