/*
 * 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.
/** * 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 BeansWrapper.
/** * 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<ReflectionCallableMemberDescriptor>(); final List<ReflectionCallableMemberDescriptor> varArgsMemberDescs = new ArrayList<ReflectionCallableMemberDescriptor>(); 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 BeansWrapper and DefaultObjectWrapper.
Params:
  • beansWrapperSubclassFactory – Creates a new read-only object wrapper of the desired BeansWrapper subclass.
/** * 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<BWC, WeakReference<BW>>(); 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<BW>(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!
/** * 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 BeansWrapper; used for BeansWrapperBuilder and such.
/** 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(); } }