AI2 Component  (Version nb184)
ComponentTranslationGenerator.java
Go to the documentation of this file.
1 // -*- mode: java; c-basic-offset: 2; -*-
2 // Copyright 2015 MIT, All rights reserved
3 // Released under the Apache License, Version 2.0
4 // http://www.apache.org/licenses/LICENSE-2.0
5 
6 package com.google.appinventor.components.scripts;
7 
8 import java.io.IOException;
9 import java.io.Writer;
10 import java.util.HashSet;
11 import java.util.TreeMap;
12 import java.util.TreeSet;
13 import java.util.LinkedHashMap;
14 import java.util.Map;
15 import java.util.Set;
16 
17 import javax.tools.Diagnostic;
18 import javax.tools.Diagnostic.Kind;
19 import javax.tools.FileObject;
20 
22  // Where to write results.
23  private static final String OUTPUT_FILE_NAME = "ComponentsTranslation.java";
24  private static final String AUTOGEN_OUTPUT_FILE_NAME = "AutogeneratedOdeMessages.java";
25 
26  private Map<String, String> tooltips = new TreeMap<>();
27  private Map<String, Set<String>> tooltipComponent = new TreeMap<>();
28  private Set<String> collisionKeys = new HashSet<>();
29 
30  private void outputComponent(ComponentInfo component, Set<String> outProperties,
31  Set<String> outMethods, Set<String> outEvents, StringBuilder sb) {
32  if (component.getExternal()) { // Avoid adding entries for external components
33  return;
34  }
35  Map<String, Parameter> parameters = new LinkedHashMap<String, Parameter>();
36  sb.append("\n\n/* Component: " + component.getName() + " */\n\n");
37  sb.append(" map.put(\"COMPONENT-" + component.getName() + "\", MESSAGES."
38  + Character.toLowerCase(component.getName().charAt(0)) + component.getName().substring(1)
39  + "ComponentPallette());\n\n");
40  sb.append(" map.put(\"" + component.getName() + "-helpString\", MESSAGES."
41  + component.getName() + "HelpStringComponentPallette());\n\n");
42  sb.append("\n\n/* Properties */\n\n");
43  for (Property prop : component.properties.values()) {
44  String propertyName = prop.name;
45  if (prop.isUserVisible()
46  || component.designerProperties.containsKey(propertyName)
47  || prop.isDeprecated() // [lyn, 2015/12/30] For deprecated AI2 blocks (but not AI1 blocks)
48  // must translate property names so they can be displayed in bad blocks.
49  ) {
50  sb.append(" map.put(\"PROPERTY-" + propertyName + "\", MESSAGES." + propertyName + "Properties());\n");
51  outProperties.add(propertyName);
52  }
53  }
54  sb.append("\n\n/* Events */\n\n");
55  for (Event event : component.events.values()) {
56  String propertyName = event.name;
57  if (event.userVisible
58  || event.deprecated // [lyn, 2015/12/30] For deprecated AI2 blocks (but not AI1 blocks)
59  // must translate property names so they can be displayed in bad blocks.
60  ) {
61  sb.append(" map.put(\"EVENT-" + propertyName + "\", MESSAGES." + propertyName + "Events());\n");
62  for (Parameter parameter : event.parameters) {
63  parameters.put(parameter.name, parameter);
64  }
65  outEvents.add(propertyName);
66  }
67  }
68  sb.append("\n\n/* Methods */\n\n");
69  for (Method method : component.methods.values()) {
70  String propertyName = method.name;
71  if (method.userVisible
72  || method.deprecated // [lyn, 2015/12/30] For deprecated AI2 blocks (but not AI1 blocks)
73  // must translate property names so they can be displayed in bad blocks.
74  ) {
75  sb.append(" map.put(\"METHOD-" + propertyName + "\", MESSAGES." + propertyName + "Methods());\n");
76  for (Parameter parameter : method.parameters) {
77  parameters.put(parameter.name, parameter);
78  }
79  outMethods.add(propertyName);
80  }
81  }
82  // This special case adds the notAlreadyHandled parameter, which is the second parameter for the generic event
83  // handlers. Since it's not explicitly declared in any event handler, we add it here for internationalization.
84  parameters.put("notAlreadyHandled", new Parameter("notAlreadyHandled", "boolean"));
85  sb.append("\n\n/* Parameters */\n\n");
86  for (Parameter parameter : parameters.values()) {
87  sb.append(" map.put(\"PARAM-" + parameter.name + "\", MESSAGES." +
88  Character.toLowerCase(parameter.name.charAt(0)) + parameter.name.substring(1) +
89  "Params());\n");
90  }
91  }
92 
93  private void outputCategory(String category, StringBuilder sb) {
94  // santize the category name
95  String[] parts = category.split(" ");
96  sb.append(" map.put(\"CATEGORY-" + category + "\", MESSAGES." + parts[0].replaceAll("[^A-Za-z0-9]", "").toLowerCase());
97  for (int i = 1; i < parts.length; i++) {
98  String lower = parts[i].replaceAll("[^A-Za-z0-9]", "").toLowerCase();
99  sb.append(Character.toUpperCase(lower.charAt(0)));
100  sb.append(lower.substring(1));
101  }
102  sb.append("ComponentPallette());\n");
103  }
104 
105  private void outputPropertyCategory(String category, StringBuilder sb) {
106  sb.append(" map.put(\"CATEGORY-");
107  sb.append(category);
108  sb.append("\", MESSAGES.");
109  sb.append(category);
110  sb.append("PropertyCategory());\n");
111  }
112 
113  private void outputComponentAutogen(ComponentInfo component,
114  Map<String, Property> outProperties, Map<String, Method> outMethods,
115  Map<String, Event> outEvents, StringBuilder sb) {
116  sb.append(" @DefaultMessage(\"");
117  sb.append(component.getName());
118  sb.append("\")\n");
119  sb.append(" @Description(\"\")\n");
120  sb.append(" String ");
121  sb.append(Character.toLowerCase(component.getName().charAt(0)));
122  sb.append(component.getName().substring(1));
123  sb.append("ComponentPallette();\n\n");
124  sb.append(" @DefaultMessage(\"");
125  sb.append(sanitize(component.description));
126  sb.append("\")\n");
127  sb.append(" @Description(\"\")\n");
128  sb.append(" String ");
129  sb.append(component.getName());
130  sb.append("HelpStringComponentPallette();\n\n");
131  for (Property property : component.properties.values()) {
132  if (property.isUserVisible() || component.designerProperties.containsKey(property.name) ||
133  property.isDeprecated()) {
134  outProperties.put(property.name, property);
135  }
136  }
137  for (Method method : component.methods.values()) {
138  if (method.userVisible || method.deprecated) {
139  outMethods.put(method.name, method);
140  }
141  }
142  for (Event event : component.events.values()) {
143  if (event.userVisible || event.deprecated) {
144  outEvents.put(event.name, event);
145  }
146  }
147  }
148 
149  private void outputPropertyAutogen(Property property, StringBuilder sb) {
150  sb.append(" @DefaultMessage(\"");
151  sb.append(sanitize(property.name));
152  sb.append("\")\n");
153  sb.append(" @Description(\"\")\n");
154  sb.append(" String ");
155  sb.append(property.name);
156  sb.append("Properties();\n\n");
157  }
158 
159  private void outputMethodAutogen(Method method, Map<String, Parameter> outParameters, StringBuilder sb) {
160  sb.append(" @DefaultMessage(\"");
161  sb.append(sanitize(method.name));
162  sb.append("\")\n");
163  sb.append(" @Description(\"\")\n");
164  sb.append(" String ");
165  sb.append(method.name);
166  sb.append("Methods();\n\n");
167  for (Parameter param : method.parameters) {
168  outParameters.put(param.name, param);
169  }
170  }
171 
172  private void outputEventAutogen(Event event, Map<String, Parameter> outParameters, StringBuilder sb) {
173  sb.append(" @DefaultMessage(\"");
174  sb.append(sanitize(event.name));
175  sb.append("\")\n");
176  sb.append(" @Description(\"\")\n");
177  sb.append(" String ");
178  sb.append(event.name);
179  sb.append("Events();\n\n");
180  for (Parameter param : event.parameters) {
181  outParameters.put(param.name, param);
182  }
183  }
184 
185  private void outputParameterAutogen(Parameter parameter, StringBuilder sb) {
186  sb.append(" @DefaultMessage(\"");
187  sb.append(sanitize(parameter.name));
188  sb.append("\")\n");
189  sb.append(" @Description(\"\")\n");
190  sb.append(" String ");
191  sb.append(parameter.name);
192  sb.append("Params();\n\n");
193  }
194 
195  private void outputCategoryAutogen(String category, StringBuilder sb) {
196  String[] parts = category.split(" ");
197  sb.append(" @DefaultMessage(\"");
198  sb.append(category);
199  sb.append("\")\n");
200  sb.append(" @Description(\"\")\n");
201  sb.append(" String ");
202  sb.append(parts[0].replaceAll("[^A-Za-z0-9]", "").toLowerCase());
203  for (int i = 1; i < parts.length; i++) {
204  String lower = parts[i].replaceAll("[^A-Za-z0-9]", "").toLowerCase();
205  sb.append(Character.toUpperCase(lower.charAt(0)));
206  sb.append(lower.substring(1));
207  }
208  sb.append("ComponentPallette();\n\n");
209  }
210 
211  private void outputPropertyCategoryAutogen(String category, StringBuilder sb) {
212  sb.append(" @DefaultMessage(\"");
213  sb.append(category);
214  sb.append("\")\n");
215  sb.append(" @Description(\"\")\n");
216  sb.append(" String ");
217  sb.append(category);
218  sb.append("PropertyCategory();\n\n");
219  }
220 
221  private String sanitize(String input) {
222  return input.replaceAll("\r", "").replaceAll("\n", "").replaceAll("\\\\", "\\\\\\\\")
223  .replaceAll("\"", "\\\\\"").replaceAll("'", "''").replaceAll("[ \t]+", " ").trim();
224  }
225 
226  private void storeTooltip(ComponentInfo component, String name, String suffix,
227  String description) {
228  String key = name + suffix;
229  String value = tooltips.get(key);
230  if (collisionKeys.contains(key)) {
231  // Already detected a collision
232  key = component.getName() + "__" + key;
233  tooltips.put(key, description);
234  } else if (value == null) {
235  // This is the first observation of this key
236  tooltips.put(key, description);
237  Set<String> components = new HashSet<>();
238  components.add(component.getName());
239  tooltipComponent.put(key, components);
240  } else if (!value.equals(description)) {
241  // Descriptions don't match == collision!
242  collisionKeys.add(key);
243  for (String componentName : tooltipComponent.get(key)) {
244  tooltips.put(componentName + "__" + key, value);
245  }
246  key = component.getName() + "__" + key;
247  tooltips.put(key, description);
248  } else {
249  // Two (or more) components have the exact same description. Technically not a collision, but
250  // we need to do some bookkeeping in case a collision is detected with another component.
251  tooltipComponent.get(key).add(component.getName());
252  }
253  }
254 
255  private void computeTooltipMap(ComponentInfo component) {
256  for (Property property : component.properties.values()) {
257  storeTooltip(component, property.name, "PropertyDescriptions", property.getDescription());
258  }
259  for (Method method : component.methods.values()) {
260  storeTooltip(component, method.name, "MethodDescriptions", method.description);
261  }
262  for (Event event : component.events.values()) {
263  storeTooltip(component, event.name, "EventDescriptions", event.description);
264  }
265  }
266 
267  protected void outputAutogenOdeMessages() throws IOException {
268  Set<String> categories = new TreeSet<>();
269  Map<String, Property> properties = new TreeMap<>();
270  Map<String, Method> methods = new TreeMap<>();
271  Map<String, Event> events = new TreeMap<>();
272  Map<String, Parameter> parameters = new TreeMap<>();
273  StringBuilder sb = new StringBuilder();
274  sb.append("// THIS FILE IS AUTOMATICALLY GENERATED DURING COMPILATION.\n");
275  sb.append("// DO NOT EDIT THIS FILE. ANY CHANGES WILL BE OVERWRITTEN.\n\n");
276  sb.append("package com.google.appinventor.client;\n\n");
277  sb.append("import com.google.gwt.i18n.client.Messages;\n\n");
278  sb.append("public interface AutogeneratedOdeMessages extends Messages {\n");
279  sb.append("\n /* Components */\n");
280  for (Map.Entry<String, ComponentInfo> entry : components.entrySet()) {
281  ComponentInfo component = entry.getValue();
282  outputComponentAutogen(component, properties, methods, events, sb);
283  computeTooltipMap(component);
284  categories.add(component.getCategory());
285  }
286  for (String key : collisionKeys) {
287  tooltips.remove(key);
288  }
289  sb.append("\n /* Properties */\n");
290  for (Map.Entry<String, Property> entry : properties.entrySet()) {
291  outputPropertyAutogen(entry.getValue(), sb);
292  }
293  sb.append("\n /* Methods */\n");
294  for (Map.Entry<String, Method> entry : methods.entrySet()) {
295  outputMethodAutogen(entry.getValue(), parameters, sb);
296  }
297  sb.append("\n /* Events */\n");
298  for (Map.Entry<String, Event> entry : events.entrySet()) {
299  outputEventAutogen(entry.getValue(), parameters, sb);
300  }
301  for (Map.Entry<String, String> entry : tooltips.entrySet()) {
302  sb.append(" @DefaultMessage(\"");
303  sb.append(sanitize(entry.getValue()));
304  sb.append("\")\n");
305  sb.append(" @Description(\"\")\n");
306  sb.append(" String ");
307  sb.append(entry.getKey());
308  sb.append("();\n\n");
309  }
310  sb.append("\n /* Parameters */\n");
311  for (Map.Entry<String, Parameter> entry : parameters.entrySet()) {
312  outputParameterAutogen(entry.getValue(), sb);
313  }
314  sb.append("\n /* Component Categories */\n");
315  for (String category : categories) {
316  outputCategoryAutogen(category, sb);
317  }
318  sb.append("\n /* Property Categories */\n");
319  outputPropertyCategoryAutogen("Appearance", sb);
320  outputPropertyCategoryAutogen("Behavior", sb);
321  outputPropertyCategoryAutogen("Unspecified", sb);
322  sb.append("}\n");
323  FileObject src = createOutputFileObject(AUTOGEN_OUTPUT_FILE_NAME);
324  Writer writer = src.openWriter();
325  writer.write(sb.toString());
326  writer.flush();
327  writer.close();
328  messager.printMessage(Kind.NOTE, "Wrote file " + src.toUri());
329  }
330 
331  @Override
332  protected void outputResults() throws IOException {
334  StringBuilder sb = new StringBuilder();
335  sb.append("package com.google.appinventor.client;\n");
336  sb.append("\n");
337  sb.append("import java.util.HashMap;\n");
338  sb.append("import java.util.Map;\n");
339  sb.append("\n");
340  sb.append("import static com.google.appinventor.client.Ode.MESSAGES;\n");
341  sb.append("\n");
342  sb.append("public class ComponentsTranslation {\n");
343  sb.append(" public static Map<String, String> myMap = map();\n\n");
344  sb.append(" private static String getName(String key) {\n");
345  sb.append(" String value = myMap.get(key);\n");
346  sb.append(" if (key == null) {\n");
347  sb.append(" return \"**Missing key in ComponentsTranslations**\";\n");
348  sb.append(" } else {\n");
349  sb.append(" return value;\n");
350  sb.append(" }\n");
351  sb.append(" }\n\n");
352  sb.append(" public static String getPropertyName(String key) {\n");
353  sb.append(" String value = getName(\"PROPERTY-\" + key);\n");
354  sb.append(" if(value == null) return key;\n");
355  sb.append(" return value;\n");
356  sb.append(" }\n\n");
357  sb.append(" public static String getPropertyDescription(String key) {\n");
358  sb.append(" String value = getName(\"PROPDESC-\" + key);\n");
359  sb.append(" if(value == null) return key;\n");
360  sb.append(" return value;\n");
361  sb.append(" }\n\n");
362  sb.append(" public static String getMethodName(String key) {\n");
363  sb.append(" String value = getName(\"METHOD-\" + key);\n");
364  sb.append(" if(value == null) return key;\n");
365  sb.append(" return value;\n");
366  sb.append(" }\n");
367  sb.append("\n");
368  sb.append(" public static String getEventName(String key) {\n");
369  sb.append(" String value = getName(\"EVENT-\" + key);\n");
370  sb.append(" if(value == null) return key;\n");
371  sb.append(" return value;\n");
372  sb.append(" }\n");
373  sb.append("\n");
374  sb.append(" public static String getComponentName(String key) {\n");
375  sb.append(" String value = getName(\"COMPONENT-\" + key);\n");
376  sb.append(" if(value == null) return key;\n");
377  sb.append(" return value;\n");
378  sb.append(" }\n");
379  sb.append("\n");
380  sb.append(" public static String getCategoryName(String key) {\n");
381  sb.append(" String value = getName(\"CATEGORY-\" + key);\n");
382  sb.append(" if(value == null) return key;\n");
383  sb.append(" return value;\n");
384  sb.append(" }\n");
385  sb.append("\n");
386  sb.append(" public static String getComponentHelpString(String key) {\n");
387  sb.append(" String value = getName(key + \"-helpString\");\n");
388  sb.append(" if(value == null) return key;\n");
389  sb.append(" return value;\n");
390  sb.append(" }\n");
391  sb.append(" public static HashMap<String, String> map() {\n");
392  sb.append(" HashMap<String, String> map = new HashMap<String, String>();\n");
393 
394  // Components are already sorted.
395  Set<String> categories = new TreeSet<>();
396  Set<String> properties = new TreeSet<>();
397  Set<String> methods = new TreeSet<>();
398  Set<String> events = new TreeSet<>();
399  for (Map.Entry<String, ComponentInfo> entry : components.entrySet()) {
400  ComponentInfo component = entry.getValue();
401  outputComponent(component, properties, methods, events, sb);
402  categories.add(component.getCategory());
403  }
404  sb.append("\n\n /* Descriptions */\n\n");
405  for (String key : tooltips.keySet()) {
406  if (key.endsWith("PropertyDescriptions")) {
407  sb.append(" map.put(\"PROPDESC-");
408  sb.append(key.replaceAll("__", "."));
409  sb.append("\", MESSAGES.");
410  sb.append(key);
411  sb.append("());\n");
412  } else if (key.endsWith("MethodDescriptions")) {
413  sb.append(" map.put(\"METHODDESC-");
414  sb.append(key.replaceAll("__", "."));
415  sb.append("\", MESSAGES.");
416  sb.append(key);
417  sb.append("());\n");
418  } else if (key.endsWith("EventDescriptions")) {
419  sb.append(" map.put(\"EVENTDESC-");
420  sb.append(key.replaceAll("__", "."));
421  sb.append("\", MESSAGES.");
422  sb.append(key);
423  sb.append("());\n");
424  }
425  }
426  sb.append("\n\n /* Categories */\n\n");
427  for (String category : categories) {
428  outputCategory(category, sb);
429  }
430  sb.append(" return map;\n");
431  sb.append(" }\n");
432  sb.append("}\n");
433  FileObject src = createOutputFileObject(OUTPUT_FILE_NAME);
434  Writer writer = src.openWriter();
435  writer.write(sb.toString());
436  writer.flush();
437  writer.close();
438  messager.printMessage(Diagnostic.Kind.NOTE, "Wrote file " + src.toUri());
439  }
440 
441 }
com.google.appinventor.components.scripts.ComponentProcessor.ComponentInfo.methods
final SortedMap< String, Method > methods
Definition: ComponentProcessor.java:746
com.google.appinventor.components.scripts.ComponentProcessor.ComponentInfo.events
final SortedMap< String, Event > events
Definition: ComponentProcessor.java:751
com.google.appinventor.components.scripts.ComponentTranslationGenerator
Definition: ComponentTranslationGenerator.java:21
com.google.appinventor.components.scripts.ComponentProcessor.Method
Definition: ComponentProcessor.java:488
com.google.appinventor.components.scripts.ComponentProcessor.Parameter
Definition: ComponentProcessor.java:223
com.google.appinventor.components.scripts.ComponentProcessor.ComponentInfo.properties
final SortedMap< String, Property > properties
Definition: ComponentProcessor.java:741
com.google.appinventor.components.scripts.ComponentTranslationGenerator.outputResults
void outputResults()
Definition: ComponentTranslationGenerator.java:332
com.google.appinventor.components.scripts.ComponentProcessor.Parameter.name
final String name
Definition: ComponentProcessor.java:227
com.google.appinventor.components.scripts.ComponentProcessor.Event
Definition: ComponentProcessor.java:461
com.google.appinventor.components.scripts.ComponentProcessor.messager
Messager messager
Definition: ComponentProcessor.java:194
com.google.appinventor.components.scripts.ComponentProcessor.ParameterizedFeature.parameters
final List< Parameter > parameters
Definition: ComponentProcessor.java:419
com.google.appinventor.components.scripts.ComponentProcessor.ComponentInfo
Definition: ComponentProcessor.java:644
com.google.appinventor.components.scripts.ComponentTranslationGenerator.outputAutogenOdeMessages
void outputAutogenOdeMessages()
Definition: ComponentTranslationGenerator.java:267
com.google.appinventor.components.scripts.ComponentProcessor.ComponentInfo.designerProperties
final SortedMap< String, DesignerProperty > designerProperties
Definition: ComponentProcessor.java:734
com.google.appinventor.components.scripts.ComponentProcessor.components
final SortedMap< String, ComponentInfo > components
Definition: ComponentProcessor.java:207
com.google.appinventor.components.scripts.ComponentProcessor.ComponentInfo.getCategory
String getCategory()
Definition: ComponentProcessor.java:917
com.google.appinventor.components.scripts.ComponentProcessor
Definition: ComponentProcessor.java:124