http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/36445930/pst/src/main/java/org/apache/wave/pst/model/Field.java
----------------------------------------------------------------------
diff --git a/pst/src/main/java/org/apache/wave/pst/model/Field.java 
b/pst/src/main/java/org/apache/wave/pst/model/Field.java
new file mode 100644
index 0000000..2477566
--- /dev/null
+++ b/pst/src/main/java/org/apache/wave/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.apache.wave.pst.model;
+
+import com.google.protobuf.Descriptors.FieldDescriptor;
+
+import org.apache.wave.pst.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/36445930/pst/src/main/java/org/apache/wave/pst/model/Message.java
----------------------------------------------------------------------
diff --git a/pst/src/main/java/org/apache/wave/pst/model/Message.java 
b/pst/src/main/java/org/apache/wave/pst/model/Message.java
new file mode 100644
index 0000000..75e2483
--- /dev/null
+++ b/pst/src/main/java/org/apache/wave/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.apache.wave.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/36445930/pst/src/main/java/org/apache/wave/pst/model/MessageProperties.java
----------------------------------------------------------------------
diff --git a/pst/src/main/java/org/apache/wave/pst/model/MessageProperties.java 
b/pst/src/main/java/org/apache/wave/pst/model/MessageProperties.java
new file mode 100644
index 0000000..782d639
--- /dev/null
+++ b/pst/src/main/java/org/apache/wave/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.apache.wave.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/36445930/pst/src/main/java/org/apache/wave/pst/model/ProtoEnum.java
----------------------------------------------------------------------
diff --git a/pst/src/main/java/org/apache/wave/pst/model/ProtoEnum.java 
b/pst/src/main/java/org/apache/wave/pst/model/ProtoEnum.java
new file mode 100644
index 0000000..abc5f8a
--- /dev/null
+++ b/pst/src/main/java/org/apache/wave/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.apache.wave.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/36445930/pst/src/main/java/org/apache/wave/pst/model/Type.java
----------------------------------------------------------------------
diff --git a/pst/src/main/java/org/apache/wave/pst/model/Type.java 
b/pst/src/main/java/org/apache/wave/pst/model/Type.java
new file mode 100644
index 0000000..7fcb7b8
--- /dev/null
+++ b/pst/src/main/java/org/apache/wave/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.apache.wave.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/36445930/pst/src/main/java/org/apache/wave/pst/model/Util.java
----------------------------------------------------------------------
diff --git a/pst/src/main/java/org/apache/wave/pst/model/Util.java 
b/pst/src/main/java/org/apache/wave/pst/model/Util.java
new file mode 100644
index 0000000..373d3a7
--- /dev/null
+++ b/pst/src/main/java/org/apache/wave/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.apache.wave.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);
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/36445930/pst/src/main/java/org/apache/wave/pst/style/PstStyler.java
----------------------------------------------------------------------
diff --git a/pst/src/main/java/org/apache/wave/pst/style/PstStyler.java 
b/pst/src/main/java/org/apache/wave/pst/style/PstStyler.java
new file mode 100644
index 0000000..ef8a437
--- /dev/null
+++ b/pst/src/main/java/org/apache/wave/pst/style/PstStyler.java
@@ -0,0 +1,513 @@
+/**
+ * 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.apache.wave.pst.style;
+
+import com.google.common.base.Joiner;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.io.CharStreams;
+import com.google.common.io.Files;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.util.ArrayDeque;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * A code styler using a Builder approach to configure styles using smaller
+ * reformatting components.
+ *
+ * TODO(kalman): take string literals into account.
+ *
+ * @author [email protected] (Benjamin Kalman)
+ */
+public final class PstStyler implements Styler {
+
+  private static final String BACKUP_SUFFIX = ".prePstStyler";
+  private static final String INDENT = "  ";
+  private static final String[] ATOMIC_TOKENS = new String[] {
+      "} else {",
+      "} else if (",
+      "for (",
+      "/*-{",
+      "}-*/",
+  };
+
+  /**
+   * Builder for a series of composed style components.
+   */
+  private static class StyleBuilder {
+
+    /**
+     * Styles a single line, outputting generated lines to a generator.
+     * The styler may output anywhere between 0 and infinite lines.
+     */
+    private interface LineStyler {
+      void next(String line, LineGenerator generator);
+    }
+
+    /**
+     * Generates lines to some output sink.
+     */
+    private interface LineGenerator {
+      void yield(CharSequence s);
+    }
+
+    /**
+     * A pipeline of line stylers as a single line generator.
+     */
+    private static final class LinePipeline implements LineGenerator {
+      private final LineStyler lineStyler;
+      private final LineGenerator next;
+
+      private LinePipeline(LineStyler lineStyler, LineGenerator next) {
+        this.lineStyler = lineStyler;
+        this.next = next;
+      }
+
+      /**
+       * Constructs a pipeline of line stylers.
+       *
+       * @param ls the line stylers to place in the pipeline
+       * @param sink the line generator at the end of the pipeline
+       * @return the head of the pipeline
+       */
+      public static LinePipeline construct(Iterable<LineStyler> ls, 
LineGenerator sink) {
+        return new LinePipeline(head(ls),
+            (Iterables.size(ls) == 1) ? sink : construct(tail(ls), sink));
+      }
+
+      private static LineStyler head(Iterable<LineStyler> ls) {
+        return ls.iterator().next();
+      }
+
+      private static Iterable<LineStyler> tail(final Iterable<LineStyler> ls) {
+        return new Iterable<LineStyler>() {
+          @Override public Iterator<LineStyler> iterator() {
+            Iterator<LineStyler> tail = ls.iterator();
+            tail.next();
+            return tail;
+          }
+        };
+      }
+
+      @Override
+      public void yield(CharSequence s) {
+        lineStyler.next(s.toString(), next);
+      }
+    }
+
+    /**
+     * Generates lines into a list.
+     */
+    private static final class ListGenerator implements LineGenerator {
+      private final List<String> list = Lists.newArrayList();
+
+      @Override
+      public void yield(CharSequence s) {
+        list.add(s.toString());
+      }
+
+      public List<String> getList() {
+        return list;
+      }
+    }
+
+    /**
+     * Maintains some helpful state across a styling.
+     */
+    private abstract class StatefulLineStyler implements LineStyler {
+      private boolean inShortComment = false;
+      private boolean inLongComment = false;
+      private boolean inStartOfComment = false;
+      private boolean previousWasComment = false;
+      private int lineNumber = 0;
+
+      protected boolean inComment() {
+        return inShortComment || inLongComment;
+      }
+
+      protected boolean inStartOfComment() {
+        return inStartOfComment;
+      }
+
+      protected boolean inLongComment() {
+        return inLongComment;
+      }
+
+      protected int getLineNumber() {
+        return lineNumber;
+      }
+
+      protected boolean previousWasComment() {
+        return previousWasComment;
+      }
+
+      @Override
+      public final void next(String line, LineGenerator generator) {
+        lineNumber++;
+        // TODO(kalman): JSNI?
+        if (line.matches("^[ \t]*/\\*.*")) {
+          inLongComment = true;
+          inStartOfComment = true;
+        }
+        if (line.matches("^[ \t]*//.*")) {
+          inShortComment = true;
+          inStartOfComment = true;
+        }
+        doNext(line, generator);
+        previousWasComment = inShortComment || inLongComment;
+        if (line.contains("*/")) {
+          inLongComment = false;
+        }
+        inShortComment = false;
+        inStartOfComment = false;
+      }
+
+      abstract void doNext(String line, LineGenerator generator);
+    }
+
+    private final List<LineStyler> lineStylers = Lists.newArrayList();
+
+    /**
+     * Applies the state of the styler to a list of lines.
+     *
+     * @return the styled lines
+     */
+    public List<String> apply(List<String> lines) {
+      ListGenerator result = new ListGenerator();
+      LinePipeline pipeline = LinePipeline.construct(lineStylers, result);
+      for (String line : lines) {
+        pipeline.yield(line);
+      }
+      return result.getList();
+    }
+
+    public StyleBuilder addNewLineBefore(final char newLineBefore) {
+      lineStylers.add(new StatefulLineStyler() {
+        @Override public void doNext(String line, LineGenerator generator) {
+          // TODO(kalman): this is heavy-handed; be fine-grained and just don't
+          // split over tokens (need regexp, presumably).
+          if (inComment() || containsAtomicToken(line)) {
+            generator.yield(line);
+            return;
+          }
+
+          StringBuilder s = new StringBuilder();
+          for (char c : line.toCharArray()) {
+            if (c == newLineBefore) {
+              generator.yield(s);
+              s = new StringBuilder();
+            }
+            s.append(c);
+          }
+          generator.yield(s);
+        }
+      });
+      return this;
+    }
+
+    public StyleBuilder addNewLineAfter(final char newLineAfter) {
+      lineStylers.add(new StatefulLineStyler() {
+        @Override public void doNext(String line, LineGenerator generator) {
+          // TODO(kalman): same as above.
+          if (inComment() || containsAtomicToken(line)) {
+            generator.yield(line);
+            return;
+          }
+
+          StringBuilder s = new StringBuilder();
+          for (char c : line.toCharArray()) {
+            s.append(c);
+            if (c == newLineAfter) {
+              generator.yield(s);
+              s = new StringBuilder();
+            }
+          }
+          generator.yield(s);
+        }
+      });
+      return this;
+    }
+
+    public StyleBuilder trim() {
+      lineStylers.add(new LineStyler() {
+        @Override public void next(String line, LineGenerator generator) {
+          generator.yield(line.trim());
+        }
+      });
+      return this;
+    }
+
+    public StyleBuilder removeRepeatedSpacing() {
+      lineStylers.add(new LineStyler() {
+        @Override public void next(String line, LineGenerator generator) {
+          generator.yield(line.replaceAll("[ \t]+", " "));
+        }
+      });
+      return this;
+    }
+
+    public StyleBuilder stripBlankLines() {
+      lineStylers.add(new LineStyler() {
+        @Override public void next(String line, LineGenerator generator) {
+          if (!line.isEmpty()) {
+            generator.yield(line);
+          }
+        }
+      });
+      return this;
+    }
+
+    public StyleBuilder stripInitialBlankLine() {
+      lineStylers.add(new LineStyler() {
+        boolean firstLine = true;
+        @Override public void next(String line, LineGenerator generator) {
+          if (!firstLine || !line.isEmpty()) {
+            generator.yield(line);
+          }
+          firstLine = false;
+        }
+      });
+      return this;
+    }
+
+    public StyleBuilder stripDuplicateBlankLines() {
+      lineStylers.add(new LineStyler() {
+        boolean previousWasEmpty = false;
+        @Override public void next(String line, LineGenerator generator) {
+          if (!previousWasEmpty || !line.isEmpty()) {
+            generator.yield(line);
+          }
+          previousWasEmpty = line.isEmpty();
+        }
+      });
+      return this;
+    }
+
+    public StyleBuilder indentBraces() {
+      lineStylers.add(new StatefulLineStyler() {
+        private int indentLevel = 0;
+
+        @Override public void doNext(String line, LineGenerator generator) {
+          if (!ignore(line) && line.contains("}")) {
+            indentLevel--;
+            Preconditions.checkState(indentLevel >= 0,
+                "Indentation level reached < 0 on line " + getLineNumber() + " 
(" + line + ")");
+          }
+          String result = "";
+          if (!line.isEmpty()) {
+            result = Strings.repeat(INDENT, indentLevel) + line;
+          }
+          if (!ignore(line) && line.contains("{")) {
+            indentLevel++;
+          }
+          generator.yield(result.toString());
+        }
+
+        private boolean ignore(String line) {
+          // Ignore self-closing braces.
+          return line.contains("{")
+              && line.contains("}")
+              && line.indexOf('{') < line.lastIndexOf('}');
+        }
+      });
+      return this;
+    }
+
+    public StyleBuilder indentLongComments() {
+      lineStylers.add(new StatefulLineStyler() {
+        @Override void doNext(String line, LineGenerator generator) {
+          if (inLongComment() && !inStartOfComment()) {
+            generator.yield(" " + line);
+          } else {
+            generator.yield(line);
+          }
+        }
+      });
+      return this;
+    }
+
+    public StyleBuilder doubleIndentUnfinishedLines() {
+      lineStylers.add(new StatefulLineStyler() {
+        boolean previousUnfinished = false;
+
+        @Override public void doNext(String line, LineGenerator generator) {
+          generator.yield((previousUnfinished ? Strings.repeat(INDENT, 2) : 
"") + line);
+          previousUnfinished =
+              !inComment() &&
+              !line.matches("^.*[;{},\\-/]$") && // Ends with an expected 
character.
+              !line.contains("@Override") &&    // or an annotation.
+              !line.isEmpty() &&
+              !line.contains("//"); // Single-line comment.
+        }
+      });
+      return this;
+    }
+
+    public StyleBuilder addBlankLineBeforeMatching(final String regex) {
+      lineStylers.add(new StatefulLineStyler() {
+        @Override public void doNext(String line, LineGenerator generator) {
+          if ((!inComment() || inStartOfComment()) && line.matches(regex)) {
+            generator.yield("");
+          }
+          generator.yield(line);
+        }
+      });
+      return this;
+    }
+
+    public StyleBuilder addBlankLineBeforeClasslikeWithNoPrecedingComment() {
+      lineStylers.add(new StatefulLineStyler() {
+        @Override public void doNext(String line, LineGenerator generator) {
+          if (!previousWasComment()
+              && line.matches(".*\\b(class|interface|enum)\\b.*")) {
+            generator.yield("");
+          }
+          generator.yield(line);
+        }
+      });
+      return this;
+    }
+
+    public StyleBuilder addBlankLineAfterBraceUnlessInMethod() {
+      lineStylers.add(new StatefulLineStyler() {
+        // true for every level of braces that is a class-like construct
+        ArrayDeque<Boolean> stack = new ArrayDeque<Boolean>();
+        boolean sawClasslike = false;
+
+        @Override public void doNext(String line, LineGenerator generator) {
+          if (inComment()) {
+            generator.yield(line);
+          } else if (line.endsWith("}") && !line.contains("{")) {
+            generator.yield(line);
+            stack.pop();
+            if (!stack.isEmpty() && stack.peek()) {
+              generator.yield("");
+            }
+          } else {
+            // Perhaps we could match anonymous classes by adding "new" here,
+            // but this is not currently needed.
+            if (line.matches(".*\\b(class|interface|enum)\\b.*")) {
+              sawClasslike = true;
+            }
+            if (line.endsWith("{")) {
+              if (line.contains("}")) {
+                stack.pop();
+              }
+              stack.push(sawClasslike);
+              sawClasslike = false;
+            } else if (line.endsWith(";")) {
+              sawClasslike = false;
+            }
+            generator.yield(line);
+          }
+        }
+      });
+      return this;
+    }
+
+    public StyleBuilder addBlankLineAfterMatching(final String regex) {
+      lineStylers.add(new StatefulLineStyler() {
+        boolean previousLineMatched = false;
+
+        @Override public void doNext(String line, LineGenerator generator) {
+          if (previousLineMatched) {
+            generator.yield("");
+          }
+          generator.yield(line);
+          previousLineMatched = line.matches(regex);
+        }
+      });
+      return this;
+    }
+
+    private boolean containsAtomicToken(String line) {
+      for (String token : ATOMIC_TOKENS) {
+        if (line.contains(token)) {
+          return true;
+        }
+      }
+      return false;
+    }
+  }
+
+  @Override
+  public void style(File f, boolean saveBackup) {
+    List<String> lines = null;
+    try {
+      lines = CharStreams.readLines(new FileReader(f));
+    } catch (IOException e) {
+      System.err.println("Couldn't find file " + f.getName() + " to style: " + 
e.getMessage());
+      return;
+    }
+
+    Joiner newlineJoiner = Joiner.on('\n');
+
+    if (saveBackup) {
+      File backup = new File(f.getAbsolutePath() + BACKUP_SUFFIX);
+      try {
+        Files.write(newlineJoiner.join(lines), backup, 
Charset.defaultCharset());
+      } catch (IOException e) {
+        System.err.println("Couldn't write backup " + backup.getName() + ": " 
+ e.getMessage());
+        return;
+      }
+    }
+
+    try {
+      Files.write(newlineJoiner.join(styleLines(lines)), f, 
Charset.defaultCharset());
+    } catch (IOException e) {
+      System.err.println("Couldn't write styled file " + f.getName() + ": " + 
e.getMessage());
+      return;
+    }
+  }
+
+  private List<String> styleLines(List<String> lines) {
+    return new StyleBuilder()
+        .trim()
+        .removeRepeatedSpacing()
+        .addNewLineBefore('}')
+        .addNewLineAfter('{')
+        .addNewLineAfter('}')
+        .addNewLineAfter(';')
+        .trim()
+        .removeRepeatedSpacing()
+        .stripBlankLines()
+        .trim()
+        .indentBraces()
+        .indentLongComments()
+        .addBlankLineBeforeMatching("[ \t]*@Override.*")
+        .addBlankLineBeforeMatching(".*/\\*\\*.*")
+        .addBlankLineAfterMatching("package.*")
+        .addBlankLineBeforeMatching("package.*")
+        .addBlankLineBeforeClasslikeWithNoPrecedingComment()
+        .addBlankLineAfterBraceUnlessInMethod()
+        .stripDuplicateBlankLines()
+        .doubleIndentUnfinishedLines()
+        .stripInitialBlankLine()
+        // TODO: blank line before first method or constructor if that has no 
javadoc
+        .apply(lines);
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/36445930/pst/src/main/java/org/apache/wave/pst/style/Styler.java
----------------------------------------------------------------------
diff --git a/pst/src/main/java/org/apache/wave/pst/style/Styler.java 
b/pst/src/main/java/org/apache/wave/pst/style/Styler.java
new file mode 100644
index 0000000..3621411
--- /dev/null
+++ b/pst/src/main/java/org/apache/wave/pst/style/Styler.java
@@ -0,0 +1,45 @@
+/**
+ * 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.apache.wave.pst.style;
+
+import java.io.File;
+
+/**
+ * Styles a source file.
+ *
+ * @author [email protected] (Benjamin Kalman)
+ */
+public interface Styler {
+
+  /**
+   * Styles a source file.
+   *
+   * @param f the file to style
+   * @param saveBackup whether to save a backup
+   */
+  void style(File f, boolean saveBackup);
+
+  /**
+   * No-op implementation.
+   */
+  Styler EMPTY = new Styler() {
+    @Override public void style(File f, boolean saveBackup) {}
+  };
+}

http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/36445930/pst/src/main/java/org/apache/wave/pst/testing/RandomProtobufGenerator.java
----------------------------------------------------------------------
diff --git 
a/pst/src/main/java/org/apache/wave/pst/testing/RandomProtobufGenerator.java 
b/pst/src/main/java/org/apache/wave/pst/testing/RandomProtobufGenerator.java
new file mode 100644
index 0000000..369e21a
--- /dev/null
+++ b/pst/src/main/java/org/apache/wave/pst/testing/RandomProtobufGenerator.java
@@ -0,0 +1,170 @@
+/**
+ * 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.apache.wave.pst.testing;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.protobuf.ByteString;
+import com.google.protobuf.Descriptors.Descriptor;
+import com.google.protobuf.Descriptors.EnumDescriptor;
+import com.google.protobuf.Descriptors.EnumValueDescriptor;
+import com.google.protobuf.Descriptors.FieldDescriptor;
+import com.google.protobuf.GeneratedMessage;
+import com.google.protobuf.Message.Builder;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+
+/**
+ * Generates random protocol buffers with all fields given a value.
+ *
+ * @author [email protected] (Benjamin Kalman)
+ */
+public final class RandomProtobufGenerator<E extends GeneratedMessage> {
+
+  private static final int MAX_LIST_LENGTH = 10;
+  private static final int MAX_STRING_LENGTH = 10;
+
+  private final Random random;
+  private final GeneratedMessage instance;
+  private final Map<Descriptor, GeneratedMessage> moreInstances;
+
+  private RandomProtobufGenerator(Random random, GeneratedMessage instance,
+      Map<Descriptor, GeneratedMessage> moreInstances) {
+    this.random = random;
+    this.instance = instance;
+    this.moreInstances = moreInstances;
+  }
+
+  /**
+   * Creates a random protobuf generator, for a main type (instance) and a list
+   * of any extra instances that will be needed to generate that instance.
+   *
+   * @param random random number generator
+   * @param instance the protobuf instance used as a template to generate more
+   *        random protobuf
+   * @param moreInstances protobuf templates for any inner messages the
+   *        protobuf depends on
+   * @return the random protobuf generator
+   */
+  public static <T extends GeneratedMessage> RandomProtobufGenerator<T> 
create(Random random,
+      GeneratedMessage instance, GeneratedMessage... moreInstances) {
+    // NOTE: it would be nice to determine this internally e.g. through (java 
or proto) reflection.
+    Map<Descriptor, GeneratedMessage> moreInstancesMap = Maps.newHashMap();
+    for (GeneratedMessage gm : moreInstances) {
+      moreInstancesMap.put(gm.getDescriptorForType(), gm);
+    }
+    return new RandomProtobufGenerator<T>(random, instance, moreInstancesMap);
+  }
+
+  /**
+   * Generates a random protocol buffer, filling in all fields and giving
+   * repeated values 0..n items.
+   */
+  public E generate() {
+    return generate(0);
+  }
+
+  /**
+   * Generates a random protocol buffer, filling in all required fields but
+   * with a p chance of not setting an optional field and p chance of having
+   * an empty repeated field.
+   */
+  @SuppressWarnings("unchecked")
+  public E generate(double p) {
+    Builder builder = instance.newBuilderForType();
+    Descriptor descriptor = instance.getDescriptorForType();
+    for (FieldDescriptor field : descriptor.getFields()) {
+      if (!field.isRequired() && random.nextDouble() < p) {
+        continue;
+      }
+      builder.setField(field, getRandomValue(field, p));
+    }
+    return (E) builder.build();
+  }
+
+  private Object getRandomValue(FieldDescriptor field, double p) {
+    if (field.isRepeated()) {
+      List<Object> values = Lists.newArrayList();
+      for (int i = 0, length = random.nextInt(MAX_LIST_LENGTH); i < length; 
i++) {
+        values.add(getRandomSingleValue(field, p));
+      }
+      return values;
+    } else {
+      return getRandomSingleValue(field, p);
+    }
+  }
+
+  private Object getRandomSingleValue(FieldDescriptor field, double p) {
+    switch (field.getJavaType()) {
+      case BOOLEAN:
+        return random.nextBoolean();
+      case BYTE_STRING:
+        return getRandomByteString();
+      case DOUBLE:
+        return random.nextDouble();
+      case ENUM:
+        return getRandomEnum(field.getEnumType());
+      case FLOAT:
+        return random.nextFloat();
+      case INT:
+        return random.nextInt();
+      case LONG:
+        return random.nextLong();
+      case MESSAGE:
+        return getRandomMessage(field, p);
+      case STRING:
+        return getRandomString();
+      default:
+        return null;
+    }
+  }
+
+  private ByteString getRandomByteString() {
+    byte[] bytes = new byte[32];
+    random.nextBytes(bytes);
+    return ByteString.copyFrom(bytes);
+  }
+
+  private EnumValueDescriptor getRandomEnum(EnumDescriptor enumD) {
+    List<EnumValueDescriptor> values = enumD.getValues();
+    return values.get(random.nextInt(values.size()));
+  }
+
+  private GeneratedMessage getRandomMessage(FieldDescriptor field, double p) {
+    GeneratedMessage instance = moreInstances.get(field.getMessageType());
+    if (instance == null) {
+      throw new IllegalArgumentException("Couldn't find instance for message "
+          + field.getMessageType().getFullName());
+    }
+    return new RandomProtobufGenerator<GeneratedMessage>(random, instance, 
moreInstances)
+        .generate(p);
+  }
+
+  private String getRandomString() {
+    String alphabet = "abc{}[]<>\\\"'";
+    StringBuilder s = new StringBuilder();
+    for (int i = 0, length = random.nextInt(MAX_STRING_LENGTH); i < length; 
i++) {
+      s.append(alphabet.charAt(random.nextInt(alphabet.length())));
+    }
+    return s.toString();
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/36445930/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
deleted file mode 100644
index 98289e3..0000000
--- a/pst/src/main/java/org/waveprotocol/pst/Pst.java
+++ /dev/null
@@ -1,188 +0,0 @@
-/**
- * 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/36445930/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
deleted file mode 100644
index dbc0223..0000000
--- a/pst/src/main/java/org/waveprotocol/pst/PstCommandLine.java
+++ /dev/null
@@ -1,150 +0,0 @@
-/**
- * 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/36445930/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
deleted file mode 100644
index 388bd77..0000000
--- a/pst/src/main/java/org/waveprotocol/pst/PstException.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/**
- * 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;
-  }
-}

Reply via email to