| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| package freemarker.ext.beans; |
| |
| import java.lang.ref.Reference; |
| import java.lang.ref.ReferenceQueue; |
| import java.lang.ref.WeakReference; |
| import java.lang.reflect.Array; |
| import java.lang.reflect.Constructor; |
| import java.lang.reflect.InvocationTargetException; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| |
| import freemarker.core.BugException; |
| import freemarker.template.DefaultObjectWrapper; |
| import freemarker.template.TemplateModelException; |
| import freemarker.template.utility.CollectionUtils; |
| |
| /** |
| * For internal use only; don't depend on this, there's no backward compatibility guarantee at all! |
| * This class is to work around the lack of module system in Java, i.e., so that other FreeMarker packages can |
| * access things inside this package that users shouldn't. |
| */ |
| public class _BeansAPI { |
| |
| private _BeansAPI() { } |
| |
| public static String getAsClassicCompatibleString(BeanModel bm) { |
| return bm.getAsClassicCompatibleString(); |
| } |
| |
| public static Object newInstance(Class<?> pClass, Object[] args, BeansWrapper bw) |
| throws NoSuchMethodException, IllegalArgumentException, InstantiationException, |
| IllegalAccessException, InvocationTargetException, TemplateModelException { |
| return newInstance(getConstructorDescriptor(pClass, args), args, bw); |
| } |
| |
| /** |
| * Gets the constructor that matches the types of the arguments the best. So this is more |
| * than what the Java reflection API provides in that it can handle overloaded constructors. This re-uses the |
| * overloaded method selection logic of {@link BeansWrapper}. |
| */ |
| private static CallableMemberDescriptor getConstructorDescriptor(Class<?> pClass, Object[] args) |
| throws NoSuchMethodException { |
| if (args == null) args = CollectionUtils.EMPTY_OBJECT_ARRAY; |
| |
| final ArgumentTypes argTypes = new ArgumentTypes(args, true); |
| final List<ReflectionCallableMemberDescriptor> fixedArgMemberDescs |
| = new ArrayList<>(); |
| final List<ReflectionCallableMemberDescriptor> varArgsMemberDescs |
| = new ArrayList<>(); |
| final Constructor<?>[] constrs = pClass.getConstructors(); |
| for (int i = 0; i < constrs.length; i++) { |
| Constructor<?> constr = constrs[i]; |
| ReflectionCallableMemberDescriptor memberDesc = new ReflectionCallableMemberDescriptor(constr, constr.getParameterTypes()); |
| if (!_MethodUtil.isVarargs(constr)) { |
| fixedArgMemberDescs.add(memberDesc); |
| } else { |
| varArgsMemberDescs.add(memberDesc); |
| } |
| } |
| |
| MaybeEmptyCallableMemberDescriptor contrDesc = argTypes.getMostSpecific(fixedArgMemberDescs, false); |
| if (contrDesc == EmptyCallableMemberDescriptor.NO_SUCH_METHOD) { |
| contrDesc = argTypes.getMostSpecific(varArgsMemberDescs, true); |
| } |
| |
| if (contrDesc instanceof EmptyCallableMemberDescriptor) { |
| if (contrDesc == EmptyCallableMemberDescriptor.NO_SUCH_METHOD) { |
| throw new NoSuchMethodException( |
| "There's no public " + pClass.getName() |
| + " constructor with compatible parameter list."); |
| } else if (contrDesc == EmptyCallableMemberDescriptor.AMBIGUOUS_METHOD) { |
| throw new NoSuchMethodException( |
| "There are multiple public " + pClass.getName() |
| + " constructors that match the compatible parameter list with the same preferability."); |
| } else { |
| throw new NoSuchMethodException(); |
| } |
| } else { |
| return (CallableMemberDescriptor) contrDesc; |
| } |
| } |
| |
| private static Object newInstance(CallableMemberDescriptor constrDesc, Object[] args, BeansWrapper bw) |
| throws InstantiationException, IllegalAccessException, InvocationTargetException, IllegalArgumentException, |
| TemplateModelException { |
| if (args == null) args = CollectionUtils.EMPTY_OBJECT_ARRAY; |
| |
| final Object[] packedArgs; |
| if (constrDesc.isVarargs()) { |
| // We have to put all the varargs arguments into a single array argument. |
| |
| final Class<?>[] paramTypes = constrDesc.getParamTypes(); |
| final int fixedArgCnt = paramTypes.length - 1; |
| |
| packedArgs = new Object[fixedArgCnt + 1]; |
| for (int i = 0; i < fixedArgCnt; i++) { |
| packedArgs[i] = args[i]; |
| } |
| |
| final Class<?> compType = paramTypes[fixedArgCnt].getComponentType(); |
| final int varArgCnt = args.length - fixedArgCnt; |
| final Object varArgsArray = Array.newInstance(compType, varArgCnt); |
| for (int i = 0; i < varArgCnt; i++) { |
| Array.set(varArgsArray, i, args[fixedArgCnt + i]); |
| } |
| packedArgs[fixedArgCnt] = varArgsArray; |
| } else { |
| packedArgs = args; |
| } |
| |
| return constrDesc.invokeConstructor(bw, packedArgs); |
| } |
| |
| /** |
| * Contains the common parts of the singleton management for {@link BeansWrapper} and {@link DefaultObjectWrapper}. |
| * |
| * @param beansWrapperSubclassFactory Creates a <em>new</em> read-only object wrapper of the desired |
| * {@link BeansWrapper} subclass. |
| */ |
| public static <BW extends BeansWrapper, BWC extends BeansWrapperConfiguration> BW getBeansWrapperSubclassSingleton( |
| BWC settings, |
| Map<ClassLoader, Map<BWC, WeakReference<BW>>> instanceCache, |
| ReferenceQueue<BW> instanceCacheRefQue, |
| _BeansWrapperSubclassFactory<BW, BWC> beansWrapperSubclassFactory) { |
| // BeansWrapper can't be cached across different Thread Context Class Loaders (TCCL), because the result of |
| // a class name (String) to Class mappings depends on it, and the staticModels and enumModels need that. |
| // (The ClassIntrospector doesn't have to consider the TCCL, as it only works with Class-es, not class |
| // names.) |
| ClassLoader tccl = Thread.currentThread().getContextClassLoader(); |
| |
| Reference<BW> instanceRef; |
| Map<BWC, WeakReference<BW>> tcclScopedCache; |
| synchronized (instanceCache) { |
| tcclScopedCache = instanceCache.get(tccl); |
| if (tcclScopedCache == null) { |
| tcclScopedCache = new HashMap<>(); |
| instanceCache.put(tccl, tcclScopedCache); |
| instanceRef = null; |
| } else { |
| instanceRef = tcclScopedCache.get(settings); |
| } |
| } |
| |
| BW instance = instanceRef != null ? instanceRef.get() : null; |
| if (instance != null) { // cache hit |
| return instance; |
| } |
| // cache miss |
| |
| settings = clone(settings); // prevent any aliasing issues |
| instance = beansWrapperSubclassFactory.create(settings); |
| if (!instance.isWriteProtected()) { |
| throw new BugException(); |
| } |
| |
| synchronized (instanceCache) { |
| instanceRef = tcclScopedCache.get(settings); |
| BW concurrentInstance = instanceRef != null ? instanceRef.get() : null; |
| if (concurrentInstance == null) { |
| tcclScopedCache.put(settings, new WeakReference<>(instance, instanceCacheRefQue)); |
| } else { |
| instance = concurrentInstance; |
| } |
| } |
| |
| removeClearedReferencesFromCache(instanceCache, instanceCacheRefQue); |
| |
| return instance; |
| } |
| |
| @SuppressWarnings("unchecked") |
| private static <BWC extends BeansWrapperConfiguration> BWC clone(BWC settings) { |
| return (BWC) settings.clone(true); |
| } |
| |
| private static <BW extends BeansWrapper, BWC extends BeansWrapperConfiguration> |
| void removeClearedReferencesFromCache( |
| Map<ClassLoader, Map<BWC, WeakReference<BW>>> instanceCache, |
| ReferenceQueue<BW> instanceCacheRefQue) { |
| Reference<? extends BW> clearedRef; |
| while ((clearedRef = instanceCacheRefQue.poll()) != null) { |
| synchronized (instanceCache) { |
| findClearedRef: for (Map<BWC, WeakReference<BW>> tcclScopedCache : instanceCache.values()) { |
| for (Iterator<WeakReference<BW>> it2 = tcclScopedCache.values().iterator(); it2.hasNext(); ) { |
| if (it2.next() == clearedRef) { |
| it2.remove(); |
| break findClearedRef; |
| } |
| } |
| } |
| } // sync |
| } // while poll |
| } |
| |
| /** |
| * For internal use only; don't depend on this, there's no backward compatibility guarantee at all! |
| */ |
| public interface _BeansWrapperSubclassFactory<BW extends BeansWrapper, BWC extends BeansWrapperConfiguration> { |
| |
| /** Creates a new read-only {@link BeansWrapper}; used for {@link BeansWrapperBuilder} and such. */ |
| BW create(BWC sa); |
| } |
| |
| public static ClassIntrospectorBuilder getClassIntrospectorBuilder(BeansWrapperConfiguration bwc) { |
| return bwc.getClassIntrospectorBuilder(); |
| } |
| |
| } |