/**
* Copyright (C) 2006-2017 INRIA and contributors
* Spoon - http://spoon.gforge.inria.fr/
*
* This software is governed by the CeCILL-C License under French law and
* abiding by the rules of distribution of free software. You can use, modify
* and/or redistribute the software under the terms of the CeCILL-C license as
* circulated by CEA, CNRS and INRIA at http://www.cecill.info.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the CeCILL-C License for more details.
*
* The fact that you are presently reading this means that you have had
* knowledge of the CeCILL-C license and that you accept its terms.
*/
package spoon.support.visitor.java;
import spoon.reflect.declaration.CtAnnotation;
import spoon.reflect.declaration.CtAnnotationType;
import spoon.reflect.declaration.CtClass;
import spoon.reflect.declaration.CtConstructor;
import spoon.reflect.declaration.CtEnum;
import spoon.reflect.declaration.CtEnumValue;
import spoon.reflect.declaration.CtField;
import spoon.reflect.declaration.CtInterface;
import spoon.reflect.declaration.CtMethod;
import spoon.reflect.declaration.CtModifiable;
import spoon.reflect.declaration.CtPackage;
import spoon.reflect.declaration.CtParameter;
import spoon.reflect.declaration.CtType;
import spoon.reflect.declaration.CtTypeParameter;
import spoon.reflect.declaration.ModifierKind;
import spoon.reflect.factory.Factory;
import spoon.reflect.reference.CtArrayTypeReference;
import spoon.reflect.reference.CtExecutableReference;
import spoon.reflect.reference.CtFieldReference;
import spoon.reflect.reference.CtTypeReference;
import spoon.reflect.reference.CtWildcardReference;
import spoon.support.visitor.java.internal.AnnotationRuntimeBuilderContext;
import spoon.support.visitor.java.internal.ExecutableRuntimeBuilderContext;
import spoon.support.visitor.java.internal.PackageRuntimeBuilderContext;
import spoon.support.visitor.java.internal.RuntimeBuilderContext;
import spoon.support.visitor.java.internal.TypeReferenceRuntimeBuilderContext;
import spoon.support.visitor.java.internal.TypeRuntimeBuilderContext;
import spoon.support.visitor.java.internal.VariableRuntimeBuilderContext;
import spoon.support.visitor.java.reflect.RtMethod;
import spoon.support.visitor.java.reflect.RtParameter;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.GenericDeclaration;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.ArrayDeque;
import java.util.Deque;
/**
* Builds Spoon model from class file using the reflection api. The Spoon model
* contains only the declaration part (type, field, method, etc.). Everything
* that isn't available with the reflection api is absent from the model. Those
* models are available when {@link CtTypeReference#getTypeDeclaration()},
* {@link CtExecutableReference#getExecutableDeclaration()} and
* {@link CtFieldReference#getFieldDeclaration()} are called. To know when an
* element comes from the reflection api, use {@link spoon.reflect.declaration.CtShadowable#isShadow()}.
*/
public class JavaReflectionTreeBuilder extends JavaReflectionVisitorImpl {
private Deque<RuntimeBuilderContext> contexts = new ArrayDeque<>();
private Factory factory;
public JavaReflectionTreeBuilder(Factory factory) {
this.factory = factory;
}
private void enter(RuntimeBuilderContext context) {
contexts.push(context);
}
private RuntimeBuilderContext exit() {
return contexts.pop();
}
public <T, R extends CtType<T>> R scan(Class<T> clazz) {
CtPackage ctPackage;
CtType<?> ctEnclosingClass;
if (clazz.getEnclosingClass() != null) {
ctEnclosingClass = scan(clazz.getEnclosingClass());
return ctEnclosingClass.getNestedType(clazz.getSimpleName());
} else {
if (clazz.getPackage() == null) {
ctPackage = factory.Package().getRootPackage();
} else {
ctPackage = factory.Package().getOrCreate(clazz.getPackage().getName());
}
if (contexts.isEmpty()) {
enter(new PackageRuntimeBuilderContext(ctPackage));
}
if (clazz.isAnnotation()) {
visitAnnotationClass((Class<Annotation>) clazz);
} else if (clazz.isInterface()) {
visitInterface(clazz);
} else if (clazz.isEnum()) {
visitEnum(clazz);
} else {
visitClass(clazz);
}
exit();
final R type = ctPackage.getType(clazz.getSimpleName());
if (clazz.isPrimitive() && type.getParent() instanceof CtPackage) {
type.setParent(null); // primitive type isn't in a package.
}
return type;
}
}
@Override
public void visitPackage(Package aPackage) {
final CtPackage ctPackage = factory.Package().getOrCreate(aPackage.getName());
enter(new PackageRuntimeBuilderContext(ctPackage));
super.visitPackage(aPackage);
exit();
contexts.peek().addPackage(ctPackage);
}
@Override
public <T> void visitClass(Class<T> clazz) {
final CtClass ctClass = factory.Core().createClass();
ctClass.setSimpleName(clazz.getSimpleName());
setModifier(ctClass, clazz.getModifiers());
enter(new TypeRuntimeBuilderContext(ctClass) {
@Override
public void addConstructor(CtConstructor<?> ctConstructor) {
ctClass.addConstructor(ctConstructor);
}
@Override
public void addClassReference(CtTypeReference<?> typeReference) {
ctClass.setSuperclass(typeReference);
}
});
super.visitClass(clazz);
exit();
contexts.peek().addType(ctClass);
}
@Override
public <T> void visitInterface(Class<T> clazz) {
final CtInterface<Object> ctInterface = factory.Core().createInterface();
ctInterface.setSimpleName(clazz.getSimpleName());
setModifier(ctInterface, clazz.getModifiers());
enter(new TypeRuntimeBuilderContext(ctInterface) {
@Override
public void addMethod(CtMethod ctMethod) {
super.addMethod(ctMethod);
ctMethod.setBody(null);
}
});
super.visitInterface(clazz);
exit();
contexts.peek().addType(ctInterface);
}
@Override
public <T> void visitEnum(Class<T> clazz) {
final CtEnum ctEnum = factory.Core().createEnum();
ctEnum.setSimpleName(clazz.getSimpleName());
setModifier(ctEnum, clazz.getModifiers());
enter(new TypeRuntimeBuilderContext(ctEnum) {
@Override
public void addConstructor(CtConstructor<?> ctConstructor) {
ctEnum.addConstructor(ctConstructor);
}
@Override
public void addEnumValue(CtEnumValue<?> ctEnumValue) {
ctEnum.addEnumValue(ctEnumValue);
}
});
super.visitEnum(clazz);
exit();
contexts.peek().addType(ctEnum);
}
@Override
public <T extends Annotation> void visitAnnotationClass(Class<T> clazz) {
final CtAnnotationType<?> ctAnnotationType = factory.Core().createAnnotationType();
ctAnnotationType.setSimpleName(clazz.getSimpleName());
setModifier(ctAnnotationType, clazz.getModifiers());
enter(new TypeRuntimeBuilderContext(ctAnnotationType) {
@Override
public void addMethod(CtMethod ctMethod) {
final CtField<Object> field = factory.Core().createField();
field.setSimpleName(ctMethod.getSimpleName());
field.setModifiers(ctMethod.getModifiers());
field.setType(ctMethod.getType());
ctAnnotationType.addField(field);
}
});
super.visitAnnotationClass(clazz);
exit();
contexts.peek().addType(ctAnnotationType);
}
@Override
public void visitAnnotation(Annotation annotation) {
final CtAnnotation<Annotation> ctAnnotation = factory.Core().createAnnotation();
enter(new AnnotationRuntimeBuilderContext(ctAnnotation));
super.visitAnnotation(annotation);
exit();
contexts.peek().addAnnotation(ctAnnotation);
}
@Override
public <T> void visitConstructor(Constructor<T> constructor) {
final CtConstructor<Object> ctConstructor = factory.Core().createConstructor();
ctConstructor.setBody(factory.Core().createBlock());
setModifier(ctConstructor, constructor.getModifiers());
enter(new ExecutableRuntimeBuilderContext(ctConstructor));
super.visitConstructor(constructor);
exit();
contexts.peek().addConstructor(ctConstructor);
}
@Override
public void visitMethod(RtMethod method) {
final CtMethod<Object> ctMethod = factory.Core().createMethod();
ctMethod.setSimpleName(method.getName());
ctMethod.setBody(factory.Core().createBlock());
setModifier(ctMethod, method.getModifiers());
ctMethod.setDefaultMethod(method.isDefault());
enter(new ExecutableRuntimeBuilderContext(ctMethod));
super.visitMethod(method);
exit();
contexts.peek().addMethod(ctMethod);
}
@Override
public void visitField(Field field) {
final CtField<Object> ctField = factory.Core().createField();
ctField.setSimpleName(field.getName());
setModifier(ctField, field.getModifiers());
enter(new VariableRuntimeBuilderContext(ctField));
super.visitField(field);
exit();
contexts.peek().addField(ctField);
}
@Override
public void visitEnumValue(Field field) {
final CtEnumValue<Object> ctEnumValue = factory.Core().createEnumValue();
ctEnumValue.setSimpleName(field.getName());
enter(new VariableRuntimeBuilderContext(ctEnumValue));
super.visitEnumValue(field);
exit();
contexts.peek().addEnumValue(ctEnumValue);
}
@Override
public void visitParameter(RtParameter parameter) {
final CtParameter ctParameter = factory.Core().createParameter();
ctParameter.setSimpleName(parameter.getName());
ctParameter.setVarArgs(parameter.isVarArgs());
enter(new VariableRuntimeBuilderContext(ctParameter));
super.visitParameter(parameter);
exit();
contexts.peek().addParameter(ctParameter);
}
@Override
public <T extends GenericDeclaration> void visitTypeParameter(TypeVariable<T> parameter) {
final CtTypeParameter typeParameter = factory.Core().createTypeParameter();
typeParameter.setSimpleName(parameter.getName());
enter(new TypeRuntimeBuilderContext(typeParameter));
super.visitTypeParameter(parameter);
exit();
contexts.peek().addFormalType(typeParameter);
}
@Override
public void visitType(Type type) {
final CtTypeReference<?> ctTypeReference = factory.Core().createTypeReference();
ctTypeReference.setSimpleName(getTypeName(type));
enter(new TypeReferenceRuntimeBuilderContext(ctTypeReference));
super.visitType(type);
exit();
contexts.peek().addTypeName(ctTypeReference);
}
@Override
public void visitType(ParameterizedType type) {
final CtTypeReference<?> ctTypeReference = factory.Core().createTypeReference();
enter(new TypeReferenceRuntimeBuilderContext(ctTypeReference) {
@Override
public void addClassReference(CtTypeReference<?> typeReference) {
ctTypeReference.setSimpleName(typeReference.getSimpleName());
ctTypeReference.setPackage(typeReference.getPackage());
ctTypeReference.setDeclaringType(typeReference.getDeclaringType());
ctTypeReference.setActualTypeArguments(typeReference.getActualTypeArguments());
ctTypeReference.setAnnotations(typeReference.getAnnotations());
}
@Override
public void addType(CtType<?> aType) {
}
});
super.visitType(type);
exit();
contexts.peek().addTypeName(ctTypeReference);
}
@Override
public void visitType(WildcardType type) {
final CtWildcardReference wildcard = factory.Core().createWildcardReference();
wildcard.setUpper(type.getUpperBounds() != null && !type.getUpperBounds()[0].equals(Object.class));
enter(new TypeReferenceRuntimeBuilderContext(wildcard));
super.visitType(type);
exit();
contexts.peek().addTypeName(wildcard);
}
private String getTypeName(Type type) {
if (!(type instanceof Class)) {
return type.toString();
}
Class clazz = (Class) type;
if (clazz.isArray()) {
try {
Class<?> cl = clazz;
int dimensions = 0;
while (cl.isArray()) {
dimensions++;
cl = cl.getComponentType();
}
StringBuilder sb = new StringBuilder();
sb.append(cl.getName());
for (int i = 0; i < dimensions; i++) {
sb.append("[]");
}
return sb.toString();
} catch (Throwable e) { /*FALLTHRU*/ }
}
return clazz.getName();
}
@Override
public <T> void visitArrayReference(final Class<T> typeArray) {
final CtArrayTypeReference<?> arrayTypeReference = factory.Core().createArrayTypeReference();
enter(new TypeReferenceRuntimeBuilderContext(arrayTypeReference) {
@Override
public void addClassReference(CtTypeReference<?> typeReference) {
if (typeArray.getSimpleName().equals(typeReference.getSimpleName())) {
arrayTypeReference.setComponentType(typeReference);
} else {
arrayTypeReference.setDeclaringType(typeReference);
}
}
@Override
public void addArrayReference(CtArrayTypeReference<?> typeReference) {
arrayTypeReference.setComponentType(typeReference);
}
});
super.visitArrayReference(typeArray);
exit();
contexts.peek().addArrayReference(arrayTypeReference);
}
@Override
public <T> void visitClassReference(Class<T> clazz) {
final CtTypeReference<Object> typeReference = factory.Core().createTypeReference();
typeReference.setSimpleName(clazz.getSimpleName());
enter(new TypeReferenceRuntimeBuilderContext(typeReference));
super.visitClassReference(clazz);
exit();
contexts.peek().addClassReference(typeReference);
}
@Override
public <T> void visitInterfaceReference(Class<T> anInterface) {
final CtTypeReference<Object> typeReference = factory.Core().createTypeReference();
typeReference.setSimpleName(anInterface.getSimpleName());
enter(new TypeReferenceRuntimeBuilderContext(typeReference));
super.visitInterfaceReference(anInterface);
exit();
contexts.peek().addInterfaceReference(typeReference);
}
private void setModifier(CtModifiable ctModifiable, int modifiers) {
if (Modifier.isAbstract(modifiers)) {
ctModifiable.addModifier(ModifierKind.ABSTRACT);
}
if (Modifier.isFinal(modifiers)) {
ctModifiable.addModifier(ModifierKind.FINAL);
}
if (Modifier.isNative(modifiers)) {
ctModifiable.addModifier(ModifierKind.NATIVE);
}
if (Modifier.isPrivate(modifiers)) {
ctModifiable.addModifier(ModifierKind.PRIVATE);
}
if (Modifier.isProtected(modifiers)) {
ctModifiable.addModifier(ModifierKind.PROTECTED);
}
if (Modifier.isPublic(modifiers)) {
ctModifiable.addModifier(ModifierKind.PUBLIC);
}
if (Modifier.isStatic(modifiers)) {
ctModifiable.addModifier(ModifierKind.STATIC);
}
if (Modifier.isStrict(modifiers)) {
ctModifiable.addModifier(ModifierKind.STRICTFP);
}
if (Modifier.isSynchronized(modifiers)) {
ctModifiable.addModifier(ModifierKind.SYNCHRONIZED);
}
if (Modifier.isTransient(modifiers)) {
ctModifiable.addModifier(ModifierKind.TRANSIENT);
}
if (Modifier.isVolatile(modifiers)) {
ctModifiable.addModifier(ModifierKind.VOLATILE);
}
}
}