/**
* 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.element;
import com.google.common.base.Function;
import com.google.common.base.Predicates;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import org.androidtransfuse.adapter.*;
import org.androidtransfuse.util.Logger;
import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.lang.model.element.*;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import java.util.*;
/**
* Factory class to build a specific AST tree element from the provided Element base type
*
* @author John Ericksen
*/
@Singleton
public class ASTElementFactory {
private final Map<TypeElement, ASTType> typeCache = new HashMap<TypeElement, ASTType>();
private final Map<PackageClass, ASTType> blacklist = new HashMap<PackageClass, ASTType>();
private final ASTElementConverterFactory astElementConverterFactory;
private final ASTTypeBuilderVisitor astTypeBuilderVisitor;
private final ASTFactory astFactory;
private final Elements elements;
private final Logger log;
@Inject
public ASTElementFactory(Elements elements,
ASTFactory astFactory,
ASTTypeBuilderVisitor astTypeBuilderVisitor,
ASTElementConverterFactory astElementConverterFactory,
Logger log) {
this.elements = elements;
this.astFactory = astFactory;
this.astTypeBuilderVisitor = astTypeBuilderVisitor;
this.astElementConverterFactory = astElementConverterFactory;
this.log = log;
//blacklist
// this is to work around a bug in the support library
// https://code.google.com/p/android/issues/detail?id=175086
blacklist.put(new PackageClass("android.support.v4.app", "DialogFragment"),
new ASTStubType("android.support.v4.app.DialogFragment", "android.support.v4.app.Fragment"));
blacklist.put(new PackageClass("android.support.v4.widget", "DrawerLayout"),
new ASTStubType("android.support.v4.widget.DrawerLayout", "android.view.ViewGroup"));
}
public ASTType buildASTElementType(DeclaredType declaredType) {
ASTType astType = getType((TypeElement) declaredType.asElement());
if (!declaredType.getTypeArguments().isEmpty()) {
return astFactory.buildGenericTypeWrapper(astType, astFactory.buildParameterBuilder(declaredType));
}
return astType;
}
/**
* Build a ASTType from the provided TypeElement.
*
* @param typeElement required input Element
* @return ASTType constructed using teh input Element
*/
public synchronized ASTType getType(final TypeElement typeElement) {
return new LazyASTType(buildPackageClass(typeElement), typeElement) {
@Override
public ASTType lazyLoad() {
if (!typeCache.containsKey(typeElement)) {
typeCache.put(typeElement, buildType(typeElement));
}
return typeCache.get(typeElement);
}
};
}
public ASTType getType(Class clazz){
return getType(elements.getTypeElement(clazz.getCanonicalName()));
}
private ASTType buildType(TypeElement typeElement) {
log.debug("ASTElementType building: " + typeElement.getQualifiedName());
//build placeholder for ASTElementType and contained data structures to allow for children population
//while avoiding back link loops
PackageClass packageClass = buildPackageClass(typeElement);
if (blacklist.containsKey(packageClass)) {
return blacklist.get(packageClass);
}
ASTTypeVirtualProxy astTypeProxy = new ASTTypeVirtualProxy(packageClass);
try{
typeCache.put(typeElement, astTypeProxy);
ASTType superClass = null;
if (typeElement.getSuperclass() != null) {
superClass = typeElement.getSuperclass().accept(astTypeBuilderVisitor, null);
}
ImmutableSet<ASTType> interfaces = FluentIterable.from(typeElement.getInterfaces())
.transform(astTypeBuilderVisitor)
.toSet();
ImmutableList.Builder<ASTGenericArgument> genericArguments = ImmutableList.builder();
ImmutableSet.Builder<ASTAnnotation> annotations = ImmutableSet.builder();
ImmutableSet.Builder<ASTConstructor> constructors = ImmutableSet.builder();
ImmutableSet.Builder<ASTField> fields = ImmutableSet.builder();
ImmutableSet.Builder<ASTMethod> methods = ImmutableSet.builder();
//iterate and build the contained elements within this TypeElement
annotations.addAll(getAnnotations(typeElement));
constructors.addAll(transformAST(typeElement.getEnclosedElements(), ASTConstructor.class));
fields.addAll(transformAST(typeElement.getEnclosedElements(), ASTField.class));
methods.addAll(transformAST(typeElement.getEnclosedElements(), ASTMethod.class));
for (TypeParameterElement typeParameter : typeElement.getTypeParameters()) {
genericArguments.add(new ASTGenericArgument(typeParameter.toString()));
}
ASTType astType = new ASTElementType(buildAccessModifier(typeElement),
packageClass,
typeElement,
genericArguments.build(),
constructors.build(),
methods.build(),
fields.build(),
superClass,
interfaces,
annotations.build());
astTypeProxy.load(astType);
typeCache.put(typeElement, astType);
}
finally {
if(!astTypeProxy.isLoaded()) {
typeCache.remove(typeElement);
}
}
return typeCache.get(typeElement);
}
private PackageClass buildPackageClass(TypeElement typeElement) {
PackageElement packageElement = elements.getPackageOf(typeElement);
String pkg = packageElement.getQualifiedName().toString();
String qualifiedName = typeElement.getQualifiedName().toString();
String name;
if(!pkg.isEmpty() && pkg.length() < qualifiedName.length()){
name = qualifiedName.substring(pkg.length() + 1);
}
else{
name = qualifiedName;
}
return new PackageClass(pkg, name);
}
private <T extends ASTBase> List<T> transformAST(List<? extends Element> enclosedElements, Class<T> astType) {
return FluentIterable
.from(enclosedElements)
.transform(astElementConverterFactory.buildASTElementConverter(astType))
.filter(Predicates.notNull())
.toList();
}
/**
* Build a ASTElementField from the given VariableElement
*
* @param variableElement required input Element
* @return ASTElementField
*/
public ASTField getField(VariableElement variableElement) {
ASTAccessModifier modifier = buildAccessModifier(variableElement);
return new ASTElementField(variableElement, astTypeBuilderVisitor, modifier, getAnnotations(variableElement));
}
private ASTAccessModifier buildAccessModifier(Element element) {
for (Modifier elementModifier : element.getModifiers()) {
switch (elementModifier) {
case PUBLIC:
return ASTAccessModifier.PUBLIC;
case PROTECTED:
return ASTAccessModifier.PROTECTED;
case PRIVATE:
return ASTAccessModifier.PRIVATE;
}
}
return ASTAccessModifier.PACKAGE_PRIVATE;
}
/**
* Build an ASTMethod from the provided ExecutableElement
*
* @param executableElement required input element
* @return ASTMethod
*/
public ASTMethod getMethod(ExecutableElement executableElement) {
ImmutableList<ASTParameter> parameters = getParameters(executableElement.getParameters());
ASTAccessModifier modifier = buildAccessModifier(executableElement);
ImmutableSet<ASTType> throwsTypes = buildASTElementTypes(executableElement.getThrownTypes());
return new ASTElementMethod(executableElement, astTypeBuilderVisitor, parameters, modifier, getAnnotations(executableElement), throwsTypes);
}
private ImmutableSet<ASTType> buildASTElementTypes(List<? extends TypeMirror> mirrorTypes) {
return FluentIterable.from(mirrorTypes)
.transform(astTypeBuilderVisitor)
.toSet();
}
/**
* Build a list of ASTParameters corresponding tot he input VariableElement list elements
*
* @param variableElements required input element
* @return list of ASTParameters
*/
private ImmutableList<ASTParameter> getParameters(List<? extends VariableElement> variableElements) {
ImmutableList.Builder<ASTParameter> astParameterBuilder = ImmutableList.builder();
for (VariableElement variables : variableElements) {
astParameterBuilder.add(getParameter(variables));
}
return astParameterBuilder.build();
}
/**
* Build an ASTParameter from the input VariableElement
*
* @param variableElement required input element
* @return ASTParameter
*/
public ASTParameter getParameter(Element variableElement) {
return new ASTElementParameter(variableElement, astTypeBuilderVisitor, getAnnotations(variableElement));
}
/**
* Build an ASTConstructor from the input ExecutableElement
*
* @param executableElement require input element
* @return ASTConstructor
*/
public ASTConstructor getConstructor(ExecutableElement executableElement) {
ImmutableList<ASTParameter> parameters = getParameters(executableElement.getParameters());
ASTAccessModifier modifier = buildAccessModifier(executableElement);
ImmutableSet<ASTType> throwsTypes = buildASTElementTypes(executableElement.getThrownTypes());
return new ASTElementConstructor(executableElement, parameters, modifier, getAnnotations(executableElement), throwsTypes);
}
private ImmutableSet<ASTAnnotation> getAnnotations(Element element) {
ImmutableSet.Builder<ASTAnnotation> annotationBuilder = ImmutableSet.builder();
for (AnnotationMirror annotationMirror : element.getAnnotationMirrors()) {
ASTType type = annotationMirror.getAnnotationType().accept(astTypeBuilderVisitor, null);
annotationBuilder.add(astFactory.buildASTElementAnnotation(annotationMirror, type));
}
return annotationBuilder.build();
}
public ASTMethod findMethod(ASTType type, String methodName, ASTType... args) {
TypeElement typeElement = elements.getTypeElement(type.getName());
ASTType updatedType = getType(typeElement);
for(ASTType typeLoop = updatedType; typeLoop != null ; typeLoop = typeLoop.getSuperClass()){
for (ASTMethod astMethod : typeLoop.getMethods()) {
if(astMethod.getName().equals(methodName) && parametersMatch(astMethod.getParameters(), args)){
return astMethod;
}
}
}
return null;
}
private boolean parametersMatch(List<ASTParameter> parameters, ASTType[] args) {
if(args == null){
return parameters.isEmpty();
}
if(parameters.size() != args.length){
return false;
}
for(int i = 0; i < parameters.size(); i++){
if(!parameters.get(i).getASTType().getName().equals(args[i].getName())){
return false;
}
}
return true;
}
public class ASTStubType extends ASTStringType {
private final Set<String> implementing = new HashSet<String>();
private final String extending;
public ASTStubType(String name, String extending) {
super(name);
this.extending = extending;
}
@Override
public ASTType getSuperClass() {
return getType(elements.getTypeElement(extending));
}
@Override
public ImmutableSet<ASTType> getInterfaces() {
return FluentIterable.from(implementing)
.transform(new Function<String, ASTType>() {
@Nullable
@Override
public ASTType apply(String input) {
return getType(elements.getTypeElement(input));
}
}).toSet();
}
@SuppressWarnings("unused")
public ASTStubType addImplements(String extension) {
implementing.add(extension);
return this;
}
}
}