// -*- mode: java; c-basic-offset: 2; -*- // Copyright 2009-2011 Google, All Rights reserved // Copyright 2011-2012 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 com.google.appinventor.common.utils.StringUtils; import com.google.appinventor.components.annotations.DesignerProperty; import java.io.IOException; import java.io.Writer; import java.util.List; import java.util.Map; import javax.tools.Diagnostic; import javax.tools.FileObject; /** * Tool to generate simple component descriptors as JSON. * * The output is a sequence of component descriptions enclosed in square * brackets and separated by commas. Each component description has the * following format: * { "type": "COMPONENT-TYPE", * "name": "COMPONENT-TYPE-NAME", * "external": "true"|"false", * "version": "VERSION", * "categoryString": "PALETTE-CATEGORY", * "helpString": “DESCRIPTION”, * "showOnPalette": "true"|"false", * "nonVisible": "true"|"false", * "iconName": "ICON-FILE-NAME", * "properties": [ * { "name": "PROPERTY-NAME", * "editorType": "EDITOR-TYPE", * "defaultValue": "DEFAULT-VALUE"},* * ], * "blockProperties": [ * { "name": "PROPERTY-NAME", * "description": "DESCRIPTION", * "type": "YAIL-TYPE", * "rw": "read-only"|"read-write"|"write-only"|"invisible"},* * ], * "events": [ * { "name": "EVENT-NAME", * "description": "DESCRIPTION", * "params": [ * { "name": "PARAM-NAME", * "type": "YAIL-TYPE"},* * ]},+ * ], * “methods”: [ * { "name": "METHOD-NAME", * "description": "DESCRIPTION", * "params": [ * { "name": "PARAM-NAME", * "type": "YAIL-TYPE"},* * ]},+ * ] * } * * @author lizlooney@google.com (Liz Looney) * @author sharon@google.com (Sharon Perl) - added events, methods, non-designer * properties (for use by browser-based blocks editor) */ public final class ComponentDescriptorGenerator extends ComponentProcessor { // Where to write results. private static final String OUTPUT_FILE_NAME = "simple_components.json"; private void outputComponent(ComponentInfo component, StringBuilder sb) { sb.append("{ \"type\": \""); sb.append(component.type); sb.append("\",\n \"name\": \""); sb.append(component.name); sb.append("\",\n \"external\": \""); sb.append(Boolean.toString(component.external)); sb.append("\",\n \"version\": \""); sb.append(component.getVersion()); sb.append("\",\n \"categoryString\": \""); sb.append(component.getCategoryString()); sb.append("\",\n \"helpString\": "); sb.append(formatDescription(component.getHelpDescription())); sb.append(",\n \"helpUrl\": "); sb.append(formatDescription(component.getHelpUrl())); sb.append(",\n \"showOnPalette\": \""); sb.append(component.getShowOnPalette()); sb.append("\",\n \"nonVisible\": \""); sb.append(component.getNonVisible()); sb.append("\",\n \"iconName\": \""); sb.append(component.getIconName()); sb.append("\",\n \"properties\": ["); String separator = ""; for (Map.Entry<String, DesignerProperty> entry : component.designerProperties.entrySet()) { String propertyName = entry.getKey(); DesignerProperty dp = entry.getValue(); sb.append(separator); outputProperty(propertyName, dp, sb); separator = ",\n"; } // We need additional information about properties in the blocks editor, // and we need all of them, not just the Designer properties. We output // the entire set separately for use by the blocks editor to keep things simple. sb.append("],\n \"blockProperties\": ["); separator = ""; for (Property prop : component.properties.values()) { sb.append(separator); // Output properties that are not user-visible, but mark them as invisible // Note: carrying this over from the old Java blocks editor. I'm not sure // that we'll actually do anything with invisible properties in the blocks // editor. (sharon@google.com) outputBlockProperty(prop.name, prop, sb); separator = ",\n "; } sb.append("],\n \"events\": ["); separator = ""; for (Event event : component.events.values()) { sb.append(separator); outputBlockEvent(event.name, event, sb, event.userVisible, event.deprecated); separator = ",\n "; } sb.append("],\n \"methods\": ["); separator = ""; for (Method method : component.methods.values()) { sb.append(separator); outputBlockMethod(method.name, method, sb, method.userVisible, method.deprecated); separator = ",\n "; } sb.append("]}\n"); } private void outputProperty(String propertyName, DesignerProperty dp, StringBuilder sb) { sb.append("{ \"name\": \""); sb.append(propertyName); sb.append("\", \"editorType\": \""); sb.append(dp.editorType()); sb.append("\", \"defaultValue\": \""); sb.append(dp.defaultValue().replace("\"", "\\\"")); sb.append("\"}"); } private void outputBlockProperty(String propertyName, Property prop, StringBuilder sb) { sb.append("{ \"name\": \""); sb.append(propertyName); sb.append("\", \"description\": "); sb.append(formatDescription(prop.getDescription())); sb.append(", \"type\": \""); sb.append(javaTypeToYailType(prop.getType())); sb.append("\", \"rw\": \""); sb.append(prop.isUserVisible() ? prop.getRwString() : "invisible"); // [lyn, 2015/12/20] Added deprecated field to JSON. // If we want to save space in simple-components.json, // we could include this field only when it is "true" sb.append("\", \"deprecated\": \"" + prop.isDeprecated() + "\""); sb.append("}"); } private void outputBlockEvent(String eventName, Event event, StringBuilder sb, boolean userVisible, boolean deprecated) { sb.append("{ \"name\": \""); sb.append(eventName); sb.append("\", \"description\": "); sb.append(formatDescription(event.description)); // [lyn, 2015/12/20] Remove userVisible field from JSON, which is no longer used for events. // sb.append(", \"userVisible\": \"" + userVisible + "\""); // [lyn, 2015/12/20] Added deprecated field to JSON. // If we want to save space in simple-components.json, // we could include this field only when it is "true" sb.append(", \"deprecated\": \"" + deprecated + "\""); sb.append(", \"params\": "); outputParameters(event.parameters, sb); sb.append("}\n"); } private void outputBlockMethod(String methodName, Method method, StringBuilder sb, boolean userVisible, boolean deprecated) { sb.append("{ \"name\": \""); sb.append(methodName); sb.append("\", \"description\": "); sb.append(formatDescription(method.description)); // [lyn, 2015/12/20] Remove userVisible field from JSON, which is no longer used for methods. // sb.append(", \"userVisible\": \"" + userVisible + "\""); // [lyn, 2015/12/20] Added deprecated field to JSON. // If we want to save space in simple-components.json, // we could include this field only when it is "true" sb.append(", \"deprecated\": \"" + deprecated + "\""); sb.append(", \"params\": "); outputParameters(method.parameters, sb); if (method.getReturnType() != null) { sb.append(", \"returnType\": \""); sb.append(javaTypeToYailType(method.getReturnType())); sb.append("\"}"); } else { sb.append("}"); } } /* * Output a parameter list (including surrounding []) */ private void outputParameters(List<Parameter> params, StringBuilder sb) { sb.append("["); String separator = ""; for (Parameter p : params) { sb.append(separator); sb.append("{ \"name\": \""); sb.append(p.name); sb.append("\", \"type\": \""); sb.append(javaTypeToYailType(p.type)); sb.append("\"}"); separator = ","; } sb.append("]"); } @Override protected void outputResults() throws IOException { StringBuilder sb = new StringBuilder(); sb.append('['); String separator = ""; // Components are already sorted. for (Map.Entry<String, ComponentInfo> entry : components.entrySet()) { ComponentInfo component = entry.getValue(); sb.append(separator); outputComponent(component, sb); separator = ",\n"; } sb.append(']'); 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()); } /* * Format a description string as a json string. Note that the returned value * include surrounding double quotes. */ private static String formatDescription(String description) { return StringUtils.toJson(description); } }