blob: 64d9797e1a767ae5d748f13fb3b64b9f9ffd0ee2 [file] [log] [blame]
/*
* 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();
}
}