Commit 4b9aad2a authored by Evan W. Patton's avatar Evan W. Patton

Enable any Component type to be used as a parameter

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
parent 746343f3
......@@ -36,6 +36,7 @@ import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
......@@ -56,7 +57,9 @@ import javax.lang.model.type.DeclaredType;
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;
......@@ -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
......@@ -1050,6 +1060,7 @@ public abstract class ComponentProcessor extends AbstractProcessor {
// Use typeMirror to set the property's type.
if (!typeMirror.getKind().equals(TypeKind.VOID)) {
property.type = typeMirror.toString();
updateComponentTypes(typeMirror);
}
property.componentInfoName = componentInfoName;
......@@ -1350,6 +1361,7 @@ public abstract class ComponentProcessor extends AbstractProcessor {
for (VariableElement ve : e.getParameters()) {
event.addParameter(ve.getSimpleName().toString(),
ve.asType().toString());
updateComponentTypes(ve.asType());
}
}
}
......@@ -1401,11 +1413,13 @@ public abstract class ComponentProcessor extends AbstractProcessor {
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());
}
}
}
......@@ -1499,4 +1513,41 @@ public abstract class ComponentProcessor extends AbstractProcessor {
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);
}
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment