blob: 2c4ae9a00401520d36c4ac08af0e5e36a4b2df6e [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.ExpandoMetaClassCreationHandle;
import groovy.lang.GroovySystem;
import groovy.lang.MetaClass;
import groovy.lang.MetaClassRegistry;
import groovy.lang.MetaMethod;
import org.codehaus.groovy.reflection.GroovyClassValue.ComputeValue;
import org.codehaus.groovy.reflection.stdclasses.ArrayCachedClass;
import org.codehaus.groovy.reflection.stdclasses.BigDecimalCachedClass;
import org.codehaus.groovy.reflection.stdclasses.BigIntegerCachedClass;
import org.codehaus.groovy.reflection.stdclasses.BooleanCachedClass;
import org.codehaus.groovy.reflection.stdclasses.ByteCachedClass;
import org.codehaus.groovy.reflection.stdclasses.CachedClosureClass;
import org.codehaus.groovy.reflection.stdclasses.CachedSAMClass;
import org.codehaus.groovy.reflection.stdclasses.CharacterCachedClass;
import org.codehaus.groovy.reflection.stdclasses.DoubleCachedClass;
import org.codehaus.groovy.reflection.stdclasses.FloatCachedClass;
import org.codehaus.groovy.reflection.stdclasses.IntegerCachedClass;
import org.codehaus.groovy.reflection.stdclasses.LongCachedClass;
import org.codehaus.groovy.reflection.stdclasses.NumberCachedClass;
import org.codehaus.groovy.reflection.stdclasses.ObjectCachedClass;
import org.codehaus.groovy.reflection.stdclasses.ShortCachedClass;
import org.codehaus.groovy.reflection.stdclasses.StringCachedClass;
import org.codehaus.groovy.util.Finalizable;
import org.codehaus.groovy.util.LazyReference;
import org.codehaus.groovy.util.LockableObject;
import org.codehaus.groovy.util.ManagedConcurrentLinkedQueue;
import org.codehaus.groovy.util.ManagedConcurrentMap;
import org.codehaus.groovy.util.ManagedReference;
import org.codehaus.groovy.util.ReferenceBundle;
import org.codehaus.groovy.vmplugin.VMPluginFactory;
import java.lang.ref.WeakReference;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Collection;
import java.util.Iterator;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Handle for all information we want to keep about the class
* <p>
* This class handles caching internally and its advisable to not store
* references directly to objects of this class. The static factory method
* {@link ClassInfo#getClassInfo(Class)} should be used to retrieve an instance
* from the cache. Internally the {@code Class} associated with a {@code ClassInfo}
* instance is kept as {@link WeakReference}, so it not safe to reference
* and instance without the Class being either strongly or softly reachable.
*/
public class ClassInfo implements Finalizable {
private final LazyCachedClassRef cachedClassRef;
private final LazyClassLoaderRef artifactClassLoader;
private final LockableObject lock = new LockableObject();
public final int hash = -1;
private final WeakReference<Class<?>> classRef;
// TODO: should be able to remove the klazz field once 2.5 becomes the mainline release
// Gradle has a cleanup mechanism in place to reflectively access this klazz field.
// The klazz field is being kept for compatibility so as to not break builds that depend
// on versions of Groovy after the field was changed to a WeakReference (classRef). It
// appears that Gradle only performs the cleanup when it detects a groovy version of 2.4.x,
// so the klazz field and placeholder Sentinel class can likely be safely removed once
// the release version bumps to 2.5 (or beyond).
// See:
// https://github.com/gradle/gradle/blob/711f64/subprojects/core/src/main/java/org/gradle/api/internal/classloading/LeakyOnJava7GroovySystemLoader.java#L74
private static final class Sentinel {}
private static final Class<?> klazz = Sentinel.class;
private final AtomicInteger version = new AtomicInteger();
private MetaClass strongMetaClass;
private ManagedReference<MetaClass> weakMetaClass;
MetaMethod[] dgmMetaMethods = CachedClass.EMPTY;
MetaMethod[] newMetaMethods = CachedClass.EMPTY;
private ManagedConcurrentMap<Object, MetaClass> perInstanceMetaClassMap;
private static final ReferenceBundle softBundle = ReferenceBundle.getSoftBundle();
private static final ReferenceBundle weakBundle = ReferenceBundle.getWeakBundle();
private static final ManagedConcurrentLinkedQueue<ClassInfo> modifiedExpandos =
new ManagedConcurrentLinkedQueue<ClassInfo>(weakBundle);
private static final GroovyClassValue<ClassInfo> globalClassValue = GroovyClassValueFactory.createGroovyClassValue(new ComputeValue<ClassInfo>(){
@Override
public ClassInfo computeValue(Class<?> type) {
ClassInfo ret = new ClassInfo(type);
globalClassSet.add(ret);
return ret;
}
});
private static final GlobalClassSet globalClassSet = new GlobalClassSet();
ClassInfo(Class klazz) {
this.classRef = new WeakReference<Class<?>>(klazz);
cachedClassRef = new LazyCachedClassRef(softBundle, this);
artifactClassLoader = new LazyClassLoaderRef(softBundle, this);
}
public int getVersion() {
return version.get();
}
public void incVersion() {
version.incrementAndGet();
VMPluginFactory.getPlugin().invalidateCallSites();
}
public ExpandoMetaClass getModifiedExpando() {
// safe value here to avoid multiple reads with possibly
// differing values due to concurrency
MetaClass strongRef = strongMetaClass;
return strongRef == null ? null : strongRef instanceof ExpandoMetaClass ? (ExpandoMetaClass)strongRef : null;
}
public static void clearModifiedExpandos() {
for (Iterator<ClassInfo> itr = modifiedExpandos.iterator(); itr.hasNext(); ) {
ClassInfo info = itr.next();
itr.remove();
info.setStrongMetaClass(null);
}
}
/**
* Returns the {@code Class} associated with this {@code ClassInfo}.
* <p>
* This method can return {@code null} if the {@code Class} is no longer reachable
* through any strong or soft references. A non-null return value indicates that this
* {@code ClassInfo} is valid.
*
* @return the {@code Class} associated with this {@code ClassInfo}, else {@code null}
*/
public final Class<?> getTheClass() {
return classRef.get();
}
public CachedClass getCachedClass() {
return cachedClassRef.get();
}
public ClassLoaderForClassArtifacts getArtifactClassLoader() {
return artifactClassLoader.get();
}
public static ClassInfo getClassInfo (Class cls) {
return globalClassValue.get(cls);
}
/**
* Removes a {@code ClassInfo} from the cache.
*
* This is useful in cases where the Class is parsed from a script, such as when
* using GroovyClassLoader#parseClass, and is executed for its result but the Class
* is not retained or cached. Removing the {@code ClassInfo} associated with the Class
* will make the Class and its ClassLoader eligible for garbage collection sooner that
* it would otherwise.
*
* @param cls the Class associated with the ClassInfo to remove
* from cache
*/
public static void remove(Class<?> cls) {
globalClassValue.remove(cls);
}
public static Collection<ClassInfo> getAllClassInfo () {
return getAllGlobalClassInfo();
}
public static void onAllClassInfo(ClassInfoAction action) {
for (ClassInfo classInfo : getAllGlobalClassInfo()) {
action.onClassInfo(classInfo);
}
}
private static Collection<ClassInfo> getAllGlobalClassInfo() {
return globalClassSet.values();
}
public MetaClass getStrongMetaClass() {
return strongMetaClass;
}
public void setStrongMetaClass(MetaClass answer) {
version.incrementAndGet();
// safe value here to avoid multiple reads with possibly
// differing values due to concurrency
MetaClass strongRef = strongMetaClass;
if (strongRef instanceof ExpandoMetaClass) {
((ExpandoMetaClass)strongRef).inRegistry = false;
for (Iterator<ClassInfo> itr = modifiedExpandos.iterator(); itr.hasNext(); ) {
ClassInfo info = itr.next();
if(info == this) {
itr.remove();
}
}
}
strongMetaClass = answer;
if (answer instanceof ExpandoMetaClass) {
((ExpandoMetaClass)answer).inRegistry = true;
modifiedExpandos.add(this);
}
replaceWeakMetaClassRef(null);
}
public MetaClass getWeakMetaClass() {
// safe value here to avoid multiple reads with possibly
// differing values due to concurrency
ManagedReference<MetaClass> weakRef = weakMetaClass;
return weakRef == null ? null : weakRef.get();
}
public void setWeakMetaClass(MetaClass answer) {
version.incrementAndGet();
strongMetaClass = null;
ManagedReference<MetaClass> newRef = null;
if (answer != null) {
newRef = new ManagedReference<MetaClass> (softBundle,answer);
}
replaceWeakMetaClassRef(newRef);
}
private void replaceWeakMetaClassRef(ManagedReference<MetaClass> newRef) {
// safe value here to avoid multiple reads with possibly
// differing values due to concurrency
ManagedReference<MetaClass> weakRef = weakMetaClass;
if (weakRef != null) {
weakRef.clear();
}
weakMetaClass = newRef;
}
public MetaClass getMetaClassForClass() {
// safe value here to avoid multiple reads with possibly
// differing values due to concurrency
MetaClass strongMc = strongMetaClass;
if (strongMc!=null) return strongMc;
MetaClass weakMc = getWeakMetaClass();
if (isValidWeakMetaClass(weakMc)) {
return weakMc;
}
return null;
}
private MetaClass getMetaClassUnderLock() {
MetaClass answer = getStrongMetaClass();
if (answer!=null) return answer;
answer = getWeakMetaClass();
final MetaClassRegistry metaClassRegistry = GroovySystem.getMetaClassRegistry();
MetaClassRegistry.MetaClassCreationHandle mccHandle = metaClassRegistry.getMetaClassCreationHandler();
if (isValidWeakMetaClass(answer, mccHandle)) {
return answer;
}
answer = mccHandle.create(classRef.get(), metaClassRegistry);
answer.initialize();
if (GroovySystem.isKeepJavaMetaClasses()) {
setStrongMetaClass(answer);
} else {
setWeakMetaClass(answer);
}
return answer;
}
private static boolean isValidWeakMetaClass(MetaClass metaClass) {
return isValidWeakMetaClass(metaClass, GroovySystem.getMetaClassRegistry().getMetaClassCreationHandler());
}
/**
* if EMC.enableGlobally() is OFF, return whatever the cached answer is.
* but if EMC.enableGlobally() is ON and the cached answer is not an EMC, come up with a fresh answer
*/
private static boolean isValidWeakMetaClass(MetaClass metaClass, MetaClassRegistry.MetaClassCreationHandle mccHandle) {
if(metaClass==null) return false;
boolean enableGloballyOn = (mccHandle instanceof ExpandoMetaClassCreationHandle);
boolean cachedAnswerIsEMC = (metaClass instanceof ExpandoMetaClass);
return (!enableGloballyOn || cachedAnswerIsEMC);
}
/**
* Returns the {@code MetaClass} for the {@code Class} associated with this {@code ClassInfo}.
* If no {@code MetaClass} exists one will be created.
* <p>
* It is not safe to call this method without a {@code Class} associated with this {@code ClassInfo}.
* It is advisable to aways retrieve a ClassInfo instance from the cache by using the static
* factory method {@link ClassInfo#getClassInfo(Class)} to ensure the referenced Class is
* strongly reachable.
*
* @return a {@code MetaClass} instance
*/
public final MetaClass getMetaClass() {
MetaClass answer = getMetaClassForClass();
if (answer != null) return answer;
lock();
try {
return getMetaClassUnderLock();
} finally {
unlock();
}
}
public MetaClass getMetaClass(Object obj) {
final MetaClass instanceMetaClass = getPerInstanceMetaClass(obj);
if (instanceMetaClass != null)
return instanceMetaClass;
return getMetaClass();
}
public static int size () {
return globalClassSet.size();
}
public static int fullSize () {
return globalClassSet.fullSize();
}
private static CachedClass createCachedClass(Class klazz, ClassInfo classInfo) {
if (klazz == Object.class)
return new ObjectCachedClass(classInfo);
if (klazz == String.class)
return new StringCachedClass(classInfo);
CachedClass cachedClass;
if (Number.class.isAssignableFrom(klazz) || klazz.isPrimitive()) {
if (klazz == Number.class) {
cachedClass = new NumberCachedClass(klazz, classInfo);
} else if (klazz == Integer.class || klazz == Integer.TYPE) {
cachedClass = new IntegerCachedClass(klazz, classInfo, klazz==Integer.class);
} else if (klazz == Double.class || klazz == Double.TYPE) {
cachedClass = new DoubleCachedClass(klazz, classInfo, klazz==Double.class);
} else if (klazz == BigDecimal.class) {
cachedClass = new BigDecimalCachedClass(klazz, classInfo);
} else if (klazz == Long.class || klazz == Long.TYPE) {
cachedClass = new LongCachedClass(klazz, classInfo, klazz==Long.class);
} else if (klazz == Float.class || klazz == Float.TYPE) {
cachedClass = new FloatCachedClass(klazz, classInfo, klazz==Float.class);
} else if (klazz == Short.class || klazz == Short.TYPE) {
cachedClass = new ShortCachedClass(klazz, classInfo, klazz==Short.class);
} else if (klazz == Boolean.TYPE) {
cachedClass = new BooleanCachedClass(klazz, classInfo, false);
} else if (klazz == Character.TYPE) {
cachedClass = new CharacterCachedClass(klazz, classInfo, false);
} else if (klazz == BigInteger.class) {
cachedClass = new BigIntegerCachedClass(klazz, classInfo);
} else if (klazz == Byte.class || klazz == Byte.TYPE) {
cachedClass = new ByteCachedClass(klazz, classInfo, klazz==Byte.class);
} else {
cachedClass = new CachedClass(klazz, classInfo);
}
} else {
if (klazz.isArray())
cachedClass = new ArrayCachedClass(klazz, classInfo);
else if (klazz == Boolean.class) {
cachedClass = new BooleanCachedClass(klazz, classInfo, true);
} else if (klazz == Character.class) {
cachedClass = new CharacterCachedClass(klazz, classInfo, true);
} else if (Closure.class.isAssignableFrom(klazz)) {
cachedClass = new CachedClosureClass (klazz, classInfo);
} else if (isSAM(klazz)) {
cachedClass = new CachedSAMClass(klazz, classInfo);
} else {
cachedClass = new CachedClass(klazz, classInfo);
}
}
return cachedClass;
}
private static boolean isSAM(Class<?> c) {
return CachedSAMClass.getSAMMethod(c) !=null;
}
public void lock () {
lock.lock();
}
public void unlock () {
lock.unlock();
}
public MetaClass getPerInstanceMetaClass(Object obj) {
if (perInstanceMetaClassMap == null)
return null;
return perInstanceMetaClassMap.get(obj);
}
public void setPerInstanceMetaClass(Object obj, MetaClass metaClass) {
version.incrementAndGet();
if (metaClass != null) {
if (perInstanceMetaClassMap == null)
perInstanceMetaClassMap = new ManagedConcurrentMap<Object, MetaClass>(ReferenceBundle.getWeakBundle());
perInstanceMetaClassMap.put(obj, metaClass);
}
else {
if (perInstanceMetaClassMap != null) {
perInstanceMetaClassMap.remove(obj);
}
}
}
public boolean hasPerInstanceMetaClasses () {
return perInstanceMetaClassMap != null;
}
private static class LazyCachedClassRef extends LazyReference<CachedClass> {
private static final long serialVersionUID = -1400274148849287400L;
private final ClassInfo info;
LazyCachedClassRef(ReferenceBundle bundle, ClassInfo info) {
super(bundle);
this.info = info;
}
public CachedClass initValue() {
return createCachedClass(info.classRef.get(), info);
}
}
private static class LazyClassLoaderRef extends LazyReference<ClassLoaderForClassArtifacts> {
private static final long serialVersionUID = 1639196133085420609L;
private final ClassInfo info;
LazyClassLoaderRef(ReferenceBundle bundle, ClassInfo info) {
super(bundle);
this.info = info;
}
public ClassLoaderForClassArtifacts initValue() {
return AccessController.doPrivileged(new PrivilegedAction<ClassLoaderForClassArtifacts>() {
public ClassLoaderForClassArtifacts run() {
return new ClassLoaderForClassArtifacts(info.classRef.get());
}
});
}
}
@Override
public void finalizeReference() {
setStrongMetaClass(null);
cachedClassRef.clear();
artifactClassLoader.clear();
}
private static class GlobalClassSet {
private final ManagedConcurrentLinkedQueue<ClassInfo> items = new ManagedConcurrentLinkedQueue<ClassInfo>(weakBundle);
public int size(){
return values().size();
}
public int fullSize(){
return values().size();
}
public Collection<ClassInfo> values(){
return items.values();
}
public void add(ClassInfo value){
items.add(value);
}
}
public interface ClassInfoAction {
void onClassInfo(ClassInfo classInfo);
}
}