001// Copyright 2011, 2023, 2024 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.ArrayList;
018import java.util.Collections;
019import java.util.HashMap;
020import java.util.HashSet;
021import java.util.List;
022import java.util.Map;
023import java.util.Set;
024import java.util.function.Consumer;
025import java.util.function.Function;
026import java.util.function.Predicate;
027
028import org.apache.tapestry5.plastic.PlasticUtils;
029
030public class PlasticClassLoader extends ClassLoader
031{
032    static
033    {
034        // TAP5-2546
035        ClassLoader.registerAsParallelCapable();
036    }
037
038    private final ClassLoaderDelegate delegate;
039    
040    private Predicate<String> filter;
041    
042    private Function<String, Class<?>> alternativeClassloading;
043    
044    private String tag;
045    
046    private Map<String, Class<?>> cache;
047    
048    private static List<String> log = new ArrayList<>();
049    
050    public PlasticClassLoader(ClassLoader parent, ClassLoaderDelegate delegate) 
051    {
052        super(parent);
053        this.delegate = delegate;
054    }
055
056    @Override
057    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
058    {
059        synchronized(getClassLoadingLock(name))
060        {
061            Class<?> loadedClass = findLoadedClass(name);
062
063            if (loadedClass != null)
064                return loadedClass;
065            
066            if (shouldInterceptClassLoading(name))
067            {
068                
069                Class<?> c = getFromCache(name);
070                
071                if (c == null)
072                {
073                
074                    if ((filter != null && filter.test(name)) || (filter == null && delegate.shouldInterceptClassLoading(name)))
075                    {
076                        c = delegate.loadAndTransformClass(name);
077                    }
078                    else if (alternativeClassloading != null)
079                    {
080                        c = alternativeClassloading.apply(name);
081                    }
082                    
083                    if (cache != null && c != null)
084                    {
085                        cache.put(name, c);
086                    }
087                    
088                }
089                
090                if (c == null)
091                {
092                    return super.loadClass(name, resolve);                    
093                }
094                
095                if (resolve)
096                    resolveClass(c);
097
098                return c;
099            } else
100            {
101                return super.loadClass(name, resolve);
102            }
103        }
104    }
105
106    private boolean shouldInterceptClassLoading(String name) {
107        return delegate.shouldInterceptClassLoading(
108                PlasticUtils.getEnclosingClassName(name));
109    }
110
111    public synchronized Class<?> defineClassWithBytecode(String className, byte[] bytecode)
112    {
113        synchronized(getClassLoadingLock(className))
114        {
115            return defineClass(className, bytecode, 0, bytecode.length);
116        }
117    }
118
119    /**
120     * When alternatingClassloader is set, this classloader delegates to it the 
121     * call to {@linkplain ClassLoader#loadClass(String)}. If it returns a non-null object,
122     * it's returned by <code>loadClass(String)</code>. Otherwise, it returns 
123     * <code>super.loadClass(name)</code>.
124     * @since 5.8.3
125     */
126    public void setAlternativeClassloading(Function<String, Class<?>> alternateClassloading) 
127    {
128        this.alternativeClassloading = alternateClassloading;
129    }
130    
131    /**
132     * @since 5.8.3
133     */
134    public void setTag(String tag) 
135    {
136        this.tag = tag;
137        if (cache == null)
138        {
139            cache = Collections.synchronizedMap(new HashMap<>());
140        }
141    }
142    
143    /**
144     * When a filter is set, only classes accepted by it will be loaded by this classloader.
145     * Instead, it will be delegated to alternate classloading first and the parent classloader
146     * in case the alternate doesn't handle it.
147     * @since 5.8.3
148     */
149    public void setFilter(Predicate<String> filter) 
150    {
151        this.filter = filter;
152    }
153
154    @Override
155    public String toString()
156    {
157        final String id = getClassLoaderId();
158        return String.format("PlasticClassLoader[%s, tag=%s, parent=%s]", id, tag, getParent());
159    }
160
161    public String getClassLoaderId() {
162        final String superToString = super.toString();
163        return superToString.substring(superToString.indexOf('@')).trim();
164    }
165    
166    private Class<?> getFromCache(String name)
167    {
168        Class<?> c = null;
169        if (cache != null && cache.containsKey(name))
170        {
171            c = cache.get(name);
172        }
173        if (c == null && getParent() instanceof PlasticClassLoader)
174        {
175            c = ((PlasticClassLoader) getParent()).getFromCache(name);
176        }
177        return c;
178    }
179    
180}