| /* |
| * 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 fixedArgMemberDescs = new ArrayList(); |
| final List 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 BeansWrapper getBeansWrapperSubclassSingleton( |
| BeansWrapperConfiguration settings, |
| Map instanceCache, |
| ReferenceQueue instanceCacheRefQue, |
| _BeansWrapperSubclassFactory 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 instanceRef; |
| Map/*<PropertyAssignments, WeakReference<BeansWrapper>>*/ tcclScopedCache; |
| synchronized (instanceCache) { |
| tcclScopedCache = (Map) instanceCache.get(tccl); |
| if (tcclScopedCache == null) { |
| tcclScopedCache = new HashMap(); |
| instanceCache.put(tccl, tcclScopedCache); |
| instanceRef = null; |
| } else { |
| instanceRef = (Reference) tcclScopedCache.get(settings); |
| } |
| } |
| |
| BeansWrapper instance = instanceRef != null ? (BeansWrapper) instanceRef.get() : null; |
| if (instance != null) { // cache hit |
| return instance; |
| } |
| // cache miss |
| |
| settings = (BeansWrapperConfiguration) settings.clone(true); // prevent any aliasing issues |
| instance = beansWrapperSubclassFactory.create(settings); |
| if (!instance.isWriteProtected()) { |
| throw new BugException(); |
| } |
| |
| synchronized (instanceCache) { |
| instanceRef = (Reference) tcclScopedCache.get(settings); |
| BeansWrapper concurrentInstance = instanceRef != null ? (BeansWrapper) instanceRef.get() : null; |
| if (concurrentInstance == null) { |
| tcclScopedCache.put(settings, new WeakReference(instance, instanceCacheRefQue)); |
| } else { |
| instance = concurrentInstance; |
| } |
| } |
| |
| removeClearedReferencesFromCache(instanceCache, instanceCacheRefQue); |
| |
| return instance; |
| } |
| |
| private static void removeClearedReferencesFromCache(Map instanceCache, ReferenceQueue instanceCacheRefQue) { |
| Reference clearedRef; |
| while ((clearedRef = instanceCacheRefQue.poll()) != null) { |
| synchronized (instanceCache) { |
| findClearedRef: for (Iterator it1 = instanceCache.values().iterator(); it1.hasNext(); ) { |
| Map tcclScopedCache = (Map) it1.next(); |
| for (Iterator 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 { |
| |
| /** Creates a new read-only {@link BeansWrapper}; used for {@link BeansWrapperBuilder} and such. */ |
| BeansWrapper create(BeansWrapperConfiguration sa); |
| } |
| |
| } |