blob: 04a3347d86a2b3cccf4d25274e2e87041364fc14 [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 org.codehaus.groovy.reflection;
import groovy.lang.Closure;
import groovy.lang.ExpandoMetaClass;
import groovy.lang.GroovyRuntimeException;
import groovy.lang.MetaClass;
import groovy.lang.MetaClassImpl;
import groovy.lang.MetaMethod;
import org.codehaus.groovy.classgen.asm.BytecodeHelper;
import org.codehaus.groovy.runtime.callsite.CallSiteClassLoader;
import org.codehaus.groovy.runtime.metaclass.ClosureMetaClass;
import org.codehaus.groovy.util.FastArray;
import org.codehaus.groovy.util.LazyReference;
import org.codehaus.groovy.util.ReferenceBundle;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import static org.codehaus.groovy.reflection.ReflectionUtils.checkCanSetAccessible;
public class CachedClass {
public static final CachedClass[] EMPTY_ARRAY = new CachedClass[0];
private static ReferenceBundle softBundle = ReferenceBundle.getSoftBundle();
private final LazyReference<CachedField[]> fields = new LazyReference<CachedField[]>(softBundle) {
private static final long serialVersionUID = 5450437842165410025L;
@Override
public CachedField[] initValue() {
PrivilegedAction<CachedField[]> action = () -> Arrays.stream(getTheClass().getDeclaredFields())
.filter(f -> checkCanSetAccessible(f, CachedClass.class))
.map(CachedField::new).toArray(CachedField[]::new);
return AccessController.doPrivileged(action);
}
};
private LazyReference<CachedConstructor[]> constructors = new LazyReference<CachedConstructor[]>(softBundle) {
private static final long serialVersionUID = -5834446523983631635L;
@Override
public CachedConstructor[] initValue() {
PrivilegedAction<CachedConstructor[]> action = () -> Arrays.stream(getTheClass().getDeclaredConstructors())
.filter(c -> !c.isSynthetic()) // GROOVY-9245: exclude inner class ctors
.filter(c -> checkCanSetAccessible(c, CachedClass.class))
.map(c -> new CachedConstructor(CachedClass.this, c))
.toArray(CachedConstructor[]::new);
return AccessController.doPrivileged(action);
}
};
private final LazyReference<CachedMethod[]> methods = new LazyReference<CachedMethod[]>(softBundle) {
private static final long serialVersionUID = 6347586066597418308L;
@Override
public CachedMethod[] initValue() {
PrivilegedAction<CachedMethod[]> action = () -> {
try {
return Arrays.stream(getTheClass().getDeclaredMethods())
// skip synthetic methods inserted by JDK 1.5+ compilers
.filter(m -> !m.isBridge() && m.getName().indexOf('+') < 0)
.filter(m -> checkCanSetAccessible(m, CachedClass.class))
.map(m -> new CachedMethod(CachedClass.this, m))
.toArray(CachedMethod[]::new);
} catch (LinkageError e) {
return CachedMethod.EMPTY_ARRAY;
}
};
CachedMethod[] declaredMethods = AccessController.doPrivileged(action);
List<CachedMethod> methods = new ArrayList<>(declaredMethods.length);
List<CachedMethod> mopMethods = new ArrayList<>(declaredMethods.length);
for (CachedMethod method : declaredMethods) {
String name = method.getName();
if (name.startsWith("this$") || name.startsWith("super$")) {
mopMethods.add(method);
} else {
methods.add(method);
}
}
Collections.sort(methods);
CachedClass superClass = getCachedSuperClass();
if (superClass != null) {
superClass.getMethods(); // populate mopMethods
Collections.addAll(mopMethods, superClass.mopMethods);
}
Collections.sort(mopMethods, CachedMethodComparatorByName.INSTANCE);
CachedClass.this.mopMethods = mopMethods.toArray(CachedMethod.EMPTY_ARRAY);
return methods.toArray(CachedMethod.EMPTY_ARRAY);
}
};
private LazyReference<CachedClass> cachedSuperClass = new LazyReference<CachedClass>(softBundle) {
private static final long serialVersionUID = -4663740963306806058L;
@Override
public CachedClass initValue() {
if (!isArray) {
return ReflectionCache.getCachedClass(getTheClass().getSuperclass());
} else if (cachedClass.getComponentType().isPrimitive() || cachedClass.getComponentType() == Object.class) {
return ReflectionCache.OBJECT_CLASS;
} else {
return ReflectionCache.OBJECT_ARRAY_CLASS;
}
}
};
private final LazyReference<CallSiteClassLoader> callSiteClassLoader = new LazyReference<CallSiteClassLoader>(softBundle) {
private static final long serialVersionUID = 4410385968428074090L;
@Override
public CallSiteClassLoader initValue() {
return AccessController.doPrivileged((PrivilegedAction<CallSiteClassLoader>) () -> new CallSiteClassLoader(CachedClass.this.cachedClass));
}
};
private final LazyReference<Collection<ClassInfo>> hierarchy = new LazyReference<Collection<ClassInfo>>(softBundle) {
private static final long serialVersionUID = 7166687623678851596L;
@Override
public Collection<ClassInfo> initValue() {
Set<ClassInfo> res = new LinkedHashSet<>();
res.add(classInfo);
for (CachedClass iface : getDeclaredInterfaces()) {
res.addAll(iface.getHierarchy());
}
CachedClass superClass = getCachedSuperClass();
if (superClass != null) {
res.addAll(superClass.getHierarchy());
}
if (isInterface) {
res.add(ReflectionCache.OBJECT_CLASS.classInfo);
}
return res;
}
};
private final LazyReference<Set<CachedClass>> declaredInterfaces = new LazyReference<Set<CachedClass>>(softBundle) {
private static final long serialVersionUID = 2139190436931329873L;
@Override
public Set<CachedClass> initValue() {
Set<CachedClass> res = new HashSet<>(0);
Class[] classes = getTheClass().getInterfaces();
for (Class cls : classes) {
res.add(ReflectionCache.getCachedClass(cls));
}
return res;
}
};
private final LazyReference<Set<CachedClass>> interfaces = new LazyReference<Set<CachedClass>>(softBundle) {
private static final long serialVersionUID = 4060471819464086940L;
@Override
public Set<CachedClass> initValue() {
Set<CachedClass> res = new HashSet<>(0);
if (getTheClass().isInterface()) {
res.add(CachedClass.this);
}
Class[] classes = getTheClass().getInterfaces();
for (Class cls : classes) {
CachedClass aClass = ReflectionCache.getCachedClass(cls);
if (!res.contains(aClass))
res.addAll(aClass.getInterfaces());
}
CachedClass superClass = getCachedSuperClass();
if (superClass != null) {
res.addAll(superClass.getInterfaces());
}
return res;
}
};
private final Class<?> cachedClass;
public ClassInfo classInfo;
public final boolean isArray;
public final boolean isPrimitive;
public final int modifiers;
public final boolean isInterface;
public final boolean isNumber;
public CachedMethod[] mopMethods;
int distance = -1;
int hashCode;
public CachedClass(Class<?> klazz, ClassInfo classInfo) {
cachedClass = klazz;
this.classInfo = classInfo;
isArray = klazz.isArray();
isPrimitive = klazz.isPrimitive();
modifiers = klazz.getModifiers();
isInterface = klazz.isInterface();
isNumber = Number.class.isAssignableFrom(klazz);
for (CachedClass inf : getInterfaces()) {
ReflectionCache.isAssignableFrom(klazz, inf.cachedClass);
}
for (CachedClass cur = this; cur != null; cur = cur.getCachedSuperClass()) {
ReflectionCache.setAssignableFrom(cur.cachedClass, klazz);
}
}
public CachedClass getCachedSuperClass() {
return cachedSuperClass.get();
}
public Set<CachedClass> getInterfaces() {
return interfaces.get();
}
public Set<CachedClass> getDeclaredInterfaces() {
return declaredInterfaces.get();
}
public CachedMethod[] getMethods() {
return methods.get();
}
public CachedField[] getFields() {
return fields.get();
}
public CachedConstructor[] getConstructors() {
return constructors.get();
}
public CachedMethod searchMethods(String name, CachedClass[] parameterTypes) {
CachedMethod[] methods = getMethods();
CachedMethod res = null;
for (CachedMethod m : methods) {
if (m.getName().equals(name)
&& ReflectionCache.arrayContentsEq(parameterTypes, m.getParameterTypes())
&& (res == null || res.getReturnType().isAssignableFrom(m.getReturnType())))
res = m;
}
return res;
}
public int getModifiers() {
return modifiers;
}
public Object coerceArgument(Object argument) {
return argument;
}
public int getSuperClassDistance() {
if (distance >= 0) return distance;
int distance = 0;
for (Class klazz = getTheClass(); klazz != null; klazz = klazz.getSuperclass()) {
distance += 1;
}
this.distance = distance;
return distance;
}
public int hashCode() {
if (hashCode == 0) {
hashCode = super.hashCode();
if (hashCode == 0)
hashCode = 0xcafebebe;
}
return hashCode;
}
public boolean isPrimitive() {
return isPrimitive;
}
public boolean isVoid() {
return getTheClass() == void.class;
}
public boolean isInterface() {
return isInterface;
}
public String getName() {
return getTheClass().getName();
}
public String getTypeDescription() {
return BytecodeHelper.getTypeDescription(getTheClass());
}
public final Class getTheClass() {
return cachedClass;
}
public MetaMethod[] getNewMetaMethods() {
List<MetaMethod> arr = new ArrayList<>(Arrays.asList(classInfo.newMetaMethods));
final MetaClass metaClass = classInfo.getStrongMetaClass();
if (metaClass instanceof ExpandoMetaClass) {
arr.addAll(((ExpandoMetaClass)metaClass).getExpandoMethods());
}
if (isInterface) {
MetaClass mc = ReflectionCache.OBJECT_CLASS.classInfo.getStrongMetaClass();
addSubclassExpandos(arr, mc);
}
else {
for (CachedClass cls = this; cls != null; cls = cls.getCachedSuperClass()) {
MetaClass mc = cls.classInfo.getStrongMetaClass();
addSubclassExpandos(arr, mc);
}
}
for (CachedClass inf : getInterfaces()) {
MetaClass mc = inf.classInfo.getStrongMetaClass();
addSubclassExpandos(arr, mc);
}
return arr.toArray(MetaMethod.EMPTY_ARRAY);
}
private void addSubclassExpandos(List<MetaMethod> arr, MetaClass mc) {
if (mc instanceof ExpandoMetaClass) {
ExpandoMetaClass emc = (ExpandoMetaClass) mc;
for (Object mm : emc.getExpandoSubclassMethods()) {
if (mm instanceof MetaMethod) {
MetaMethod method = (MetaMethod) mm;
if (method.getDeclaringClass() == this)
arr.add(method);
}
else {
FastArray farr = (FastArray) mm;
for (int i = 0; i != farr.size; ++i) {
MetaMethod method = (MetaMethod) farr.get(i);
if (method.getDeclaringClass() == this)
arr.add(method);
}
}
}
}
}
public void setNewMopMethods(List<MetaMethod> arr) {
final MetaClass metaClass = classInfo.getStrongMetaClass();
if (metaClass != null) {
if (metaClass.getClass() == MetaClassImpl.class) {
classInfo.setStrongMetaClass(null);
updateSetNewMopMethods(arr);
classInfo.setStrongMetaClass(new MetaClassImpl(metaClass.getTheClass()));
return;
}
if (metaClass.getClass() == ExpandoMetaClass.class) {
classInfo.setStrongMetaClass(null);
updateSetNewMopMethods(arr);
ExpandoMetaClass newEmc = new ExpandoMetaClass(metaClass.getTheClass());
newEmc.initialize();
classInfo.setStrongMetaClass(newEmc);
return;
}
throw new GroovyRuntimeException("Can't add methods to class " + getTheClass().getName() + ". Strong custom meta class already set.");
}
classInfo.setWeakMetaClass(null);
updateSetNewMopMethods(arr);
}
private void updateSetNewMopMethods(List<MetaMethod> arr) {
if (arr != null) {
final MetaMethod[] metaMethods = arr.toArray(MetaMethod.EMPTY_ARRAY);
classInfo.dgmMetaMethods = metaMethods;
classInfo.newMetaMethods = metaMethods;
}
else
classInfo.newMetaMethods = classInfo.dgmMetaMethods;
}
public void addNewMopMethods(List<MetaMethod> arr) {
final MetaClass metaClass = classInfo.getStrongMetaClass();
if (metaClass != null) {
if (metaClass.getClass() == MetaClassImpl.class) {
classInfo.setStrongMetaClass(null);
List<MetaMethod> res = new ArrayList<>();
Collections.addAll(res, classInfo.newMetaMethods);
res.addAll(arr);
updateSetNewMopMethods(res);
MetaClassImpl answer = new MetaClassImpl(((MetaClassImpl)metaClass).getRegistry(),metaClass.getTheClass());
answer.initialize();
classInfo.setStrongMetaClass(answer);
return;
}
if (metaClass.getClass() == ExpandoMetaClass.class) {
ExpandoMetaClass emc = (ExpandoMetaClass)metaClass;
classInfo.setStrongMetaClass(null);
updateAddNewMopMethods(arr);
ExpandoMetaClass newEmc = new ExpandoMetaClass(metaClass.getTheClass());
for (MetaMethod mm : emc.getExpandoMethods()) {
newEmc.registerInstanceMethod(mm);
}
newEmc.initialize();
classInfo.setStrongMetaClass(newEmc);
return;
}
throw new GroovyRuntimeException("Can't add methods to class " + getTheClass().getName() + ". Strong custom meta class already set.");
}
classInfo.setWeakMetaClass(null);
updateAddNewMopMethods(arr);
}
private void updateAddNewMopMethods(List<MetaMethod> arr) {
List<MetaMethod> res = new ArrayList<>();
res.addAll(Arrays.asList(classInfo.newMetaMethods));
res.addAll(arr);
classInfo.newMetaMethods = res.toArray(MetaMethod.EMPTY_ARRAY);
Class theClass = classInfo.getCachedClass().getTheClass();
if (theClass == Closure.class || theClass == Class.class) {
ClosureMetaClass.resetCachedMetaClasses();
}
}
public boolean isAssignableFrom(Class argument) {
return argument == null || ReflectionCache.isAssignableFrom(getTheClass(), argument);
}
public boolean isDirectlyAssignable(Object argument) {
return ReflectionCache.isAssignableFrom(getTheClass(), argument.getClass());
}
public CallSiteClassLoader getCallSiteLoader() {
return callSiteClassLoader.get();
}
public Collection<ClassInfo> getHierarchy() {
return hierarchy.get();
}
public static class CachedMethodComparatorByName implements Comparator<CachedMethod> {
public static final Comparator INSTANCE = new CachedMethodComparatorByName();
@Override
public int compare(CachedMethod o1, CachedMethod o2) {
return o1.getName().compareTo(o2.getName());
}
}
public static class CachedMethodComparatorWithString implements Comparator {
public static final Comparator INSTANCE = new CachedMethodComparatorWithString();
public int compare(Object o1, Object o2) {
if (o1 instanceof CachedMethod)
return ((CachedMethod) o1).getName().compareTo((String) o2);
else
return ((String) o1).compareTo(((CachedMethod) o2).getName());
}
}
@Override
public String toString() {
return cachedClass.toString();
}
/**
* compatibility method
* @return this
*/
public CachedClass getCachedClass () {
return this;
}
}