/*
* Copyright (C) 2011 Red Hat, Inc. and/or its affiliates.
*
* 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.impl.gwt;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Stream;
import org.jboss.errai.codegen.DefModifiers;
import org.jboss.errai.codegen.Parameter;
import org.jboss.errai.codegen.builder.impl.Scope;
import org.jboss.errai.codegen.meta.MetaClass;
import org.jboss.errai.codegen.meta.MetaClassFactory;
import org.jboss.errai.codegen.meta.MetaConstructor;
import org.jboss.errai.codegen.meta.MetaField;
import org.jboss.errai.codegen.meta.MetaMethod;
import org.jboss.errai.codegen.meta.MetaTypeVariable;
import org.jboss.errai.codegen.meta.impl.AbstractMetaClass;
import org.jboss.errai.codegen.util.GWTPrivateMemberAccessor;
import org.jboss.errai.codegen.util.GenUtil;
import org.jboss.errai.codegen.util.PrivateAccessUtil;
import org.jboss.errai.common.rebind.CacheStore;
import org.jboss.errai.common.rebind.CacheUtil;
import org.jboss.errai.reflections.scanners.AbstractScanner;
import com.google.common.collect.Lists;
import com.google.gwt.core.ext.typeinfo.JArrayType;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.JConstructor;
import com.google.gwt.core.ext.typeinfo.JEnumType;
import com.google.gwt.core.ext.typeinfo.JField;
import com.google.gwt.core.ext.typeinfo.JGenericType;
import com.google.gwt.core.ext.typeinfo.JMethod;
import com.google.gwt.core.ext.typeinfo.JParameter;
import com.google.gwt.core.ext.typeinfo.JParameterizedType;
import com.google.gwt.core.ext.typeinfo.JType;
import com.google.gwt.core.ext.typeinfo.NotFoundException;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
/**
* @author Mike Brock <cbrock@redhat.com>
* @author Christian Sadilek <csadilek@redhat.com>
*/
public class GWTClass extends AbstractMetaClass<JType> {
protected final Annotation[] annotations;
protected TypeOracle oracle;
private String fqcn;
static {
GenUtil.addClassAlias(GWTClass.class);
PrivateAccessUtil.registerPrivateMemberAccessor("jsni", new GWTPrivateMemberAccessor());
}
protected GWTClass(final TypeOracle oracle, final JType classType, final boolean erased) {
super(classType);
this.oracle = oracle;
final JClassType classOrInterface = classType.isClassOrInterface();
if (classOrInterface != null) {
annotations = classOrInterface.getAnnotations();
}
else {
annotations = new Annotation[0];
}
if (classType.isTypeParameter() != null || classType.isWildcard() != null) {
throw new IllegalArgumentException("Cannot represent \"" + classType
+ "\" as a class. Try a different meta type such as GWTWildcardType or GWTTypeVariable.");
}
final JParameterizedType parameterizedType = classType.isParameterized();
if (!erased) {
if (parameterizedType != null) {
super.parameterizedType = new GWTParameterizedType(oracle, parameterizedType);
}
}
}
public static class GWTClassCache implements CacheStore {
private final Map<String, MetaClass> reloadableClasses = new ConcurrentHashMap<>();
// Classes in .jar files can't change between refreshes so we can hold on to them
private final Map<String, MetaClass> classesInJar = new ConcurrentHashMap<>();
@Override
public void clear() {
reloadableClasses.clear();
}
public void put(final String name, final MetaClass clazz) {
if (AbstractScanner.isInJar(name) && !name.contains("<")) {
classesInJar.put(name, clazz);
}
else {
reloadableClasses.put(name, clazz);
}
}
public MetaClass get(final String name) {
final MetaClass clazz = classesInJar.get(name);
if (clazz != null) {
if (AbstractScanner.isInJar(name)) {
return clazz;
}
else {
classesInJar.remove(name);
return null;
}
}
else {
return reloadableClasses.get(name);
}
}
}
final static GWTClassCache cache = CacheUtil.getCache(GWTClassCache.class);
public static MetaClass newInstance(final TypeOracle oracle, final JType type) {
MetaClass clazz = cache.get(type.getParameterizedQualifiedSourceName());
if (clazz == null) {
clazz = newUncachedInstance(oracle, type);
cache.put(type.getParameterizedQualifiedSourceName(), clazz);
}
return clazz;
}
public static MetaClass newInstance(final TypeOracle oracle, final String type) {
try {
return newUncachedInstance(oracle, oracle.getType(type));
}
catch (final NotFoundException e) {
return null;
}
}
public static MetaClass newUncachedInstance(final TypeOracle oracle, final JType type) {
return new GWTClass(oracle, type, false);
}
public static MetaClass newUncachedInstance(final TypeOracle oracle, final JType type, final boolean erased) {
return new GWTClass(oracle, type, erased);
}
public static MetaClass[] fromClassArray(final TypeOracle oracle, final JClassType[] classes) {
return Arrays.stream(classes).map(c -> newInstance(oracle, c)).toArray(s -> new MetaClass[s]);
}
public static Class<?>[] jParmToClass(final JParameter[] parms) throws ClassNotFoundException {
return Arrays.stream(parms).map(p -> getPrimitiveOrClass(p)).toArray(s -> new Class<?>[s]);
}
public static Class<?> getPrimitiveOrClass(final JParameter parm) {
final JType type = parm.getType();
final String name =
type.isArray() != null ? type.getJNISignature().replace("/", ".") : type.getQualifiedSourceName();
if (parm.getType().isPrimitive() != null) {
final char sig = parm.getType().isPrimitive().getJNISignature().charAt(0);
switch (sig) {
case 'Z':
return boolean.class;
case 'B':
return byte.class;
case 'C':
return char.class;
case 'D':
return double.class;
case 'F':
return float.class;
case 'I':
return int.class;
case 'J':
return long.class;
case 'S':
return short.class;
case 'V':
return void.class;
default:
return null;
}
}
else {
try {
return Class.forName(name, false, Thread.currentThread().getContextClassLoader());
}
catch (final ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
}
@Override
public String getName() {
return getEnclosedMetaObject().getSimpleSourceName();
}
@Override
public String getFullyQualifiedName() {
if (fqcn != null) {
return fqcn;
}
if (isArray()) {
if (getOuterComponentType().isPrimitive()) {
fqcn = getInternalName();
}
else {
fqcn = getInternalName().replace('/', '.');
}
}
else {
fqcn = getEnclosedMetaObject().getQualifiedBinaryName();
}
return fqcn;
}
@Override
public String getCanonicalName() {
return getEnclosedMetaObject().getQualifiedSourceName();
}
@Override
public String getInternalName() {
return getEnclosedMetaObject().getJNISignature();
}
private String _packageName = null;
@Override
public String getPackageName() {
if (_packageName != null) {
return _packageName;
}
_packageName = getEnclosedMetaObject().isClassOrInterface().getPackage().getName();
return _packageName;
}
private static MetaMethod[] fromMethodArray(final TypeOracle oracle, final JMethod[] methods) {
return Arrays.stream(methods).map(m -> new GWTMethod(oracle, m)).toArray(s -> new MetaMethod[s]);
}
private List<MetaMethod> getSpecialTypeMethods() {
final List<MetaMethod> meths = new ArrayList<>();
final JEnumType type = getEnclosedMetaObject().isEnum();
if (type != null) {
meths.add(new GWTSpecialMethod(this, DefModifiers.none(), Scope.Public, String.class, "name"));
meths.add(new GWTSpecialMethod(this, DefModifiers.none(), Scope.Public, Enum.class, "valueOf", Parameter.of(
String.class, "p").getMetaParameter()));
meths.add(new GWTSpecialMethod(this, DefModifiers.none(), Scope.Public, Enum[].class, "values"));
}
return meths;
}
// TODO report this to be fixed in GWT: getClass() in java.lang.Object is reported as non-final method.
private static final List<MetaMethod> overrideMethods =
Arrays.asList(MetaClassFactory.get(Object.class).getMethods());
private MetaMethod[] _methodsCache = null;
@Override
public MetaMethod[] getMethods() {
if (_methodsCache != null) {
return _methodsCache;
}
final Set<MetaMethod> meths = new LinkedHashSet<>();
meths.addAll(getSpecialTypeMethods());
JClassType type = getEnclosedMetaObject().isClassOrInterface();
if (type == null) {
return null;
}
final Set<String> processedMethods = new HashSet<>();
do {
for (final JMethod jMethod : type.getMethods()) {
final GWTMethod gwtMethod = new GWTMethod(oracle, jMethod);
final String readableMethodDecl = GenUtil.getMethodString(gwtMethod);
if (!jMethod.isPrivate() && !processedMethods.contains(readableMethodDecl)) {
meths.add(gwtMethod);
processedMethods.add(readableMethodDecl);
}
}
for (final JClassType interfaceType : type.getImplementedInterfaces()) {
for (final MetaMethod ifaceMethod : Arrays.asList(GWTClass.newInstance(oracle, interfaceType).getMethods())) {
final String readableMethodDecl = GenUtil.getMethodString(ifaceMethod);
if (!processedMethods.contains(readableMethodDecl)) {
meths.add(ifaceMethod);
processedMethods.add(readableMethodDecl);
}
}
}
}
while ((type = type.getSuperclass()) != null && !type.getQualifiedSourceName().equals("java.lang.Object"));
meths.addAll(overrideMethods);
_methodsCache = meths.toArray(new MetaMethod[meths.size()]);
return _methodsCache;
}
@Override
public MetaMethod[] getDeclaredMethods() {
final JClassType type = getEnclosedMetaObject().isClassOrInterface();
if (type == null) {
return null;
}
return fromMethodArray(oracle, type.getMethods());
}
@Override
public MetaClass getErased() {
if (getParameterizedType() == null) {
return this;
}
else {
return new GWTClass(oracle, getEnclosedMetaObject().getErasedType(), true);
}
}
@Override
public MetaField[] getFields() {
final List<MetaField> fields = Lists.newArrayList();
JClassType type = getEnclosedMetaObject().isClass();
while (type != null) {
for (final JField field : type.getFields()) {
// In GWT 2.7 java.lang.Object contains two public fields castableTypeMap and typeMarker that we don't want.
if (field.isPublic() && !field.getEnclosingType().getQualifiedSourceName().equals("java.lang.Object")) {
fields.add(new GWTField(oracle, field));
}
}
type = type.getSuperclass();
}
return fields.toArray(new MetaField[fields.size()]);
}
@Override
public MetaField[] getDeclaredFields() {
final JClassType type = getEnclosedMetaObject().isClass();
if (type != null) {
return Arrays.stream(type.getFields()).map(f -> new GWTField(oracle, f)).toArray(s -> new MetaField[s]);
}
return new MetaField[0];
}
@Override
public MetaField getField(final String name) {
JClassType type = getEnclosedMetaObject().isClassOrInterface();
if (type == null) {
if ("length".equals(name) && getEnclosedMetaObject().isArray() != null) {
return new MetaField.ArrayLengthMetaField(this);
}
return null;
}
JField field = type.findField(name);
while ((field == null || (field != null && !field.isPublic())) &&
(type = type.getSuperclass()) != null && !type.getQualifiedSourceName().equals("java.lang.Object")) {
field = type.findField(name);
for (final JClassType interfaceType : type.getImplementedInterfaces()) {
field = interfaceType.findField(name);
}
}
if (field == null) {
throw new RuntimeException("no such field: " + name + " in class: " + this);
}
return new GWTField(oracle, field);
}
@Override
public MetaField getDeclaredField(final String name) {
final JClassType type = getEnclosedMetaObject().isClassOrInterface();
if (type == null) {
if ("length".equals(name) && getEnclosedMetaObject().isArray() != null) {
return new MetaField.ArrayLengthMetaField(this);
}
return null;
}
final JField field = type.findField(name);
if (field == null) {
return null;
}
return new GWTField(oracle, field);
}
private static MetaConstructor[] fromMethodArray(final TypeOracle oracle, final Stream<JConstructor> constructors) {
return constructors.map(c -> new GWTConstructor(oracle, c)).toArray(s -> new MetaConstructor[s]);
}
@Override
public MetaConstructor[] getConstructors() {
final JClassType type = getEnclosedMetaObject().isClassOrInterface();
if (type == null) {
return null;
}
return fromMethodArray(oracle,
Arrays
.stream(type.getConstructors())
.filter(ctor -> ctor.isPublic()));
}
@Override
public MetaConstructor[] getDeclaredConstructors() {
final JClassType type = getEnclosedMetaObject().isClassOrInterface();
if (type == null) {
return null;
}
return fromMethodArray(oracle, Arrays.stream(type.getConstructors()));
}
@Override
public MetaClass[] getDeclaredClasses() {
final JClassType[] nestedTypes = getEnclosedMetaObject().isClassOrInterface().getNestedTypes();
final MetaClass[] declaredClasses = new MetaClass[nestedTypes.length];
int i = 0;
for (final JClassType type : nestedTypes) {
declaredClasses[i++] = GWTClass.newInstance(oracle, type);
}
return declaredClasses;
}
private MetaClass[] _intefacesCache = null;
@Override
public MetaClass[] getInterfaces() {
if (_intefacesCache != null) {
return _intefacesCache;
}
final JClassType jClassType = getEnclosedMetaObject().isClassOrInterface();
if (jClassType == null)
return new MetaClass[0];
return _intefacesCache = Arrays.stream(jClassType.getImplementedInterfaces())
.map(i -> new GWTClass(oracle, i, false)).toArray(s -> new MetaClass[s]);
}
@Override
public boolean isArray() {
return getEnclosedMetaObject().isArray() != null;
}
@Override
public MetaClass getSuperClass() {
JClassType type = getEnclosedMetaObject().isClassOrInterface();
if (type == null) {
return null;
}
type = type.getSuperclass();
if (type == null) {
return null;
}
return newInstance(oracle, type);
}
@Override
public MetaClass getComponentType() {
final JArrayType type = getEnclosedMetaObject().isArray();
if (type == null) {
return null;
}
return newUncachedInstance(oracle, type.getComponentType());
}
@Override
public Annotation[] getAnnotations() {
return annotations;
}
@Override
public MetaTypeVariable[] getTypeParameters() {
final JGenericType genericType;
if (getEnclosedMetaObject().isGenericType() != null) {
genericType = getEnclosedMetaObject().isGenericType();
}
else if (getEnclosedMetaObject().isParameterized() != null) {
genericType = getEnclosedMetaObject().isParameterized().getBaseType();
}
else if (getEnclosedMetaObject().isRawType() != null) {
genericType = getEnclosedMetaObject().isRawType().getGenericType();
}
else {
return new MetaTypeVariable[0];
}
return Arrays.stream(genericType.getTypeParameters())
.map(p -> new GWTTypeVariable(oracle, p)).toArray(s -> new MetaTypeVariable[s]);
}
@Override
public boolean isVoid() {
return getEnclosedMetaObject().getSimpleSourceName().equals("void");
}
@Override
public boolean isPrimitive() {
return getEnclosedMetaObject().isPrimitive() != null;
}
@Override
public boolean isInterface() {
return getEnclosedMetaObject().isInterface() != null;
}
@Override
public boolean isAbstract() {
return getEnclosedMetaObject().isClass() != null && getEnclosedMetaObject().isClass().isAbstract();
}
@Override
public boolean isEnum() {
return getEnclosedMetaObject().isEnum() != null;
}
@Override
public boolean isAnnotation() {
return getEnclosedMetaObject().isAnnotation() != null;
}
@Override
public boolean isPublic() {
return getEnclosedMetaObject().isClassOrInterface() != null &&
getEnclosedMetaObject().isClassOrInterface().isPublic();
}
@Override
public boolean isPrivate() {
return getEnclosedMetaObject().isClassOrInterface() != null &&
getEnclosedMetaObject().isClassOrInterface().isPrivate();
}
@Override
public boolean isProtected() {
return getEnclosedMetaObject().isClassOrInterface() != null &&
getEnclosedMetaObject().isClassOrInterface().isProtected();
}
@Override
public boolean isFinal() {
return getEnclosedMetaObject().isClassOrInterface() != null &&
getEnclosedMetaObject().isClassOrInterface().isFinal();
}
@Override
public boolean isStatic() {
return getEnclosedMetaObject().isClassOrInterface() != null &&
getEnclosedMetaObject().isClassOrInterface().isStatic();
}
@Override
public boolean isSynthetic() {
return false;
}
@Override
public boolean isAnonymousClass() {
return false;
}
@Override
public MetaClass asArrayOf(final int dimensions) {
JType type = getEnclosedMetaObject();
for (int i = 0; i < dimensions; i++) {
type = oracle.getArrayType(type);
}
return new GWTClass(oracle, type, false);
}
}