/*
* Copyright 2011 JBoss, by Red Hat, Inc
*
* Licensed 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.jboss.errai.codegen.meta;
import java.io.File;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.enterprise.util.TypeLiteral;
import org.jboss.errai.codegen.BlockStatement;
import org.jboss.errai.codegen.Context;
import org.jboss.errai.codegen.DefParameters;
import org.jboss.errai.codegen.Parameter;
import org.jboss.errai.codegen.Statement;
import org.jboss.errai.codegen.ThrowsDeclaration;
import org.jboss.errai.codegen.builder.callstack.LoadClassReference;
import org.jboss.errai.codegen.meta.impl.build.BuildMetaClass;
import org.jboss.errai.codegen.meta.impl.build.BuildMetaConstructor;
import org.jboss.errai.codegen.meta.impl.build.BuildMetaField;
import org.jboss.errai.codegen.meta.impl.build.BuildMetaMethod;
import org.jboss.errai.codegen.meta.impl.build.BuildMetaParameterizedType;
import org.jboss.errai.codegen.meta.impl.build.ShadowBuildMetaField;
import org.jboss.errai.codegen.meta.impl.build.ShadowBuildMetaMethod;
import org.jboss.errai.codegen.meta.impl.java.JavaReflectionClass;
import org.jboss.errai.codegen.util.ClassChangeUtil;
import org.jboss.errai.codegen.util.EmptyStatement;
import org.jboss.errai.codegen.util.GenUtil;
import org.jboss.errai.common.metadata.RebindUtils;
import org.jboss.errai.common.rebind.CacheUtil;
import org.mvel2.ConversionHandler;
import org.mvel2.DataConversion;
/**
* @author Mike Brock <cbrock@redhat.com>
*/
public final class MetaClassFactory {
static {
DataConversion.addConversionHandler(Class.class, new ConversionHandler() {
@Override
public Object convertFrom(final Object in) {
if (MetaClass.class.isAssignableFrom(in.getClass())) {
return ((MetaClass) in).asClass();
}
else {
throw new RuntimeException("cannot convert from " + in.getClass() + "; to " + Class.class.getName());
}
}
@Override
public boolean canConvertFrom(final Class cls) {
return MetaType.class.isAssignableFrom(cls);
}
});
DataConversion.addConversionHandler(Class[].class, new ConversionHandler() {
@Override
public Object convertFrom(final Object in) {
final int length = Array.getLength(in);
final Class[] cls = new Class[length];
for (int i = 0; i < length; i++) {
final Object el = Array.get(in, i);
if (el instanceof MetaClass) {
cls[i] = ((MetaClass) el).asClass();
}
else if (el != null && el.getClass().isArray()) {
cls[i] = (Class) convertFrom(el);
}
else {
cls[i] = null;
}
}
return cls;
}
@Override
public boolean canConvertFrom(Class cls) {
while (cls.isArray()) {
cls = cls.getComponentType();
}
return MetaClass.class.isAssignableFrom(cls) || MetaType.class.isAssignableFrom(cls);
}
});
}
public static MetaClassCache getMetaClassCache() {
return CacheUtil.getCache(MetaClassCache.class);
}
public static MetaClass get(final String fullyQualifiedClassName, final boolean erased) {
return createOrGet(fullyQualifiedClassName, erased);
}
public static MetaClass get(final String fullyQualifiedClassName) {
return createOrGet(fullyQualifiedClassName);
}
public static MetaClass get(final Class<?> clazz) {
if (clazz == null)
return null;
return createOrGet(clazz, null);
}
public static MetaClass getArrayOf(final Class<?> clazz, final int dims) {
final int[] da = new int[dims];
for (int i = 0; i < da.length; i++) {
da[i] = 0;
}
return getArrayOf(clazz, da);
}
public static MetaClass getArrayOf(final Class<?> clazz, int... dims) {
if (dims.length == 0) {
dims = new int[1];
}
return JavaReflectionClass.newInstance(Array.newInstance(clazz, dims).getClass());
}
public static MetaClass get(final Class<?> clazz, final Type type) {
return createOrGet(clazz, type);
}
public static MetaClass get(final TypeLiteral<?> literal) {
return createOrGet(literal);
}
public static MetaMethod get(final Method method) {
return get(method.getDeclaringClass()).getDeclaredMethod(method.getName(), method.getParameterTypes());
}
public static MetaField get(final Field field) {
return get(field.getDeclaringClass()).getDeclaredField(field.getName());
}
public static Statement getAsStatement(final Class<?> clazz) {
return getAsStatement(createOrGet(clazz.getName(), false));
}
public static Statement getAsStatement(final MetaClass metaClass) {
return new Statement() {
@Override
public String generate(final Context context) {
return LoadClassReference.getClassReference(metaClass, context);
}
@Override
public MetaClass getType() {
return MetaClassFactory.get(Class.class);
}
};
}
public static boolean isCached(final String name) {
return getMetaClassCache().isKnownErasedType(name);
}
private static MetaClass createOrGet(final String fullyQualifiedClassName) {
if (!getMetaClassCache().isKnownErasedType(fullyQualifiedClassName)) {
return createOrGet(fullyQualifiedClassName, false);
}
return getMetaClassCache().getErased(fullyQualifiedClassName);
}
private static MetaClass createOrGet(final TypeLiteral type) {
if (type == null)
return null;
if (!getMetaClassCache().isKnownErasedType(type.toString())) {
final MetaClass gwtClass = JavaReflectionClass.newUncachedInstance(type);
addLookups(type, gwtClass);
return gwtClass;
}
return getMetaClassCache().getErased(type.toString());
}
private static MetaClass createOrGet(final String clsName, final boolean erased) {
if (clsName == null)
return null;
MetaClass mCls;
if (erased) {
mCls = getMetaClassCache().getErased(clsName);
if (mCls == null) {
getMetaClassCache().pushErasedCache(clsName, mCls = JavaReflectionClass.newUncachedInstance(loadClass(clsName), erased));
}
}
else {
mCls = getMetaClassCache().get(clsName);
if (mCls == null) {
getMetaClassCache().pushCache(clsName, mCls = JavaReflectionClass.newUncachedInstance(loadClass(clsName), erased));
}
}
return mCls;
}
private static MetaClass createOrGet(final Class cls, final Type type) {
if (cls == null)
return null;
if (type != null) {
if (cls.getTypeParameters() != null) {
return JavaReflectionClass.newUncachedInstance(cls, type);
}
if (!getMetaClassCache().isKnownErasedType(cls.getName())) {
final MetaClass javaReflectionClass = JavaReflectionClass.newUncachedInstance(cls, type);
addLookups(cls, javaReflectionClass);
return javaReflectionClass;
}
return getMetaClassCache().getErased(cls.getName());
}
else {
MetaClass mCls;
mCls = getMetaClassCache().get(cls.getName());
if (mCls == null) {
getMetaClassCache().pushCache(cls.getName(), mCls = JavaReflectionClass.newUncachedInstance(cls));
}
return mCls;
}
}
public static MetaClass parameterizedAs(final Class clazz, final MetaParameterizedType parameterizedType) {
return parameterizedAs(MetaClassFactory.get(clazz), parameterizedType);
}
public static MetaClass parameterizedAs(final MetaClass clazz, final MetaParameterizedType parameterizedType) {
return parameterizedAs(clazz, parameterizedType, true);
}
public static MetaClass parameterizedAs(final MetaClass clazz,
final MetaParameterizedType parameterizedType,
final boolean reifyRecursively) {
return cloneToBuildMetaClass(clazz, parameterizedType, reifyRecursively);
}
private static BuildMetaClass cloneToBuildMetaClass(final MetaClass clazz) {
return cloneToBuildMetaClass(clazz, null, false);
}
private static BuildMetaClass cloneToBuildMetaClass(final MetaClass clazz,
final MetaParameterizedType parameterizedType,
final boolean reifyRecursively) {
final BuildMetaClass buildMetaClass = new BuildMetaClass(Context.create(), clazz.getFullyQualifiedName());
buildMetaClass.setReifiedFormOf(clazz);
buildMetaClass.setAbstract(clazz.isAbstract());
buildMetaClass.setFinal(clazz.isFinal());
buildMetaClass.setStatic(clazz.isStatic());
buildMetaClass.setInterface(clazz.isInterface());
buildMetaClass.setInterfaces(Arrays.asList(clazz.getInterfaces()));
buildMetaClass.setScope(GenUtil.scopeOf(clazz));
buildMetaClass.setSuperClass(clazz.getSuperClass());
for (final MetaTypeVariable typeVariable : clazz.getTypeParameters()) {
buildMetaClass.addTypeVariable(typeVariable);
}
if (parameterizedType != null) {
buildMetaClass.setParameterizedType(parameterizedType);
}
else {
buildMetaClass.setParameterizedType(clazz.getParameterizedType());
}
for (final MetaField field : clazz.getDeclaredFields()) {
final BuildMetaField bmf = new ShadowBuildMetaField(buildMetaClass, EmptyStatement.INSTANCE,
GenUtil.scopeOf(field), field.getType(), field.getName(), field);
bmf.setFinal(field.isFinal());
bmf.setStatic(field.isStatic());
bmf.setVolatile(field.isVolatile());
bmf.setTransient(field.isTransient());
buildMetaClass.addField(bmf);
}
for (final MetaConstructor c : clazz.getDeclaredConstructors()) {
final BuildMetaConstructor newConstructor = new BuildMetaConstructor(buildMetaClass, EmptyStatement.INSTANCE,
GenUtil.scopeOf(c),
DefParameters.from(c));
newConstructor.setReifiedFormOf(c);
buildMetaClass.addConstructor(newConstructor);
}
for (final MetaMethod method : clazz.getDeclaredMethods()) {
MetaClass returnType = method.getReturnType();
if (method.getGenericReturnType() instanceof MetaTypeVariable) {
final MetaTypeVariable typeVariable = (MetaTypeVariable) method.getGenericReturnType();
final MetaClass tVarVal = getTypeVariableValue(typeVariable, buildMetaClass);
if (tVarVal != null) {
returnType = tVarVal;
}
}
else if (method.getGenericReturnType() instanceof MetaParameterizedType) {
final MetaParameterizedType metaParameterizedType
= (MetaParameterizedType) method.getGenericReturnType();
final List<MetaType> typeVarValues = new ArrayList<MetaType>();
boolean defaultOnly = true;
for (final MetaType metaType : metaParameterizedType.getTypeParameters()) {
if (metaType instanceof MetaTypeVariable) {
final MetaTypeVariable typeVariable = (MetaTypeVariable) metaType;
final MetaClass tVarVar = getTypeVariableValue(typeVariable, buildMetaClass);
if (tVarVar != null) {
defaultOnly = false;
typeVarValues.add(tVarVar);
}
else {
typeVarValues.add(MetaClassFactory.get(Object.class));
}
}
}
if (reifyRecursively && !defaultOnly) {
returnType = parameterizedAs(returnType, typeParametersOf(typeVarValues.toArray(new MetaType[typeVarValues.size()])), false);
}
}
final List<Parameter> parameters = new ArrayList<Parameter>();
int i = 0;
for (final MetaParameter parm : method.getParameters()) {
MetaClass parmType = null;
if (method.getGenericParameterTypes() != null) {
if (method.getGenericParameterTypes()[i] instanceof MetaTypeVariable) {
final MetaTypeVariable typeVariable = (MetaTypeVariable) method.getGenericParameterTypes()[i];
final MetaClass tVarVal = getTypeVariableValue(typeVariable, buildMetaClass);
if (tVarVal != null) {
parmType = tVarVal;
}
}
}
if (parmType == null) {
parmType = parm.getType();
}
parameters.add(Parameter.of(parmType, parm.getName()));
i++;
}
final BuildMetaMethod newMethod = new ShadowBuildMetaMethod(buildMetaClass, BlockStatement.EMPTY_BLOCK,
GenUtil.scopeOf(method), GenUtil.modifiersOf(method), method.getName(), returnType,
DefParameters.fromParameters(parameters), ThrowsDeclaration.of(method.getCheckedExceptions()), method);
newMethod.setReifiedFormOf(method);
buildMetaClass.addMethod(newMethod);
}
return buildMetaClass;
}
private static MetaClass getTypeVariableValue(final MetaTypeVariable typeVariable, final MetaClass clazz) {
int idx = -1;
final MetaTypeVariable[] typeVariables = clazz.getTypeParameters();
for (int i = 0; i < typeVariables.length; i++) {
if (typeVariables[i].getName().equals(typeVariable.getName())) {
idx = i;
break;
}
}
if (idx != -1) {
final MetaType type = clazz.getParameterizedType().getTypeParameters()[idx];
if (type instanceof MetaClass) {
return (MetaClass) type;
}
}
return null;
}
public static MetaParameterizedType typeParametersOf(final Object... classes) {
final MetaType[] types = new MetaType[classes.length];
int i = 0;
for (final Object o : classes) {
if (o instanceof Class) {
types[i++] = MetaClassFactory.get((Class) o);
}
else if (o instanceof TypeLiteral) {
types[i++] = MetaClassFactory.get((TypeLiteral) o);
}
else if (o instanceof MetaType) {
types[i++] = (MetaType) o;
}
else {
throw new RuntimeException("not a recognized type reference: " + o.getClass().getName());
}
}
return typeParametersOf(types);
}
public static MetaParameterizedType typeParametersOf(final Class<?>... classes) {
return typeParametersOf(fromClassArray(classes));
}
public static MetaParameterizedType typeParametersOf(final MetaType... classes) {
return new BuildMetaParameterizedType(classes, null, null);
}
private static void addLookups(final TypeLiteral literal, final MetaClass metaClass) {
getMetaClassCache().pushErasedCache(literal.toString(), metaClass);
}
private static void addLookups(final Class cls, final MetaClass metaClass) {
getMetaClassCache().pushErasedCache(cls.getName(), metaClass);
}
public static final Map<String, Class<?>> PRIMITIVE_LOOKUP = Collections.unmodifiableMap(new HashMap<String, Class<?>>() {
{
put("void", void.class);
put("boolean", boolean.class);
put("int", int.class);
put("long", long.class);
put("float", float.class);
put("double", double.class);
put("short", short.class);
put("byte", byte.class);
put("char", char.class);
}
});
public static Class<?> loadClass(final String fullyQualifiedName) {
try {
Class<?> cls = PRIMITIVE_LOOKUP.get(fullyQualifiedName);
if (cls == null) {
cls = Class.forName(fullyQualifiedName, false, Thread.currentThread().getContextClassLoader());
}
return cls;
}
catch (ClassNotFoundException e) {
final URL url = MetaClassFactory.class.getClassLoader()
.getResource(fullyQualifiedName.replace('.', '/') + ".java");
if (url != null) {
final File sourceFile = new File(url.getFile()).getAbsoluteFile();
if (sourceFile.exists()) {
final File directory = sourceFile.getParentFile();
final String packageName = fullyQualifiedName.substring(0, fullyQualifiedName.lastIndexOf('.'));
final String className = fullyQualifiedName.substring(fullyQualifiedName.lastIndexOf('.') + 1);
final String location = ClassChangeUtil.compileClass(directory.getAbsolutePath(),
packageName,
className,
RebindUtils.getTempDirectory() + "/hotload/");
try {
return ClassChangeUtil.loadClassDefinition(location,
packageName, className);
}
catch (Exception e2) {
throw new RuntimeException("Could not load class: " + fullyQualifiedName, e2);
}
}
}
throw new RuntimeException("Could not load class: " + fullyQualifiedName);
}
}
public static boolean canLoadClass(final String fullyQualifiedName) {
try {
final Class cls = loadClass(fullyQualifiedName);
return cls != null;
}
catch (Throwable t) {
return false;
}
}
public static MetaClass[] fromClassArray(final Class<?>[] classes) {
final MetaClass[] newClasses = new MetaClass[classes.length];
for (int i = 0; i < classes.length; i++) {
newClasses[i] = createOrGet(classes[i].getName(), false);
}
return newClasses;
}
public static MetaClass[] asMetaClassArray(final MetaParameter[] parms) {
final MetaClass[] type = new MetaClass[parms.length];
for (int i = 0; i < parms.length; i++) {
type[i] = parms[i].getType();
}
return type;
}
public static Class<?>[] asClassArray(final MetaParameter[] parms) {
final MetaType[] type = new MetaType[parms.length];
for (int i = 0; i < parms.length; i++) {
type[i] = parms[i].getType();
}
return asClassArray(type);
}
public static Class<?>[] asClassArray(final MetaType[] cls) {
final Class<?>[] newClasses = new Class<?>[cls.length];
for (int i = 0; i < cls.length; i++) {
if (cls[i] instanceof MetaParameterizedType) {
newClasses[i] = ((MetaClass) ((MetaParameterizedType) cls[i]).getRawType()).asClass();
}
else {
newClasses[i] = ((MetaClass) cls[i]).asClass();
}
}
return newClasses;
}
public static Collection<MetaClass> getAllNewOrUpdatedClasses() {
return getMetaClassCache().getAllNewOrUpdated();
}
public static Collection<MetaClass> getNewClasses() {
return getMetaClassCache().getAllNewClasses();
}
public static boolean isChangedOrDeleted(String fqcn) {
return getMetaClassCache().getAllDeletedClasses().contains(fqcn) || getMetaClassCache().isNewOrUpdated(fqcn);
}
public static Set<String> getAllDeletedClasses() {
return getMetaClassCache().getAllDeletedClasses();
}
public static Collection<MetaClass> getAllCachedClasses() {
return getMetaClassCache().getAllCached();
}
public static boolean isKnownType(String fqcn) {
return getMetaClassCache().isKnownType(fqcn);
}
public static boolean hasAnyChanges() {
return !getAllNewOrUpdatedClasses().isEmpty() || !getAllDeletedClasses().isEmpty();
}
}