// -*- mode: java; c-basic-offset: 2; -*-
// Copyright 2015 MIT, All rights reserved
// Released under the Apache License, Version 2.0
// http://www.apache.org/licenses/LICENSE-2.0
package com.google.appinventor.components.scripts;
import java.io.IOException;
import java.io.Writer;
import java.util.HashSet;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import javax.tools.Diagnostic;
import javax.tools.Diagnostic.Kind;
import javax.tools.FileObject;
public final class ComponentTranslationGenerator extends ComponentProcessor {
// Where to write results.
private static final String OUTPUT_FILE_NAME = "ComponentsTranslation.java";
private static final String AUTOGEN_OUTPUT_FILE_NAME = "AutogeneratedOdeMessages.java";
private final Map<String, String> tooltips = new TreeMap<>();
private final Map<String, Set<String>> tooltipComponent = new TreeMap<>();
private final Set<String> collisionKeys = new HashSet<>();
private void outputComponent(ComponentInfo component, Set<String> outProperties,
Set<String> outMethods, Set<String> outEvents, Set<String> outParameters, StringBuilder sb) {
if (component.getExternal()) { // Avoid adding entries for external components
return;
}
Map<String, Parameter> parameters = new LinkedHashMap<>();
sb.append("\n\n/* Component: ").append(component.getName()).append(" */\n\n");
sb.append(" map.put(\"COMPONENT-")
.append(component.getName())
.append("\", MESSAGES.")
.append(Character.toLowerCase(component.getName().charAt(0)))
.append(component.getName().substring(1))
.append("ComponentPallette());\n\n");
sb.append(" map.put(\"")
.append(component.getName())
.append("-helpString\", MESSAGES.")
.append(component.getName())
.append("HelpStringComponentPallette());\n\n");
sb.append("\n\n/* Properties */\n\n");
for (Property prop : component.properties.values()) {
String propertyName = prop.name;
if (outProperties.contains(propertyName)) {
continue;
}
if (prop.isUserVisible()
|| component.designerProperties.containsKey(propertyName)
|| prop.isDeprecated() // [lyn, 2015/12/30] For deprecated AI2 blocks (but not AI1 blocks)
// must translate property names so they can be displayed in bad blocks.
) {
sb.append(" map.put(\"PROPERTY-")
.append(propertyName)
.append("\", MESSAGES.")
.append(propertyName)
.append("Properties());\n");
outProperties.add(propertyName);
}
}
sb.append("\n\n/* Events */\n\n");
for (Event event : component.events.values()) {
String propertyName = event.name;
if (outEvents.contains(propertyName)) {
continue;
}
if (event.userVisible
|| event.deprecated // [lyn, 2015/12/30] For deprecated AI2 blocks (but not AI1 blocks)
// must translate property names so they can be displayed in bad blocks.
) {
sb.append(" map.put(\"EVENT-")
.append(propertyName)
.append("\", MESSAGES.")
.append(propertyName)
.append("Events());\n");
for (Parameter parameter : event.parameters) {
parameters.put(parameter.name, parameter);
}
outEvents.add(propertyName);
}
}
sb.append("\n\n/* Methods */\n\n");
for (Method method : component.methods.values()) {
String propertyName = method.name;
if (outMethods.contains(propertyName)) {
continue;
}
if (method.userVisible
|| method.deprecated // [lyn, 2015/12/30] For deprecated AI2 blocks (but not AI1 blocks)
// must translate property names so they can be displayed in bad blocks.
) {
sb.append(" map.put(\"METHOD-")
.append(propertyName)
.append("\", MESSAGES.")
.append(propertyName)
.append("Methods());\n");
for (Parameter parameter : method.parameters) {
parameters.put(parameter.name, parameter);
}
outMethods.add(propertyName);
}
}
// This special case adds the notAlreadyHandled parameter, which is the second parameter for the generic event
// handlers. Since it's not explicitly declared in any event handler, we add it here for internationalization.
parameters.put("notAlreadyHandled", new Parameter("notAlreadyHandled", "boolean"));
sb.append("\n\n/* Parameters */\n\n");
for (Parameter parameter : parameters.values()) {
String name = Character.toLowerCase(parameter.name.charAt(0)) + parameter.name.substring(1);
if (outParameters.contains(parameter.name)) {
continue;
}
sb.append(" map.put(\"PARAM-")
.append(parameter.name).append("\", MESSAGES.")
.append(name)
.append("Params());\n");
outParameters.add(parameter.name);
}
}
private void outputCategory(String category, StringBuilder sb) {
// santize the category name
String[] parts = category.split(" ");
sb.append(" map.put(\"CATEGORY-")
.append(category)
.append("\", MESSAGES.")
.append(parts[0].replaceAll("[^A-Za-z0-9]", "").toLowerCase());
for (int i = 1; i < parts.length; i++) {
String lower = parts[i].replaceAll("[^A-Za-z0-9]", "").toLowerCase();
if (lower.isEmpty()) {
continue;
}
sb.append(Character.toUpperCase(lower.charAt(0)));
sb.append(lower.substring(1));
}
sb.append("ComponentCategory());\n");
}
private void outputPropertyCategory(String category, StringBuilder sb) {
sb.append(" map.put(\"CATEGORY-");
sb.append(category);
sb.append("\", MESSAGES.");
sb.append(category);
sb.append("PropertyCategory());\n");
}
private void outputComponentAutogen(ComponentInfo component,
Map<String, Property> outProperties, Map<String, Method> outMethods,
Map<String, Event> outEvents, StringBuilder sb) {
sb.append(" @DefaultMessage(\"");
sb.append(component.getName());
sb.append("\")\n");
sb.append(" @Description(\"\")\n");
sb.append(" String ");
sb.append(Character.toLowerCase(component.getName().charAt(0)));
sb.append(component.getName().substring(1));
sb.append("ComponentPallette();\n\n");
sb.append(" @DefaultMessage(\"");
sb.append(sanitize(component.description));
sb.append("\")\n");
sb.append(" @Description(\"\")\n");
sb.append(" String ");
sb.append(component.getName());
sb.append("HelpStringComponentPallette();\n\n");
for (Property property : component.properties.values()) {
if (property.isUserVisible() || component.designerProperties.containsKey(property.name) ||
property.isDeprecated()) {
outProperties.put(property.name, property);
}
}
for (Method method : component.methods.values()) {
if (method.userVisible || method.deprecated) {
outMethods.put(method.name, method);
}
}
for (Event event : component.events.values()) {
if (event.userVisible || event.deprecated) {
outEvents.put(event.name, event);
}
}
}
private void outputPropertyAutogen(Property property, StringBuilder sb) {
sb.append(" @DefaultMessage(\"");
sb.append(sanitize(property.name));
sb.append("\")\n");
sb.append(" @Description(\"\")\n");
sb.append(" String ");
sb.append(property.name);
sb.append("Properties();\n\n");
}
private void outputMethodAutogen(Method method, Map<String, Parameter> outParameters, StringBuilder sb) {
sb.append(" @DefaultMessage(\"");
sb.append(sanitize(method.name));
sb.append("\")\n");
sb.append(" @Description(\"\")\n");
sb.append(" String ");
sb.append(method.name);
sb.append("Methods();\n\n");
for (Parameter param : method.parameters) {
outParameters.put(param.name, param);
}
}
private void outputEventAutogen(Event event, Map<String, Parameter> outParameters, StringBuilder sb) {
sb.append(" @DefaultMessage(\"");
sb.append(sanitize(event.name));
sb.append("\")\n");
sb.append(" @Description(\"\")\n");
sb.append(" String ");
sb.append(event.name);
sb.append("Events();\n\n");
for (Parameter param : event.parameters) {
outParameters.put(param.name, param);
}
}
private void outputParameterAutogen(Parameter parameter, StringBuilder sb) {
if (parameter.name.equals("Url")) {
return;
}
sb.append(" @DefaultMessage(\"");
sb.append(sanitize(parameter.name));
sb.append("\")\n");
sb.append(" @Description(\"\")\n");
sb.append(" String ");
sb.append(Character.toLowerCase(parameter.name.charAt(0)));
sb.append(parameter.name.substring(1));
sb.append("Params();\n\n");
}
private void outputCategoryAutogen(String category, StringBuilder sb) {
String[] parts = category.split(" ");
sb.append(" @DefaultMessage(\"");
sb.append(category);
sb.append("\")\n");
sb.append(" @Description(\"\")\n");
sb.append(" String ");
sb.append(parts[0].replaceAll("[^A-Za-z0-9]", "").toLowerCase());
for (int i = 1; i < parts.length; i++) {
String lower = parts[i].replaceAll("[^A-Za-z0-9]", "").toLowerCase();
if (lower.isEmpty()) {
continue;
}
sb.append(Character.toUpperCase(lower.charAt(0)));
sb.append(lower.substring(1));
}
sb.append("ComponentCategory();\n\n");
}
private void outputPropertyCategoryAutogen(String category, StringBuilder sb) {
sb.append(" @DefaultMessage(\"");
sb.append(category);
sb.append("\")\n");
sb.append(" @Description(\"\")\n");
sb.append(" String ");
sb.append(category);
sb.append("PropertyCategory();\n\n");
}
private String sanitize(String input) {
return input.replaceAll("\r", "").replaceAll("\n", "").replaceAll("\\\\", "\\\\\\\\")
.replaceAll("\"", "\\\\\"").replaceAll("'", "''").replaceAll("[ \t]+", " ").trim();
}
private void storeTooltip(ComponentInfo component, String name, String suffix,
String description) {
String key = name + suffix;
String value = tooltips.get(key);
if (collisionKeys.contains(key)) {
// Already detected a collision
key = component.getName() + "__" + key;
tooltips.put(key, description);
} else if (value == null) {
// This is the first observation of this key
tooltips.put(key, description);
Set<String> components = new HashSet<>();
components.add(component.getName());
tooltipComponent.put(key, components);
} else if (!value.equals(description)) {
// Descriptions don't match == collision!
collisionKeys.add(key);
for (String componentName : tooltipComponent.get(key)) {
tooltips.put(componentName + "__" + key, value);
}
key = component.getName() + "__" + key;
tooltips.put(key, description);
} else {
// Two (or more) components have the exact same description. Technically not a collision, but
// we need to do some bookkeeping in case a collision is detected with another component.
tooltipComponent.get(key).add(component.getName());
}
}
private void computeTooltipMap(ComponentInfo component) {
for (Property property : component.properties.values()) {
storeTooltip(component, property.name, "PropertyDescriptions", property.getDescription());
}
for (Method method : component.methods.values()) {
storeTooltip(component, method.name, "MethodDescriptions", method.description);
}
for (Event event : component.events.values()) {
storeTooltip(component, event.name, "EventDescriptions", event.description);
}
}
protected void outputAutogenOdeMessages() throws IOException {
Set<String> categories = new TreeSet<>();
Map<String, Property> properties = new TreeMap<>();
Map<String, Method> methods = new TreeMap<>();
Map<String, Event> events = new TreeMap<>();
Map<String, Parameter> parameters = new TreeMap<>();
StringBuilder sb = new StringBuilder();
sb.append("// THIS FILE IS AUTOMATICALLY GENERATED DURING COMPILATION.\n");
sb.append("// DO NOT EDIT THIS FILE. ANY CHANGES WILL BE OVERWRITTEN.\n\n");
sb.append("package com.google.appinventor.client;\n\n");
sb.append("import com.google.gwt.i18n.client.Messages;\n\n");
sb.append("public interface AutogeneratedOdeMessages extends Messages {\n");
sb.append("\n /* Components */\n");
for (Map.Entry<String, ComponentInfo> entry : components.entrySet()) {
ComponentInfo component = entry.getValue();
outputComponentAutogen(component, properties, methods, events, sb);
computeTooltipMap(component);
categories.add(component.getCategory());
}
for (String key : collisionKeys) {
tooltips.remove(key);
}
sb.append("\n /* Properties */\n");
for (Map.Entry<String, Property> entry : properties.entrySet()) {
outputPropertyAutogen(entry.getValue(), sb);
}
sb.append("\n /* Methods */\n");
for (Map.Entry<String, Method> entry : methods.entrySet()) {
outputMethodAutogen(entry.getValue(), parameters, sb);
}
sb.append("\n /* Events */\n");
for (Map.Entry<String, Event> entry : events.entrySet()) {
outputEventAutogen(entry.getValue(), parameters, sb);
}
for (Map.Entry<String, String> entry : tooltips.entrySet()) {
sb.append(" @DefaultMessage(\"");
sb.append(sanitize(entry.getValue()));
sb.append("\")\n");
sb.append(" @Description(\"\")\n");
sb.append(" String ");
sb.append(entry.getKey());
sb.append("();\n\n");
}
sb.append("\n /* Parameters */\n");
for (Map.Entry<String, Parameter> entry : parameters.entrySet()) {
outputParameterAutogen(entry.getValue(), sb);
}
sb.append("\n /* Component Categories */\n");
for (String category : categories) {
outputCategoryAutogen(category, sb);
}
sb.append("\n /* Property Categories */\n");
outputPropertyCategoryAutogen("Appearance", sb);
outputPropertyCategoryAutogen("Behavior", sb);
outputPropertyCategoryAutogen("Unspecified", sb);
sb.append("}\n");
FileObject src = createOutputFileObject(AUTOGEN_OUTPUT_FILE_NAME);
Writer writer = src.openWriter();
writer.write(sb.toString());
writer.flush();
writer.close();
messager.printMessage(Kind.NOTE, "Wrote file " + src.toUri());
}
@Override
protected void outputResults() throws IOException {
outputAutogenOdeMessages();
StringBuilder sb = new StringBuilder();
sb.append("package com.google.appinventor.client;\n");
sb.append("\n");
sb.append("import java.util.HashMap;\n");
sb.append("import java.util.Map;\n");
sb.append("\n");
sb.append("import static com.google.appinventor.client.Ode.MESSAGES;\n");
sb.append("\n");
sb.append("public class ComponentsTranslation {\n");
sb.append(" public static Map<String, String> myMap = map();\n\n");
sb.append(" private static String getName(String key) {\n");
sb.append(" String value = myMap.get(key);\n");
sb.append(" if (key == null) {\n");
sb.append(" return \"**Missing key in ComponentsTranslations**\";\n");
sb.append(" } else {\n");
sb.append(" return value;\n");
sb.append(" }\n");
sb.append(" }\n\n");
sb.append(" public static String getPropertyName(String key) {\n");
sb.append(" String value = getName(\"PROPERTY-\" + key);\n");
sb.append(" if(value == null) return key;\n");
sb.append(" return value;\n");
sb.append(" }\n\n");
sb.append(" public static String getPropertyDescription(String key) {\n");
sb.append(" String value = getName(\"PROPDESC-\" + key);\n");
sb.append(" if(value == null) return key;\n");
sb.append(" return value;\n");
sb.append(" }\n\n");
sb.append(" public static String getMethodName(String key) {\n");
sb.append(" String value = getName(\"METHOD-\" + key);\n");
sb.append(" if(value == null) return key;\n");
sb.append(" return value;\n");
sb.append(" }\n");
sb.append("\n");
sb.append(" public static String getEventName(String key) {\n");
sb.append(" String value = getName(\"EVENT-\" + key);\n");
sb.append(" if(value == null) return key;\n");
sb.append(" return value;\n");
sb.append(" }\n");
sb.append("\n");
sb.append(" public static String getComponentName(String key) {\n");
sb.append(" String value = getName(\"COMPONENT-\" + key);\n");
sb.append(" if(value == null) return key;\n");
sb.append(" return value;\n");
sb.append(" }\n");
sb.append("\n");
sb.append(" public static String getCategoryName(String key) {\n");
sb.append(" String value = getName(\"CATEGORY-\" + key);\n");
sb.append(" if(value == null) return key;\n");
sb.append(" return value;\n");
sb.append(" }\n");
sb.append("\n");
sb.append(" public static String getComponentHelpString(String key) {\n");
sb.append(" String value = getName(key + \"-helpString\");\n");
sb.append(" if(value == null) return key;\n");
sb.append(" return value;\n");
sb.append(" }\n");
sb.append(" public static HashMap<String, String> map() {\n");
sb.append(" HashMap<String, String> map = new HashMap<String, String>();\n");
// Components are already sorted.
Set<String> categories = new TreeSet<>();
Set<String> properties = new TreeSet<>();
Set<String> methods = new TreeSet<>();
Set<String> events = new TreeSet<>();
Set<String> parameters = new TreeSet<>();
for (Map.Entry<String, ComponentInfo> entry : components.entrySet()) {
ComponentInfo component = entry.getValue();
outputComponent(component, properties, methods, events, parameters, sb);
categories.add(component.getCategory());
}
sb.append("\n\n /* Descriptions */\n\n");
for (String key : tooltips.keySet()) {
if (key.endsWith("PropertyDescriptions")) {
sb.append(" map.put(\"PROPDESC-");
sb.append(key.replaceAll("__", "."));
sb.append("\", MESSAGES.");
sb.append(key);
sb.append("());\n");
} else if (key.endsWith("MethodDescriptions")) {
sb.append(" map.put(\"METHODDESC-");
sb.append(key.replaceAll("__", "."));
sb.append("\", MESSAGES.");
sb.append(key);
sb.append("());\n");
} else if (key.endsWith("EventDescriptions")) {
sb.append(" map.put(\"EVENTDESC-");
sb.append(key.replaceAll("__", "."));
sb.append("\", MESSAGES.");
sb.append(key);
sb.append("());\n");
}
}
sb.append("\n\n /* Categories */\n\n");
for (String category : categories) {
outputCategory(category, sb);
}
sb.append(" return map;\n");
sb.append(" }\n");
sb.append("}\n");
FileObject src = createOutputFileObject(OUTPUT_FILE_NAME);
Writer writer = src.openWriter();
writer.write(sb.toString());
writer.flush();
writer.close();
messager.printMessage(Diagnostic.Kind.NOTE, "Wrote file " + src.toUri());
}
}
Replace your ComponentTranslationGenerator.java with this code, I hope this will work for you because it is working fine for me.