7 package com.google.appinventor.components.scripts;
40 import com.
google.common.collect.ImmutableSet;
45 import java.io.IOException;
46 import java.io.Writer;
48 import java.text.SimpleDateFormat;
49 import java.util.ArrayList;
50 import java.util.Collections;
51 import java.util.Date;
52 import java.util.HashMap;
53 import java.util.HashSet;
54 import java.util.List;
57 import java.util.SortedMap;
59 import javax.annotation.processing.AbstractProcessor;
60 import javax.annotation.processing.Messager;
61 import javax.annotation.processing.ProcessingEnvironment;
62 import javax.annotation.processing.RoundEnvironment;
63 import javax.lang.model.SourceVersion;
64 import javax.lang.model.element.AnnotationMirror;
65 import javax.lang.model.element.AnnotationValue;
66 import javax.lang.model.element.AnnotationValueVisitor;
67 import javax.lang.model.element.Element;
68 import javax.lang.model.element.ElementKind;
69 import javax.lang.model.element.ExecutableElement;
70 import javax.lang.model.element.Modifier;
71 import javax.lang.model.element.TypeElement;
72 import javax.lang.model.element.VariableElement;
73 import javax.lang.model.type.DeclaredType;
74 import javax.lang.model.type.ExecutableType;
75 import javax.lang.model.type.TypeKind;
76 import javax.lang.model.type.TypeMirror;
77 import javax.lang.model.util.Elements;
78 import javax.lang.model.util.SimpleTypeVisitor7;
79 import javax.lang.model.util.Types;
81 import java.lang.annotation.Annotation;
83 import java.lang.reflect.InvocationTargetException;
84 import java.util.regex.Matcher;
85 import java.util.regex.Pattern;
87 import javax.tools.Diagnostic;
88 import javax.tools.Diagnostic.Kind;
89 import javax.tools.FileObject;
90 import javax.tools.StandardLocation;
125 private static final String OUTPUT_PACKAGE =
"";
127 private static final String MISSING_SIMPLE_PROPERTY_ANNOTATION =
128 "Designer property %s does not have a corresponding @SimpleProperty annotation.";
129 private static final String BOXED_TYPE_ERROR =
130 "Found use of boxed type %s. Please use the primitive type %s instead";
133 private static final Set<String> SUPPORTED_ANNOTATION_TYPES = ImmutableSet.of(
134 "com.google.appinventor.components.annotations.DesignerComponent",
135 "com.google.appinventor.components.annotations.DesignerProperty",
136 "com.google.appinventor.components.annotations.SimpleEvent",
137 "com.google.appinventor.components.annotations.SimpleFunction",
138 "com.google.appinventor.components.annotations.SimpleObject",
139 "com.google.appinventor.components.annotations.SimpleProperty",
144 "com.google.appinventor.components.annotations.SimpleBroadcastReceiver",
145 "com.google.appinventor.components.annotations.UsesAssets",
146 "com.google.appinventor.components.annotations.UsesLibraries",
147 "com.google.appinventor.components.annotations.UsesNativeLibraries",
148 "com.google.appinventor.components.annotations.UsesActivities",
149 "com.google.appinventor.components.annotations.UsesBroadcastReceivers",
150 "com.google.appinventor.components.annotations.UsesPermissions",
151 "com.google.appinventor.components.annotations.UsesServices",
152 "com.google.appinventor.components.annotations.UsesContentProviders");
155 private static final String READ_WRITE =
"read-write";
156 private static final String READ_ONLY =
"read-only";
157 private static final String WRITE_ONLY =
"write-only";
160 private static final String ARMEABI_V7A_SUFFIX =
"-v7a";
162 private static final String ARM64_V8A_SUFFIX =
"-v8a";
164 private static final String X86_64_SUFFIX =
"-x8a";
166 private static final String TYPE_PLACEHOLDER =
"%type%";
168 private static final Map<String, String> BOXED_TYPES =
new HashMap<>();
171 BOXED_TYPES.put(
"java.lang.Boolean",
"boolean");
172 BOXED_TYPES.put(
"java.lang.Byte",
"byte");
173 BOXED_TYPES.put(
"java.lang.Char",
"char");
174 BOXED_TYPES.put(
"java.lang.Short",
"short");
175 BOXED_TYPES.put(
"java.lang.Integer",
"int");
176 BOXED_TYPES.put(
"java.lang.Long",
"long");
177 BOXED_TYPES.put(
"java.lang.Float",
"float");
178 BOXED_TYPES.put(
"java.lang.Double",
"double");
186 private Elements elementUtils;
187 private Types typeUtils;
199 private int pass = 0;
207 protected final SortedMap<String, ComponentInfo>
components = Maps.newTreeMap();
209 private final List<String> componentTypes = Lists.newArrayList();
216 private final Set<String> visitedTypes =
new HashSet<>();
264 return javaTypeToYailType(
type);
271 protected abstract static class Feature {
272 private static final Pattern AT_SIGN = Pattern.compile(
"[^\\\\]@");
273 private static final Pattern LINK_FORM = Pattern.compile(
"\\{@link ([A-Za-z]*#?)([A-Za-z]*)[^}]*}");
274 private static final Pattern CODE_FORM = Pattern.compile(
"\\{@code ([^}]*)}");
276 private final String featureType;
277 protected final String name;
278 protected String description;
279 protected boolean defaultDescription =
false;
280 protected String longDescription;
281 protected boolean userVisible;
282 protected boolean deprecated;
284 protected Feature(String name, String description, String longDescription, String featureType,
285 boolean userVisible,
boolean deprecated) {
286 this.featureType = featureType;
288 setDescription(description);
289 setLongDescription(longDescription);
290 this.userVisible = userVisible;
291 this.deprecated = deprecated;
294 public boolean isDefaultDescription() {
295 return defaultDescription;
298 public void setDescription(String description) {
299 if (description ==
null || description.isEmpty()) {
300 this.description = featureType +
" for " + name;
301 defaultDescription =
true;
305 this.description = description.split(
"[@{]")[0].trim();
306 defaultDescription =
false;
310 public void setLongDescription(String longDescription) {
311 if (longDescription ==
null || longDescription.isEmpty()) {
312 this.longDescription = this.description;
313 }
else if (longDescription.contains(
"@suppressdoc")) {
314 this.longDescription =
"";
316 this.longDescription = longDescription;
319 Matcher linkMatcher = LINK_FORM.matcher(this.longDescription);
320 StringBuffer sb =
new StringBuffer();
322 while (linkMatcher.find(lastEnd)) {
323 sb.append(this.longDescription, lastEnd, linkMatcher.start());
324 String clazz = linkMatcher.group(1);
325 if (clazz.endsWith(
"#")) {
326 clazz = clazz.substring(0, clazz.length() - 1);
328 if (
"Form".equals(clazz)) {
331 String func = linkMatcher.group(2);
333 if (!clazz.isEmpty()) {
337 if (!func.isEmpty()) {
341 if (!func.isEmpty()) {
347 if (clazz.isEmpty()) {
348 sb.append(
"%type%.");
351 if (!func.isEmpty()) {
355 if (!func.isEmpty()) {
359 lastEnd = linkMatcher.end();
361 sb.append(this.longDescription.substring(lastEnd));
362 this.longDescription = sb.toString();
364 sb =
new StringBuffer();
365 Matcher codeMatcher = CODE_FORM.matcher(this.longDescription);
367 while (codeMatcher.find(lastEnd)) {
368 sb.append(this.longDescription, lastEnd, codeMatcher.start());
370 sb.append(codeMatcher.group(1));
372 lastEnd = codeMatcher.end();
374 sb.append(this.longDescription.substring(lastEnd));
375 this.longDescription = sb.toString();
377 Matcher m = AT_SIGN.matcher(this.longDescription);
379 this.longDescription = this.longDescription.substring(0, m.start() + 1);
382 this.longDescription = this.longDescription.replaceAll(
"\\\\@",
"@").trim();
385 public String getLongDescription(ComponentInfo component) {
386 if (longDescription ==
null || longDescription.isEmpty()) {
389 String name = component.name.equals(
"Form") ?
"Screen" : component.name;
390 return longDescription.replaceAll(
"%type%", name).trim();
399 protected boolean isUserVisible() {
408 protected boolean isDeprecated() {
422 String feature,
boolean userVisible,
boolean deprecated) {
423 super(name, description, longDescription, feature, userVisible, deprecated);
444 StringBuilder sb =
new StringBuilder();
447 sb.append(param.parameterToYailType(param));
449 sb.append(param.name);
454 return new String(sb);
462 implements Cloneable, Comparable<Event> {
465 protected Event(String name, String description, String longDescription,
boolean userVisible,
boolean deprecated) {
466 super(name, description, longDescription,
"Event", userVisible, deprecated);
471 Event that =
new Event(name, description, longDescription, userVisible, deprecated);
480 return name.compareTo(e.name);
489 implements Cloneable, Comparable<Method> {
491 private String returnType;
492 private boolean color;
494 protected Method(String name, String description, String longDescription,
boolean userVisible,
495 boolean deprecated) {
496 super(name, description, longDescription,
"Method", userVisible, deprecated);
510 Method that =
new Method(name, description, longDescription, userVisible, deprecated);
514 that.returnType = returnType;
520 return name.compareTo(f.name);
528 protected static final class Property
extends Feature implements Cloneable {
529 protected final String name;
532 private boolean readable;
533 private boolean writable;
534 private String componentInfoName;
535 private boolean color;
537 protected Property(String name, String description, String longDescription,
539 super(name, description, longDescription,
"Property", userVisible, deprecated);
541 this.propertyCategory = category;
547 public Property clone() {
548 Property that =
new Property(name, description, longDescription, propertyCategory,
549 isUserVisible(), isDeprecated());
551 that.readable = readable;
552 that.writable = writable;
553 that.componentInfoName = componentInfoName;
559 public String toString() {
560 StringBuilder sb =
new StringBuilder(
"<Property name: ");
562 sb.append(
", type: ");
565 sb.append(
" readable");
568 sb.append(
" writable");
571 return sb.toString();
580 protected String getDescription() {
589 protected String getType() {
598 protected boolean isReadable() {
607 protected boolean isWritable() {
611 protected boolean isColor() {
623 protected String getRwString() {
632 throw new RuntimeException(
"Property " + name +
633 " is neither readable nor writable");
746 protected final SortedMap<String, Method>
methods;
751 protected final SortedMap<String, Event>
events;
770 private String helpDescription;
771 private String helpUrl;
772 private String category;
773 private String categoryString;
774 private boolean simpleObject;
775 private boolean designerComponent;
777 private boolean showOnPalette;
778 private boolean nonVisible;
779 private String iconName;
780 private int androidMinSdk;
781 private String versionName;
782 private String dateBuilt;
785 super(element.getSimpleName().toString(),
786 elementUtils.getDocComment(element),
787 elementUtils.getDocComment(element),
788 "Component",
false, elementUtils.isDeprecated(element));
789 type = element.asType().toString();
790 displayName = getDisplayNameForComponentType(name);
798 assets = Sets.newHashSet();
809 events = Maps.newTreeMap();
810 abstractClass = element.getModifiers().contains(Modifier.ABSTRACT);
813 dateBuilt =
new SimpleDateFormat(
"yyyy-MM-dd'T'HH:mm:ssZ").format(
new Date());
814 for (AnnotationMirror am : element.getAnnotationMirrors()) {
815 DeclaredType dt = am.getAnnotationType();
816 String annotationName = am.getAnnotationType().toString();
817 if (annotationName.equals(
SimpleObject.class.getName())) {
823 designerComponent =
true;
826 Map values = elementUtils.getElementValuesWithDefaults(am);
827 for (Map.Entry entry : (Set<Map.Entry>) values.entrySet()) {
828 if (((ExecutableElement) entry.getKey()).getSimpleName().contentEquals(
"dateBuilt")) {
831 String tempDate =
"" + entry.getValue();
832 if (tempDate.length() > 2) {
833 tempDate = tempDate.substring(1, tempDate.length() - 1);
834 dateBuilt = tempDate;
838 entry.setValue(
new AnnotationValue() {
840 public Object getValue() {
845 public <R, P> R accept(AnnotationValueVisitor<R, P> v, P p) {
846 return v.visit(
this, p);
850 public String toString() {
851 return "\"" + dateBuilt +
"\"";
859 String explicitDescription = designerComponentAnnotation.
description();
860 if (!explicitDescription.isEmpty()) {
861 setDescription(explicitDescription);
867 if (helpDescription.isEmpty()) {
868 helpDescription = description;
870 helpUrl = designerComponentAnnotation.
helpUrl();
871 if (!helpUrl.startsWith(
"http:") && !helpUrl.startsWith(
"https:")) {
876 categoryString = designerComponentAnnotation.
category().toString();
877 version = designerComponentAnnotation.
version();
879 nonVisible = designerComponentAnnotation.
nonVisible();
880 iconName = designerComponentAnnotation.
iconName();
882 versionName = designerComponentAnnotation.
versionName();
899 return helpDescription;
929 return categoryString;
949 return showOnPalette;
960 protected boolean getNonVisible() {
969 protected boolean getExternal() {
979 protected String getIconName() {
989 protected int getAndroidMinSdk() {
990 return androidMinSdk;
993 protected String getVersionName() {
997 protected String getDateBuilt() {
1001 private String getDisplayNameForComponentType(String componentTypeName) {
1003 return "Form".equals(componentTypeName) ?
"Screen" : componentTypeName;
1006 protected String getName() {
1007 if (name.equals(
"Form")) {
1023 public Set<String> getSupportedAnnotationTypes() {
1024 return SUPPORTED_ANNOTATION_TYPES;
1028 public SourceVersion getSupportedSourceVersion() {
1029 return SourceVersion.RELEASE_7;
1033 public void init(ProcessingEnvironment processingEnv) {
1034 super.init(processingEnv);
1035 elementUtils = processingEnv.getElementUtils();
1036 typeUtils = processingEnv.getTypeUtils();
1052 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
1060 messager = processingEnv.getMessager();
1062 List<Element> elements =
new ArrayList<>();
1063 List<Element> excludedElements =
new ArrayList<>();
1064 for (TypeElement te : annotations) {
1065 if (te.getSimpleName().toString().equals(
"DesignerComponent")) {
1066 elements.addAll(roundEnv.getElementsAnnotatedWith(te));
1067 }
else if (te.getSimpleName().toString().equals(
"SimpleObject")) {
1068 for (Element element : roundEnv.getElementsAnnotatedWith(te)) {
1069 SimpleObject annotation = element.getAnnotation(SimpleObject.class);
1070 if (!annotation.external()) {
1071 elements.add(element);
1073 excludedElements.add(element);
1078 for (Element element : elements) {
1079 processComponent(element);
1084 for (Element element : excludedElements) {
1085 componentTypes.add(element.asType().toString());
1089 List<String> removeList = Lists.newArrayList();
1090 for (Map.Entry<String, ComponentInfo> entry :
components.entrySet()) {
1091 ComponentInfo component = entry.getValue();
1092 if (component.abstractClass || !component.designerComponent) {
1093 removeList.add(entry.getKey());
1101 }
catch (IOException e) {
1102 throw new RuntimeException(e);
1115 private void processComponent(Element element) {
1116 boolean isForDesigner = element.getAnnotation(DesignerComponent.class) !=
null;
1118 if (element.getAnnotation(SimpleObject.class) ==
null && !isForDesigner) {
1123 String longComponentName = ((TypeElement) element).getQualifiedName().toString();
1124 if (
components.containsKey(longComponentName)) {
1129 ComponentInfo componentInfo =
new ComponentInfo(element);
1132 List<? extends TypeMirror> directSupertypes = typeUtils.directSupertypes(element.asType());
1133 if (!directSupertypes.isEmpty()) {
1136 String parentName = directSupertypes.get(0).toString();
1137 Element e = ((DeclaredType) directSupertypes.get(0)).asElement();
1138 parentName = ((TypeElement) e).getQualifiedName().toString();
1139 ComponentInfo parentComponent =
components.get(parentName);
1140 if (parentComponent ==
null) {
1142 Element parentElement = elementUtils.getTypeElement(parentName);
1143 if (parentElement !=
null) {
1144 processComponent(parentElement);
1145 parentComponent =
components.get(parentName);
1152 if (parentComponent !=
null) {
1154 componentInfo.permissions.addAll(parentComponent.permissions);
1155 componentInfo.libraries.addAll(parentComponent.libraries);
1156 componentInfo.nativeLibraries.addAll(parentComponent.nativeLibraries);
1157 componentInfo.assets.addAll(parentComponent.assets);
1158 componentInfo.activities.addAll(parentComponent.activities);
1159 componentInfo.metadata.addAll(parentComponent.metadata);
1160 componentInfo.activityMetadata.addAll(parentComponent.activityMetadata);
1161 componentInfo.broadcastReceivers.addAll(parentComponent.broadcastReceivers);
1162 componentInfo.services.addAll(parentComponent.services);
1163 componentInfo.contentProviders.addAll(parentComponent.contentProviders);
1168 componentInfo.classNameAndActionsBR.addAll(parentComponent.classNameAndActionsBR);
1171 componentInfo.designerProperties.putAll(parentComponent.designerProperties);
1177 for (Map.Entry<String, Event> entry : parentComponent.events.entrySet()) {
1178 componentInfo.events.put(entry.getKey(), entry.getValue().clone());
1180 for (Map.Entry<String, Property> entry : parentComponent.properties.entrySet()) {
1181 componentInfo.properties.put(entry.getKey(), entry.getValue().clone());
1183 for (Map.Entry<String, Method> entry : parentComponent.methods.entrySet()) {
1184 componentInfo.methods.put(entry.getKey(), entry.getValue().clone());
1190 UsesPermissions usesPermissions = element.getAnnotation(UsesPermissions.class);
1191 if (usesPermissions !=
null) {
1192 for (String permission : usesPermissions.permissionNames().split(
",")) {
1193 updateWithNonEmptyValue(componentInfo.permissions, permission);
1195 Collections.addAll(componentInfo.permissions, usesPermissions.value());
1199 UsesLibraries usesLibraries = element.getAnnotation(UsesLibraries.class);
1200 if (usesLibraries !=
null) {
1201 for (String library : usesLibraries.libraries().split(
",")) {
1202 updateWithNonEmptyValue(componentInfo.libraries, library);
1204 Collections.addAll(componentInfo.libraries, usesLibraries.value());
1208 UsesNativeLibraries usesNativeLibraries = element.getAnnotation(UsesNativeLibraries.class);
1209 if (usesNativeLibraries !=
null) {
1210 for (String nativeLibrary : usesNativeLibraries.libraries().split(
",")) {
1211 updateWithNonEmptyValue(componentInfo.nativeLibraries, nativeLibrary);
1213 for (String v7aLibrary : usesNativeLibraries.v7aLibraries().split(
",")) {
1214 updateWithNonEmptyValue(componentInfo.nativeLibraries, v7aLibrary.trim() + ARMEABI_V7A_SUFFIX);
1216 for (String v8aLibrary : usesNativeLibraries.v8aLibraries().split(
",")) {
1217 updateWithNonEmptyValue(componentInfo.nativeLibraries, v8aLibrary.trim() + ARM64_V8A_SUFFIX);
1219 for (String x8664Library : usesNativeLibraries.x86_64Libraries().split(
",")) {
1220 updateWithNonEmptyValue(componentInfo.nativeLibraries, x8664Library.trim() + X86_64_SUFFIX);
1226 UsesAssets usesAssets = element.getAnnotation(UsesAssets.class);
1227 if (usesAssets !=
null) {
1228 for (String file : usesAssets.fileNames().split(
",")) {
1229 updateWithNonEmptyValue(componentInfo.assets, file);
1234 UsesActivities usesActivities = element.getAnnotation(UsesActivities.class);
1235 if (usesActivities !=
null) {
1237 for (ActivityElement ae : usesActivities.activities()) {
1238 updateWithNonEmptyValue(componentInfo.activities, activityElementToString(ae));
1240 }
catch (IllegalAccessException e) {
1241 messager.printMessage(Diagnostic.Kind.ERROR,
"IllegalAccessException when gathering " +
1242 "activity attributes and subelements for component " + componentInfo.name);
1243 throw new RuntimeException(e);
1244 }
catch (InvocationTargetException e) {
1245 messager.printMessage(Diagnostic.Kind.ERROR,
"InvocationTargetException when gathering " +
1246 "activity attributes and subelements for component " + componentInfo.name);
1247 throw new RuntimeException(e);
1252 UsesApplicationMetadata usesApplicationMetadata = element.getAnnotation(UsesApplicationMetadata.class);
1253 if (usesApplicationMetadata !=
null) {
1255 for (MetaDataElement me : usesApplicationMetadata.metaDataElements()) {
1256 updateWithNonEmptyValue(componentInfo.metadata, metaDataElementToString(me));
1258 }
catch (IllegalAccessException e) {
1259 messager.printMessage(Diagnostic.Kind.ERROR,
"IllegalAccessException when gathering " +
1260 "application metadata and subelements for component " + componentInfo.name);
1261 throw new RuntimeException(e);
1262 }
catch (InvocationTargetException e) {
1263 messager.printMessage(Diagnostic.Kind.ERROR,
"InvocationTargetException when gathering " +
1264 "application metadata and subelements for component " + componentInfo.name);
1265 throw new RuntimeException(e);
1270 UsesActivityMetadata usesActivityMetadata = element.getAnnotation(UsesActivityMetadata.class);
1271 if (usesActivityMetadata !=
null) {
1273 for (MetaDataElement me : usesActivityMetadata.metaDataElements()) {
1274 updateWithNonEmptyValue(componentInfo.activityMetadata, metaDataElementToString(me));
1276 }
catch (IllegalAccessException e) {
1277 messager.printMessage(Diagnostic.Kind.ERROR,
"IllegalAccessException when gathering " +
1278 "application metadata and subelements for component " + componentInfo.name);
1279 throw new RuntimeException(e);
1280 }
catch (InvocationTargetException e) {
1281 messager.printMessage(Diagnostic.Kind.ERROR,
"InvocationTargetException when gathering " +
1282 "application metadata and subelements for component " + componentInfo.name);
1283 throw new RuntimeException(e);
1288 UsesBroadcastReceivers usesBroadcastReceivers = element.getAnnotation(UsesBroadcastReceivers.class);
1289 if (usesBroadcastReceivers !=
null) {
1291 for (ReceiverElement re : usesBroadcastReceivers.receivers()) {
1292 updateWithNonEmptyValue(componentInfo.broadcastReceivers, receiverElementToString(re));
1294 }
catch (IllegalAccessException e) {
1295 messager.printMessage(Diagnostic.Kind.ERROR,
"IllegalAccessException when gathering " +
1296 "broadcast receiver attributes and subelements for component " + componentInfo.name);
1297 throw new RuntimeException(e);
1298 }
catch (InvocationTargetException e) {
1299 messager.printMessage(Diagnostic.Kind.ERROR,
"InvocationTargetException when gathering " +
1300 "broadcast receiver attributes and subelements for component " + componentInfo.name);
1301 throw new RuntimeException(e);
1306 UsesServices usesServices = element.getAnnotation(UsesServices.class);
1307 if (usesServices !=
null) {
1309 for (ServiceElement se : usesServices.services()) {
1310 updateWithNonEmptyValue(componentInfo.services, serviceElementToString(se));
1312 }
catch (IllegalAccessException e) {
1313 messager.printMessage(Diagnostic.Kind.ERROR,
"IllegalAccessException when gathering " +
1314 "service attributes and subelements for component " + componentInfo.name);
1315 throw new RuntimeException(e);
1316 }
catch (InvocationTargetException e) {
1317 messager.printMessage(Diagnostic.Kind.ERROR,
"InvocationTargetException when gathering " +
1318 "service attributes and subelements for component " + componentInfo.name);
1319 throw new RuntimeException(e);
1324 UsesContentProviders usesContentProviders = element.getAnnotation(UsesContentProviders.class);
1325 if (usesContentProviders !=
null) {
1327 for (ProviderElement pe : usesContentProviders.providers()) {
1328 updateWithNonEmptyValue(componentInfo.contentProviders, providerElementToString(pe));
1330 }
catch (IllegalAccessException e) {
1331 messager.printMessage(Diagnostic.Kind.ERROR,
"IllegalAccessException when gathering " +
1332 "provider attributes and subelements for component " + componentInfo.name);
1333 throw new RuntimeException(e);
1334 }
catch (InvocationTargetException e) {
1335 messager.printMessage(Diagnostic.Kind.ERROR,
"InvocationTargetException when gathering " +
1336 "provider attributes and subelements for component " + componentInfo.name);
1337 throw new RuntimeException(e);
1351 SimpleBroadcastReceiver simpleBroadcastReceiver = element.getAnnotation(SimpleBroadcastReceiver.class);
1352 if (simpleBroadcastReceiver !=
null) {
1353 for (String className : simpleBroadcastReceiver.className().split(
",")){
1354 StringBuffer nameAndActions =
new StringBuffer();
1355 nameAndActions.append(className.trim());
1356 for (String action : simpleBroadcastReceiver.actions().split(
",")) {
1357 nameAndActions.append(
"," + action.trim());
1359 componentInfo.classNameAndActionsBR.add(nameAndActions.toString());
1365 processEvents(componentInfo, element);
1368 processProperties(componentInfo, element);
1371 processMethods(componentInfo, element);
1373 if (isForDesigner) {
1374 processDescriptions(componentInfo);
1378 components.put(longComponentName, componentInfo);
1381 private void processDescriptions(ComponentInfo info) {
1382 final String name = info.displayName;
1383 info.description = info.description.replaceAll(TYPE_PLACEHOLDER, name);
1384 info.helpUrl = info.helpUrl.replaceAll(TYPE_PLACEHOLDER, name);
1385 for (Property property : info.properties.values()) {
1386 property.description =
property.description.replaceAll(TYPE_PLACEHOLDER, name);
1388 for (Event event : info.events.values()) {
1389 event.description =
event.description.replaceAll(TYPE_PLACEHOLDER, name);
1391 for (Method method : info.methods.values()) {
1392 method.description = method.description.replaceAll(TYPE_PLACEHOLDER, name);
1396 private boolean isPublicMethod(Element element) {
1397 return element.getModifiers().contains(Modifier.PUBLIC)
1398 && element.getKind() == ElementKind.METHOD;
1401 private Property executableElementToProperty(Element element, String componentInfoName) {
1402 String propertyName = element.getSimpleName().toString();
1403 SimpleProperty simpleProperty = element.getAnnotation(SimpleProperty.class);
1405 if (!(element.asType() instanceof ExecutableType)) {
1406 throw new RuntimeException(
"element.asType() is not an ExecutableType for " +
1411 String description = elementUtils.getDocComment(element);
1412 String longDescription = description;
1413 if (!simpleProperty.description().isEmpty()) {
1414 description = simpleProperty.description();
1416 if (description ==
null) {
1420 description = description.split(
"[^\\\\][@{]")[0].trim();
1422 Property
property =
new Property(propertyName,
1425 simpleProperty.category(),
1426 simpleProperty.userVisible(),
1427 elementUtils.isDeprecated(element));
1430 ExecutableType executableType = (ExecutableType) element.asType();
1431 List<? extends TypeMirror> parameters = executableType.getParameterTypes();
1435 TypeMirror typeMirror;
1436 if (parameters.size() == 0) {
1438 property.readable =
true;
1439 typeMirror = executableType.getReturnType();
1440 if (typeMirror.getKind().equals(TypeKind.VOID)) {
1441 throw new RuntimeException(
"Property method is void and has no parameters: "
1444 if (element.getAnnotation(IsColor.class) !=
null) {
1445 property.color =
true;
1449 property.writable =
true;
1450 if (parameters.size() != 1) {
1451 throw new RuntimeException(
"Too many parameters for setter for " +
1454 typeMirror = parameters.get(0);
1455 for (VariableElement ve : ((ExecutableElement) element).getParameters()) {
1456 if (ve.getAnnotation(IsColor.class) !=
null) {
1457 property.color =
true;
1463 if (!typeMirror.getKind().equals(TypeKind.VOID)) {
1464 property.type = typeMirror.toString();
1465 updateComponentTypes(typeMirror);
1468 property.componentInfoName = componentInfoName;
1475 private static String activityElementToString(ActivityElement element)
1476 throws IllegalAccessException, InvocationTargetException {
1479 StringBuilder elementString =
new StringBuilder(
" <activity ");
1480 elementString.append(elementAttributesToString(element));
1481 elementString.append(
">\\n");
1484 elementString.append(subelementsToString(element.metaDataElements()));
1485 elementString.append(subelementsToString(element.intentFilters()));
1488 return elementString.append(
" </activity>\\n").toString();
1493 private static String receiverElementToString(ReceiverElement element)
1494 throws IllegalAccessException, InvocationTargetException {
1497 StringBuilder elementString =
new StringBuilder(
" <receiver ");
1498 elementString.append(elementAttributesToString(element));
1499 elementString.append(
">\\n");
1502 elementString.append(subelementsToString(element.metaDataElements()));
1503 elementString.append(subelementsToString(element.intentFilters()));
1506 return elementString.append(
" </receiver>\\n").toString();
1511 private static String serviceElementToString(ServiceElement element)
1512 throws IllegalAccessException, InvocationTargetException {
1515 StringBuilder elementString =
new StringBuilder(
" <service ");
1516 elementString.append(elementAttributesToString(element));
1517 elementString.append(
">\\n");
1520 elementString.append(subelementsToString(element.metaDataElements()));
1521 elementString.append(subelementsToString(element.intentFilters()));
1524 return elementString.append(
" </service>\\n").toString();
1529 private static String providerElementToString(ProviderElement element)
1530 throws IllegalAccessException, InvocationTargetException {
1533 StringBuilder elementString =
new StringBuilder(
" <provider ");
1534 elementString.append(elementAttributesToString(element));
1535 elementString.append(
">\\n");
1538 elementString.append(subelementsToString(element.metaDataElements()));
1539 elementString.append(subelementsToString(element.pathPermissionElement()));
1540 elementString.append(subelementsToString(element.grantUriPermissionElement()));
1543 return elementString.append(
" </provider>\\n").toString();
1548 private static String metaDataElementToString(MetaDataElement element)
1549 throws IllegalAccessException, InvocationTargetException {
1552 StringBuilder elementString =
new StringBuilder(
" <meta-data ");
1553 elementString.append(elementAttributesToString(element));
1555 return elementString.append(
"/>\\n").toString();
1560 private static String intentFilterElementToString(IntentFilterElement element)
1561 throws IllegalAccessException, InvocationTargetException {
1564 StringBuilder elementString =
new StringBuilder(
" <intent-filter ");
1565 elementString.append(elementAttributesToString(element));
1566 elementString.append(
">\\n");
1569 elementString.append(subelementsToString(element.actionElements()));
1570 elementString.append(subelementsToString(element.categoryElements()));
1571 elementString.append(subelementsToString(element.dataElements()));
1574 return elementString.append(
" </intent-filter>\\n").toString();
1579 private static String actionElementToString(ActionElement element)
1580 throws IllegalAccessException, InvocationTargetException {
1583 StringBuilder elementString =
new StringBuilder(
" <action ");
1584 elementString.append(elementAttributesToString(element));
1586 return elementString.append(
"/>\\n").toString();
1591 private static String categoryElementToString(CategoryElement element)
1592 throws IllegalAccessException, InvocationTargetException {
1595 StringBuilder elementString =
new StringBuilder(
" <category ");
1596 elementString.append(elementAttributesToString(element));
1598 return elementString.append(
"/>\\n").toString();
1603 private static String dataElementToString(DataElement element)
1604 throws IllegalAccessException, InvocationTargetException {
1607 StringBuilder elementString =
new StringBuilder(
" <data ");
1608 elementString.append(elementAttributesToString(element));
1610 return elementString.append(
"/>\\n").toString();
1615 private static String pathPermissionElementToString(PathPermissionElement element)
1616 throws IllegalAccessException, InvocationTargetException {
1619 StringBuilder elementString =
new StringBuilder(
" <path-permission ");
1620 elementString.append(elementAttributesToString(element));
1622 return elementString.append(
"/>\\n").toString();
1627 private static String grantUriPermissionElementToString(GrantUriPermissionElement element)
1628 throws IllegalAccessException, InvocationTargetException {
1631 StringBuilder elementString =
new StringBuilder(
" <grant-uri-permission ");
1632 elementString.append(elementAttributesToString(element));
1634 return elementString.append(
"/>\\n").toString();
1642 private static String elementAttributesToString(Annotation element)
1643 throws IllegalAccessException, InvocationTargetException {
1644 StringBuilder attributeString =
new StringBuilder(
"");
1645 Class<? extends Annotation> clazz = element.annotationType();
1646 java.lang.reflect.Method[] methods = clazz.getDeclaredMethods();
1647 String attributeSeparator =
"";
1648 for (java.lang.reflect.Method method : methods) {
1649 int modCode = method.getModifiers();
1650 if (java.lang.reflect.Modifier.isPublic(modCode)
1651 && !java.lang.reflect.Modifier.isStatic(modCode)) {
1652 if (method.getReturnType().getSimpleName().equals(
"String")) {
1654 String attributeValue = (String) method.invoke(clazz.cast(element));
1655 if (!attributeValue.equals(
"")) {
1656 attributeString.append(attributeSeparator);
1657 attributeString.append(
"android:");
1658 attributeString.append(method.getName());
1659 attributeString.append(
"=\\\"");
1660 attributeString.append(attributeValue);
1661 attributeString.append(
"\\\"");
1662 attributeSeparator =
" ";
1667 return attributeString.toString();
1672 private static String subelementsToString(Annotation[] subelements)
1673 throws IllegalAccessException, InvocationTargetException {
1674 StringBuilder subelementString =
new StringBuilder(
"");
1675 for (Annotation subelement : subelements) {
1676 if (subelement instanceof MetaDataElement) {
1677 subelementString.append(metaDataElementToString((MetaDataElement) subelement));
1678 }
else if (subelement instanceof IntentFilterElement) {
1679 subelementString.append(intentFilterElementToString((IntentFilterElement) subelement));
1680 }
else if (subelement instanceof ActionElement) {
1681 subelementString.append(actionElementToString((ActionElement) subelement));
1682 }
else if (subelement instanceof CategoryElement) {
1683 subelementString.append(categoryElementToString((CategoryElement) subelement));
1684 }
else if (subelement instanceof DataElement) {
1685 subelementString.append(dataElementToString((DataElement) subelement));
1686 }
else if (subelement instanceof PathPermissionElement) {
1687 subelementString.append(pathPermissionElementToString((PathPermissionElement) subelement));
1688 }
else if (subelement instanceof GrantUriPermissionElement) {
1689 subelementString.append(grantUriPermissionElementToString((GrantUriPermissionElement) subelement));
1692 return subelementString.toString();
1695 private void processProperties(ComponentInfo componentInfo,
1696 Element componentElement) {
1699 Map<String, Element> propertyElementsToCheck =
new HashMap<>();
1701 for (Element element : componentElement.getEnclosedElements()) {
1702 if (!isPublicMethod(element)) {
1707 String propertyName = element.getSimpleName().toString();
1708 processConditionalAnnotations(componentInfo, element, propertyName);
1711 DesignerProperty designerProperty = element.getAnnotation(DesignerProperty.class);
1712 if (designerProperty !=
null) {
1713 componentInfo.designerProperties.put(propertyName, designerProperty);
1714 propertyElementsToCheck.put(propertyName, element);
1720 if (element.getAnnotation(SimpleProperty.class) ==
null) {
1721 if (componentInfo.properties.containsKey(propertyName)) {
1723 Property priorProperty = componentInfo.properties.get(propertyName);
1724 if (priorProperty.componentInfoName.equals(componentInfo.name)) {
1732 componentInfo.properties.remove(propertyName);
1739 Property newProperty = executableElementToProperty(element, componentInfo.name);
1740 if (designerProperty !=
null
1741 && designerProperty.editorType().equals(PropertyTypeConstants.PROPERTY_TYPE_COLOR)) {
1743 newProperty.color =
true;
1746 if (componentInfo.properties.containsKey(propertyName)) {
1747 Property priorProperty = componentInfo.properties.get(propertyName);
1749 if (!priorProperty.type.equals(newProperty.type)) {
1754 if (newProperty.readable) {
1755 priorProperty.type = newProperty.type;
1756 }
else if (priorProperty.writable) {
1758 throw new RuntimeException(
"Inconsistent types " + priorProperty.type +
1759 " and " + newProperty.type +
" for property " +
1760 propertyName +
" in component " + componentInfo.name);
1765 if ((priorProperty.description.isEmpty() || priorProperty.isDefaultDescription()
1766 || element.getAnnotation(Override.class) !=
null)
1767 && !newProperty.description.isEmpty() && !newProperty.isDefaultDescription()) {
1768 priorProperty.setDescription(newProperty.description);
1770 if (!newProperty.longDescription.isEmpty()) {
1772 priorProperty.longDescription = newProperty.longDescription;
1774 if (priorProperty.propertyCategory == PropertyCategory.UNSET) {
1775 priorProperty.propertyCategory = newProperty.propertyCategory;
1776 }
else if (newProperty.propertyCategory != priorProperty.propertyCategory &&
1777 newProperty.propertyCategory != PropertyCategory.UNSET) {
1778 throw new RuntimeException(
1779 "Property " + propertyName +
" has inconsistent categories " +
1780 priorProperty.propertyCategory +
" and " +
1781 newProperty.propertyCategory +
" in component " +
1782 componentInfo.name);
1784 priorProperty.readable = priorProperty.readable || newProperty.readable;
1785 priorProperty.writable = priorProperty.writable || newProperty.writable;
1786 priorProperty.userVisible = priorProperty.isUserVisible() && newProperty.isUserVisible();
1787 priorProperty.deprecated = priorProperty.isDeprecated() && newProperty.isDeprecated();
1788 priorProperty.componentInfoName = componentInfo.name;
1789 priorProperty.color = newProperty.color || priorProperty.color;
1792 componentInfo.properties.put(propertyName, newProperty);
1801 Set<String> propertyNames =
new HashSet<>(componentInfo.designerProperties.keySet());
1802 propertyNames.removeAll(componentInfo.properties.keySet());
1803 if (!propertyNames.isEmpty()) {
1804 for (String propertyName : propertyNames) {
1806 String.format(MISSING_SIMPLE_PROPERTY_ANNOTATION, propertyName),
1807 propertyElementsToCheck.get(propertyName));
1815 private void processEvents(ComponentInfo componentInfo,
1816 Element componentElement) {
1817 for (Element element : componentElement.getEnclosedElements()) {
1818 if (!isPublicMethod(element)) {
1823 String eventName = element.getSimpleName().toString();
1824 processConditionalAnnotations(componentInfo, element, eventName);
1826 SimpleEvent simpleEventAnnotation = element.getAnnotation(SimpleEvent.class);
1830 if (simpleEventAnnotation ==
null) {
1831 if (componentInfo.events.containsKey(eventName)) {
1832 componentInfo.events.remove(eventName);
1835 String eventDescription = simpleEventAnnotation.description();
1836 String longEventDescription = elementUtils.getDocComment(element);
1837 if (eventDescription.isEmpty()) {
1838 eventDescription = longEventDescription;
1839 if (eventDescription ==
null) {
1840 messager.printMessage(Diagnostic.Kind.WARNING,
1841 "In component " + componentInfo.name +
1842 ", event " + eventName +
1843 " is missing a description.");
1844 eventDescription =
"";
1847 boolean userVisible = simpleEventAnnotation.userVisible();
1848 boolean deprecated = elementUtils.isDeprecated(element);
1849 Event
event =
new Event(eventName, eventDescription, longEventDescription, userVisible, deprecated);
1850 componentInfo.events.put(event.name, event);
1853 if (!(element instanceof ExecutableElement)) {
1854 throw new RuntimeException(
"In component " + componentInfo.name +
1855 ", the representation of SimpleEvent " + eventName +
1856 " does not implement ExecutableElement.");
1858 ExecutableElement e = (ExecutableElement) element;
1861 for (VariableElement ve : e.getParameters()) {
1862 event.addParameter(ve.getSimpleName().toString(),
1863 ve.asType().toString(),
1864 ve.getAnnotation(IsColor.class) !=
null);
1865 updateComponentTypes(ve.asType());
1871 private void processMethods(ComponentInfo componentInfo,
1872 Element componentElement) {
1873 for (Element element : componentElement.getEnclosedElements()) {
1874 if (!isPublicMethod(element)) {
1879 String methodName = element.getSimpleName().toString();
1880 processConditionalAnnotations(componentInfo, element, methodName);
1882 SimpleFunction simpleFunctionAnnotation = element.getAnnotation(SimpleFunction.class);
1886 if (simpleFunctionAnnotation ==
null) {
1887 if (componentInfo.methods.containsKey(methodName)) {
1888 componentInfo.methods.remove(methodName);
1891 String methodLongDescription = elementUtils.getDocComment(element);
1892 String methodDescription = simpleFunctionAnnotation.description();
1893 if (methodDescription.isEmpty()) {
1894 methodDescription = methodLongDescription;
1895 if (methodDescription ==
null) {
1896 messager.printMessage(Diagnostic.Kind.WARNING,
1897 "In component " + componentInfo.name +
1898 ", method " + methodName +
1899 " is missing a description.");
1900 methodDescription =
"";
1903 boolean userVisible = simpleFunctionAnnotation.userVisible();
1904 boolean deprecated = elementUtils.isDeprecated(element);
1905 Method method =
new Method(methodName, methodDescription, methodLongDescription, userVisible, deprecated);
1906 componentInfo.methods.put(method.name, method);
1909 if (!(element instanceof ExecutableElement)) {
1910 throw new RuntimeException(
"In component " + componentInfo.name +
1911 ", the representation of SimpleFunction " + methodName +
1912 " does not implement ExecutableElement.");
1914 ExecutableElement e = (ExecutableElement) element;
1917 for (VariableElement ve : e.getParameters()) {
1918 method.addParameter(ve.getSimpleName().toString(),
1919 ve.asType().toString(),
1920 ve.getAnnotation(IsColor.class) !=
null);
1921 updateComponentTypes(ve.asType());
1925 if (e.getReturnType().getKind() != TypeKind.VOID) {
1926 method.returnType = e.getReturnType().toString();
1927 if (e.getAnnotation(IsColor.class) !=
null) {
1928 method.color =
true;
1930 updateComponentTypes(e.getReturnType());
1945 private void processConditionalAnnotations(ComponentInfo componentInfo, Element element,
1948 UsesPermissions usesPermissions = element.getAnnotation(UsesPermissions.class);
1949 if (usesPermissions !=
null) {
1950 componentInfo.conditionalPermissions.put(blockName, usesPermissions.value());
1953 UsesBroadcastReceivers broadcastReceiver = element.getAnnotation(UsesBroadcastReceivers.class);
1954 if (broadcastReceiver !=
null) {
1956 Set<String> receivers =
new HashSet<>();
1957 for (ReceiverElement re : broadcastReceiver.receivers()) {
1958 updateWithNonEmptyValue(receivers, receiverElementToString(re));
1960 componentInfo.conditionalBroadcastReceivers.put(blockName, receivers.toArray(
new String[0]));
1961 }
catch (Exception e) {
1962 messager.printMessage(Kind.ERROR,
"Unable to process broadcast receiver", element);
1966 UsesServices service = element.getAnnotation(UsesServices.class);
1967 if (service !=
null) {
1969 Set<String> services =
new HashSet<>();
1970 for (ServiceElement se : service.services()) {
1971 updateWithNonEmptyValue(services, serviceElementToString(se));
1973 componentInfo.conditionalServices.put(blockName, services.toArray(
new String[0]));
1974 }
catch (Exception e) {
1975 messager.printMessage(Kind.ERROR,
"Unable to process service", element);
1979 UsesContentProviders contentProvider = element.getAnnotation(UsesContentProviders.class);
1980 if (contentProvider !=
null) {
1982 Set<String> providers =
new HashSet<>();
1983 for (ProviderElement pe : contentProvider.providers()) {
1984 updateWithNonEmptyValue(providers, providerElementToString(pe));
1986 componentInfo.conditionalContentProviders.put(blockName, providers.toArray(
new String[0]));
1987 }
catch (Exception e) {
1988 messager.printMessage(Kind.ERROR,
"Unable to process content provider", element);
2002 protected abstract void outputResults() throws IOException;
2014 protected final String javaTypeToYailType(String type) {
2015 if (BOXED_TYPES.containsKey(type)) {
2016 throw new IllegalArgumentException(String.format(BOXED_TYPE_ERROR, type,
2017 BOXED_TYPES.get(type)));
2020 if (type.equals(
"boolean")) {
2024 if (type.equals(
"java.lang.String")) {
2028 if (type.equals(
"float") || type.equals(
"double") || type.equals(
"int") ||
2029 type.equals(
"short") || type.equals(
"long") || type.equals(
"byte")) {
2033 if (type.equals(
"com.google.appinventor.components.runtime.util.YailList")) {
2037 if (type.startsWith(
"java.util.List")) {
2040 if (type.equals(
"com.google.appinventor.components.runtime.util.YailDictionary")) {
2041 return "dictionary";
2043 if (type.equals(
"com.google.appinventor.components.runtime.util.YailObject")) {
2044 return "yailobject";
2048 if (type.equals(
"java.util.Calendar")) {
2049 return "InstantInTime";
2052 if (type.equals(
"java.lang.Object")) {
2056 if (type.equals(
"com.google.appinventor.components.runtime.Component")) {
2061 if (componentTypes.contains(type)) {
2065 throw new IllegalArgumentException(
"Cannot convert Java type '" + type +
"' to Yail type");
2075 protected FileObject createOutputFileObject(String fileName)
throws IOException {
2076 return processingEnv.getFiler().
2077 createResource(StandardLocation.SOURCE_OUTPUT, OUTPUT_PACKAGE, fileName);
2090 protected Writer getOutputWriter(String fileName)
throws IOException {
2091 return createOutputFileObject(fileName).openWriter();
2102 private void updateComponentTypes(TypeMirror type) {
2103 if (type.getKind() == TypeKind.DECLARED) {
2104 type.accept(
new SimpleTypeVisitor7<Boolean, Set<String>>(
false) {
2106 public Boolean visitDeclared(DeclaredType t, Set<String> types) {
2107 final String typeName = t.asElement().toString();
2108 if (
"com.google.appinventor.components.runtime.Component".equals(typeName)) {
2111 if (!types.contains(typeName)) {
2112 types.add(typeName);
2113 final TypeElement typeElement = (TypeElement) t.asElement();
2114 if (typeElement.getSuperclass().accept(
this, types)) {
2115 componentTypes.add(typeName);
2118 for (TypeMirror iface : typeElement.getInterfaces()) {
2119 if (iface.accept(
this, types)) {
2120 componentTypes.add(typeName);
2125 return componentTypes.contains(typeName);
2131 private void updateWithNonEmptyValue(Set<String> collection, String value) {
2132 String trimmedValue = value.trim();
2133 if (!trimmedValue.isEmpty()) {
2134 collection.add(trimmedValue);