/*
* 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<
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 {@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!
*/
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();
}
}