/**
* Copyright 2011-2015 John Ericksen
*
* 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.androidtransfuse.adapter.classes;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import org.androidtransfuse.adapter.*;
import org.androidtransfuse.util.TransfuseRuntimeException;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.lang.annotation.*;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Map;
/**
* Factory building AST objects from the relevant class attributes
*
* @author John Ericksen
*/
@Singleton
public class ASTClassFactory {
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface ASTParameterName{
String value();
}
private final Map<String, ASTType> typeCache = new HashMap<String, ASTType>();
@Inject
public ASTClassFactory() {
//seed with primitives and void
typeCache.put(ASTVoidType.VOID.getName(), ASTVoidType.VOID);
for (ASTPrimitiveType primitive : ASTPrimitiveType.values()) {
typeCache.put(primitive.getName(), primitive);
}
}
/**
* Build an ASTType from the given class
*
* @param clazz input Class
* @return ASTType representing input class
*/
public ASTType getType(Class<?> clazz) {
return getType(clazz, null);
}
public synchronized ASTType getType(Class<?> clazz, Type genericType) {
if(clazz.isArray()){
return new ASTArrayType(getType(clazz.getComponentType(), genericType));
}
if (!typeCache.containsKey(clazz.getName())) {
typeCache.put(clazz.getName(), buildType(clazz));
}
ASTType astType = typeCache.get(clazz.getName());
//wrap with a parametrized type
if (genericType instanceof ParameterizedType) {
astType = new ASTGenericTypeWrapper(astType, new LazyParametrizedTypeParameterBuilder(astType, (ParameterizedType) genericType, this));
} else if (genericType instanceof TypeVariable) {
astType = new ASTGenericParameterType(new ASTGenericArgument(((TypeVariable) genericType).getName()), astType);
}
return astType;
}
private ASTType buildType(Class<?> clazz) {
ImmutableList.Builder<ASTGenericArgument> genericArgumentsBuilder = ImmutableList.builder();
ImmutableSet.Builder<ASTConstructor> constructorBuilder = ImmutableSet.builder();
ImmutableSet.Builder<ASTMethod> methodBuilder = ImmutableSet.builder();
ImmutableSet.Builder<ASTField> fieldBuilder = ImmutableSet.builder();
ImmutableSet.Builder<ASTType> interfaceBuilder = ImmutableSet.builder();
ImmutableSet.Builder<ASTAnnotation> annotationBuilder = ImmutableSet.builder();
ASTType superClass = null;
if (clazz.getSuperclass() != null) {
superClass = getType(clazz.getSuperclass(), clazz.getGenericSuperclass());
}
PackageClass packageClass = new PackageClass(clazz);
ASTTypeVirtualProxy astClassTypeProxy = new ASTTypeVirtualProxy(packageClass);
typeCache.put(clazz.getName(), astClassTypeProxy);
TypeVariable<? extends Class<?>>[] typeParameters = clazz.getTypeParameters();
for (TypeVariable<? extends Class<?>> typeParameter : typeParameters) {
genericArgumentsBuilder.add(new ASTGenericArgument(typeParameter.getName()));
}
Class<?>[] classInterfaces = clazz.getInterfaces();
Type[] classGenericInterfaces = clazz.getGenericInterfaces();
for (int i = 0; i < classInterfaces.length; i++) {
interfaceBuilder.add(getType(classInterfaces[i], classGenericInterfaces[i]));
}
//fill in the guts after building the class tree
//building after the class types have been defined avoids an infinite loop between
//defining classes and their attributes
for (Constructor constructor : clazz.getDeclaredConstructors()) {
if (!constructor.isSynthetic()) {
constructorBuilder.add(getConstructor(constructor,
clazz.isEnum(),
(clazz.getDeclaringClass() != null && !Modifier.isStatic(clazz.getModifiers()))));
}
}
for (Method method : clazz.getDeclaredMethods()) {
if(!method.isBridge() && !method.isSynthetic()) {
methodBuilder.add(getMethod(method));
}
}
for (Field field : clazz.getDeclaredFields()) {
if(!field.isSynthetic()) {
fieldBuilder.add(getField(field));
}
}
annotationBuilder.addAll(getAnnotations(clazz));
ASTType astType = new ASTClassType(clazz,
packageClass,
genericArgumentsBuilder.build(),
annotationBuilder.build(),
constructorBuilder.build(),
methodBuilder.build(),
fieldBuilder.build(),
superClass,
interfaceBuilder.build());
astClassTypeProxy.load(astType);
return astType;
}
/**
* Builds the parameters for a given method
*
* @param method
* @return AST parameters
*/
public ImmutableList<ASTParameter> getParameters(Method method) {
return getParameters(method.getParameterTypes(), method.getGenericParameterTypes(), method.getParameterAnnotations());
}
/**
* Builds the parameters for a set of parallel arrays: type and annotations
*
* @param parameterTypes
* @param genericParameterTypes
* @param parameterAnnotations @return AST Parameters
*/
public ImmutableList<ASTParameter> getParameters(Class<?>[] parameterTypes, Type[] genericParameterTypes, Annotation[][] parameterAnnotations) {
ImmutableList.Builder<ASTParameter> astParameterBuilder = ImmutableList.builder();
for (int i = 0; i < parameterTypes.length; i++) {
ASTType parameterType = getType(parameterTypes[i], nullSafeAccess(genericParameterTypes, i));
String name = null;
for (Annotation parameterAnnotation : parameterAnnotations[i]) {
if(parameterAnnotation.annotationType().equals(ASTParameterName.class)){
name = ((ASTParameterName)parameterAnnotation).value();
}
}
astParameterBuilder.add(
new ASTClassParameter(
name,
parameterAnnotations[i],
parameterType,
getAnnotations(parameterAnnotations[i])));
}
return astParameterBuilder.build();
}
private Type nullSafeAccess(Type[] typeArray, int i) {
if (typeArray.length > i) {
return typeArray[i];
}
return null;
}
/**
* Builds an AST Method fromm the given input method.
*
* @param method
* @return AST Method
*/
public ASTMethod getMethod(Method method) {
ImmutableList<ASTParameter> astParameters = getParameters(method);
ASTAccessModifier modifier = ASTAccessModifier.getModifier(method.getModifiers());
ImmutableSet<ASTType> throwsTypes = getTypes(method.getExceptionTypes());
return new ASTClassMethod(method, getType(method.getReturnType(), method.getGenericReturnType()), astParameters, modifier, getAnnotations(method), throwsTypes);
}
private ImmutableSet<ASTType> getTypes(Class<?>[] inputClasses) {
ImmutableSet.Builder<ASTType> typesBuilder = ImmutableSet.builder();
for (Class<?> inputClass : inputClasses) {
typesBuilder.add(getType(inputClass));
}
return typesBuilder.build();
}
/**
* Builds an AST Field from the given field
*
* @param field
* @return AST Field
*/
public ASTField getField(Field field) {
ASTAccessModifier modifier = ASTAccessModifier.getModifier(field.getModifiers());
return new ASTClassField(field, getType(field.getType(), field.getGenericType()), modifier, getAnnotations(field));
}
/**
* Build an AST Constructor from the given constructor
*
* @param constructor
* @return AST Constructor
*/
public ASTConstructor getConstructor(Constructor constructor, boolean isEnum, boolean isInnerClass) {
ASTAccessModifier modifier = ASTAccessModifier.getModifier(constructor.getModifiers());
Annotation[][] parameterAnnotations = constructor.getParameterAnnotations();
if(constructor.getDeclaringClass().getEnclosingClass() != null && !Modifier.isStatic(constructor.getDeclaringClass().getModifiers())){
// An inner class constructor contains a hidden non-annotated prameter
Annotation[][] paddedParameterAnnotations = new Annotation[parameterAnnotations.length + 1][];
paddedParameterAnnotations[0] = new Annotation[0];
System.arraycopy(parameterAnnotations, 0, paddedParameterAnnotations, 1, parameterAnnotations.length);
parameterAnnotations = paddedParameterAnnotations;
}
ImmutableList<ASTParameter> constructorParameters = getParameters(constructor.getParameterTypes(), constructor.getGenericParameterTypes(), parameterAnnotations);
if(isEnum) {
constructorParameters = constructorParameters.subList(2, constructorParameters.size());
}
if(isInnerClass){
constructorParameters = constructorParameters.subList(1, constructorParameters.size());
}
ImmutableSet<ASTType> throwsTypes = getTypes(constructor.getExceptionTypes());
return new ASTClassConstructor(getAnnotations(constructor), constructor, constructorParameters, modifier, throwsTypes);
}
private ImmutableSet<ASTAnnotation> getAnnotations(AnnotatedElement element) {
return getAnnotations(element.getAnnotations());
}
/**
* Build the AST Annotations from the given input annotation array.
*
* @param annotations
* @return List of AST Annotations
*/
private ImmutableSet<ASTAnnotation> getAnnotations(Annotation[] annotations) {
ImmutableSet.Builder<ASTAnnotation> astAnnotationBuilder = ImmutableSet.builder();
for (Annotation annotation : annotations) {
astAnnotationBuilder.add(getAnnotation(annotation));
}
return astAnnotationBuilder.build();
}
public ASTAnnotation getAnnotation(Annotation annotation) {
ASTType type = getType(annotation.annotationType());
return new ASTClassAnnotation(annotation, type, this);
}
}