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;
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);
}
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()) {
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);
}
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) {
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) {
return instance;
}
settings = clone(settings);
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;
}
}
}
}
}
}
public interface _BeansWrapperSubclassFactory<BW extends BeansWrapper, BWC extends BeansWrapperConfiguration> {
BW create(BWC sa);
}
public static ClassIntrospectorBuilder getClassIntrospectorBuilder(BeansWrapperConfiguration bwc) {
return bwc.getClassIntrospectorBuilder();
}
}