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}