    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}