http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/7d8609e7/pst/build.gradle ---------------------------------------------------------------------- diff --git a/pst/build.gradle b/pst/build.gradle new file mode 100644 index 0000000..530ee83 --- /dev/null +++ b/pst/build.gradle @@ -0,0 +1,43 @@ +// Plugins +apply plugin: 'java' + +// Settings +version = 0.1 + +// Dependencies +repositories { + mavenCentral() +} + +// - Project - Compile +dependencies { + compile group: 'com.google.protobuf', name: 'protobuf-java', version: '2.6.1' + compile group: 'com.google.guava', name: 'guava', version: '15.0' + compile group: 'org.antlr', name: 'antlr', version: '3.2' + compile group: 'commons-cli', name: 'commons-cli', version: '1.2' + compile fileTree(dir: '../wave-proto/build/libs', include: "**/*.jar") +// - Project - Testing + testCompile group: 'junit', name: 'junit', version: '4.11' +} + +// Source Sets +sourceSets { + main { + java { + srcDir 'src/main/java' + //srcDir '../wave-proto/build/classes/main/' + } + resources { + srcDir 'src/main/resources' + } + } + + test { + java { + srcDir 'src/test/java' + } + resources { + srcDir 'src/test/resources' + } + } +} \ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/7d8609e7/pst/src/main/java/org/waveprotocol/pst/Pst.java ---------------------------------------------------------------------- diff --git a/pst/src/main/java/org/waveprotocol/pst/Pst.java b/pst/src/main/java/org/waveprotocol/pst/Pst.java new file mode 100644 index 0000000..98289e3 --- /dev/null +++ b/pst/src/main/java/org/waveprotocol/pst/Pst.java @@ -0,0 +1,188 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.waveprotocol.pst; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.base.Strings; +import com.google.common.collect.Lists; +import com.google.protobuf.Descriptors.Descriptor; +import com.google.protobuf.Descriptors.FileDescriptor; + +import org.antlr.stringtemplate.StringTemplate; +import org.antlr.stringtemplate.StringTemplateGroup; +import org.waveprotocol.pst.model.Message; +import org.waveprotocol.pst.model.MessageProperties; +import org.waveprotocol.pst.style.Styler; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileWriter; +import java.io.IOException; +import java.util.List; + +/** + * PST stands for protobuf-stringtemplate. + * + * The tool allows arbitrary code generation given a series of string + * templates, each passed the description of a protocol buffer file. This allows + * protobuf- based file generation beyond what existing protocol compilers + * (protoc, protostuff, etc) are capable of (without modification), using the + * convenience and safety of <a href="http://stringtemplate.org">string + * template</a>. + * + * A number of sample templates are bundles (in templates), so see these as + * examples. These templates give a complete client/server JSON message stack: + * <ul> + * <li><em>message</em> is a common interface.</li> + * <li><em>messageTestImpl</em> is a simple/pure Java in-memory implementation + * of the interface, for testing.</li> + * <li><em>messagePojoImpl</em> is a like messageTestImpl with JSON + * serialization and deserialization using the Gson library.</li> + * <li><em>messageServerImpl</em> is a protobuf-backed implementation, useful + * for a multi-server environment where the efficient serialization of protocol + * buffers is an advantage. JSON is also supported.</li> + * <li><em>messageClientImpl</em> is an efficent javascript implementation for + * use with GWT.</li> + * </ul> + * + * There is no particular reason why PST can only be used to generate Java, for + * example, a pure JS rather than GWT implementation of the client JSON message + * component could be generated[1]. + * + * PST is implemented using the protocol buffer reflection generated by protoc + * alongside the actual Message classes it generates; these are converted into + * simple Java model objects with simple accessors suitable for accessing from + * stringtemplate. + * + * The code generated by stringtemplate is then post-processed using a simple + * custom code formatter since the output from stringtemplate can be hard to + * humanly read (e.g. the indentation is unpredictable). + * + * [1] although, currently it is hardcoded in PST to generate .java files, and + * the model has Java-centric methods. The code formatter also assumes that + * it is run over a Java file. These all could be easily modified, however. + * + * @author [email protected] (Benjamin Kalman) + */ +public final class Pst { + + private final File outputDir; + private final FileDescriptor fd; + private final Styler styler; + private final Iterable<File> templates; + private final boolean saveBackups; + private final boolean useInt52; + + /** + * @param outputDir the base directory to write the generated files + * @param fd the {@link FileDescriptor} of the protobuf to use (i.e. pass to + * each string template) + * @param styler the code styler to post-process generated code with + * @param templates the collection of string templates to use + * @param saveBackups whether to save intermediate generated files + * @param useInt52 whether we use doubles to serialize 64-bit integers + */ + public Pst(File outputDir, FileDescriptor fd, Styler styler, Iterable<File> templates, + boolean saveBackups, boolean useInt52) { + this.outputDir = checkNotNull(outputDir, "outputDir cannot be null"); + this.fd = checkNotNull(fd, "fd cannot be null"); + this.styler = checkNotNull(styler, "styler cannot be null"); + this.templates = checkNotNull(templates, "templates cannot be null"); + this.saveBackups = saveBackups; + this.useInt52 = useInt52; + } + + /** + * Runs the code generation for all templates. + */ + public void run() throws PstException { + List<PstException.TemplateException> exceptions = Lists.newArrayList(); + for (File template : templates) { + try { + MessageProperties properties = createProperties(template); + String groupName = stripSuffix(".st", template.getName()); + String templateName = properties.hasTemplateName() ? + properties.getTemplateName() : ""; + StringTemplateGroup group = new StringTemplateGroup(groupName + "Group", dir(template)); + StringTemplate st = group.getInstanceOf(groupName); + for (Descriptor messageDescriptor : fd.getMessageTypes()) { + Message message = new Message(messageDescriptor, templateName, properties); + st.reset(); + st.setAttribute("m", message); + write(st, new File( + outputDir.getPath() + File.separator + + message.getFullJavaType().replace('.', File.separatorChar) + "." + + (properties.hasFileExtension() ? properties.getFileExtension() : "java"))); + } + } catch (Exception e) { + exceptions.add(new PstException.TemplateException(template.getPath(), e)); + } + } + if (!exceptions.isEmpty()) { + throw new PstException(exceptions); + } + } + + /** + * @return the path to the directory which contains a file, or just the path + * to the file itself if it's already a directory + */ + private String dir(File f) { + return f.isDirectory() + ? f.getPath() + : (Strings.isNullOrEmpty(f.getParent()) ? "." : f.getParent()); + } + + private String stripSuffix(String suffix, String s) { + return s.endsWith(suffix) ? s.substring(0, s.length() - suffix.length()) : s; + } + + private void write(StringTemplate st, File output) throws IOException { + output.getParentFile().mkdirs(); + BufferedWriter writer = new BufferedWriter(new FileWriter(output)); + try { + writer.write(st.toString()); + } finally { + try { + writer.close(); + } catch (IOException e) { + // If another exception is already propagating, we don't + // want to throw a secondary one. + // This means that exceptions on close() are ignored, + // but what could usefully be done for a close() + // exception anyway? + } + } + styler.style(output, saveBackups); + } + + private MessageProperties createProperties(File template) + throws FileNotFoundException, IOException { + File propertiesFile = + new File(template.getParentFile().getPath() + File.separator + "properties"); + MessageProperties properties = propertiesFile.exists() + ? MessageProperties.createFromFile(propertiesFile) : MessageProperties.createEmpty(); + properties.setUseInt52(useInt52); + return properties; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/7d8609e7/pst/src/main/java/org/waveprotocol/pst/PstCommandLine.java ---------------------------------------------------------------------- diff --git a/pst/src/main/java/org/waveprotocol/pst/PstCommandLine.java b/pst/src/main/java/org/waveprotocol/pst/PstCommandLine.java new file mode 100644 index 0000000..dbc0223 --- /dev/null +++ b/pst/src/main/java/org/waveprotocol/pst/PstCommandLine.java @@ -0,0 +1,150 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.waveprotocol.pst; + +import com.google.common.base.Function; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; + +import org.apache.commons.cli.BasicParser; +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.HelpFormatter; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; +import org.waveprotocol.pst.style.PstStyler; +import org.waveprotocol.pst.style.Styler; + +import java.io.File; +import java.util.Map; + +/** + * Encapsulates the command line options to protobuf-stringtemplate. + * + * @author [email protected] (Benjamin Kalman) + */ +public final class PstCommandLine { + + private static final String DEFAULT_OUTPUT_DIR = "."; + private static final String DEFAULT_PROTO_PATH = "."; + private static final Map<String, Styler> STYLERS = ImmutableMap.<String, Styler> builder() + .put("none", Styler.EMPTY) + .put("pst", new PstStyler()) + .build(); + + private final CommandLine cl; + + public PstCommandLine(String... args) throws ParseException { + cl = new BasicParser().parse(getOptions(), args); + checkArgs(); + } + + private void checkArgs() throws ParseException { + if (!hasFile()) { + throw new ParseException("Must specify file"); + } + if (cl.getArgList().isEmpty()) { + throw new ParseException("Must specify at least one template"); + } + } + + private static Options getOptions() { + Options options = new Options(); + options.addOption("h", "help", false, "Show this help"); + options.addOption("f", "file", true, "The protobuf specification file to use"); + options.addOption("d", "dir", true, String.format( + "The base directory to output generated files to (default: %s)", DEFAULT_OUTPUT_DIR)); + options.addOption("s", "styler", true, "The styler to use, if any (default: none). " + + "Available options: " + STYLERS.keySet()); + options.addOption("i", "save_pre_styled", false, "Save the intermediate pre-styled files"); + options.addOption("j", "save_java", false, "Save the protoc-generated Java file, if any"); + options.addOption("I", "proto_path", true, "Extra path to search for proto extensions. " + + "This needs to be specified if the target file is a .proto file with any of the PST-" + + "specific extensions, in which case the path should include both PST source " + + "base and the protoc source base; i.e., /PATH/TO/PST/src:/PATH/TO/PROTOC/src"); + options.addOption("t", "int52", true, + "Specifies if pst should store 64-bit integers should be serialized to" + + "doubles which will use 52-bit precision. It's useful " + + "when data is meant to be serialized/deserialized in JavaScript, since it doesn't " + + "support 64-bit integers (default: false)."); + return options; + } + + public boolean hasHelp() { + return cl.hasOption('h'); + } + + // NOTE: private because it's always true, checked in checkArgs(). + private boolean hasFile() { + return cl.hasOption('f'); + } + + public static void printHelp() { + new HelpFormatter().printHelp( + PstMain.class.getSimpleName() + " [options] templates...", getOptions()); + } + + public File getProtoFile() { + return new File(cl.getOptionValue('f')); + } + + @SuppressWarnings("unchecked") + public Iterable<File> getTemplateFiles() { + return Iterables.transform(cl.getArgList(), new Function<String, File>() { + @Override public File apply(String filename) { + return new File(filename); + } + }); + } + + public File getOutputDir() { + return new File(cl.hasOption('d') ? cl.getOptionValue('d') : DEFAULT_OUTPUT_DIR); + } + + public File getProtoPath() { + return new File(cl.hasOption('I') ? cl.getOptionValue('I') : DEFAULT_PROTO_PATH); + } + + public Styler getStyler() { + if (cl.hasOption('s')) { + String stylerName = cl.getOptionValue('s'); + if (STYLERS.containsKey(stylerName)) { + return STYLERS.get(stylerName); + } else { + System.err.println("WARNING: unrecognised styler: " + stylerName + ", using none"); + return Styler.EMPTY; + } + } else { + return Styler.EMPTY; + } + } + + public boolean shouldSavePreStyled() { + return cl.hasOption('p'); + } + + public boolean shouldSaveJava() { + return cl.hasOption('j'); + } + + public boolean shouldUseInt52() { + return !cl.hasOption('t') // + || (cl.hasOption('t') && "true".equals(cl.getOptionValue('t'))); + } +} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/7d8609e7/pst/src/main/java/org/waveprotocol/pst/PstException.java ---------------------------------------------------------------------- diff --git a/pst/src/main/java/org/waveprotocol/pst/PstException.java b/pst/src/main/java/org/waveprotocol/pst/PstException.java new file mode 100644 index 0000000..388bd77 --- /dev/null +++ b/pst/src/main/java/org/waveprotocol/pst/PstException.java @@ -0,0 +1,67 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.waveprotocol.pst; + +import com.google.common.collect.ImmutableList; + +import java.util.List; + +/** + * Exception caused by any errors caused in code generation. + * + * @author [email protected] (Benjamin Kalman) + */ +public final class PstException extends Exception { + + public static final class TemplateException extends Exception { + private final String templateName; + + public TemplateException(String templateName, String message, Throwable cause) { + super(message, cause); + this.templateName = templateName; + } + + public TemplateException(String templateName, Throwable cause) { + super(cause); + this.templateName = templateName; + } + + /** + * @return the name of the template being parsed when the exception occurred + */ + public String getTemplateName() { + return templateName; + } + } + + private final ImmutableList<TemplateException> exceptions; + + public PstException(List<TemplateException> exceptions) { + super(); + this.exceptions = ImmutableList.copyOf(exceptions); + } + + /** + * @return all exceptions caused + */ + public ImmutableList<TemplateException> getTemplateExceptions() { + return exceptions; + } +} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/7d8609e7/pst/src/main/java/org/waveprotocol/pst/PstFileDescriptor.java ---------------------------------------------------------------------- diff --git a/pst/src/main/java/org/waveprotocol/pst/PstFileDescriptor.java b/pst/src/main/java/org/waveprotocol/pst/PstFileDescriptor.java new file mode 100644 index 0000000..cfc8b18 --- /dev/null +++ b/pst/src/main/java/org/waveprotocol/pst/PstFileDescriptor.java @@ -0,0 +1,345 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.waveprotocol.pst; + +import com.google.common.base.Joiner; +import com.google.common.base.Predicate; +import com.google.common.io.CharStreams; +import com.google.common.io.Files; +import com.google.protobuf.Descriptors.FileDescriptor; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * A loader for a {@link FileDescriptor}, accepting and handling a proto file + * specified as: + * <ul> + * <li>A path to a .proto file.</li> + * <li>A path to a .java (protoc-)compiled proto spec.</li> + * <li>A path to a .class (javac-) compiled proto spec.</li> + * <li>A proto spec that is already on the classpath.</li> + * </ul> + * + * @author [email protected] (Benjamin Kalman) + */ +public final class PstFileDescriptor { + + private final FileDescriptor descriptor; + + /** + * Loads the {@link FileDescriptor} from a path. The path may be a class name + * (e.g. foo.Bar), path to a class (e.g. bin/foo/Bar.class), or a path to a + * Java source file (e.g. src/foo/Bar.java). + * + * In each case it is the caller's responsibility to ensure that the classpath + * of the Java runtime is correct. + * + * @param path the path to load the proto description from + * @param saveintermediateJavaDir path to save the intermediate protoc- + * generated Java file (if any) + * @param protoPath any additional path to pass to the protoc compiler + */ + public static FileDescriptor load(String path, File intermediateJavaDir, File protoPath) { + return new PstFileDescriptor(path, intermediateJavaDir, protoPath).get(); + } + + private PstFileDescriptor(String path, File intermediateJavaDir, File protoPath) { + Class<?> clazz = null; + if (path.endsWith(".class")) { + clazz = fromPathToClass(path); + } else if (path.endsWith(".java")) { + clazz = fromPathToJava(path); + } else if (path.endsWith(".proto")) { + clazz = fromPathToProto(path, intermediateJavaDir, protoPath); + } else { + clazz = fromClassName(path); + } + + if (clazz == null) { + descriptor = null; + } else { + descriptor = asFileDescriptor(clazz); + } + } + + private FileDescriptor get() { + return descriptor; + } + + private Class<?> fromClassName(String className) { + try { + return Class.forName(className); + } catch (ClassNotFoundException e) { + return null; + } + } + + private Class<?> fromPathToClass(String pathToClass) { + String currentBaseDir = new File(pathToClass).isAbsolute() ? "" : "."; + String currentPath = pathToClass; + Class<?> clazz = null; + while (clazz == null) { + clazz = loadClassAtPath(currentBaseDir, currentPath); + if (clazz == null) { + int indexOfSep = currentPath.indexOf(File.separatorChar); + if (indexOfSep == -1) { + break; + } else { + currentBaseDir += File.separator + currentPath.substring(0, indexOfSep); + currentPath = currentPath.substring(indexOfSep + 1); + } + } + } + return clazz; + } + + private Class<?> loadClassAtPath(String baseDir, String path) { + try { + ClassLoader classLoader = new URLClassLoader(new URL[] {new File(baseDir).toURI().toURL()}); + return classLoader.loadClass(getBinaryName(path)); + } catch (Throwable t) { + return null; + } + } + + private String getBinaryName(String path) { + return path.replace(File.separatorChar, '.').substring(0, path.length() - ".class".length()); + } + + private Class<?> fromPathToJava(String pathToJava) { + try { + File dir = Files.createTempDir(); + String[] javacCommand = new String[] { + "javac", pathToJava, "-d", dir.getAbsolutePath(), "-verbose", + "-cp", determineClasspath(pathToJava) + ":" + determineSystemClasspath() + }; + Process javac = Runtime.getRuntime().exec(javacCommand); + consumeStdOut(javac); + List<String> stdErr = readLines(javac.getErrorStream()); + int exitCode = javac.waitFor(); + if (exitCode != 0) { + // Couldn't compile the file. + System.err.printf("ERROR: running \"%s\" failed (%s):", + Joiner.on(' ').join(javacCommand), exitCode); + for (String line : stdErr) { + System.err.println(line); + } + return null; + } else { + // Compiled the file! Now to determine where javac put it. + Pattern pattern = Pattern.compile("\\[wrote ([^\\]]*)\\]"); + String pathToClass = null; + for (String line : stdErr) { + Matcher lineMatcher = pattern.matcher(line); + if (lineMatcher.matches()) { + pathToClass = lineMatcher.group(1); + // NOTE: don't break, as the correct path is the last one matched. + } + } + if (pathToClass != null) { + return fromPathToClass(pathToClass); + } else { + System.err.println("WARNING: couldn't find javac output from javac " + pathToJava); + return null; + } + } + } catch (Exception e) { + System.err.println("WARNING: exception while processing " + pathToJava + ": " + + e.getMessage()); + return null; + } + } + + /** + * Fires off a background thread to consume anything written to a process' + * standard output. Without running this, a process that outputs too much data + * will block. + */ + private void consumeStdOut(Process p) { + final InputStream o = p.getInputStream(); + Thread t = new Thread() { + @Override + public void run() { + try { + while (o.read() != -1) {} + } catch (IOException e) { + e.printStackTrace(); + } + } + }; + t.setDaemon(true); + t.start(); + } + + private String determineClasspath(String pathToJava) { + // Try to determine the classpath component of a path by looking at the + // path components. + StringBuilder classpath = new StringBuilder(); + if (new File(pathToJava).isAbsolute()) { + classpath.append(File.separator); + } + + // This is just silly, but it will get by for now. + for (String component : pathToJava.split(File.separator)) { + if (component.equals("org") + || component.equals("com") + || component.equals("au")) { + return classpath.toString(); + } else { + classpath.append(component + File.separator); + } + } + + System.err.println("WARNING: couldn't determine classpath for " + pathToJava); + return "."; + } + + private String determineSystemClasspath() { + StringBuilder s = new StringBuilder(); + boolean needsColon = false; + for (URL url : ((URLClassLoader) ClassLoader.getSystemClassLoader()).getURLs()) { + if (needsColon) { + s.append(':'); + } + s.append(url.getPath()); + needsColon = true; + } + return s.toString(); + } + + private Class<?> fromPathToProto(String pathToProto, File intermediateJavaDir, File protoPath) { + try { + intermediateJavaDir.mkdirs(); + File proto = new File(pathToProto); + String[] protocCommand = new String[] { + "protoc", tryGetRelativePath(proto), + "-I" + protoPath.getPath(), + "--java_out", intermediateJavaDir.getAbsolutePath() + }; + Process protoc = Runtime.getRuntime().exec(protocCommand); + // TODO(ben): configure timeout? + killProcessAfter(10, TimeUnit.SECONDS, protoc); + int exitCode = protoc.waitFor(); + if (exitCode != 0) { + // Couldn't compile the file. + System.err.printf("ERROR: running \"%s\" failed (%s):", + Joiner.on(' ').join(protocCommand), exitCode); + for (String line : readLines(protoc.getErrorStream())) { + System.err.println(line); + } + return null; + } else { + final String javaFileName = capitalize(stripSuffix(".proto", proto.getName())) + ".java"; + String maybeJavaFilePath = find(intermediateJavaDir, new Predicate<File>() { + @Override public boolean apply(File f) { + return f.getName().equals(javaFileName); + } + }); + if (maybeJavaFilePath == null) { + System.err.println("ERROR: couldn't find result of protoc in " + intermediateJavaDir); + return null; + } + return fromPathToJava(maybeJavaFilePath); + } + } catch (Exception e) { + System.err.println("WARNING: exception while processing " + pathToProto + ": " + + e.getMessage()); + e.printStackTrace(); + return null; + } + } + + private String find(File dir, Predicate<File> predicate) { + for (File file : dir.listFiles()) { + if (file.isDirectory()) { + String path = find(file, predicate); + if (path != null) { + return path; + } + } + if (predicate.apply(file)) { + return file.getAbsolutePath(); + } + } + return null; + } + + private String tryGetRelativePath(File file) { + String pwd = System.getProperty("user.dir"); + return stripPrefix(pwd + File.separator, file.getAbsolutePath()); + } + + private String stripPrefix(String prefix, String s) { + return s.startsWith(prefix) ? s.substring(prefix.length()) : s; + } + + private String stripSuffix(String suffix, String s) { + return s.endsWith(suffix) ? s.substring(0, s.length() - suffix.length()) : s; + } + + private String capitalize(String s) { + return Character.toUpperCase(s.charAt(0)) + s.substring(1); + } + + private List<String> readLines(InputStream is) { + try { + return CharStreams.readLines(new InputStreamReader(is)); + } catch (IOException e) { + e.printStackTrace(); + // TODO(kalman): this is a bit hacky, deal with it properly. + return Collections.singletonList("(Error, couldn't read lines from the input stream. " + + "Try running the command external to PST to view the output.)"); + } + } + + private FileDescriptor asFileDescriptor(Class<?> clazz) { + try { + Method method = clazz.getMethod("getDescriptor"); + return (FileDescriptor) method.invoke(null); + } catch (Exception e) { + return null; + } + } + + private void killProcessAfter(final long delay, final TimeUnit unit, final Process process) { + Thread processKiller = new Thread() { + @Override public void run() { + try { + Thread.sleep(unit.toMillis(delay)); + process.destroy(); + } catch (InterruptedException e) { + } + } + }; + processKiller.setDaemon(true); + processKiller.start(); + } +} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/7d8609e7/pst/src/main/java/org/waveprotocol/pst/PstMain.java ---------------------------------------------------------------------- diff --git a/pst/src/main/java/org/waveprotocol/pst/PstMain.java b/pst/src/main/java/org/waveprotocol/pst/PstMain.java new file mode 100644 index 0000000..ea0693f --- /dev/null +++ b/pst/src/main/java/org/waveprotocol/pst/PstMain.java @@ -0,0 +1,92 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.waveprotocol.pst; + +import com.google.common.collect.Lists; +import com.google.common.io.Files; +import com.google.protobuf.Descriptors.FileDescriptor; + +import org.apache.commons.cli.ParseException; + +import java.io.File; +import java.util.List; + +/** + * Entry point for command line protobuf-stringtemplate. + * + * @author [email protected] (Benjamin Kalman) + */ +public final class PstMain { + + public static void main(String[] args) { + PstCommandLine cl = null; + try { + cl = new PstCommandLine(args); + } catch (ParseException e) { + System.err.println("Error parsing command line arguments: " + e.getMessage()); + PstCommandLine.printHelp(); + System.exit(1); + } + + if (cl.hasHelp()) { + PstCommandLine.printHelp(); + System.exit(0); + } + + FileDescriptor fd = PstFileDescriptor.load( + cl.getProtoFile().getPath(), + cl.shouldSaveJava() ? cl.getOutputDir() : Files.createTempDir(), + cl.getProtoPath()); + if (fd == null) { + System.err.println("Error: cannot find file descriptor for " + cl.getProtoFile()); + System.exit(1); + } + + boolean failed = false; + + List<File> templates = Lists.newArrayList(); + for (File maybeTemplate : cl.getTemplateFiles()) { + if (maybeTemplate.exists()) { + templates.add(maybeTemplate); + } else { + System.err.println("ERROR: template " + maybeTemplate.getPath() + " does not exist."); + failed = true; + } + } + + Pst pst = new Pst(cl.getOutputDir(), fd, cl.getStyler(), templates, cl.shouldSavePreStyled(), + cl.shouldUseInt52()); + try { + pst.run(); + } catch (PstException e) { + System.err.printf("ERROR: generation failed for %d/%d templates:\n", + e.getTemplateExceptions().size(), templates.size()); + for (PstException.TemplateException te : e.getTemplateExceptions()) { + System.err.println('\n' + te.getTemplateName() + " failed:"); + te.printStackTrace(System.err); + } + failed = true; + } + + if (failed) { + System.exit(1); + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/7d8609e7/pst/src/main/java/org/waveprotocol/pst/model/EnumValue.java ---------------------------------------------------------------------- diff --git a/pst/src/main/java/org/waveprotocol/pst/model/EnumValue.java b/pst/src/main/java/org/waveprotocol/pst/model/EnumValue.java new file mode 100644 index 0000000..f3fd68c --- /dev/null +++ b/pst/src/main/java/org/waveprotocol/pst/model/EnumValue.java @@ -0,0 +1,60 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.waveprotocol.pst.model; + +import com.google.protobuf.Descriptors.EnumValueDescriptor; + +/** + * Wraps a {@link EnumValueDescriptor} with methods suitable for stringtemplate. + * + * @author [email protected] (Benjamin Kalman) + */ +public final class EnumValue { + + private final EnumValueDescriptor descriptor; + + public EnumValue(EnumValueDescriptor descriptor) { + this.descriptor = descriptor; + } + + /** + * Gets the name of the enum value, for example: + * <ul> + * <li>org.waveprotocol.pst.examples.Example1.Person.Gender.MALE = "MALE".</li> + * </ul> + * + * @return the name of the enum value + */ + public String getName() { + return descriptor.getName(); + } + + /** + * Gets the number of the enum value, for example: + * <ul> + * <li>org.waveprotocol.pst.examples.Example1.Person.Gender.MALE = 1.</li> + * </ul> + * + * @return the name of the enum value + */ + public int getNumber() { + return descriptor.getNumber(); + } +} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/7d8609e7/pst/src/main/java/org/waveprotocol/pst/model/Field.java ---------------------------------------------------------------------- diff --git a/pst/src/main/java/org/waveprotocol/pst/model/Field.java b/pst/src/main/java/org/waveprotocol/pst/model/Field.java new file mode 100644 index 0000000..97d3ac6 --- /dev/null +++ b/pst/src/main/java/org/waveprotocol/pst/model/Field.java @@ -0,0 +1,283 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.waveprotocol.pst.model; + +import com.google.protobuf.Descriptors.FieldDescriptor; + +import org.waveprotocol.protobuf.Extensions; + +/** + * Wraps a {@link FieldDescriptor} with methods suitable for stringtemplate. + * + * @author [email protected] (Benjamin Kalman) + */ +public final class Field { + + private final FieldDescriptor field; + private final Type type; + private final MessageProperties properties; + + public Field(FieldDescriptor field, Type type, MessageProperties properties) { + this.field = field; + this.type = type; + this.properties = properties; + } + + /** + * Returns the type of the field as the Java type, for example: + * <ul> + * <li>org.waveprotocol.pst.examples.Example1.Person.first_name = "String"</li> + * <li>org.waveprotocol.pst.examples.Example1.Person.age = "int"</li> + * <li>org.waveprotocol.pst.examples.Example1.Person.gender = "Gender"</li> + * <li>org.waveprotocol.pst.examples.Example1.Person.address = <ul> + * <li>"AddressMessage" (if template name is "message")</li> + * <li>"AddressMessageServerImpl" (if template name is "messageServerImpl")</li></ul></li> + * </ul> + * + * @return the type of the field as the Java type + */ + public String getJavaType() { + return type.getJavaType(isInt52()); + } + + /** + * Returns the type of the field as the Java type capitalized, for example: + * <ul> + * <li>org.waveprotocol.pst.examples.Example1.Person.first_name = "String"</li> + * <li>org.waveprotocol.pst.examples.Example1.Person.age = "Int"</li> + * <li>org.waveprotocol.pst.examples.Example1.Person.gender = "Gender"</li> + * <li>org.waveprotocol.pst.examples.Example1.Person.address = <ul> + * <li>"AddressMessage" (if template name is "message")</li> + * <li>"AddressMessageServerImpl" (if template name is "messageServerImpl")</li></ul></li> + * </ul> + * + * @return the type of the field as the Java type + */ + public String getCapJavaType() { + return type.getCapJavaType(isInt52()); + } + + /** + * Returns the type of the field as the boxed Java type, for example: + * <ul> + * <li>org.waveprotocol.pst.examples.Example1.Person.first_name = "String"</li> + * <li>org.waveprotocol.pst.examples.Example1.Person.age = "Integer"</li> + * <li>org.waveprotocol.pst.examples.Example1.Person.gender = "Gender"</li> + * <li>org.waveprotocol.pst.examples.Example1.Person.address = <ul> + * <li>"AddressMessage" (if template name is "message")</li> + * <li>"AddressMessageServerImpl" (if template name is "messageServerImpl")</li></ul></li> + * </ul> + * + * @return the type of the field as a boxed Java type + */ + public String getBoxedJavaType() { + return type.getBoxedJavaType(isInt52()); + } + + /** + * Returns the message type of the field without template suffix, for example: + * <ul> + * <li>org.waveprotocol.pst.examples.Example1.Person.first_name = undefined</li> + * <li>org.waveprotocol.pst.examples.Example1.Person.age = undefined</li> + * <li>org.waveprotocol.pst.examples.Example1.Person.gender = undefined</li> + * <li>org.waveprotocol.pst.examples.Example1.Person.address = + * "Address" (regardless of template name)</li> + * </ul> + * + * @return the message type of the field without template suffix + */ + public String getMessageType() { + return type.getMessage().getName(); + } + + /** + * Gets the type of this field. + * + * @return the type of this field. + */ + public Type getType() { + return type; + } + + /** + * Returns the name of the field as uncapitalizedCamelCase, for example + * <ul> + * <li>org.waveprotocol.pst.examples.Example1.Person.first_name = "firstName"</li> + * <li>org.waveprotocol.pst.examples.Example1.Person.age = "age"</li> + * <li>org.waveprotocol.pst.examples.Example1.Person.lucky_numbers = "luckyNumbers"</li> + * </ul> + * + * @return the name of the field as uncapitalizedCamelCase + */ + public String getName() { + return Util.uncapitalize(getCapName()); + } + + /** + * Returns the name of the field as CapitalizedCamelCase, for example + * <ul> + * <li>org.waveprotocol.pst.examples.Example1.Person.first_name = "FirstName"</li> + * <li>org.waveprotocol.pst.examples.Example1.Person.age = "age"</li> + * <li>org.waveprotocol.pst.examples.Example1.Person.lucky_numbers = "LuckyNumbers"</li> + * </ul> + * + * @return the name of the field as CapitalizedCamelCase + */ + public String getCapName() { + StringBuilder result = new StringBuilder(); + for (String s : getNameParts()) { + result.append(Util.capitalize(s)); + } + return result.toString(); + } + + private String[] getNameParts() { + // Assumes that the field is separated by underscores... not sure if this + // is always the case. + return field.getName().split("_"); + } + + /** + * Returns the number of the field, for example + * <ul> + * <li>org.waveprotocol.pst.examples.Example1.Person.first_name = 1</li> + * <li>org.waveprotocol.pst.examples.Example1.Person.age = 4</li> + * <li>org.waveprotocol.pst.examples.Example1.Person.lucky_numbers = 5</li> + * </ul> + * + * @return the number of the field + */ + public int getNumber() { + return field.getNumber(); + } + + /** + * Gets the default value of the field (null for objects, empty strings/arrays, zero, false, etc). + * + * @return the "default value" of the field + */ + public String getDefaultValue() { + return type.getDefaultValue(); + } + + /** + * Gets the name of a Java getter for this field. + * + * @return the name of a Java getter for this field. + */ + public String getGetter() { + return "get" + getCapName(); + } + + /** + * Gets the name of a Java setter for this field. + * + * @return the name of a Java getter for this field. + */ + public String getSetter() { + return "set" + getCapName(); + } + + /** + * Gets whether the field is of type int52. This means that although the + * field's native type is int64, only 52 bits of information are used. + * + * @return whether the field is a 52-bit integer + */ + public boolean isInt52() { + return properties.getUseInt52() // + && field.getOptions().hasExtension(Extensions.int52) + && field.getOptions().getExtension(Extensions.int52); + } + + /** + * Gets whether the field is of type long (int64). This means that the field + * may use up to 64 bits of information. + * + * @return whether the field is a long (64-bit integer) + */ + public boolean isLong() { + return field.getJavaType() == FieldDescriptor.JavaType.LONG && !isInt52(); + } + + // + // These map directly to the .proto definitions (except for isPrimitive, but that's pretty + // self explanatory). + // + + /** + * @return whether the field is required + */ + public boolean isRequired() { + return field.isRequired(); + } + + /** + * @return whether the field is optional + */ + public boolean isOptional() { + return field.isOptional(); + } + + /** + * @return whether the field is repeated + */ + public boolean isRepeated() { + return field.isRepeated(); + } + + /** + * @return whether the field is a message + */ + public boolean isMessage() { + return type.isMessage(); + } + + /** + * @return whether the field is an enum + */ + public boolean isEnum() { + return type.isEnum(); + } + + /** + * @return whether the field type is a Java primitive + */ + public boolean isPrimitive() { + return type.isPrimitive(); + } + + /** + * @return whether the field type is a data blob. + */ + public boolean isBlob() { + return type.isBlob(); + } + + /** + * @return whether the field type is a Java primitive and not repeated + */ + public boolean isPrimitiveAndNotRepeated() { + // NOTE: If stringtemplate could handle statements like + // $if (f.primitive && !f.repeated)$ + // then this method would be unnecessary. However, from what I can tell, it can't. + return isPrimitive() && !isRepeated(); + } +} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/7d8609e7/pst/src/main/java/org/waveprotocol/pst/model/Message.java ---------------------------------------------------------------------- diff --git a/pst/src/main/java/org/waveprotocol/pst/model/Message.java b/pst/src/main/java/org/waveprotocol/pst/model/Message.java new file mode 100644 index 0000000..6d945e7 --- /dev/null +++ b/pst/src/main/java/org/waveprotocol/pst/model/Message.java @@ -0,0 +1,343 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.waveprotocol.pst.model; + + import com.google.common.base.Joiner; +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import com.google.protobuf.Descriptors.Descriptor; +import com.google.protobuf.Descriptors.EnumDescriptor; +import com.google.protobuf.Descriptors.FieldDescriptor; +import com.google.protobuf.Descriptors.FieldDescriptor.JavaType; + +import java.util.Collection; +import java.util.Deque; +import java.util.List; + +/** + * Wraps a {@link Descriptor} with methods suitable for stringtemplate. + * + * @author [email protected] (Benjamin Kalman) + */ +public final class Message { + + private final Descriptor descriptor; + private final String templateName; + private final MessageProperties extraProperties; + + // Lazily created. + private List<Field> fields = null; + private List<Message> messages = null; + private List<ProtoEnum> enums = null; + private List<Message> referencedMessages = null; + private List<ProtoEnum> referencedEnums = null; + private String fullName; + private String fullJavaType; + + public Message(Descriptor descriptor, String templateName, MessageProperties extraProperties) { + this.descriptor = descriptor; + this.templateName = templateName; + this.extraProperties = extraProperties; + } + + /** + * Returns the short name of the Java type of this message, for example: + * <ul> + * <li>org.waveprotocol.pst.examples.Example1.Person = "Person"</li> + * </ul> + * + * @return the name of the protocol buffer message + */ + public String getName() { + return descriptor.getName(); + } + + /** + * Returns the short name of Java type being generated. For example: + * <ul> + * <li>org.waveprotocol.pst.examples.Example1.Person = <ul> + * <li>"PersonMessage" (for template name "message")</li> + * <li>"PersonMessageServerImpl" (for template name "messageServerImpl")</li></ul> + * </li> + * </ul> + * + * @return the name of the Java message + */ + public String getJavaType() { + return descriptor.getName() + Util.capitalize(templateName); + } + + /** + * Returns the full name of the this message in abstract Java space. For + * example: + * <ul> + * <li>org.waveprotocol.pst.examples.Example1.Person = <ul> + * <li>"org.waveprotocol.pst.examples.Person" + * (for template name "Message" and package suffix "dto")</li></ul> + * </li> + * </ul> + * + * @return the name of the protocol buffer message + */ + public String getFullName() { + if (fullName == null) { + fullName = getFullName(false); + } + return fullName; + } + + /** + * Returns the full name of the Java type of this message, for example: + * <ul> + * <li>org.waveprotocol.pst.examples.Example1.Person = <ul> + * <li>"org.waveprotocol.pst.examples.dto.PersonMessage" + * (for template name "Message" and package suffix "dto")</li> + * <li>"org.waveprotocol.pst.examples.impl.PersonImpl" + * (for template name "Impl" and package suffix "impl")</li></ul> + * </li> + * </ul> + * + * @return the name of the protocol buffer message + */ + public String getFullJavaType() { + if (fullJavaType == null) { + fullJavaType = getFullName(true); + } + return fullJavaType; + } + + /** + * Gets the fully-qualified name of this message. + * + * @param covariant if true, the name refers to the Java type being generated + * for this message. Otherwise, the name refers to a + * template-independent Java type, which may or may not exist. This is + * intended to be used so that the generated Java type for this message + * can refer to other Java types derived from this message. + * @return the fully-qualified name of this message. + */ + private String getFullName(boolean covariant) { + String prefix; + if (descriptor.getContainingType() != null) { + prefix = adapt(descriptor.getContainingType()).getFullName(covariant); + } else { + prefix = covariant ? getPackage() : getPackageBase(); + } + + return prefix + "." + (covariant ? getJavaType() : getName()); + } + + /** + * Returns the package of the Java messageas the base plus the suffix + * components of the package, for example given org.waveprotocol.pst.examples.Example1.Person: + * <ul> + * <li>Message = "org.waveprotocol.pst.examples"</li> + * <li>MessageServerImpl (package suffix "server") = "org.waveprotocol.pst.examples.server"</li> + * <li>MessageClientImpl (package suffix "client") = "org.waveprotocol.pst.examples.client"</li> + * </ul> + * + * @return the Java package of the message + */ + public String getPackage() { + String suffix = getPackageSuffix(); + return getPackageBase() + (!Strings.isNullOrEmpty(suffix) ? "." + suffix : ""); + } + + /** + * Returns the base component of the Java message package, for example, given + * org.waveprotocol.pst.examples.Example1.Person: + * <ul> + * <li>Message = "org.waveprotocol.pst.examples"</li> + * <li>MessageServerImpl (package suffix "server") = "org.waveprotocol.pst.examples"</li> + * </ul> + * + * @return the base component of the Java package + */ + public String getPackageBase() { + String javaPackage = descriptor.getFile().getOptions().getJavaPackage(); + if (Strings.isNullOrEmpty(javaPackage)) { + javaPackage = descriptor.getFile().getPackage(); + } + return javaPackage; + } + + /** + * Returns the suffix component of the Java message package, as configured in + * the message's properties file, for example: + * <ul> + * <li>Message = null</li> + * <li>MessageServerImpl = "server"</li> + * <li>MessageClientImpl = "client"</li> + * </ul> + */ + public String getPackageSuffix() { + return extraProperties.getPackageSuffix(); + } + + /** + * @return the filename of the protocol buffer (.proto) file where the message + * is defined + */ + public String getFilename() { + return descriptor.getFile().getName(); + } + + /** + * Returns the qualified type of the protobuf message, for example: + * <ul> + * <li>org.waveprotocol.pst.examples.Example1.Person = + * "org.waveprotocol.pst.examples.Example1.Person"</li> + * </ul> + * + * @return the full type of the protocol buffer message + */ + public String getProtoType() { + Deque<String> scopes = Lists.newLinkedList(); + for (Descriptor message = descriptor; message != null; message = message.getContainingType()) { + scopes.push(message.getName()); + } + scopes.push(descriptor.getFile().getOptions().getJavaOuterClassname()); + scopes.push(getPackageBase()); + return Joiner.on('.').join(scopes); + } + + /** + * @return the fields of the message + */ + public List<Field> getFields() { + if (fields == null) { + ImmutableList.Builder<Field> builder = ImmutableList.builder(); + for (FieldDescriptor fd : descriptor.getFields()) { + builder.add(new Field(fd, new Type(fd, templateName, extraProperties), extraProperties)); + } + fields = builder.build(); + } + return fields; + } + + /** + * @return the set of all messages referred to be this message and its nested + * messages. Message references are due to message-typed fields. + */ + public List<Message> getReferencedMessages() { + if (referencedMessages == null) { + referencedMessages = Lists.newArrayList(); + for (Descriptor d : collectMessages(descriptor, Sets.<Descriptor>newLinkedHashSet())) { + referencedMessages.add(adapt(d)); + } + } + return referencedMessages; + } + + /** + * @return the set of all enums referred to be this message and its nested + * messages. Enum references are due to message-typed fields. + */ + public List<ProtoEnum> getReferencedEnums() { + if (referencedEnums == null) { + referencedEnums = Lists.newArrayList(); + for (EnumDescriptor d : collectEnums(descriptor, Sets.<EnumDescriptor> newLinkedHashSet())) { + referencedEnums.add(adapt(d)); + } + } + return referencedEnums; + } + + /** + * Collects messages referred to by a message and its nested messages. + * + * @return {@code referenced} + */ + private static Collection<Descriptor> collectMessages( + Descriptor message, Collection<Descriptor> referenced) { + for (FieldDescriptor fd : message.getFields()) { + if (fd.getJavaType() == JavaType.MESSAGE) { + referenced.add(fd.getMessageType()); + } + } + for (Descriptor nd : message.getNestedTypes()) { + collectMessages(nd, referenced); + } + return referenced; + } + + /** + * Collects enums referred to by a message and its nested messages. + * + * @return {@code referenced} + */ + private static Collection<EnumDescriptor> collectEnums( + Descriptor d, Collection<EnumDescriptor> referenced) { + for (FieldDescriptor fd : d.getFields()) { + if (fd.getJavaType() == JavaType.ENUM) { + referenced.add(fd.getEnumType()); + } + } + for (Descriptor nd : d.getNestedTypes()) { + collectEnums(nd, referenced); + } + return referenced; + } + + /** + * @return the nested messages of the message + */ + public List<Message> getNestedMessages() { + if (messages == null) { + ImmutableList.Builder<Message> builder = ImmutableList.builder(); + for (Descriptor d : descriptor.getNestedTypes()) { + builder.add(adapt(d)); + } + messages = builder.build(); + } + return messages; + } + + /** + * @return the nested enums of the message + */ + public List<ProtoEnum> getNestedEnums() { + if (enums == null) { + ImmutableList.Builder<ProtoEnum> builder = ImmutableList.builder(); + for (EnumDescriptor ed : descriptor.getEnumTypes()) { + builder.add(adapt(ed)); + } + enums = builder.build(); + } + return enums; + } + + /** + * @return whether this is an inner class + */ + public boolean isInner() { + return descriptor.getContainingType() != null; + } + + private Message adapt(Descriptor d) { + return new Message(d, templateName, extraProperties); + } + + private ProtoEnum adapt(EnumDescriptor d) { + return new ProtoEnum(d, templateName, extraProperties); + } +} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/7d8609e7/pst/src/main/java/org/waveprotocol/pst/model/MessageProperties.java ---------------------------------------------------------------------- diff --git a/pst/src/main/java/org/waveprotocol/pst/model/MessageProperties.java b/pst/src/main/java/org/waveprotocol/pst/model/MessageProperties.java new file mode 100644 index 0000000..498f230 --- /dev/null +++ b/pst/src/main/java/org/waveprotocol/pst/model/MessageProperties.java @@ -0,0 +1,112 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.waveprotocol.pst.model; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.util.Properties; + +/** + * Container for the Properties of a message. + * + * @author [email protected] + */ +public final class MessageProperties { + + private static final String PACKAGE_SUFFIX = "package.suffix"; + private static final String FILE_EXTENSION = "file.extension"; + private static final String TEMPLATE_NAME = "template.name"; + + private final Properties properties; + private boolean useInt52; + + private MessageProperties(Properties properties) { + this.properties = properties; + } + + public static MessageProperties createFromFile(File propertiesFile) throws FileNotFoundException, + IOException { + Properties properties = new Properties(); + properties.load(new FileReader(propertiesFile)); + return new MessageProperties(properties); + } + + public static MessageProperties createEmpty() { + return new MessageProperties(new Properties()); + } + + /** + * @return the package suffix, or null if one isn't specified. + */ + public String getPackageSuffix() { + return properties.getProperty(PACKAGE_SUFFIX); + } + + /** + * @return whether a package suffix has been specified. + */ + public boolean hasPackageSuffix() { + return getPackageSuffix() != null; + } + + /** + * @return the file extension, or null if it isn't specified. + */ + public String getFileExtension() { + return properties.getProperty(FILE_EXTENSION); + } + + /** + * @return whether a file extension has been specified. + */ + public boolean hasFileExtension() { + return getFileExtension() != null; + } + + /** + * @return the template name, or null if it isn't specified. + */ + public String getTemplateName() { + return properties.getProperty(TEMPLATE_NAME); + } + + /** + * @return whether a template name has been specified. + */ + public boolean hasTemplateName() { + return getTemplateName() != null; + } + + /** + * Sets the global int52 type property + */ + public void setUseInt52(boolean useInt52) { + this.useInt52 = useInt52; + } + + /** + * @return the int52 type or null if it isn't specified. + */ + public boolean getUseInt52() { + return useInt52; + } +} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/7d8609e7/pst/src/main/java/org/waveprotocol/pst/model/ProtoEnum.java ---------------------------------------------------------------------- diff --git a/pst/src/main/java/org/waveprotocol/pst/model/ProtoEnum.java b/pst/src/main/java/org/waveprotocol/pst/model/ProtoEnum.java new file mode 100644 index 0000000..ec33f4d --- /dev/null +++ b/pst/src/main/java/org/waveprotocol/pst/model/ProtoEnum.java @@ -0,0 +1,140 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.waveprotocol.pst.model; + +import com.google.common.collect.Lists; +import com.google.protobuf.Descriptors.EnumDescriptor; +import com.google.protobuf.Descriptors.EnumValueDescriptor; + +import java.util.List; + +/** + * Wraps a {@link EnumDescriptor} with methods suitable for stringtemplate. + * + * Called ProtoEnum rather than Enum to avoid java.lang namespace conflict. + * + * @author [email protected] (Benjamnin Kalman) + */ +public final class ProtoEnum { + + private final EnumDescriptor descriptor; + private final String templateName; + private final MessageProperties extra; + + private String fullName; + private String fullJavaType; + + public ProtoEnum(EnumDescriptor descriptor, String templateName, MessageProperties extra) { + this.descriptor = descriptor; + this.templateName = templateName; + this.extra = extra; + } + + /** + * Returns the enum, for example: + * <ul> + * <li>org.waveprotocol.pst.examples.Example1.Person.Gender = "Gender"</li> + * </ul> + * + * @return the name of the enum + */ + public String getName() { + return descriptor.getName(); + } + + /** + * Returns the short name of the Java type generated for this enum, for example: + * <ul> + * <li>org.waveprotocol.pst.examples.Example1.Person.Gender = "Gender"</li> + * </ul> + * + * @return the name of the java type of the enum + */ + public String getJavaType() { + return getName(); + } + + /** + * Returns the fully-qualified name of the enum in abstract space. For + * example: + * <ul> + * <li>org.waveprotocol.pst.examples.Example1.Person.Gender = + * "org.waveprotocol.pst.examples.Person.Gender"</li> + * </ul> + * + * @return the name of the enum + */ + public String getFullName() { + if (fullName == null) { + fullName = getContainingMessage().getFullName() + "." + getName(); + } + return fullName; + } + + /** + * Returns the fully-qualified name of the Java type for this enum. + * example: + * <ul> + * <li>org.waveprotocol.pst.examples.Example1.Person.Gender = + * "org.waveprotocol.pst.examples.Person.Gender"</li> + * </ul> + * + * @return the name of the enum + */ + public String getFullJavaType() { + if (fullJavaType == null) { + fullJavaType = getContainingMessage().getFullJavaType() + "." + getName(); + } + return fullJavaType; + } + + /** + * Returns the qualified type of the protobuf enum, for example: + * <ul> + * <li>org.waveprotocol.pst.examples.Example1.Person = + * "org.waveprotocol.pst.examples.Example1.Person"</li> + * </ul> + * + * @return the full type of the protocol buffer enum + */ + public String getProtoType() { + return getContainingMessage().getProtoType() + "." + getName(); + } + + /** + * Returns the enum values, for example: + * <ul> + * <li>org.waveprotocol.pst.examples.Example1.Person.Gender = [MALE, FEMALE, OTHER]</li> + * </ul> + * + * @return the enum values + */ + public List<EnumValue> getValues() { + List<EnumValue> enums = Lists.newArrayList(); + for (EnumValueDescriptor evd : descriptor.getValues()) { + enums.add(new EnumValue(evd)); + } + return enums; + } + + private Message getContainingMessage() { + return new Message(descriptor.getContainingType(), templateName, extra); + } +} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/7d8609e7/pst/src/main/java/org/waveprotocol/pst/model/Type.java ---------------------------------------------------------------------- diff --git a/pst/src/main/java/org/waveprotocol/pst/model/Type.java b/pst/src/main/java/org/waveprotocol/pst/model/Type.java new file mode 100644 index 0000000..7b64abd --- /dev/null +++ b/pst/src/main/java/org/waveprotocol/pst/model/Type.java @@ -0,0 +1,244 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.waveprotocol.pst.model; + +import com.google.protobuf.Descriptors.Descriptor; +import com.google.protobuf.Descriptors.FieldDescriptor; + +/** + * Wraps a {@link FieldDescriptor} to expose type-only information for + * stringtemplate. + * + * @author [email protected] (Benjamin Kalman) + */ +public final class Type { + + private final FieldDescriptor field; + private final String templateName; + private final MessageProperties extraProperties; + private Message messageType; + + public Type(FieldDescriptor field, String templateName, MessageProperties extraProperties) { + this.field = field; + this.templateName = templateName; + this.extraProperties = extraProperties; + } + + /** + * Returns the type of the field as the Java type, for example: + * <ul> + * <li>org.waveprotocol.pst.examples.Example1.Person.first_name = "String"</li> + * <li>org.waveprotocol.pst.examples.Example1.Person.age = "int"</li> + * <li>org.waveprotocol.pst.examples.Example1.Person.gender = "Gender"</li> + * <li>org.waveprotocol.pst.examples.Example1.Person.address = <ul> + * <li>"AddressMessage" (if template name is "message")</li> + * <li>"AddressMessageServerImpl" (if template name is "messageServerImpl")</li></ul></li> + * </ul> + * + * @return the type of the field as the Java type + */ + public String getJavaType(boolean hasInt52Ext) { + switch (field.getJavaType()) { + case BOOLEAN: + return "boolean"; + case BYTE_STRING: + return "Blob"; + case DOUBLE: + return "double"; + case ENUM: + return field.getEnumType().getName(); + case FLOAT: + return "float"; + case INT: + return "int"; + case LONG: + return hasInt52Ext && extraProperties.getUseInt52() ? "double" : "long"; + case MESSAGE: + return getMessage().getJavaType(); + case STRING: + return "String"; + default: + throw new UnsupportedOperationException("Unsupported field type " + field.getJavaType()); + } + } + + /** + * Returns the type of the field as the Java type capitalized, for example: + * <ul> + * <li>org.waveprotocol.pst.examples.Example1.Person.first_name = "String"</li> + * <li>org.waveprotocol.pst.examples.Example1.Person.age = "Int"</li> + * <li>org.waveprotocol.pst.examples.Example1.Person.gender = "Gender"</li> + * <li>org.waveprotocol.pst.examples.Example1.Person.address = <ul> + * <li>"AddressMessage" (if template name is "message")</li> + * <li>"AddressMessageServerImpl" (if template name is "messageServerImpl")</li></ul></li> + * </ul> + * + * @return the type of the field as the Java type + */ + public String getCapJavaType(boolean hasInt52Ext) { + return Util.capitalize(getJavaType(hasInt52Ext)); + } + + /** + * Returns the type of the field as the boxed Java type, for example: + * <ul> + * <li>org.waveprotocol.pst.examples.Example1.Person.first_name = "String"</li> + * <li>org.waveprotocol.pst.examples.Example1.Person.age = "Integer"</li> + * <li>org.waveprotocol.pst.examples.Example1.Person.gender = "Gender"</li> + * <li>org.waveprotocol.pst.examples.Example1.Person.address = <ul> + * <li>"AddressMessage" (if template name is "message")</li> + * <li>"AddressMessageServerImpl" (if template name is "messageServerImpl")</li></ul></li> + * </ul> + * + * @return the type of the field as a boxed Java type + */ + public String getBoxedJavaType(boolean hasInt52Ext) { + switch (field.getJavaType()) { + case BOOLEAN: + return "Boolean"; + case DOUBLE: + return "Double"; + case FLOAT: + return "Float"; + case INT: + return "Integer"; + case LONG: + return hasInt52Ext && extraProperties.getUseInt52() ? "Double" : "Long"; + default: + return getJavaType(hasInt52Ext); + } + } + + /** + * Gets the default value of the field (null for objects, zero, false, etc). + * + * @return the "default value" of the field + */ + public String getDefaultValue() { + switch (field.getJavaType()) { + case BOOLEAN: + return "false"; + case BYTE_STRING: + return "null"; + case DOUBLE: + return "0.0"; + case ENUM: + return field.getEnumType().getName() + ".UNKNOWN"; + case FLOAT: + return "0.0f"; + case INT: + return "0"; + case LONG: + return "0L"; + case MESSAGE: + return "null"; + case STRING: + return "null"; + default: + throw new UnsupportedOperationException("Unsupported field type " + field.getJavaType()); + } + } + + /** + * @return this type as a message. + */ + public Message getMessage() { + if (messageType == null) { + messageType = adapt(field.getMessageType()); + } + return messageType; + } + + /** + * @return whether the field is a message + */ + public boolean isMessage() { + return field.getType().equals(FieldDescriptor.Type.MESSAGE); + } + + /** + * @return whether the field is an enum + */ + public boolean isEnum() { + return field.getType().equals(FieldDescriptor.Type.ENUM); + } + + /** + * @return whether the field is a byte string. + */ + public boolean isBlob() { + return field.getType().equals(FieldDescriptor.Type.BYTES); + } + + /** + * @return whether the field type is a Java primitive + */ + public boolean isPrimitive() { + switch (field.getJavaType()) { + case BOOLEAN: + case DOUBLE: + case FLOAT: + case INT: + case LONG: + return true; + default: + return false; + } + } + + private Message adapt(Descriptor d) { + return new Message(d, templateName, extraProperties); + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } else if (o instanceof Type) { + Type t = (Type) o; + if (field.getType() == t.field.getType()) { + switch (field.getType()) { + case MESSAGE: + return field.getMessageType().equals(t.field.getMessageType()); + case ENUM: + return field.getEnumType().equals(t.field.getEnumType()); + default: + return true; + } + } else { + return false; + } + } else { + return false; + } + } + + @Override + public int hashCode() { + switch (field.getType()) { + case MESSAGE: + return field.getMessageType().hashCode(); + case ENUM: + return field.getEnumType().hashCode(); + default: + return field.getType().hashCode(); + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/7d8609e7/pst/src/main/java/org/waveprotocol/pst/model/Util.java ---------------------------------------------------------------------- diff --git a/pst/src/main/java/org/waveprotocol/pst/model/Util.java b/pst/src/main/java/org/waveprotocol/pst/model/Util.java new file mode 100644 index 0000000..683bf7c --- /dev/null +++ b/pst/src/main/java/org/waveprotocol/pst/model/Util.java @@ -0,0 +1,46 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + + +package org.waveprotocol.pst.model; + +/** + * Util methods for model objects. + * + * @author [email protected] (Benjamin Kalman) + */ +public final class Util { + + private Util() { + } + + /** + * @return the given string, capitalized ("fooBar" = "FooBar") + */ + public static String capitalize(String s) { + return s.isEmpty() ? "" : Character.toUpperCase(s.charAt(0)) + s.substring(1); + } + + /** + * @return the given string, uncapitalized ("FooBar" = "fooBar") + */ + public static String uncapitalize(String s) { + return s.isEmpty() ? "" : Character.toLowerCase(s.charAt(0)) + s.substring(1); + } +}
