http://git-wip-us.apache.org/repos/asf/groovy/blob/a188738d/src/main/java/org/codehaus/groovy/reflection/CachedMethod.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/reflection/CachedMethod.java b/src/main/java/org/codehaus/groovy/reflection/CachedMethod.java new file mode 100644 index 0000000..58314e2 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/reflection/CachedMethod.java @@ -0,0 +1,345 @@ +/* + * 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.MetaClassImpl; +import groovy.lang.MetaMethod; +import groovy.lang.MissingMethodException; +import org.codehaus.groovy.classgen.asm.BytecodeHelper; +import org.codehaus.groovy.runtime.InvokerInvocationException; +import org.codehaus.groovy.runtime.callsite.CallSite; +import org.codehaus.groovy.runtime.callsite.CallSiteGenerator; +import org.codehaus.groovy.runtime.callsite.PogoMetaMethodSite; +import org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite; +import org.codehaus.groovy.runtime.callsite.StaticMetaMethodSite; +import org.codehaus.groovy.runtime.metaclass.MethodHelper; + +import java.io.Serializable; +import java.lang.ref.SoftReference; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Comparator; + +/** + * @author Alex.Tkachman + */ +public class CachedMethod extends MetaMethod implements Comparable { + public final CachedClass cachedClass; + + private final Method cachedMethod; + private int hashCode; + + private static final MyComparator COMPARATOR = new MyComparator(); + + private SoftReference<Constructor> pogoCallSiteConstructor, pojoCallSiteConstructor, staticCallSiteConstructor; + + private boolean skipCompiled; + + public CachedMethod(CachedClass clazz, Method method) { + this.cachedMethod = method; + this.cachedClass = clazz; + } + + public CachedMethod(Method method) { + this(ReflectionCache.getCachedClass(method.getDeclaringClass()),method); + } + + public static CachedMethod find(Method method) { + CachedMethod[] methods = ReflectionCache.getCachedClass(method.getDeclaringClass()).getMethods(); +// for (int i = 0; i < methods.length; i++) { +// CachedMethod cachedMethod = methods[i]; +// if (cachedMethod.cachedMethod.equals(method)) +// return cachedMethod; +// } +// return null; + int i = Arrays.binarySearch(methods, method, COMPARATOR); + if (i < 0) + return null; + + return methods[i]; + } + + protected Class[] getPT() { + return cachedMethod.getParameterTypes(); + } + + public String getName() { + return cachedMethod.getName(); + } + + public String getDescriptor() { + return BytecodeHelper.getMethodDescriptor(getReturnType(), getNativeParameterTypes()); + } + + public CachedClass getDeclaringClass() { + return cachedClass; + } + + public final Object invoke(Object object, Object[] arguments) { + try { + AccessPermissionChecker.checkAccessPermission(cachedMethod); + } catch (CacheAccessControlException ex) { + throw new InvokerInvocationException(ex); + } + try { + return cachedMethod.invoke(object, arguments); + } catch (IllegalArgumentException e) { + throw new InvokerInvocationException(e); + } catch (IllegalAccessException e) { + throw new InvokerInvocationException(e); + } catch (InvocationTargetException e) { + Throwable cause = e.getCause(); + throw (cause instanceof RuntimeException && !(cause instanceof MissingMethodException)) ? + (RuntimeException) cause : new InvokerInvocationException(e); + } + } + + public ParameterTypes getParamTypes() { + return null; + } + + public Class getReturnType() { + return cachedMethod.getReturnType(); + } + + public int getParamsCount() { + return getParameterTypes().length; + } + + public int getModifiers() { + return cachedMethod.getModifiers(); + } + + + public String getSignature() { + return getName() + getDescriptor(); + } + + public final Method setAccessible() { + AccessPermissionChecker.checkAccessPermission(cachedMethod); +// if (queuedToCompile.compareAndSet(false,true)) { +// if (isCompilable()) +// CompileThread.addMethod(this); +// } + return cachedMethod; + } + + public boolean isStatic() { + return MethodHelper.isStatic(cachedMethod); + } + + public int compareTo(Object o) { + if (o instanceof CachedMethod) + return compareToCachedMethod((CachedMethod)o); + else + return compareToMethod((Method)o); + } + + private int compareToCachedMethod(CachedMethod other) { + if (other == null) + return -1; + + final int strComp = getName().compareTo(other.getName()); + if (strComp != 0) + return strComp; + + final int retComp = getReturnType().getName().compareTo(other.getReturnType().getName()); + if (retComp != 0) + return retComp; + + CachedClass[] params = getParameterTypes(); + CachedClass[] otherParams = other.getParameterTypes(); + + final int pd = params.length - otherParams.length; + if (pd != 0) + return pd; + + for (int i = 0; i != params.length; ++i) { + final int nameComp = params[i].getName().compareTo(otherParams[i].getName()); + if (nameComp != 0) + return nameComp; + } + + final int classComp = cachedClass.toString().compareTo(other.getDeclaringClass().toString()); + if (classComp != 0) + return classComp; + + throw new RuntimeException("Should never happen"); + } + + private int compareToMethod(Method other) { + if (other == null) + return -1; + + final int strComp = getName().compareTo(other.getName()); + if (strComp != 0) + return strComp; + + final int retComp = getReturnType().getName().compareTo(other.getReturnType().getName()); + if (retComp != 0) + return retComp; + + CachedClass[] params = getParameterTypes(); + Class[] mparams = other.getParameterTypes(); + + final int pd = params.length - mparams.length; + if (pd != 0) + return pd; + + for (int i = 0; i != params.length; ++i) { + final int nameComp = params[i].getName().compareTo(mparams[i].getName()); + if (nameComp != 0) + return nameComp; + } + + return 0; + } + + public boolean equals(Object o) { + return (o instanceof CachedMethod && cachedMethod.equals(((CachedMethod)o).cachedMethod)) + || (o instanceof Method && cachedMethod.equals(o)); + } + + public int hashCode() { + if (hashCode == 0) { + hashCode = cachedMethod.hashCode(); + if (hashCode == 0) + hashCode = 0xcafebebe; + } + return hashCode; + } + + public String toString() { + return cachedMethod.toString(); + } + + private static Constructor getConstructor(SoftReference<Constructor> ref) { + if (ref==null) return null; + return ref.get(); + } + + public CallSite createPogoMetaMethodSite(CallSite site, MetaClassImpl metaClass, Class[] params) { + if (!skipCompiled) { + Constructor constr = getConstructor(pogoCallSiteConstructor); + if (constr==null) { + if (CallSiteGenerator.isCompilable(this)) { + constr = CallSiteGenerator.compilePogoMethod(this); + } + if (constr != null) { + pogoCallSiteConstructor = new SoftReference<Constructor> (constr); + } else { + skipCompiled = true; + } + } + + if (constr!=null) { + try { + return (CallSite) constr.newInstance(site, metaClass, this, params, constr); + } catch (Error e) { + skipCompiled=true; + throw e; + } catch (Throwable e) { + skipCompiled=true; + } + } + } + return new PogoMetaMethodSite.PogoCachedMethodSiteNoUnwrapNoCoerce(site, metaClass, this, params); + } + + + public CallSite createPojoMetaMethodSite(CallSite site, MetaClassImpl metaClass, Class[] params) { + if (!skipCompiled) { + Constructor constr = getConstructor(pojoCallSiteConstructor); + if (constr==null) { + if (CallSiteGenerator.isCompilable(this)) { + constr = CallSiteGenerator.compilePojoMethod(this); + } + if (constr != null) { + pojoCallSiteConstructor = new SoftReference<Constructor> (constr); + } else { + skipCompiled = true; + } + } + + if (constr!=null) { + try { + return (CallSite) constr.newInstance(site, metaClass, this, params, constr); + } catch (Error e) { + skipCompiled=true; + throw e; + } catch (Throwable e) { + skipCompiled=true; + } + } + } + return new PojoMetaMethodSite.PojoCachedMethodSiteNoUnwrapNoCoerce(site, metaClass, this, params); + } + + public CallSite createStaticMetaMethodSite(CallSite site, MetaClassImpl metaClass, Class[] params) { + if (!skipCompiled) { + Constructor constr = getConstructor(staticCallSiteConstructor); + if (constr==null) { + if (CallSiteGenerator.isCompilable(this)) { + constr = CallSiteGenerator.compileStaticMethod(this); + } + if (constr != null) { + staticCallSiteConstructor = new SoftReference<Constructor> (constr); + } else { + skipCompiled = true; + } + } + + if (constr!=null) { + try { + return (CallSite) constr.newInstance(site, metaClass, this, params, constr); + } catch (Error e) { + skipCompiled=true; + throw e; + } catch (Throwable e) { + skipCompiled=true; + } + } + } + + return new StaticMetaMethodSite.StaticMetaMethodSiteNoUnwrapNoCoerce(site, metaClass, this, params); + } + + private static class MyComparator implements Comparator, Serializable { + private static final long serialVersionUID = 8909277090690131302L; + + public int compare(Object o1, Object o2) { + if (o1 instanceof CachedMethod) + return ((CachedMethod)o1).compareTo(o2); + else if (o2 instanceof CachedMethod) + return -((CachedMethod)o2).compareTo(o1); + else + // really, this should never happen, it's evidence of corruption if it does + throw new ClassCastException("One of the two comparables must be a CachedMethod"); + } + } + + public Method getCachedMethod() { + AccessPermissionChecker.checkAccessPermission(cachedMethod); + return cachedMethod; + } + +} +
http://git-wip-us.apache.org/repos/asf/groovy/blob/a188738d/src/main/java/org/codehaus/groovy/reflection/ClassInfo.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/reflection/ClassInfo.java b/src/main/java/org/codehaus/groovy/reflection/ClassInfo.java new file mode 100644 index 0000000..b4dc133 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/reflection/ClassInfo.java @@ -0,0 +1,505 @@ +/* + * 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. + * + * @author Alex.Tkachman + */ +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.getName().charAt(0) == '[') + 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 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 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); + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/a188738d/src/main/java/org/codehaus/groovy/reflection/ClassLoaderForClassArtifacts.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/reflection/ClassLoaderForClassArtifacts.java b/src/main/java/org/codehaus/groovy/reflection/ClassLoaderForClassArtifacts.java new file mode 100644 index 0000000..ba24a6d --- /dev/null +++ b/src/main/java/org/codehaus/groovy/reflection/ClassLoaderForClassArtifacts.java @@ -0,0 +1,88 @@ +/* + * 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.MetaClassImpl; +import groovy.lang.MetaMethod; +import org.codehaus.groovy.runtime.callsite.CallSite; +import org.codehaus.groovy.runtime.callsite.GroovySunClassLoader; + +import java.lang.ref.SoftReference; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.concurrent.atomic.AtomicInteger; + +public class ClassLoaderForClassArtifacts extends ClassLoader { + public final SoftReference<Class> klazz; + private final AtomicInteger classNamesCounter = new AtomicInteger(-1); + + public ClassLoaderForClassArtifacts(Class klazz) { + super(klazz.getClassLoader()); + this.klazz = new SoftReference<Class> (klazz); + } + + public Class define (String name, byte [] bytes) { + Class cls = defineClass(name, bytes, 0, bytes.length, klazz.get().getProtectionDomain()); + resolveClass(cls); + return cls; + } + + public Class loadClass(String name) throws ClassNotFoundException { + Class cls = findLoadedClass(name); + if (cls != null) + return cls; + + if (GroovySunClassLoader.sunVM != null) { + cls = GroovySunClassLoader.sunVM.doesKnow(name); + if (cls != null) + return cls; + } + + return super.loadClass(name); + } + + public String createClassName(Method method) { + final String name; + final String clsName = klazz.get().getName(); + if (clsName.startsWith("java.")) + name = clsName.replace('.','_') + "$" + method.getName(); + else + name = clsName + "$" + method.getName(); + int suffix = classNamesCounter.getAndIncrement(); + return suffix == -1? name : name + "$" + suffix; + } + + public Constructor defineClassAndGetConstructor(final String name, final byte[] bytes) { + final Class cls = AccessController.doPrivileged( new PrivilegedAction<Class>(){ + public Class run() { + return define(name, bytes); + } + }); + + if (cls != null) { + try { + return cls.getConstructor(CallSite.class, MetaClassImpl.class, MetaMethod.class, Class[].class, Constructor.class); + } catch (NoSuchMethodException e) { // + } + } + return null; + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/a188738d/src/main/java/org/codehaus/groovy/reflection/GeneratedMetaMethod.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/reflection/GeneratedMetaMethod.java b/src/main/java/org/codehaus/groovy/reflection/GeneratedMetaMethod.java new file mode 100644 index 0000000..3f830fd --- /dev/null +++ b/src/main/java/org/codehaus/groovy/reflection/GeneratedMetaMethod.java @@ -0,0 +1,240 @@ +/* + * 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.GroovyRuntimeException; +import groovy.lang.MetaMethod; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.Serializable; +import java.lang.reflect.Constructor; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +public abstract class GeneratedMetaMethod extends MetaMethod { + private final String name; + private final CachedClass declaringClass; + private final Class returnType; + + public GeneratedMetaMethod(String name, CachedClass declaringClass, Class returnType, Class[] parameters) { + this.name = name; + this.declaringClass = declaringClass; + this.returnType = returnType; + nativeParamTypes = parameters; + } + + public int getModifiers() { + return Modifier.PUBLIC; + } + + public String getName() { + return name; + } + + public Class getReturnType() { + return returnType; + } + + public CachedClass getDeclaringClass() { + return declaringClass; + } + + public static class Proxy extends GeneratedMetaMethod { + private volatile MetaMethod proxy; + private final String className; + + public Proxy(String className, String name, CachedClass declaringClass, Class returnType, Class[] parameters) { + super(name, declaringClass, returnType, parameters); + this.className = className; + } + + @Override + public boolean isValidMethod(Class[] arguments) { + return proxy().isValidMethod(arguments); + } + + @Override + public Object doMethodInvoke(Object object, Object[] argumentArray) { + return proxy().doMethodInvoke(object, argumentArray); + } + + public Object invoke(Object object, Object[] arguments) { + return proxy().invoke(object, arguments); + } + + public final MetaMethod proxy() { + if (proxy == null) { + synchronized(this) { + if (proxy == null) createProxy(); + } + } + return proxy; + } + + private void createProxy() { + try { + Class<?> aClass = getClass().getClassLoader().loadClass(className.replace('/', '.')); + Constructor<?> constructor = aClass.getConstructor(String.class, CachedClass.class, Class.class, Class[].class); + proxy = (MetaMethod) constructor.newInstance(getName(), getDeclaringClass(), getReturnType(), getNativeParameterTypes()); + } catch (Throwable t) { + t.printStackTrace(); + throw new GroovyRuntimeException("Failed to create DGM method proxy : " + t, t); + } + } + } + + public static class DgmMethodRecord implements Serializable { + private static final long serialVersionUID = -5639988016452884450L; + public String className; + public String methodName; + public Class returnType; + public Class[] parameters; + + private static final Class[] PRIMITIVE_CLASSES = { + Boolean.TYPE, Character.TYPE, Byte.TYPE, Short.TYPE, + Integer.TYPE, Long.TYPE, Double.TYPE, Float.TYPE, Void.TYPE, + + boolean[].class, char[].class, byte[].class, short[].class, + int[].class, long[].class, double[].class, float[].class, + + Object[].class, String[].class, Class[].class, Byte[].class, CharSequence[].class, + }; + + public static void saveDgmInfo(List<DgmMethodRecord> records, String file) throws IOException { + DataOutputStream out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(file))); + Map<String, Integer> classes = new LinkedHashMap<String, Integer>(); + + int nextClassId = 0; + for (Class primitive : PRIMITIVE_CLASSES) { + classes.put(primitive.getName(), nextClassId++); + } + + for (DgmMethodRecord record : records) { + String name = record.returnType.getName(); + Integer id = classes.get(name); + if (id == null) { + id = nextClassId++; + classes.put(name, id); + } + + for (int i = 0; i < record.parameters.length; i++) { + name = record.parameters[i].getName(); + id = classes.get(name); + if (id == null) { + id = nextClassId++; + classes.put(name, id); + } + } + } + + for (Map.Entry<String, Integer> stringIntegerEntry : classes.entrySet()) { + out.writeUTF(stringIntegerEntry.getKey()); + out.writeInt(stringIntegerEntry.getValue()); + } + out.writeUTF(""); + + out.writeInt(records.size()); + for (DgmMethodRecord record : records) { + out.writeUTF(record.className); + out.writeUTF(record.methodName); + out.writeInt(classes.get(record.returnType.getName())); + + out.writeInt(record.parameters.length); + for (int i = 0; i < record.parameters.length; i++) { + Integer key = classes.get(record.parameters[i].getName()); + out.writeInt(key); + } + } + out.close(); + } + + public static List<DgmMethodRecord> loadDgmInfo() throws IOException { + + ClassLoader loader = DgmMethodRecord.class.getClassLoader(); + DataInputStream in = new DataInputStream(new BufferedInputStream(loader.getResourceAsStream("META-INF/dgminfo"))); + + Map<Integer, Class> classes = new HashMap<Integer, Class>(); + for (int i = 0; i < PRIMITIVE_CLASSES.length; i++) { + classes.put(i, PRIMITIVE_CLASSES[i]); + } + + int skip = 0; + for (; ; ) { + String name = in.readUTF(); + if (name.length() == 0) + break; + + int key = in.readInt(); + + if (skip++ < PRIMITIVE_CLASSES.length) + continue; + + Class cls = null; + try { + cls = loader.loadClass(name); + } catch (ClassNotFoundException e) { + // under certain restrictive environments, loading certain classes may be forbidden + // and could yield a ClassNotFoundException (Google App Engine) + continue; + } + classes.put(key, cls); + } + + int size = in.readInt(); + List<DgmMethodRecord> res = new ArrayList<DgmMethodRecord>(size); + for (int i = 0; i != size; ++i) { + boolean skipRecord = false; + DgmMethodRecord record = new DgmMethodRecord(); + record.className = in.readUTF(); + record.methodName = in.readUTF(); + record.returnType = classes.get(in.readInt()); + + if (record.returnType == null) { + skipRecord = true; + } + + int psize = in.readInt(); + record.parameters = new Class[psize]; + for (int j = 0; j < record.parameters.length; j++) { + record.parameters[j] = classes.get(in.readInt()); + + if (record.parameters[j] == null) { + skipRecord = true; + } + } + if (!skipRecord) { + res.add(record); + } + } + + in.close(); + + return res; + } + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/a188738d/src/main/java/org/codehaus/groovy/reflection/GroovyClassValue.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/reflection/GroovyClassValue.java b/src/main/java/org/codehaus/groovy/reflection/GroovyClassValue.java new file mode 100644 index 0000000..909f9b1 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/reflection/GroovyClassValue.java @@ -0,0 +1,36 @@ +/* + * 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; + +/** Abstraction for Java version dependent ClassValue implementations. + * @see java.lang.ClassValue + * + * @param <T> + */ +public interface GroovyClassValue<T> { + + interface ComputeValue<T>{ + T computeValue(Class<?> type); + } + + T get(Class<?> type); + + void remove(Class<?> type); + +} http://git-wip-us.apache.org/repos/asf/groovy/blob/a188738d/src/main/java/org/codehaus/groovy/reflection/GroovyClassValueFactory.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/reflection/GroovyClassValueFactory.java b/src/main/java/org/codehaus/groovy/reflection/GroovyClassValueFactory.java new file mode 100644 index 0000000..c367791 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/reflection/GroovyClassValueFactory.java @@ -0,0 +1,42 @@ +/* + * 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 org.codehaus.groovy.reflection.GroovyClassValue.ComputeValue; +import org.codehaus.groovy.reflection.v7.GroovyClassValueJava7; + +class GroovyClassValueFactory { + /** + * This flag is introduced as a (hopefully) temporary workaround for a JVM bug, that is to say that using + * ClassValue prevents the classes and classloaders from being unloaded. + * See https://bugs.openjdk.java.net/browse/JDK-8136353 + * This issue does not exist on IBM Java (J9) so use ClassValue by default on that JVM. + */ + private static final boolean USE_CLASSVALUE; + static { + String isJ9 = "IBM J9 VM".equals(System.getProperty("java.vm.name")) ? "true" : "false"; + USE_CLASSVALUE = Boolean.valueOf(System.getProperty("groovy.use.classvalue", isJ9)); + } + + public static <T> GroovyClassValue<T> createGroovyClassValue(ComputeValue<T> computeValue) { + return (USE_CLASSVALUE) + ? new GroovyClassValueJava7<>(computeValue) + : new GroovyClassValuePreJava7<>(computeValue); + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/a188738d/src/main/java/org/codehaus/groovy/reflection/GroovyClassValuePreJava7.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/reflection/GroovyClassValuePreJava7.java b/src/main/java/org/codehaus/groovy/reflection/GroovyClassValuePreJava7.java new file mode 100644 index 0000000..56ad0c1 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/reflection/GroovyClassValuePreJava7.java @@ -0,0 +1,104 @@ +/* + * 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 org.codehaus.groovy.util.Finalizable; +import org.codehaus.groovy.util.ManagedConcurrentMap; +import org.codehaus.groovy.util.ReferenceBundle; + +/** Approximation of Java 7's {@link java.lang.ClassValue} that works on earlier versions of Java. + * Note that this implementation isn't as good at Java 7's; it doesn't allow for some GC'ing that Java 7 would allow. + * But, it's good enough for our use. + * + * @param <T> + */ +class GroovyClassValuePreJava7<T> implements GroovyClassValue<T> { + private static final ReferenceBundle weakBundle = ReferenceBundle.getWeakBundle(); + + private class EntryWithValue extends ManagedConcurrentMap.EntryWithValue<Class<?>,T>{ + + public EntryWithValue(GroovyClassValuePreJava7Segment segment, Class<?> key, int hash) { + super(weakBundle, segment, key, hash, computeValue.computeValue(key)); + } + + @Override + public void setValue(T value) { + if(value!=null) super.setValue(value); + } + + @Override + public void finalizeReference() { + T value = getValue(); + if (value instanceof Finalizable) { + ((Finalizable) value).finalizeReference(); + } + super.finalizeReference(); + } + } + + private class GroovyClassValuePreJava7Segment extends ManagedConcurrentMap.Segment<Class<?>,T> { + + GroovyClassValuePreJava7Segment(ReferenceBundle bundle, int initialCapacity) { + super(bundle, initialCapacity); + } + + @Override + protected EntryWithValue createEntry(Class<?> key, int hash, + T unused) { + return new EntryWithValue(this, key, hash); + } + } + + private class GroovyClassValuePreJava7Map extends ManagedConcurrentMap<Class<?>,T> { + + public GroovyClassValuePreJava7Map() { + super(weakBundle); + } + + @Override + protected GroovyClassValuePreJava7Segment createSegment(Object segmentInfo, int cap) { + ReferenceBundle bundle = (ReferenceBundle) segmentInfo; + if (bundle==null) throw new IllegalArgumentException("bundle must not be null "); + return new GroovyClassValuePreJava7Segment(bundle, cap); + } + + } + + private final ComputeValue<T> computeValue; + + private final GroovyClassValuePreJava7Map map = new GroovyClassValuePreJava7Map(); + + public GroovyClassValuePreJava7(ComputeValue<T> computeValue){ + this.computeValue = computeValue; + } + + @Override + public T get(Class<?> type) { + // the value isn't use in the getOrPut call - see the EntryWithValue constructor above + T value = ((EntryWithValue)map.getOrPut(type, null)).getValue(); + //all entries are guaranteed to be EntryWithValue. Value can only be null if computeValue returns null + return value; + } + + @Override + public void remove(Class<?> type) { + map.remove(type); + } + +} http://git-wip-us.apache.org/repos/asf/groovy/blob/a188738d/src/main/java/org/codehaus/groovy/reflection/MixinInMetaClass.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/reflection/MixinInMetaClass.java b/src/main/java/org/codehaus/groovy/reflection/MixinInMetaClass.java new file mode 100644 index 0000000..4dfa083 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/reflection/MixinInMetaClass.java @@ -0,0 +1,206 @@ +/* + * 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.DelegatingMetaClass; +import groovy.lang.ExpandoMetaClass; +import groovy.lang.GroovyRuntimeException; +import groovy.lang.GroovySystem; +import groovy.lang.MetaClass; +import groovy.lang.MetaMethod; +import groovy.lang.MetaProperty; +import org.codehaus.groovy.runtime.HandleMetaClass; +import org.codehaus.groovy.runtime.MetaClassHelper; +import org.codehaus.groovy.runtime.metaclass.MixedInMetaClass; +import org.codehaus.groovy.runtime.metaclass.MixinInstanceMetaMethod; +import org.codehaus.groovy.runtime.metaclass.MixinInstanceMetaProperty; +import org.codehaus.groovy.runtime.metaclass.NewInstanceMetaMethod; +import org.codehaus.groovy.util.ManagedConcurrentMap; +import org.codehaus.groovy.util.ReferenceBundle; + +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.List; + +public class MixinInMetaClass extends ManagedConcurrentMap { + final ExpandoMetaClass emc; + final CachedClass mixinClass; + final CachedConstructor constructor; + + private static final ReferenceBundle softBundle = ReferenceBundle.getSoftBundle(); + + public MixinInMetaClass(ExpandoMetaClass emc, CachedClass mixinClass) { + super(softBundle); + this.emc = emc; + this.mixinClass = mixinClass; + + constructor = findDefaultConstructor(mixinClass); + emc.addMixinClass(this); + } + + private static CachedConstructor findDefaultConstructor(CachedClass mixinClass) { + for (CachedConstructor constr : mixinClass.getConstructors()) { + if (!Modifier.isPublic(constr.getModifiers())) + continue; + + CachedClass[] classes = constr.getParameterTypes(); + if (classes.length == 0) + return constr; + } + + throw new GroovyRuntimeException("No default constructor for class " + mixinClass.getName() + "! Can't be mixed in."); + } + + public synchronized Object getMixinInstance(Object object) { + Object mixinInstance = get(object); + if (mixinInstance == null) { + mixinInstance = constructor.invoke(MetaClassHelper.EMPTY_ARRAY); + new MixedInMetaClass(mixinInstance, object); + put(object, mixinInstance); + } + return mixinInstance; + } + + public synchronized void setMixinInstance(Object object, Object mixinInstance) { + if (mixinInstance == null) { + remove(object); + } else { + put(object, mixinInstance); + } + } + + public CachedClass getInstanceClass() { + return emc.getTheCachedClass(); + } + + public CachedClass getMixinClass() { + return mixinClass; + } + + public static void mixinClassesToMetaClass(MetaClass self, List<Class> categoryClasses) { + final Class selfClass = self.getTheClass(); + + if (self instanceof HandleMetaClass) { + self = (MetaClass) ((HandleMetaClass) self).replaceDelegate(); + } + + if (!(self instanceof ExpandoMetaClass)) { + if (self instanceof DelegatingMetaClass && ((DelegatingMetaClass) self).getAdaptee() instanceof ExpandoMetaClass) { + self = ((DelegatingMetaClass) self).getAdaptee(); + } else { + throw new GroovyRuntimeException("Can't mixin methods to meta class: " + self); + } + } + + ExpandoMetaClass mc = (ExpandoMetaClass) self; + + List<MetaMethod> arr = new ArrayList<MetaMethod>(); + for (Class categoryClass : categoryClasses) { + + final CachedClass cachedCategoryClass = ReflectionCache.getCachedClass(categoryClass); + final MixinInMetaClass mixin = new MixinInMetaClass(mc, cachedCategoryClass); + + final MetaClass metaClass = GroovySystem.getMetaClassRegistry().getMetaClass(categoryClass); + final List<MetaProperty> propList = metaClass.getProperties(); + for (MetaProperty prop : propList) + if (self.getMetaProperty(prop.getName()) == null) { + mc.registerBeanProperty(prop.getName(), new MixinInstanceMetaProperty(prop, mixin)); + } + + for (MetaProperty prop : cachedCategoryClass.getFields()) + if (self.getMetaProperty(prop.getName()) == null) { + mc.registerBeanProperty(prop.getName(), new MixinInstanceMetaProperty(prop, mixin)); + } + + for (MetaMethod method : metaClass.getMethods()) { + final int mod = method.getModifiers(); + + if (!Modifier.isPublic(mod)) + continue; + + if (method instanceof CachedMethod && ((CachedMethod) method).getCachedMethod().isSynthetic()) + continue; + + if (Modifier.isStatic(mod)) { + if (method instanceof CachedMethod) + staticMethod(self, arr, (CachedMethod) method); + } else if (method.getDeclaringClass().getTheClass() != Object.class || method.getName().equals("toString")) { +// if (self.pickMethod(method.getName(), method.getNativeParameterTypes()) == null) { + final MixinInstanceMetaMethod metaMethod = new MixinInstanceMetaMethod(method, mixin); + arr.add(metaMethod); +// } + } + } + } + + for (Object res : arr) { + final MetaMethod metaMethod = (MetaMethod) res; + if (metaMethod.getDeclaringClass().isAssignableFrom(selfClass)) + mc.registerInstanceMethod(metaMethod); + else { + mc.registerSubclassInstanceMethod(metaMethod); + } + } + } + + private static void staticMethod(final MetaClass self, List<MetaMethod> arr, final CachedMethod method) { + CachedClass[] paramTypes = method.getParameterTypes(); + + if (paramTypes.length == 0) + return; + + NewInstanceMetaMethod metaMethod; + if (paramTypes[0].isAssignableFrom(self.getTheClass())) { + if (paramTypes[0].getTheClass() == self.getTheClass()) + metaMethod = new NewInstanceMetaMethod(method); + else + metaMethod = new NewInstanceMetaMethod(method) { + public CachedClass getDeclaringClass() { + return ReflectionCache.getCachedClass(self.getTheClass()); + } + }; + arr.add(metaMethod); + } else { + if (self.getTheClass().isAssignableFrom(paramTypes[0].getTheClass())) { + metaMethod = new NewInstanceMetaMethod(method); + arr.add(metaMethod); + } + } + } + + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof MixinInMetaClass)) return false; + if (!super.equals(o)) return false; + + MixinInMetaClass that = (MixinInMetaClass) o; + + if (mixinClass != null ? !mixinClass.equals(that.mixinClass) : that.mixinClass != null) return false; + + return true; + } + + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + (emc != null ? emc.hashCode() : 0); + result = 31 * result + (mixinClass != null ? mixinClass.hashCode() : 0); + result = 31 * result + (constructor != null ? constructor.hashCode() : 0); + return result; + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/a188738d/src/main/java/org/codehaus/groovy/reflection/ParameterTypes.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/reflection/ParameterTypes.java b/src/main/java/org/codehaus/groovy/reflection/ParameterTypes.java new file mode 100644 index 0000000..4c5d5fa --- /dev/null +++ b/src/main/java/org/codehaus/groovy/reflection/ParameterTypes.java @@ -0,0 +1,385 @@ +/* + * 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 org.codehaus.groovy.GroovyBugError; +import org.codehaus.groovy.runtime.MetaClassHelper; +import org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation; +import org.codehaus.groovy.runtime.wrappers.Wrapper; + +import java.lang.reflect.Array; + +public class ParameterTypes { + private final static Class[] NO_PARAMETERS = new Class[0]; + + protected volatile Class[] nativeParamTypes; + protected volatile CachedClass[] parameterTypes; + + protected boolean isVargsMethod; + + public ParameterTypes() { + } + + public ParameterTypes(Class pt[]) { + nativeParamTypes = pt; + } + + public ParameterTypes(String pt[]) { + nativeParamTypes = new Class[pt.length]; + for (int i = 0; i != pt.length; ++i) { + try { + nativeParamTypes[i] = Class.forName(pt[i]); + } catch (ClassNotFoundException e) { + NoClassDefFoundError err = new NoClassDefFoundError(); + err.initCause(e); + throw err; + } + } + } + + public ParameterTypes(CachedClass[] parameterTypes) { + setParametersTypes(parameterTypes); + } + + protected final void setParametersTypes(CachedClass[] pt) { + this.parameterTypes = pt; + isVargsMethod = pt.length > 0 && pt[pt.length - 1].isArray; + } + + public CachedClass[] getParameterTypes() { + if (parameterTypes == null) { + getParametersTypes0(); + } + + return parameterTypes; + } + + private synchronized void getParametersTypes0() { + if (parameterTypes != null) + return; + + Class[] npt = nativeParamTypes == null ? getPT() : nativeParamTypes; + if (npt.length == 0) { + nativeParamTypes = NO_PARAMETERS; + setParametersTypes(CachedClass.EMPTY_ARRAY); + } else { + + CachedClass[] pt = new CachedClass[npt.length]; + for (int i = 0; i != npt.length; ++i) + pt[i] = ReflectionCache.getCachedClass(npt[i]); + + nativeParamTypes = npt; + setParametersTypes(pt); + } + } + + public Class[] getNativeParameterTypes() { + if (nativeParamTypes == null) { + getNativeParameterTypes0(); + } + return nativeParamTypes; + } + + private synchronized void getNativeParameterTypes0() { + if (nativeParamTypes != null) + return; + + Class[] npt; + if (parameterTypes != null) { + npt = new Class[parameterTypes.length]; + for (int i = 0; i != parameterTypes.length; ++i) { + npt[i] = parameterTypes[i].getTheClass(); + } + } else + npt = getPT(); + nativeParamTypes = npt; + } + + protected Class[] getPT() { + throw new UnsupportedOperationException(getClass().getName()); + } + + public boolean isVargsMethod() { + return isVargsMethod; + } + + public boolean isVargsMethod(Object[] arguments) { + // Uncomment if at some point this method can be called before parameterTypes initialized + // getParameterTypes(); + if (!isVargsMethod) + return false; + + final int lenMinus1 = parameterTypes.length - 1; + // -1 because the varg part is optional + if (lenMinus1 == arguments.length) return true; + if (lenMinus1 > arguments.length) return false; + if (arguments.length > parameterTypes.length) return true; + + // only case left is arguments.length == parameterTypes.length + Object last = arguments[arguments.length - 1]; + if (last == null) return true; + Class clazz = last.getClass(); + return !clazz.equals(parameterTypes[lenMinus1].getTheClass()); + + } + + public final Object[] coerceArgumentsToClasses(Object[] argumentArray) { + // Uncomment if at some point this method can be called before parameterTypes initialized + // getParameterTypes(); + argumentArray = correctArguments(argumentArray); + + final CachedClass[] pt = parameterTypes; + final int len = argumentArray.length; + for (int i = 0; i < len; i++) { + final Object argument = argumentArray[i]; + if (argument != null) { + argumentArray[i] = pt[i].coerceArgument(argument); + } + } + return argumentArray; + } + + public Object[] correctArguments(Object[] argumentArray) { + // correct argumentArray's length + if (argumentArray == null) { + return MetaClassHelper.EMPTY_ARRAY; + } + + final CachedClass[] pt = getParameterTypes(); + if (pt.length == 1 && argumentArray.length == 0) { + if (isVargsMethod) + return new Object[]{Array.newInstance(pt[0].getTheClass().getComponentType(), 0)}; + else + return MetaClassHelper.ARRAY_WITH_NULL; + } + + if (isVargsMethod && isVargsMethod(argumentArray)) { + return fitToVargs(argumentArray, pt); + } + + return argumentArray; + } + + /** + * this method is called when the number of arguments to a method is greater than 1 + * and if the method is a vargs method. This method will then transform the given + * arguments to make the method callable + * + * @param argumentArrayOrig the arguments used to call the method + * @param paramTypes the types of the parameters the method takes + */ + private static Object[] fitToVargs(Object[] argumentArrayOrig, CachedClass[] paramTypes) { + Class vargsClassOrig = paramTypes[paramTypes.length - 1].getTheClass().getComponentType(); + Class vargsClass = ReflectionCache.autoboxType(vargsClassOrig); + Object[] argumentArray = argumentArrayOrig.clone(); + MetaClassHelper.unwrap(argumentArray); + + if (argumentArray.length == paramTypes.length - 1) { + // the vargs argument is missing, so fill it with an empty array + Object[] newArgs = new Object[paramTypes.length]; + System.arraycopy(argumentArray, 0, newArgs, 0, argumentArray.length); + Object vargs = Array.newInstance(vargsClass, 0); + newArgs[newArgs.length - 1] = vargs; + return newArgs; + } else if (argumentArray.length == paramTypes.length) { + // the number of arguments is correct, but if the last argument + // is no array we have to wrap it in a array. If the last argument + // is null, then we don't have to do anything + Object lastArgument = argumentArray[argumentArray.length - 1]; + if (lastArgument != null && !lastArgument.getClass().isArray()) { + // no array so wrap it + Object wrapped = makeCommonArray(argumentArray, paramTypes.length - 1, vargsClass); + Object[] newArgs = new Object[paramTypes.length]; + System.arraycopy(argumentArray, 0, newArgs, 0, paramTypes.length - 1); + newArgs[newArgs.length - 1] = wrapped; + return newArgs; + } else { + // we may have to box the argument! + return argumentArray; + } + } else if (argumentArray.length > paramTypes.length) { + // the number of arguments is too big, wrap all exceeding elements + // in an array, but keep the old elements that are no vargs + Object[] newArgs = new Object[paramTypes.length]; + // copy arguments that are not a varg + System.arraycopy(argumentArray, 0, newArgs, 0, paramTypes.length - 1); + // create a new array for the vargs and copy them + Object vargs = makeCommonArray(argumentArray, paramTypes.length - 1, vargsClass); + newArgs[newArgs.length - 1] = vargs; + return newArgs; + } else { + throw new GroovyBugError("trying to call a vargs method without enough arguments"); + } + } + + private static Object makeCommonArray(Object[] arguments, int offset, Class baseClass) { + Object[] result = (Object[]) Array.newInstance(baseClass, arguments.length - offset); + for (int i = offset; i < arguments.length; i++) { + Object v = arguments[i]; + v = DefaultTypeTransformation.castToType(v, baseClass); + result[i - offset] = v; + } + return result; + } + + public boolean isValidMethod(Class[] arguments) { + if (arguments == null) return true; + + final int size = arguments.length; + CachedClass[] pt = getParameterTypes(); + final int paramMinus1 = pt.length - 1; + + if (isVargsMethod && size >= paramMinus1) + return isValidVarargsMethod(arguments, size, pt, paramMinus1); + else if (pt.length == size) + return isValidExactMethod(arguments, pt); + else if (pt.length == 1 && size == 0 && !pt[0].isPrimitive) + return true; + return false; + } + + private static boolean isValidExactMethod(Class[] arguments, CachedClass[] pt) { + // lets check the parameter types match + int size = pt.length; + for (int i = 0; i < size; i++) { + if (!pt[i].isAssignableFrom(arguments[i])) { + return false; + } + } + return true; + } + + public boolean isValidExactMethod(Object[] args) { + // lets check the parameter types match + getParametersTypes0(); + int size = args.length; + if (size != parameterTypes.length) + return false; + + for (int i = 0; i < size; i++) { + if (args[i] != null && !parameterTypes[i].isAssignableFrom(args[i].getClass())) { + return false; + } + } + return true; + } + + public boolean isValidExactMethod(Class[] args) { + // lets check the parameter types match + getParametersTypes0(); + int size = args.length; + if (size != parameterTypes.length) + return false; + + for (int i = 0; i < size; i++) { + if (args[i] != null && !parameterTypes[i].isAssignableFrom(args[i])) { + return false; + } + } + return true; + } + + private static boolean testComponentAssignable(Class toTestAgainst, Class toTest) { + Class component = toTest.getComponentType(); + if (component == null) return false; + return MetaClassHelper.isAssignableFrom(toTestAgainst, component); + } + + private static boolean isValidVarargsMethod(Class[] arguments, int size, CachedClass[] pt, int paramMinus1) { + // first check normal number of parameters + for (int i = 0; i < paramMinus1; i++) { + if (pt[i].isAssignableFrom(arguments[i])) continue; + return false; + } + + // check direct match + CachedClass varg = pt[paramMinus1]; + Class clazz = varg.getTheClass().getComponentType(); + if (size == pt.length && + (varg.isAssignableFrom(arguments[paramMinus1]) || + testComponentAssignable(clazz, arguments[paramMinus1]))) { + return true; + } + + // check varged + for (int i = paramMinus1; i < size; i++) { + if (MetaClassHelper.isAssignableFrom(clazz, arguments[i])) continue; + return false; + } + return true; + } + + public boolean isValidMethod(Object[] arguments) { + if (arguments == null) return true; + + final int size = arguments.length; + CachedClass[] paramTypes = getParameterTypes(); + final int paramMinus1 = paramTypes.length - 1; + + if (size >= paramMinus1 && paramTypes.length > 0 && + paramTypes[(paramMinus1)].isArray) { + // first check normal number of parameters + for (int i = 0; i < paramMinus1; i++) { + if (paramTypes[i].isAssignableFrom(getArgClass(arguments[i]))) continue; + return false; + } + + + // check direct match + CachedClass varg = paramTypes[paramMinus1]; + Class clazz = varg.getTheClass().getComponentType(); + if (size == paramTypes.length && + (varg.isAssignableFrom(getArgClass(arguments[paramMinus1])) || + testComponentAssignable(clazz, getArgClass(arguments[paramMinus1])))) { + return true; + } + + + // check varged + for (int i = paramMinus1; i < size; i++) { + if (MetaClassHelper.isAssignableFrom(clazz, getArgClass(arguments[i]))) continue; + return false; + } + return true; + } else if (paramTypes.length == size) { + // lets check the parameter types match + for (int i = 0; i < size; i++) { + if (paramTypes[i].isAssignableFrom(getArgClass(arguments[i]))) continue; + return false; + } + return true; + } else if (paramTypes.length == 1 && size == 0 && !paramTypes[0].isPrimitive) { + return true; + } + return false; + } + + private static Class getArgClass(Object arg) { + Class cls; + if (arg == null) { + cls = null; + } else { + if (arg instanceof Wrapper) { + cls = ((Wrapper) arg).getType(); + } else + cls = arg.getClass(); + } + return cls; + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/a188738d/src/main/java/org/codehaus/groovy/reflection/ReflectionCache.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/reflection/ReflectionCache.java b/src/main/java/org/codehaus/groovy/reflection/ReflectionCache.java new file mode 100644 index 0000000..372a0a5 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/reflection/ReflectionCache.java @@ -0,0 +1,113 @@ +/* + * 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 org.codehaus.groovy.util.TripleKeyHashMap; + +import java.util.HashMap; +import java.util.Map; + +public class ReflectionCache { + private static final Map primitiveTypesMap = new HashMap(); + + static { + primitiveTypesMap.put(byte.class, Byte.class); + primitiveTypesMap.put(boolean.class, Boolean.class); + primitiveTypesMap.put(char.class, Character.class); + primitiveTypesMap.put(double.class, Double.class); + primitiveTypesMap.put(float.class, Float.class); + primitiveTypesMap.put(int.class, Integer.class); + primitiveTypesMap.put(long.class, Long.class); + primitiveTypesMap.put(short.class, Short.class); + } + + public static Class autoboxType(Class type) { + final Class res = (Class) primitiveTypesMap.get(type); + return res == null ? type : res; + } + + static TripleKeyHashMap mopNames = new TripleKeyHashMap(); + + public static String getMOPMethodName(CachedClass declaringClass, String name, boolean useThis) { + TripleKeyHashMap.Entry mopNameEntry = mopNames.getOrPut(declaringClass, name, Boolean.valueOf(useThis)); + if (mopNameEntry.value == null) { + mopNameEntry.value = new StringBuffer().append(useThis ? "this$" : "super$").append(declaringClass.getSuperClassDistance()).append("$").append(name).toString(); + } + return (String) mopNameEntry.value; + } + + static final CachedClass STRING_CLASS = getCachedClass(String.class); + + public static boolean isArray(Class klazz) { + return klazz.getName().charAt(0) == '['; + } + + static void setAssignableFrom(Class klazz, Class aClass) { +// SoftDoubleKeyMap.Entry val = (SoftDoubleKeyMap.Entry) assignableMap.getOrPut(klazz, aClass, null); +// if (val.getValue() == null) { +// val.setValue(Boolean.TRUE); +// } + } + + public static boolean isAssignableFrom(Class klazz, Class aClass) { + if (klazz == aClass) + return true; + +// SoftDoubleKeyMap.Entry val = (SoftDoubleKeyMap.Entry) assignableMap.getOrPut(klazz, aClass, null); +// if (val.getValue() == null) { +// val.setValue(Boolean.valueOf(klazz.isAssignableFrom(aClass))); +// } +// return ((Boolean)val.getValue()).booleanValue(); + return klazz.isAssignableFrom(aClass); + } + + static boolean arrayContentsEq(Object[] a1, Object[] a2) { + if (a1 == null) { + return a2 == null || a2.length == 0; + } + + if (a2 == null) { + return a1.length == 0; + } + + if (a1.length != a2.length) { + return false; + } + + for (int i = 0; i < a1.length; i++) { + if (a1[i] != a2[i]) { + return false; + } + } + + return true; + } + + public static final CachedClass OBJECT_CLASS = getCachedClass(Object.class); + + public static final CachedClass OBJECT_ARRAY_CLASS = getCachedClass(Object[].class); + + public static CachedClass getCachedClass(Class klazz) { + if (klazz == null) + return null; + + return ClassInfo.getClassInfo(klazz).getCachedClass (); + } + +}
