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);
+  }
+}

Reply via email to