Skip to content

Commit

Permalink
Enable any Component type to be used as a parameter
Browse files Browse the repository at this point in the history
The component processor only allows concrete types to be specified as
Java method parameters and return values. However, for abstraction it
is sometimes useful to use an interface or abstract base class in
order to reduce repeat code. This commit updates ComponentProcessor to
use the class hierarchy of any parameter or return type of a property,
method, or event to determine whether an entity passed through would
be a Component.

Change-Id: Ie2487ed20ad1b4ca6e8acc08ca8102cb6b7eb258
  • Loading branch information
ewpatton committed Sep 12, 2017
1 parent 746343f commit 4b9aad2
Showing 1 changed file with 51 additions and 0 deletions.
Expand Up @@ -36,6 +36,7 @@
import java.io.Writer;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
Expand All @@ -56,7 +57,9 @@
import javax.lang.model.type.ExecutableType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.AbstractTypeVisitor7;
import javax.lang.model.util.Elements;
import javax.lang.model.util.SimpleTypeVisitor7;
import javax.lang.model.util.Types;

import java.lang.annotation.Annotation;
Expand Down Expand Up @@ -160,6 +163,13 @@ public abstract class ComponentProcessor extends AbstractProcessor {

private final List<String> componentTypes = Lists.newArrayList();

/**
* A set of visited types in the class hierarchy. This is used to reduce the complexity of
* detecting whether a class implements {@link com.google.appinventor.components.runtime.Component}
* from O(n^2) to O(n) by tracking visited nodes to prevent repeat explorations of the class tree.
*/
private final Set<String> visitedTypes = new HashSet<>();

/**
* Represents a parameter consisting of a name and a type. The type is a
* String representation of the java type, such as "int", "double", or
Expand Down Expand Up @@ -1050,6 +1060,7 @@ private Property executableElementToProperty(Element element, String componentIn
// Use typeMirror to set the property's type.
if (!typeMirror.getKind().equals(TypeKind.VOID)) {
property.type = typeMirror.toString();
updateComponentTypes(typeMirror);
}

property.componentInfoName = componentInfoName;
Expand Down Expand Up @@ -1350,6 +1361,7 @@ private void processEvents(ComponentInfo componentInfo,
for (VariableElement ve : e.getParameters()) {
event.addParameter(ve.getSimpleName().toString(),
ve.asType().toString());
updateComponentTypes(ve.asType());
}
}
}
Expand Down Expand Up @@ -1401,11 +1413,13 @@ private void processMethods(ComponentInfo componentInfo,
for (VariableElement ve : e.getParameters()) {
method.addParameter(ve.getSimpleName().toString(),
ve.asType().toString());
updateComponentTypes(ve.asType());
}

// Extract the return type.
if (e.getReturnType().getKind() != TypeKind.VOID) {
method.returnType = e.getReturnType().toString();
updateComponentTypes(e.getReturnType());
}
}
}
Expand Down Expand Up @@ -1499,4 +1513,41 @@ protected FileObject createOutputFileObject(String fileName) throws IOException
protected Writer getOutputWriter(String fileName) throws IOException {
return createOutputFileObject(fileName).openWriter();
}

/**
* Tracks the superclass and superinterfaces for the given type and if the type inherits from
* {@link com.google.appinventor.components.runtime.Component} then it adds the class to the
* componentTypes list. This allows properties, methods, and events to use concrete Component
* types as parameters and return values.
*
* @param type a TypeMirror representing a type on the class path
*/
private void updateComponentTypes(TypeMirror type) {
if (type.getKind() == TypeKind.DECLARED) {
type.accept(new SimpleTypeVisitor7<Boolean, Set<String>>(false) {
@Override
public Boolean visitDeclared(DeclaredType t, Set<String> types) {
final String typeName = t.asElement().toString();
if ("com.google.appinventor.components.runtime.Component".equals(typeName)) {
return true;
}
if (!types.contains(typeName)) {
types.add(typeName);
final TypeElement typeElement = (TypeElement) t.asElement();
if (typeElement.getSuperclass().accept(this, types)) {
componentTypes.add(typeName);
return true;
}
for (TypeMirror iface : typeElement.getInterfaces()) {
if (iface.accept(this, types)) {
componentTypes.add(typeName);
return true;
}
}
}
return componentTypes.contains(typeName);
}
}, visitedTypes);
}
}
}

0 comments on commit 4b9aad2

Please sign in to comment.