Fixes WAVE-435, Refactoring of PST compiler and general cleanup https://reviews.apache.org/r/42114
diff --git a/.gitignore b/.gitignore
index dafa041..b3db961 100755
--- a/.gitignore
+++ b/.gitignore
@@ -48,7 +48,7 @@
 ### Reports
 reports/
 ### Generated Sources
-wave/src/generated/
+**/generated/
 ### Gwt Testing
 wave/gwt-unitCache
 ### config
diff --git a/build.gradle b/build.gradle
index a96142d..2f445c9 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,64 +1,52 @@
+// 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.
+
+//=============================================================================
+// Plugins
+//=============================================================================
 buildscript {
+    repositories {
+        mavenCentral()
+    }
     dependencies {
-        classpath 'com.google.protobuf:protobuf-gradle-plugin:0.7.0'
+        classpath 'com.google.protobuf:protobuf-gradle-plugin:0.7.3'
     }
 }
-
 plugins {
     id "org.nosphere.apache.rat" version "0.2.0"
-    id "org.sonarqube" version "1.1"
 }
-
 allprojects {
     apply plugin: 'eclipse';
     apply plugin: 'idea';
     apply plugin: 'jacoco'
     group = 'apache-wave'
-    version = '0.4'
 }
 
-// Repositories
-repositories {
-    mavenCentral()
-}
+//=============================================================================
+// Project Level Settings
+//=============================================================================
+version = 0.4 //only applies to the wave server & client, should be changed once split
 
-subprojects {
-    apply plugin: 'java';
-    //apply plugin: 'scala';
-    //apply plugin: 'checkstyle'
-    sourceCompatibility = 1.7
-    targetCompatibility = 1.7
-    test.ignoreFailures = true
-
-    compileJava{
-        options.incremental = true
-    }
-
-    task sourcesJar(type: Jar, dependsOn: classes) {
-        classifier = 'sources'
-        from sourceSets.main.allSource
-    }
-}
-
-project(':wave'){
-    dependencies {
-        compile project(':pst')
-    }
-}
-
-project(':pst'){
-    dependencies {
-        compile project(':wave-proto')
-    }
-}
-
-// Apache Rat Plugin
+//=============================================================================
+// Apache Rat Configuration
+//=============================================================================
 rat {
-    // Input directory, defaults to '.'
     inputDir = '.'
-    // XML and HTML reports directory, defaults to project.buildDir + '/reports/rat'
     reportDir = project.file('reports/rat')
-    // List of exclude directives, defaults to ['**/.gradle/**']
     excludes = [
             '**/build/**',
             'reports/**',
@@ -69,10 +57,8 @@
             '**/*.iml',
             '**/generated/**'
     ]
-    // Fail the build on rat errors, defaults to true
     failOnError = false
 }
-
 rat.doFirst {
     println ''
     println '----------------------------------------------'
@@ -82,62 +68,51 @@
     println ''
 }
 
+//=============================================================================
+// Source Distribution
+//=============================================================================
+
+def srcName = this.group + "-src-" + this.version
+def srcExcludes = [
+        'distributions/',
+        '.gradle/',
+        '.git/',
+        '.vagrant/',
+        '*/build/*',
+        '*/_*',
+        '*/gwt-unitCache/',
+        '*.iml',
+        '*/*.iml',
+        '*.iws',
+        '*.ipr',
+        '*.project',
+        '*/*.project',
+        '*/*.classpath',
+        '*/.settings/',
+        '*/*.log*',
+        'reports/',
+        'wave/war/WEB-INF',
+        'wave/war/webclient'
+]
+
 task createDistSourceZip(type: Zip) {
-    baseName = this.group + "-src-" + this.version
+    baseName = srcName
     destinationDir = file('distributions')
     from('./') {
         into 'apache-wave-src'
     }
-    excludes = [
-            'distributions/',
-            '.gradle/',
-            '.git/',
-            '.vagrant/',
-            '*/build/*',
-            '*/_*',
-            '*/gwt-unitCache/',
-            '*.iml',
-            '*/*.iml',
-            '*.iws',
-            '*.ipr',
-            '*.project',
-            '*/*.project',
-            '*/*.classpath',
-            '*/.settings/',
-            '*/*.log*',
-            'reports/',
-            'wave/war/WEB-INF',
-            'wave/war/webclient'
-    ]
+    excludes = srcExcludes
 }
 
 task createDistSourceTar(type: Tar) {
-    baseName = this.group + "-src-" + this.version
+    compression = Compression.GZIP
+    extension = 'tar.gz'
+    baseName = srcName
     destinationDir = file('distributions')
     from('./') {
         into 'apache-wave-src'
     }
-    excludes = [
-            'distributions/',
-            '.gradle/',
-            '.git/',
-            '.vagrant/',
-            '*/build/*',
-            '*/_*',
-            '*/gwt-unitCache/',
-            '*.iml',
-            '*/*.iml',
-            '*.iws',
-            '*.ipr',
-            '*.project',
-            '*/*.project',
-            '*/*.classpath',
-            '*/.settings/',
-            '*/*.log*',
-            'reports/',
-            'wave/war/WEB-INF',
-            'wave/war/webclient'
-    ]
+    excludes = srcExcludes
 }
 
 task createDistSource() {
@@ -150,8 +125,11 @@
     }
 }
 
-createDistSource.dependsOn createDistSourceZip, createDistSourceTar
+createDistSource.dependsOn createDistSourceZip, createDistSourceTar, ":pst:createDistSource"
 
+//=============================================================================
+// Distribution's
+//=============================================================================
 task createDist() {
     doFirst {
         println ''
@@ -163,4 +141,4 @@
     }
 }
 
-createDist.dependsOn createDistSource, ":wave:createDistBin"
\ No newline at end of file
+createDist.dependsOn createDistSource, ":wave:createDistBin", ":pst:createDist"
\ No newline at end of file
diff --git a/pst/LICENSE b/pst/LICENSE
new file mode 100644
index 0000000..1dd0347
--- /dev/null
+++ b/pst/LICENSE
@@ -0,0 +1,235 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright 2013 The Apache Software Foundation
+
+   Licensed 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.
+
+
+***THE FOLLOWING LICENSE APPLIES TO***
+- Protobuf Descriptors located at /src/google/protobuf/descriptor.proto
+
+Protocol Buffers - Google's data interchange format
+Copyright 2008 Google Inc.  All rights reserved.
+http://code.google.com/p/protobuf/
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+    * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+    * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE
diff --git a/pst/build.gradle b/pst/build.gradle
index 530ee83..0e53b8b 100644
--- a/pst/build.gradle
+++ b/pst/build.gradle
@@ -1,34 +1,64 @@
+// 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.
+
+//=============================================================================
 // Plugins
-apply plugin: 'java'
+//=============================================================================
+plugins {
+    id 'java'
+    id 'com.github.johnrengelman.shadow' version '1.2.2'
+}
+apply plugin: 'com.google.protobuf'
 
-// Settings
+//=============================================================================
+// Project Level Settings
+//=============================================================================
 version = 0.1
+sourceCompatibility = 1.7
+targetCompatibility = 1.7
+def title = 'Apache Wave PST Compiler'
+def vendor = 'The Apache Software Foundation'
 
-// Dependencies
-repositories {
-    mavenCentral()
+jar {
+    manifest {
+        attributes "Specification-Vendor": vendor,
+                "Specification-Title": title,
+                "Specification-Version": version,
+                "Implementation-Vendor": vendor,
+                "Implementation-Title": title,
+                "Implementation-Version": version,
+                "Built-By": "No one in particular",
+                "Main-Class": "org.apache.wave.pst.PstMain"
+    }
 }
 
-// - 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
+//=============================================================================
+// Source's
+//=============================================================================
 sourceSets {
     main {
         java {
-            srcDir 'src/main/java'
-            //srcDir '../wave-proto/build/classes/main/'
+            srcDirs = [
+                    'src/main/java',
+                    'generated/main/java'
+            ]
         }
-        resources {
-            srcDir 'src/main/resources'
+        proto {
+            srcDir 'src/main/proto'
         }
     }
 
@@ -36,8 +66,152 @@
         java {
             srcDir 'src/test/java'
         }
-        resources {
-            srcDir 'src/test/resources'
-        }
     }
-}
\ No newline at end of file
+}
+
+//=============================================================================
+// Dependencies
+// Note: next to each dependency is a review stamp [last review, next review].
+//       If a dependency is past its review date pls create a jira issue.
+//       https://issues.apache.org/jira/browse/WAVE
+//=============================================================================
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    compile group: 'com.google.protobuf', name: 'protobuf-java', version: '2.6.1' // [12/2015, 3/2016]
+    compile group: 'com.google.guava', name: 'guava', version: '19.0'             // [1/2016, 6/2016]
+    compile group: 'org.antlr', name: 'antlr', version: '3.2'                     // [12/2015, 3/2016]
+    compile group: 'commons-cli', name: 'commons-cli', version: '1.3.1'           // [1/2016, 6/2016]
+
+    testCompile group: 'junit', name: 'junit', version: '4.12'                    // [1/2016, 1/2017]
+}
+
+//=============================================================================
+// Protobuf Config
+//=============================================================================
+protobuf {
+    protoc {
+        artifact = 'com.google.protobuf:protoc:2.6.1'
+    }
+    generatedFilesBaseDir = "$projectDir/generated"
+}
+
+//=============================================================================
+// UberJar (shadowJar) config
+//=============================================================================
+
+shadowJar {
+    baseName = 'wave-pst'
+    classifier = ''
+    exclude 'META-INF/**/*'
+}
+
+//=============================================================================
+// Clean - Must remove generated sources.
+//=============================================================================
+clean {
+    delete "generated/"
+}
+
+//=============================================================================
+// Source Distribution
+//=============================================================================
+
+def srcName = this.group + "-" + this.name + "-src"
+def srcExcludes = [
+        'build/*',
+        '*.iml'
+]
+
+task createDistSourceTar(type: Tar) {
+    compression = Compression.GZIP
+    extension = 'tar.gz'
+    baseName = srcName
+    destinationDir = file('../distributions')
+    from('./') {
+        into 'apache-wave-pst-src'
+    }
+    excludes = srcExcludes
+
+}
+
+task createDistSourceZip(type: Zip) {
+    baseName = srcName
+    destinationDir = file('../distributions')
+    from('./') {
+        into 'apache-wave-pst-src'
+    }
+    excludes = srcExcludes
+}
+
+task createDistSource() {
+    doFirst {
+        println ''
+        println '--------------------------------------------------------'
+        println '     Creating Deployment Source - Apache Wave PST       '
+        println '--------------------------------------------------------'
+        println ''
+    }
+}
+
+createDistSource.dependsOn createDistSourceTar, createDistSourceZip
+
+//=============================================================================
+// Binary Distribution
+//=============================================================================
+
+def binName = this.group + "-" + this.name + "-bin"
+
+task createDistBinTar(type: Tar) {
+    compression = Compression.GZIP
+    extension = 'tar.gz'
+    baseName = binName
+    destinationDir = file('../distributions')
+    from(shadowJar) {
+        into 'apache-wave-pst/bin'
+    }
+    from('src/dist') {
+        into 'apache-wave-pst'
+    }
+}
+
+task createDistBinZip(type: Zip) {
+    baseName = binName
+    destinationDir = file('../distributions')
+    from(shadowJar) {
+        into 'apache-wave-pst/bin'
+    }
+    from('src/dist') {
+        into 'apache-wave-pst'
+    }
+}
+
+task createDistBin() {
+    doFirst {
+        println ''
+        println '--------------------------------------------------------'
+        println '     Creating Deployment Binary - Apache Wave PST       '
+        println '--------------------------------------------------------'
+        println ''
+    }
+}
+
+createDistBin.dependsOn createDistBinTar, createDistBinZip
+
+//=============================================================================
+// Distribution's
+//=============================================================================
+
+task createDist() {
+    doFirst {
+        println ''
+        println '--------------------------------------------------------'
+        println '      Creating Deployments - Apache Wave PST            '
+        println '--------------------------------------------------------'
+        println ''
+    }
+}
+
+createDist.dependsOn createDistSource, createDistBin
\ No newline at end of file
diff --git a/pst/src/dist/LICENSE b/pst/src/dist/LICENSE
new file mode 100644
index 0000000..c60ce90
--- /dev/null
+++ b/pst/src/dist/LICENSE
@@ -0,0 +1,244 @@
+ ***THE FOLLOWING LICENSE APPLIES TO***
+- Apache Wave PST
+- Apache Commons CLI included in the generated jar files
+- Google Guava included in the generated jar files
+
+                                Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright 2013 The Apache Software Foundation
+
+   Licensed 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.
+
+***THE FOLLOWING LICENSE APPLIES TO***
+- Protobuf Java libraries included in the generated jar files
+
+Copyright 2008, Google Inc.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+    * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+    * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+Code generated by the Protocol Buffer compiler is owned by the owner
+of the input file used when generating it.  This code is not
+standalone and requires a support library to be linked with it.  This
+support library is itself covered by the above license.
+
diff --git a/pst/src/dist/NOTICE b/pst/src/dist/NOTICE
new file mode 100644
index 0000000..ac0e02a
--- /dev/null
+++ b/pst/src/dist/NOTICE
@@ -0,0 +1,8 @@
+   Apache Wave - PST
+   Copyright 2011-2016 The Apache Software Foundation
+
+   This product includes software developed at
+   The Apache Software Foundation (http://www.apache.org/).
+
+   Portions of this software were developed at Google Inc. and
+   have been kindly donated to the Apache Software Foundation.
diff --git a/pst/src/main/java/org/apache/wave/pst/Pst.java b/pst/src/main/java/org/apache/wave/pst/Pst.java
new file mode 100644
index 0000000..1265899
--- /dev/null
+++ b/pst/src/main/java/org/apache/wave/pst/Pst.java
@@ -0,0 +1,189 @@
+/**
+ * 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;
+
+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.apache.wave.pst.model.Message;
+import org.apache.wave.pst.model.MessageProperties;
+import org.apache.wave.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 kalman@google.com (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;
+  }
+
+}
diff --git a/pst/src/main/java/org/apache/wave/pst/PstCommandLine.java b/pst/src/main/java/org/apache/wave/pst/PstCommandLine.java
new file mode 100644
index 0000000..b725704
--- /dev/null
+++ b/pst/src/main/java/org/apache/wave/pst/PstCommandLine.java
@@ -0,0 +1,151 @@
+/**
+ * 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;
+
+import com.google.common.base.Function;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.DefaultParser;
+import org.apache.commons.cli.HelpFormatter;
+import org.apache.commons.cli.Options;
+import org.apache.commons.cli.ParseException;
+
+import org.apache.wave.pst.style.PstStyler;
+import org.apache.wave.pst.style.Styler;
+
+import java.io.File;
+import java.util.Map;
+
+/**
+ * Encapsulates the command line options to protobuf-stringtemplate.
+ *
+ * @author kalman@google.com (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 DefaultParser().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')));
+  }
+}
diff --git a/pst/src/main/java/org/apache/wave/pst/PstException.java b/pst/src/main/java/org/apache/wave/pst/PstException.java
new file mode 100644
index 0000000..9b17bd7
--- /dev/null
+++ b/pst/src/main/java/org/apache/wave/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.apache.wave.pst;
+
+import com.google.common.collect.ImmutableList;
+
+import java.util.List;
+
+/**
+ * Exception caused by any errors caused in code generation.
+ *
+ * @author kalman@google.com (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;
+  }
+}
diff --git a/pst/src/main/java/org/apache/wave/pst/PstFileDescriptor.java b/pst/src/main/java/org/apache/wave/pst/PstFileDescriptor.java
new file mode 100644
index 0000000..9c870ba
--- /dev/null
+++ b/pst/src/main/java/org/apache/wave/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.apache.wave.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 kalman@google.com (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();
+  }
+}
diff --git a/pst/src/main/java/org/apache/wave/pst/PstMain.java b/pst/src/main/java/org/apache/wave/pst/PstMain.java
new file mode 100644
index 0000000..3faeba1
--- /dev/null
+++ b/pst/src/main/java/org/apache/wave/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.apache.wave.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 kalman@google.com (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);
+    }
+  }
+}
diff --git a/pst/src/main/java/org/apache/wave/pst/model/EnumValue.java b/pst/src/main/java/org/apache/wave/pst/model/EnumValue.java
new file mode 100644
index 0000000..f246d13
--- /dev/null
+++ b/pst/src/main/java/org/apache/wave/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.apache.wave.pst.model;
+
+import com.google.protobuf.Descriptors.EnumValueDescriptor;
+
+/**
+ * Wraps a {@link EnumValueDescriptor} with methods suitable for stringtemplate.
+ *
+ * @author kalman@google.com (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();
+  }
+}
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 kalman@google.com (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();
+  }
+}
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 kalman@google.com (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);
+  }
+}
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 kalman@google.com
+ */
+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;
+  }
+}
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 kalman@google.com (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);
+  }
+}
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 kalman@google.com (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();
+    }
+  }
+}
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 kalman@google.com (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);
+  }
+}
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 kalman@google.com (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);
+  }
+}
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 kalman@google.com (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) {}
+  };
+}
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 kalman@google.com (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();
+  }
+}
diff --git a/pst/src/main/proto/google/protobuf/descriptor.proto b/pst/src/main/proto/google/protobuf/descriptor.proto
new file mode 100644
index 0000000..a753601
--- /dev/null
+++ b/pst/src/main/proto/google/protobuf/descriptor.proto
@@ -0,0 +1,687 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// Author: kenton@google.com (Kenton Varda)
+//  Based on original Protocol Buffers design by
+//  Sanjay Ghemawat, Jeff Dean, and others.
+//
+// The messages in this file describe the definitions found in .proto files.
+// A valid .proto file can be translated directly to a FileDescriptorProto
+// without any other information (e.g. without reading its imports).
+
+
+
+package google.protobuf;
+option java_package = "com.google.protobuf";
+option java_outer_classname = "DescriptorProtos";
+
+// descriptor.proto must be optimized for speed because reflection-based
+// algorithms don't work during bootstrapping.
+option optimize_for = SPEED;
+
+// The protocol compiler can output a FileDescriptorSet containing the .proto
+// files it parses.
+message FileDescriptorSet {
+  repeated FileDescriptorProto file = 1;
+}
+
+// Describes a complete .proto file.
+message FileDescriptorProto {
+  optional string name = 1;       // file name, relative to root of source tree
+  optional string package = 2;    // e.g. "foo", "foo.bar", etc.
+
+  // Names of files imported by this file.
+  repeated string dependency = 3;
+  // Indexes of the public imported files in the dependency list above.
+  repeated int32 public_dependency = 10;
+  // Indexes of the weak imported files in the dependency list.
+  // For Google-internal migration only. Do not use.
+  repeated int32 weak_dependency = 11;
+
+  // All top-level definitions in this file.
+  repeated DescriptorProto message_type = 4;
+  repeated EnumDescriptorProto enum_type = 5;
+  repeated ServiceDescriptorProto service = 6;
+  repeated FieldDescriptorProto extension = 7;
+
+  optional FileOptions options = 8;
+
+  // This field contains optional information about the original source code.
+  // You may safely remove this entire field whithout harming runtime
+  // functionality of the descriptors -- the information is needed only by
+  // development tools.
+  optional SourceCodeInfo source_code_info = 9;
+}
+
+// Describes a message type.
+message DescriptorProto {
+  optional string name = 1;
+
+  repeated FieldDescriptorProto field = 2;
+  repeated FieldDescriptorProto extension = 6;
+
+  repeated DescriptorProto nested_type = 3;
+  repeated EnumDescriptorProto enum_type = 4;
+
+  message ExtensionRange {
+    optional int32 start = 1;
+    optional int32 end = 2;
+  }
+  repeated ExtensionRange extension_range = 5;
+
+  repeated OneofDescriptorProto oneof_decl = 8;
+
+  optional MessageOptions options = 7;
+}
+
+// Describes a field within a message.
+message FieldDescriptorProto {
+  enum Type {
+    // 0 is reserved for errors.
+    // Order is weird for historical reasons.
+    TYPE_DOUBLE         = 1;
+    TYPE_FLOAT          = 2;
+    // Not ZigZag encoded.  Negative numbers take 10 bytes.  Use TYPE_SINT64 if
+    // negative values are likely.
+    TYPE_INT64          = 3;
+    TYPE_UINT64         = 4;
+    // Not ZigZag encoded.  Negative numbers take 10 bytes.  Use TYPE_SINT32 if
+    // negative values are likely.
+    TYPE_INT32          = 5;
+    TYPE_FIXED64        = 6;
+    TYPE_FIXED32        = 7;
+    TYPE_BOOL           = 8;
+    TYPE_STRING         = 9;
+    TYPE_GROUP          = 10;  // Tag-delimited aggregate.
+    TYPE_MESSAGE        = 11;  // Length-delimited aggregate.
+
+    // New in version 2.
+    TYPE_BYTES          = 12;
+    TYPE_UINT32         = 13;
+    TYPE_ENUM           = 14;
+    TYPE_SFIXED32       = 15;
+    TYPE_SFIXED64       = 16;
+    TYPE_SINT32         = 17;  // Uses ZigZag encoding.
+    TYPE_SINT64         = 18;  // Uses ZigZag encoding.
+  };
+
+  enum Label {
+    // 0 is reserved for errors
+    LABEL_OPTIONAL      = 1;
+    LABEL_REQUIRED      = 2;
+    LABEL_REPEATED      = 3;
+    // TODO(sanjay): Should we add LABEL_MAP?
+  };
+
+  optional string name = 1;
+  optional int32 number = 3;
+  optional Label label = 4;
+
+  // If type_name is set, this need not be set.  If both this and type_name
+  // are set, this must be one of TYPE_ENUM, TYPE_MESSAGE or TYPE_GROUP.
+  optional Type type = 5;
+
+  // For message and enum types, this is the name of the type.  If the name
+  // starts with a '.', it is fully-qualified.  Otherwise, C++-like scoping
+  // rules are used to find the type (i.e. first the nested types within this
+  // message are searched, then within the parent, on up to the root
+  // namespace).
+  optional string type_name = 6;
+
+  // For extensions, this is the name of the type being extended.  It is
+  // resolved in the same manner as type_name.
+  optional string extendee = 2;
+
+  // For numeric types, contains the original text representation of the value.
+  // For booleans, "true" or "false".
+  // For strings, contains the default text contents (not escaped in any way).
+  // For bytes, contains the C escaped value.  All bytes >= 128 are escaped.
+  // TODO(kenton):  Base-64 encode?
+  optional string default_value = 7;
+
+  // If set, gives the index of a oneof in the containing type's oneof_decl
+  // list.  This field is a member of that oneof.  Extensions of a oneof should
+  // not set this since the oneof to which they belong will be inferred based
+  // on the extension range containing the extension's field number.
+  optional int32 oneof_index = 9;
+
+  optional FieldOptions options = 8;
+}
+
+// Describes a oneof.
+message OneofDescriptorProto {
+  optional string name = 1;
+}
+
+// Describes an enum type.
+message EnumDescriptorProto {
+  optional string name = 1;
+
+  repeated EnumValueDescriptorProto value = 2;
+
+  optional EnumOptions options = 3;
+}
+
+// Describes a value within an enum.
+message EnumValueDescriptorProto {
+  optional string name = 1;
+  optional int32 number = 2;
+
+  optional EnumValueOptions options = 3;
+}
+
+// Describes a service.
+message ServiceDescriptorProto {
+  optional string name = 1;
+  repeated MethodDescriptorProto method = 2;
+
+  optional ServiceOptions options = 3;
+}
+
+// Describes a method of a service.
+message MethodDescriptorProto {
+  optional string name = 1;
+
+  // Input and output type names.  These are resolved in the same way as
+  // FieldDescriptorProto.type_name, but must refer to a message type.
+  optional string input_type = 2;
+  optional string output_type = 3;
+
+  optional MethodOptions options = 4;
+}
+
+
+// ===================================================================
+// Options
+
+// Each of the definitions above may have "options" attached.  These are
+// just annotations which may cause code to be generated slightly differently
+// or may contain hints for code that manipulates protocol messages.
+//
+// Clients may define custom options as extensions of the *Options messages.
+// These extensions may not yet be known at parsing time, so the parser cannot
+// store the values in them.  Instead it stores them in a field in the *Options
+// message called uninterpreted_option. This field must have the same name
+// across all *Options messages. We then use this field to populate the
+// extensions when we build a descriptor, at which point all protos have been
+// parsed and so all extensions are known.
+//
+// Extension numbers for custom options may be chosen as follows:
+// * For options which will only be used within a single application or
+//   organization, or for experimental options, use field numbers 50000
+//   through 99999.  It is up to you to ensure that you do not use the
+//   same number for multiple options.
+// * For options which will be published and used publicly by multiple
+//   independent entities, e-mail protobuf-global-extension-registry@google.com
+//   to reserve extension numbers. Simply provide your project name (e.g.
+//   Object-C plugin) and your porject website (if available) -- there's no need
+//   to explain how you intend to use them. Usually you only need one extension
+//   number. You can declare multiple options with only one extension number by
+//   putting them in a sub-message. See the Custom Options section of the docs
+//   for examples:
+//   https://developers.google.com/protocol-buffers/docs/proto#options
+//   If this turns out to be popular, a web service will be set up
+//   to automatically assign option numbers.
+
+
+message FileOptions {
+
+  // Sets the Java package where classes generated from this .proto will be
+  // placed.  By default, the proto package is used, but this is often
+  // inappropriate because proto packages do not normally start with backwards
+  // domain names.
+  optional string java_package = 1;
+
+
+  // If set, all the classes from the .proto file are wrapped in a single
+  // outer class with the given name.  This applies to both Proto1
+  // (equivalent to the old "--one_java_file" option) and Proto2 (where
+  // a .proto always translates to a single class, but you may want to
+  // explicitly choose the class name).
+  optional string java_outer_classname = 8;
+
+  // If set true, then the Java code generator will generate a separate .java
+  // file for each top-level message, enum, and service defined in the .proto
+  // file.  Thus, these types will *not* be nested inside the outer class
+  // named by java_outer_classname.  However, the outer class will still be
+  // generated to contain the file's getDescriptor() method as well as any
+  // top-level extensions defined in the file.
+  optional bool java_multiple_files = 10 [default=false];
+
+  // If set true, then the Java code generator will generate equals() and
+  // hashCode() methods for all messages defined in the .proto file.
+  // - In the full runtime, this is purely a speed optimization, as the
+  // AbstractMessage base class includes reflection-based implementations of
+  // these methods.
+  //- In the lite runtime, setting this option changes the semantics of
+  // equals() and hashCode() to more closely match those of the full runtime;
+  // the generated methods compute their results based on field values rather
+  // than object identity. (Implementations should not assume that hashcodes
+  // will be consistent across runtimes or versions of the protocol compiler.)
+  optional bool java_generate_equals_and_hash = 20 [default=false];
+
+  // If set true, then the Java2 code generator will generate code that
+  // throws an exception whenever an attempt is made to assign a non-UTF-8
+  // byte sequence to a string field.
+  // Message reflection will do the same.
+  // However, an extension field still accepts non-UTF-8 byte sequences.
+  // This option has no effect on when used with the lite runtime.
+  optional bool java_string_check_utf8 = 27 [default=false];
+
+
+  // Generated classes can be optimized for speed or code size.
+  enum OptimizeMode {
+    SPEED = 1;        // Generate complete code for parsing, serialization,
+                      // etc.
+    CODE_SIZE = 2;    // Use ReflectionOps to implement these methods.
+    LITE_RUNTIME = 3; // Generate code using MessageLite and the lite runtime.
+  }
+  optional OptimizeMode optimize_for = 9 [default=SPEED];
+
+  // Sets the Go package where structs generated from this .proto will be
+  // placed.  There is no default.
+  optional string go_package = 11;
+
+
+
+  // Should generic services be generated in each language?  "Generic" services
+  // are not specific to any particular RPC system.  They are generated by the
+  // main code generators in each language (without additional plugins).
+  // Generic services were the only kind of service generation supported by
+  // early versions of proto2.
+  //
+  // Generic services are now considered deprecated in favor of using plugins
+  // that generate code specific to your particular RPC system.  Therefore,
+  // these default to false.  Old code which depends on generic services should
+  // explicitly set them to true.
+  optional bool cc_generic_services = 16 [default=false];
+  optional bool java_generic_services = 17 [default=false];
+  optional bool py_generic_services = 18 [default=false];
+
+  // Is this file deprecated?
+  // Depending on the target platform, this can emit Deprecated annotations
+  // for everything in the file, or it will be completely ignored; in the very
+  // least, this is a formalization for deprecating files.
+  optional bool deprecated = 23 [default=false];
+
+
+  // The parser stores options it doesn't recognize here. See above.
+  repeated UninterpretedOption uninterpreted_option = 999;
+
+  // Clients can define custom options in extensions of this message. See above.
+  extensions 1000 to max;
+}
+
+message MessageOptions {
+  // Set true to use the old proto1 MessageSet wire format for extensions.
+  // This is provided for backwards-compatibility with the MessageSet wire
+  // format.  You should not use this for any other reason:  It's less
+  // efficient, has fewer features, and is more complicated.
+  //
+  // The message must be defined exactly as follows:
+  //   message Foo {
+  //     option message_set_wire_format = true;
+  //     extensions 4 to max;
+  //   }
+  // Note that the message cannot have any defined fields; MessageSets only
+  // have extensions.
+  //
+  // All extensions of your type must be singular messages; e.g. they cannot
+  // be int32s, enums, or repeated messages.
+  //
+  // Because this is an option, the above two restrictions are not enforced by
+  // the protocol compiler.
+  optional bool message_set_wire_format = 1 [default=false];
+
+  // Disables the generation of the standard "descriptor()" accessor, which can
+  // conflict with a field of the same name.  This is meant to make migration
+  // from proto1 easier; new code should avoid fields named "descriptor".
+  optional bool no_standard_descriptor_accessor = 2 [default=false];
+
+  // Is this message deprecated?
+  // Depending on the target platform, this can emit Deprecated annotations
+  // for the message, or it will be completely ignored; in the very least,
+  // this is a formalization for deprecating messages.
+  optional bool deprecated = 3 [default=false];
+
+  // The parser stores options it doesn't recognize here. See above.
+  repeated UninterpretedOption uninterpreted_option = 999;
+
+  // Clients can define custom options in extensions of this message. See above.
+  extensions 1000 to max;
+}
+
+message FieldOptions {
+  // The ctype option instructs the C++ code generator to use a different
+  // representation of the field than it normally would.  See the specific
+  // options below.  This option is not yet implemented in the open source
+  // release -- sorry, we'll try to include it in a future version!
+  optional CType ctype = 1 [default = STRING];
+  enum CType {
+    // Default mode.
+    STRING = 0;
+
+    CORD = 1;
+
+    STRING_PIECE = 2;
+  }
+  // The packed option can be enabled for repeated primitive fields to enable
+  // a more efficient representation on the wire. Rather than repeatedly
+  // writing the tag and type for each element, the entire array is encoded as
+  // a single length-delimited blob.
+  optional bool packed = 2;
+
+
+
+  // Should this field be parsed lazily?  Lazy applies only to message-type
+  // fields.  It means that when the outer message is initially parsed, the
+  // inner message's contents will not be parsed but instead stored in encoded
+  // form.  The inner message will actually be parsed when it is first accessed.
+  //
+  // This is only a hint.  Implementations are free to choose whether to use
+  // eager or lazy parsing regardless of the value of this option.  However,
+  // setting this option true suggests that the protocol author believes that
+  // using lazy parsing on this field is worth the additional bookkeeping
+  // overhead typically needed to implement it.
+  //
+  // This option does not affect the public interface of any generated code;
+  // all method signatures remain the same.  Furthermore, thread-safety of the
+  // interface is not affected by this option; const methods remain safe to
+  // call from multiple threads concurrently, while non-const methods continue
+  // to require exclusive access.
+  //
+  //
+  // Note that implementations may choose not to check required fields within
+  // a lazy sub-message.  That is, calling IsInitialized() on the outher message
+  // may return true even if the inner message has missing required fields.
+  // This is necessary because otherwise the inner message would have to be
+  // parsed in order to perform the check, defeating the purpose of lazy
+  // parsing.  An implementation which chooses not to check required fields
+  // must be consistent about it.  That is, for any particular sub-message, the
+  // implementation must either *always* check its required fields, or *never*
+  // check its required fields, regardless of whether or not the message has
+  // been parsed.
+  optional bool lazy = 5 [default=false];
+
+  // Is this field deprecated?
+  // Depending on the target platform, this can emit Deprecated annotations
+  // for accessors, or it will be completely ignored; in the very least, this
+  // is a formalization for deprecating fields.
+  optional bool deprecated = 3 [default=false];
+
+  // EXPERIMENTAL.  DO NOT USE.
+  // For "map" fields, the name of the field in the enclosed type that
+  // is the key for this map.  For example, suppose we have:
+  //   message Item {
+  //     required string name = 1;
+  //     required string value = 2;
+  //   }
+  //   message Config {
+  //     repeated Item items = 1 [experimental_map_key="name"];
+  //   }
+  // In this situation, the map key for Item will be set to "name".
+  // TODO: Fully-implement this, then remove the "experimental_" prefix.
+  optional string experimental_map_key = 9;
+
+  // For Google-internal migration only. Do not use.
+  optional bool weak = 10 [default=false];
+
+
+
+  // The parser stores options it doesn't recognize here. See above.
+  repeated UninterpretedOption uninterpreted_option = 999;
+
+  // Clients can define custom options in extensions of this message. See above.
+  extensions 1000 to max;
+}
+
+message EnumOptions {
+
+  // Set this option to true to allow mapping different tag names to the same
+  // value.
+  optional bool allow_alias = 2;
+
+  // Is this enum deprecated?
+  // Depending on the target platform, this can emit Deprecated annotations
+  // for the enum, or it will be completely ignored; in the very least, this
+  // is a formalization for deprecating enums.
+  optional bool deprecated = 3 [default=false];
+
+  // The parser stores options it doesn't recognize here. See above.
+  repeated UninterpretedOption uninterpreted_option = 999;
+
+  // Clients can define custom options in extensions of this message. See above.
+  extensions 1000 to max;
+}
+
+message EnumValueOptions {
+  // Is this enum value deprecated?
+  // Depending on the target platform, this can emit Deprecated annotations
+  // for the enum value, or it will be completely ignored; in the very least,
+  // this is a formalization for deprecating enum values.
+  optional bool deprecated = 1 [default=false];
+
+  // The parser stores options it doesn't recognize here. See above.
+  repeated UninterpretedOption uninterpreted_option = 999;
+
+  // Clients can define custom options in extensions of this message. See above.
+  extensions 1000 to max;
+}
+
+message ServiceOptions {
+
+  // Note:  Field numbers 1 through 32 are reserved for Google's internal RPC
+  //   framework.  We apologize for hoarding these numbers to ourselves, but
+  //   we were already using them long before we decided to release Protocol
+  //   Buffers.
+
+  // Is this service deprecated?
+  // Depending on the target platform, this can emit Deprecated annotations
+  // for the service, or it will be completely ignored; in the very least,
+  // this is a formalization for deprecating services.
+  optional bool deprecated = 33 [default=false];
+
+  // The parser stores options it doesn't recognize here. See above.
+  repeated UninterpretedOption uninterpreted_option = 999;
+
+  // Clients can define custom options in extensions of this message. See above.
+  extensions 1000 to max;
+}
+
+message MethodOptions {
+
+  // Note:  Field numbers 1 through 32 are reserved for Google's internal RPC
+  //   framework.  We apologize for hoarding these numbers to ourselves, but
+  //   we were already using them long before we decided to release Protocol
+  //   Buffers.
+
+  // Is this method deprecated?
+  // Depending on the target platform, this can emit Deprecated annotations
+  // for the method, or it will be completely ignored; in the very least,
+  // this is a formalization for deprecating methods.
+  optional bool deprecated = 33 [default=false];
+
+  // The parser stores options it doesn't recognize here. See above.
+  repeated UninterpretedOption uninterpreted_option = 999;
+
+  // Clients can define custom options in extensions of this message. See above.
+  extensions 1000 to max;
+}
+
+
+// A message representing a option the parser does not recognize. This only
+// appears in options protos created by the compiler::Parser class.
+// DescriptorPool resolves these when building Descriptor objects. Therefore,
+// options protos in descriptor objects (e.g. returned by Descriptor::options(),
+// or produced by Descriptor::CopyTo()) will never have UninterpretedOptions
+// in them.
+message UninterpretedOption {
+  // The name of the uninterpreted option.  Each string represents a segment in
+  // a dot-separated name.  is_extension is true iff a segment represents an
+  // extension (denoted with parentheses in options specs in .proto files).
+  // E.g.,{ ["foo", false], ["bar.baz", true], ["qux", false] } represents
+  // "foo.(bar.baz).qux".
+  message NamePart {
+    required string name_part = 1;
+    required bool is_extension = 2;
+  }
+  repeated NamePart name = 2;
+
+  // The value of the uninterpreted option, in whatever type the tokenizer
+  // identified it as during parsing. Exactly one of these should be set.
+  optional string identifier_value = 3;
+  optional uint64 positive_int_value = 4;
+  optional int64 negative_int_value = 5;
+  optional double double_value = 6;
+  optional bytes string_value = 7;
+  optional string aggregate_value = 8;
+}
+
+// ===================================================================
+// Optional source code info
+
+// Encapsulates information about the original source file from which a
+// FileDescriptorProto was generated.
+message SourceCodeInfo {
+  // A Location identifies a piece of source code in a .proto file which
+  // corresponds to a particular definition.  This information is intended
+  // to be useful to IDEs, code indexers, documentation generators, and similar
+  // tools.
+  //
+  // For example, say we have a file like:
+  //   message Foo {
+  //     optional string foo = 1;
+  //   }
+  // Let's look at just the field definition:
+  //   optional string foo = 1;
+  //   ^       ^^     ^^  ^  ^^^
+  //   a       bc     de  f  ghi
+  // We have the following locations:
+  //   span   path               represents
+  //   [a,i)  [ 4, 0, 2, 0 ]     The whole field definition.
+  //   [a,b)  [ 4, 0, 2, 0, 4 ]  The label (optional).
+  //   [c,d)  [ 4, 0, 2, 0, 5 ]  The type (string).
+  //   [e,f)  [ 4, 0, 2, 0, 1 ]  The name (foo).
+  //   [g,h)  [ 4, 0, 2, 0, 3 ]  The number (1).
+  //
+  // Notes:
+  // - A location may refer to a repeated field itself (i.e. not to any
+  //   particular index within it).  This is used whenever a set of elements are
+  //   logically enclosed in a single code segment.  For example, an entire
+  //   extend block (possibly containing multiple extension definitions) will
+  //   have an outer location whose path refers to the "extensions" repeated
+  //   field without an index.
+  // - Multiple locations may have the same path.  This happens when a single
+  //   logical declaration is spread out across multiple places.  The most
+  //   obvious example is the "extend" block again -- there may be multiple
+  //   extend blocks in the same scope, each of which will have the same path.
+  // - A location's span is not always a subset of its parent's span.  For
+  //   example, the "extendee" of an extension declaration appears at the
+  //   beginning of the "extend" block and is shared by all extensions within
+  //   the block.
+  // - Just because a location's span is a subset of some other location's span
+  //   does not mean that it is a descendent.  For example, a "group" defines
+  //   both a type and a field in a single declaration.  Thus, the locations
+  //   corresponding to the type and field and their components will overlap.
+  // - Code which tries to interpret locations should probably be designed to
+  //   ignore those that it doesn't understand, as more types of locations could
+  //   be recorded in the future.
+  repeated Location location = 1;
+  message Location {
+    // Identifies which part of the FileDescriptorProto was defined at this
+    // location.
+    //
+    // Each element is a field number or an index.  They form a path from
+    // the root FileDescriptorProto to the place where the definition.  For
+    // example, this path:
+    //   [ 4, 3, 2, 7, 1 ]
+    // refers to:
+    //   file.message_type(3)  // 4, 3
+    //       .field(7)         // 2, 7
+    //       .name()           // 1
+    // This is because FileDescriptorProto.message_type has field number 4:
+    //   repeated DescriptorProto message_type = 4;
+    // and DescriptorProto.field has field number 2:
+    //   repeated FieldDescriptorProto field = 2;
+    // and FieldDescriptorProto.name has field number 1:
+    //   optional string name = 1;
+    //
+    // Thus, the above path gives the location of a field name.  If we removed
+    // the last element:
+    //   [ 4, 3, 2, 7 ]
+    // this path refers to the whole field declaration (from the beginning
+    // of the label to the terminating semicolon).
+    repeated int32 path = 1 [packed=true];
+
+    // Always has exactly three or four elements: start line, start column,
+    // end line (optional, otherwise assumed same as start line), end column.
+    // These are packed into a single field for efficiency.  Note that line
+    // and column numbers are zero-based -- typically you will want to add
+    // 1 to each before displaying to a user.
+    repeated int32 span = 2 [packed=true];
+
+    // If this SourceCodeInfo represents a complete declaration, these are any
+    // comments appearing before and after the declaration which appear to be
+    // attached to the declaration.
+    //
+    // A series of line comments appearing on consecutive lines, with no other
+    // tokens appearing on those lines, will be treated as a single comment.
+    //
+    // Only the comment content is provided; comment markers (e.g. //) are
+    // stripped out.  For block comments, leading whitespace and an asterisk
+    // will be stripped from the beginning of each line other than the first.
+    // Newlines are included in the output.
+    //
+    // Examples:
+    //
+    //   optional int32 foo = 1;  // Comment attached to foo.
+    //   // Comment attached to bar.
+    //   optional int32 bar = 2;
+    //
+    //   optional string baz = 3;
+    //   // Comment attached to baz.
+    //   // Another line attached to baz.
+    //
+    //   // Comment attached to qux.
+    //   //
+    //   // Another line attached to qux.
+    //   optional double qux = 4;
+    //
+    //   optional string corge = 5;
+    //   /* Block comment attached
+    //    * to corge.  Leading asterisks
+    //    * will be removed. */
+    //   /* Block comment attached to
+    //    * grault. */
+    //   optional int32 grault = 6;
+    optional string leading_comments = 3;
+    optional string trailing_comments = 4;
+  }
+}
diff --git a/pst/src/main/proto/org/apache/wave/pst/protobuf/extensions.proto b/pst/src/main/proto/org/apache/wave/pst/protobuf/extensions.proto
new file mode 100644
index 0000000..d9b8e9a
--- /dev/null
+++ b/pst/src/main/proto/org/apache/wave/pst/protobuf/extensions.proto
@@ -0,0 +1,34 @@
+// 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.
+
+syntax = "proto2";
+
+import "google/protobuf/descriptor.proto";
+
+option java_package = "org.apache.wave.pst.protobuf";
+option java_outer_classname = "Extensions";
+
+extend google.protobuf.FieldOptions {
+  // Annotates an int64, noting that only the lower 52 bits are important.
+  // This allows languages without 64-bit primitives (like JavaScript) to use
+  // other primtive types instead.
+  //
+  // Annotation ids are apparently meant to be globally unique.  Not sure why,
+  // given that proto names and field ids do not have to be globally unique.
+  // If it becomes an issue, get a unique number from the number distributor.
+  optional bool int52 = 50000 [default = false];
+}
diff --git a/scripts/vagrant/setup-fedora.sh b/scripts/vagrant/setup-fedora.sh
index c15a83f..7c1123f 100644
--- a/scripts/vagrant/setup-fedora.sh
+++ b/scripts/vagrant/setup-fedora.sh
@@ -32,6 +32,6 @@
 WAVE_VERSION=`sed "s/[\\t ]*=[\\t ]*/=/g" wave/config/wave.conf | grep ^version= | cut -f2 -d=`
 
 cd distributions
-sudo tar -C /opt/apache/wave -xvf apache-wave-bin-$WAVE_VERSION.tar
+sudo tar -C /opt/apache/wave -zxvf apache-wave-bin-$WAVE_VERSION.tar
 cd ..
 cp scripts/vagrant/application.conf /opt/apache/wave/apache-wave/config/application.conf
\ No newline at end of file
diff --git a/scripts/vagrant/setup-ubuntu.sh b/scripts/vagrant/setup-ubuntu.sh
index 907576a..41eae45 100644
--- a/scripts/vagrant/setup-ubuntu.sh
+++ b/scripts/vagrant/setup-ubuntu.sh
@@ -39,6 +39,6 @@
 WAVE_VERSION=`sed "s/[\\t ]*=[\\t ]*/=/g" wave/config/wave.conf | grep ^version= | cut -f2 -d=`
 
 cd distributions
-sudo tar -C /opt/apache/wave -xvf apache-wave-bin-$WAVE_VERSION.tar
+sudo tar -C /opt/apache/wave -zxvf apache-wave-bin-$WAVE_VERSION.tar.gz
 cd ..
 cp scripts/vagrant/application.conf /opt/apache/wave/apache-wave/config/application.conf
diff --git a/settings.gradle b/settings.gradle
index cf6215d..049f73d 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1 +1 @@
-include "wave", "pst", "wave-proto"
\ No newline at end of file
+include "wave", "pst"
\ No newline at end of file
diff --git a/sonar-project.properties b/sonar-project.properties
new file mode 100644
index 0000000..b8e0e6a
--- /dev/null
+++ b/sonar-project.properties
@@ -0,0 +1,22 @@
+# 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.
+
+sonar.projectKey=org.apache.wave:wave
+sonar.projectName=Apache Wave
+sonar.projectVersion=1.0
+sonar.sources=./pst/src/main/java, ./wave/src/main/java
+sonar.tests=./wave/src/test/java
\ No newline at end of file
diff --git a/wave/build.gradle b/wave/build.gradle
index 9d57241..fcb199a 100644
--- a/wave/build.gradle
+++ b/wave/build.gradle
@@ -1,14 +1,95 @@
+// 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.
+
+//=============================================================================
 // Plugins
+//=============================================================================
 plugins {
     id 'java'
     id 'application'
 }
+apply plugin: 'com.google.protobuf'
 
+//=============================================================================
+// Project Level Settings
+//=============================================================================
 /* Meta Data Info */
 def title = 'Apache Wave Server'
 def vendor = 'The Apache Software Foundation'
+version = 0.4
+mainClassName = "org.waveprotocol.box.server.ServerMain"
+applicationDefaultJvmArgs = [
+        "-Xmx1024M",
+        "-Dorg.eclipse.jetty.LEVEL=DEBUG",
+        "-Djava.security.auth.login.config=config/jaas.config"
+]
+sourceCompatibility = 1.7
+targetCompatibility = 1.7
+compileJava {
+    options.incremental = true
+}
 
-/* 3rd Part Repositories */
+//=============================================================================
+// Extra Configurations (used for separation of dependencies)
+//=============================================================================
+configurations {
+    generateMessages
+    generateGXP
+    gwt
+}
+
+//=============================================================================
+// Source's
+//=============================================================================
+sourceSets {
+    main {
+        java {
+            srcDirs = [
+                    'src/main/java',
+                    'generated/main/java',
+                    'generated/proto/java'
+            ]
+        }
+        resources {
+            srcDir 'src/main/resources'
+        }
+    }
+    proto {
+        proto {
+            srcDir 'src/proto/proto'
+            include '**/*.protodevel'
+        }
+    }
+    test {
+        java {
+            srcDir 'src/test/java'
+        }
+        resources {
+            srcDir 'src/test/resources'
+        }
+    }
+}
+
+//=============================================================================
+// Dependencies
+// Note: next to each dependency is a review stamp [last review, next review].
+//       If a dependency is past its review date pls create a jira issue.
+//       https://issues.apache.org/jira/browse/WAVE
+//=============================================================================
 repositories {
     mavenCentral()
     maven {
@@ -20,183 +101,143 @@
     maven {
         url 'https://oss.sonatype.org/content/repositories/google-snapshots/'
     }
-
 }
 
-mainClassName = "org.waveprotocol.box.server.ServerMain"
-applicationDefaultJvmArgs = [
-        "-Xmx1024M",
-        "-Dorg.eclipse.jetty.LEVEL=DEBUG",
-        "-Djava.security.auth.login.config=config/jaas.config"
-]
-
-configurations {
-    compile {
-        description = 'compile classpath'
-    }
-    generateGXP {
-        description = 'classpath for generating GXP files'
-    }
-    gwt {
-        description = 'classpath for compiling the gwt sources'
-    }
-}
-
-sonarqube {
-    properties {
-        property "sonar.exclusions", "src/generated/**/*.java"
-    }
-}
-
-/* Project Dependencies */
 dependencies {
-    // code-gen
-    compile(
-            [group: "org.antlr", name: "antlr", version: "3.2"],
-            //TODO(wisebaldone) renable when gwt using jetty 9
-            //[group: "com.google.gwt", name: "gwt-dev", version: "2.8.0"],
-            //[group: "com.google.gwt", name: "gwt-user", version: "2.8.0"],
-            //[group: "com.google.gwt", name: "gwt-codeserver", version: "2.8.0"],
-            [group: "org.apache.velocity", name: "velocity", version: "1.6.3"]
-    )
-
     gwt(
-            [group: "org.antlr", name: "antlr", version: "3.2"],
-            [group: "org.apache.velocity", name: "velocity", version: "1.6.3"],
-            [group: "javax.validation", name: "validation-api", version: "1.1.0.Final"],
-            [group: "javax.validation", name: "validation-api", version: "1.1.0.Final", classifier: "sources"]
+            [group: "javax.validation", name: "validation-api", version: "1.1.0.Final"],                        // [?, ?]
+            [group: "javax.validation", name: "validation-api", version: "1.1.0.Final", classifier: "sources"]  // [?, ?]
     )
-    // compile
     compile (
-            [group: "aopalliance", name: "aopalliance", version: "1.0"],
-            [group: "org.bouncycastle", name: "bcprov-jdk16", version: "1.45"],
-            [group: "commons-fileupload", name: "commons-fileupload", version: "1.2.2"],
-            [group: "commons-cli", name: "commons-cli", version: "1.2"],
-            [group: "commons-codec", name: "commons-codec", version: "1.4"],
-            [group: "commons-io", name: "commons-io", version: "2.4"],
-            [group: "commons-collections", name: "commons-collections", version: "3.2.1"],
-            [group: "commons-configuration", name: "commons-configuration", version: "1.6"],
-            [group: "commons-httpclient", name: "commons-httpclient", version: "3.1"],
-            [group: "commons-lang", name: "commons-lang", version: "2.5"],
-            [group: "commons-logging", name: "commons-logging-api", version: "1.1"],
-            [group: "commons-logging", name: "commons-logging", version: "1.1.1"],
-            [group: "dom4j", name: "dom4j", version: "1.6.1"],
-            [group: "com.google.code.gson", name: "gson", version: "2.2.4"],
-            [group: "com.google.guava", name: "guava", version: "15.0"],
-            [group: "com.google.guava", name: "guava-gwt", version: "15.0"],
-            [group: "com.google.inject.extensions", name: "guice-assistedinject", version: "3.0"],
-            [group: "com.google.inject.extensions", name: "guice-servlet", version: "3.0"],
-            [group: "com.google.inject", name: "guice", version: "3.0"],
-            [group: "javax.inject", name: "javax.inject", version: "1"],
-            [group: "com.google.gxp", name: "google-gxp", version: "0.2.4-beta"],
-            [group: "javax.jdo", name: "jdo2-api", version: "2.1"],
-            [group: "org.jdom", name: "jdom", version: "1.1.3"],
-            [group: "com.google.code.findbugs", name: "jsr305", version: "2.0.1"],
-            [group: "jline", name: "jline", version: "0.9.94"],
-            [group: "joda-time", name: "joda-time", version: "1.6"],
-            [group: "org.apache.lucene", name: "lucene-core", version: "3.5.0"],
-            [group: "org.mongodb", name: "mongo-java-driver", version: "2.11.2"],
-            [group: "net.oauth.core", name: "oauth-provider", version: "20100527"],
-            [group: "net.oauth.core", name: "oauth", version: "20100527"],
-            [group: "net.oauth.core", name: "oauth-consumer", version: "20100527"],
-            [group: "com.google.protobuf", name: "protobuf-java", version: "2.5.0"],
-            [group: "com.googlecode.protobuf-java-format", name: "protobuf-java-format", version: "1.2"],
-            [group: "org.igniterealtime", name: "tinder", version: "1.2.1"],
-            [group: "xpp3", name: "xpp3", version: "1.1.4c"],
-            [group: "xpp3", name: "xpp3_xpath", version: "1.1.4c"],
-            [group: "org.gnu.inet", name: "libidn", version: "1.15"],
-            [group: "cc.kune", name: "gwt-initials-avatars-shared", version: "1.0-SNAPSHOT"],
-            [group: "cc.kune", name: "gwt-initials-avatars-server", version: "1.0-SNAPSHOT"],
-            [group: "com.typesafe", name: "config", version: "1.2.1"],
-            [group: "xerces", name: "xerces", version: "2.4.0"],
-            [group: "org.slf4j", name: "slf4j-api", version: "1.6.1"],
-            [group: "org.slf4j", name: "slf4j-simple", version: "1.6.1"],
-            [group: "org.eclipse.jetty", name: "jetty-annotations", version: "9.1.1.v20140108"],
-            [group: "org.eclipse.jetty", name: "jetty-client", version: "9.1.1.v20140108"],
-            [group: "org.eclipse.jetty", name: "jetty-continuation", version: "9.1.1.v20140108"],
-            [group: "org.eclipse.jetty", name: "jetty-http", version: "9.1.1.v20140108"],
-            [group: "org.eclipse.jetty", name: "jetty-io", version: "9.1.1.v20140108"],
-            [group: "org.eclipse.jetty", name: "jetty-proxy", version: "9.1.1.v20140108"],
-            [group: "org.eclipse.jetty", name: "jetty-security", version: "9.1.1.v20140108"],
-            [group: "org.eclipse.jetty", name: "jetty-server", version: "9.1.1.v20140108"],
-            [group: "org.eclipse.jetty", name: "jetty-servlet", version: "9.1.1.v20140108"],
-            [group: "org.eclipse.jetty", name: "jetty-servlets", version: "9.1.1.v20140108"],
-            [group: "org.eclipse.jetty", name: "jetty-util", version: "9.1.1.v20140108"],
-            [group: "org.eclipse.jetty", name: "jetty-webapp", version: "9.1.1.v20140108"],
-            [group: "org.eclipse.jetty", name: "jetty-xml", version: "9.1.1.v20140108"],
-            [group: "org.eclipse.jetty.websocket", name: "websocket-api", version: "9.1.1.v20140108"],
-            [group: "org.eclipse.jetty.websocket", name: "websocket-client", version: "9.1.1.v20140108"],
-            [group: "org.eclipse.jetty.websocket", name: "websocket-common", version: "9.1.1.v20140108"],
-            [group: "org.eclipse.jetty.websocket", name: "websocket-server", version: "9.1.1.v20140108"],
-            [group: "org.eclipse.jetty.websocket", name: "websocket-servlet", version: "9.1.1.v20140108"],
+            [group: "aopalliance", name: "aopalliance", version: "1.0"],                                        // [?, ?]
+            [group: "cc.kune", name: "gwt-initials-avatars-shared", version: "1.0-SNAPSHOT"],                   // [?, ?]
+            [group: "cc.kune", name: "gwt-initials-avatars-server", version: "1.0-SNAPSHOT"],                   // [?, ?]
+            [group: "commons-fileupload", name: "commons-fileupload", version: "1.2.2"],                        // [?, ?]
+            [group: "commons-cli", name: "commons-cli", version: "1.2"],                                        // [?, ?]
+            [group: "commons-codec", name: "commons-codec", version: "1.4"],                                    // [?, ?]
+            [group: "commons-io", name: "commons-io", version: "2.4"],                                          // [?, ?]
+            [group: "commons-collections", name: "commons-collections", version: "3.2.1"],                      // [?, ?]
+            [group: "commons-configuration", name: "commons-configuration", version: "1.6"],                    // [?, ?]
+            [group: "commons-httpclient", name: "commons-httpclient", version: "3.1"],                          // [?, ?]
+            [group: "commons-lang", name: "commons-lang", version: "2.5"],                                      // [?, ?]
+            [group: "commons-logging", name: "commons-logging-api", version: "1.1"],                            // [?, ?]
+            [group: "commons-logging", name: "commons-logging", version: "1.1.1"],                              // [?, ?]
+            [group: "com.google.code.findbugs", name: "jsr305", version: "2.0.1"],                              // [?, ?]
+            [group: "com.google.code.gson", name: "gson", version: "2.2.4"],                                    // [?, ?]
+            [group: "com.google.guava", name: "guava", version: "15.0"],                                        // [?, ?]
+            [group: "com.google.guava", name: "guava-gwt", version: "15.0"],                                    // [?, ?]
+            [group: "com.google.gxp", name: "google-gxp", version: "0.2.4-beta"],                               // [?, ?]
+            [group: "com.google.inject.extensions", name: "guice-assistedinject", version: "3.0"],              // [?, ?]
+            [group: "com.google.inject.extensions", name: "guice-servlet", version: "3.0"],                     // [?, ?]
+            [group: "com.google.inject", name: "guice", version: "3.0"],                                        // [?, ?]
+            [group: "com.google.protobuf", name: "protobuf-java", version: "2.6.1"],                            // [?, ?]
+            [group: "com.googlecode.protobuf-java-format", name: "protobuf-java-format", version: "1.2"],       // [?, ?]
+            [group: "com.typesafe", name: "config", version: "1.2.1"],                                          // [?, ?]
+            [group: "dom4j", name: "dom4j", version: "1.6.1"],                                                  // [?, ?]
+            [group: "eu.infomas", name: "annotation-detector", version: "3.0.0"],                               // [?, ?]
+            [group: "org.antlr", name: "antlr", version: "3.2"],                                                // [?, ?]
+            [group: "org.apache.velocity", name: "velocity", version: "1.6.3"],                                 // [?, ?]
+            [group: "org.apache.lucene", name: "lucene-core", version: "3.5.0"],                                // [?, ?]
+            [group: "org.atmosphere", name: "atmosphere-guice", version: "0.8.3"],                              // [?, ?]
+            [group: "org.atmosphere", name: "atmosphere-runtime", version: "2.1.0"],                            // [?, ?]
+            [group: "org.bouncycastle", name: "bcprov-jdk16", version: "1.45"],                                 // [?, ?]
+            [group: "org.eclipse.jetty", name: "jetty-annotations", version: "9.1.1.v20140108"],                // [?, ?]
+            [group: "org.eclipse.jetty", name: "jetty-client", version: "9.1.1.v20140108"],                     // [?, ?]
+            [group: "org.eclipse.jetty", name: "jetty-continuation", version: "9.1.1.v20140108"],               // [?, ?]
+            [group: "org.eclipse.jetty", name: "jetty-http", version: "9.1.1.v20140108"],                       // [?, ?]
+            [group: "org.eclipse.jetty", name: "jetty-io", version: "9.1.1.v20140108"],                         // [?, ?]
+            [group: "org.eclipse.jetty", name: "jetty-proxy", version: "9.1.1.v20140108"],                      // [?, ?]
+            [group: "org.eclipse.jetty", name: "jetty-security", version: "9.1.1.v20140108"],                   // [?, ?]
+            [group: "org.eclipse.jetty", name: "jetty-server", version: "9.1.1.v20140108"],                     // [?, ?]
+            [group: "org.eclipse.jetty", name: "jetty-servlet", version: "9.1.1.v20140108"],                    // [?, ?]
+            [group: "org.eclipse.jetty", name: "jetty-servlets", version: "9.1.1.v20140108"],                   // [?, ?]
+            [group: "org.eclipse.jetty", name: "jetty-util", version: "9.1.1.v20140108"],                       // [?, ?]
+            [group: "org.eclipse.jetty", name: "jetty-webapp", version: "9.1.1.v20140108"],                     // [?, ?]
+            [group: "org.eclipse.jetty", name: "jetty-xml", version: "9.1.1.v20140108"],                        // [?, ?]
+            [group: "org.eclipse.jetty.websocket", name: "websocket-api", version: "9.1.1.v20140108"],          // [?, ?]
+            [group: "org.eclipse.jetty.websocket", name: "websocket-client", version: "9.1.1.v20140108"],       // [?, ?]
+            [group: "org.eclipse.jetty.websocket", name: "websocket-common", version: "9.1.1.v20140108"],       // [?, ?]
+            [group: "org.eclipse.jetty.websocket", name: "websocket-server", version: "9.1.1.v20140108"],       // [?, ?]
+            [group: "org.eclipse.jetty.websocket", name: "websocket-servlet", version: "9.1.1.v20140108"],      // [?, ?]
+            [group: "org.gnu.inet", name: "libidn", version: "1.15"],                                           // [?, ?]
+            [group: "org.igniterealtime", name: "tinder", version: "1.2.3"],                                    // [1/2016, 6/2016]
+            [group: "org.igniterealtime.whack", name: "core", version: "2.0.0"],                                // [1/2016, 6/2016]
+            [group: "org.jdom", name: "jdom", version: "1.1.3"],                                                // [?, ?]
+            [group: "org.mongodb", name: "mongo-java-driver", version: "2.11.2"],                               // [?, ?]
+            [group: "org.slf4j", name: "slf4j-api", version: "1.6.1"],                                          // [?, ?]
+            [group: "org.slf4j", name: "slf4j-simple", version: "1.6.1"],                                       // [?, ?]
+            [group: "javax.inject", name: "javax.inject", version: "1"],                                        // [?, ?]
+            [group: "javax.servlet", name: "javax.servlet-api", version: "3.1.0"],                              // [?, ?]
+            [group: "javax.jdo", name: "jdo2-api", version: "2.1"],                                             // [?, ?]
+            [group: "jline", name: "jline", version: "0.9.94"],                                                 // [?, ?]
+            [group: "joda-time", name: "joda-time", version: "1.6"],                                            // [?, ?]
+            [group: "net.oauth.core", name: "oauth-provider", version: "20100527"],                             // [?, ?]
+            [group: "net.oauth.core", name: "oauth", version: "20100527"],                                      // [?, ?]
+            [group: "net.oauth.core", name: "oauth-consumer", version: "20100527"],                             // [?, ?]
+            [group: "xerces", name: "xerces", version: "2.4.0"],                                                // [?, ?]
+            [group: "xpp3", name: "xpp3", version: "1.1.4c"],                                                   // [?, ?]
+            [group: "xpp3", name: "xpp3_xpath", version: "1.1.4c"],                                             // [?, ?]
             //TODO: Following are included due to tests being in the main src directory
-            [group: "org.mockito", name: "mockito-all", version: "1.9.5"],
-            [group: "org.hamcrest", name: "hamcrest-all", version: "1.3"]
+            [group: "org.mockito", name: "mockito-all", version: "1.9.5"],                                      // [?, ?]
+            [group: "org.hamcrest", name: "hamcrest-all", version: "1.3"]                                       // [?, ?]
 
     )
-    compile fileTree(dir: 'dependencies/compile', include: "**/*.jar")
-    compile fileTree(dir: '../wave-proto/build/libs', include: "**/*.jar")
-
+    compile fileTree(dir: 'dependencies/compile', include: "**/*.jar")                                          // [?, ?]
+    compile fileTree(dir: '../pst/build/libs', include: '**/*.jar')                                             // [?, ?]
     generateGXP (
-        [group: "com.google.gxp", name: "google-gxp", version: "0.2.4-beta"]
+        [group: "com.google.gxp", name: "google-gxp", version: "0.2.4-beta"]                                    // [?, ?]
     )
-
-    // tests
+    protoCompile (
+        [group: "com.google.protobuf", name: "protobuf-java", version: "2.6.1"],                                // [?, ?]
+        fileTree(dir: '../pst/build/libs', include: '**/*.jar')                                                 // [?, ?]
+    )
+    generateMessages (
+         fileTree(dir: '../pst/build/libs', include: '**/*.jar')                                                // [?, ?]
+    )
     testCompile(
-            [group: 'junit', name: 'junit', version: '4.11'],
-            [group: "org.ow2.asm", name: "asm", version: "5.0.4"],
-            [group: "cglib", name: "cglib", version: "2.2"],
-            [group: "com.novocode", name: "junit-interface", version: "0.11"],
-            [group: "emma", name: "emma", version: "2.0.5312"],
-            [group: "emma", name: "emma_ant", version: "2.1.5320"],
-            [group: "org.hamcrest", name: "hamcrest-all", version: "1.3"],
-            [group: "org.jmock", name: "jmock-junit3", version: "2.6.0"],
-            [group: "org.jmock", name: "jmock", version: "2.6.0"],
-            [group: "org.mockito", name: "mockito-all", version: "1.9.5"]
+            [group: 'junit', name: 'junit', version: '4.12'],                                                   // [?, ?]
+            [group: "org.ow2.asm", name: "asm", version: "5.0.4"],                                              // [?, ?]
+            [group: "cglib", name: "cglib", version: "2.2"],                                                    // [?, ?]
+            [group: "com.novocode", name: "junit-interface", version: "0.11"],                                  // [?, ?]
+            [group: "emma", name: "emma", version: "2.0.5312"],                                                 // [?, ?]
+            [group: "emma", name: "emma_ant", version: "2.1.5320"],                                             // [?, ?]
+            [group: "org.hamcrest", name: "hamcrest-all", version: "1.3"],                                      // [?, ?]
+            [group: "org.jmock", name: "jmock-junit3", version: "2.6.0"],                                       // [?, ?]
+            [group: "org.jmock", name: "jmock", version: "2.6.0"],                                              // [?, ?]
+            [group: "org.mockito", name: "mockito-all", version: "1.9.5"]                                       // [?, ?]
     )
 }
 
-/* Source Sets */
-sourceSets {
-    main {
-        java {
-            srcDirs = [
-                    'src/main/java',
-                    'src/generated/gxp',
-                    'src/generated/messages'
-            ]
-        }
-        resources {
-            srcDir 'src/main/resources'
-        }
+//=============================================================================
+// Protobuf Config
+//=============================================================================
+protobuf {
+    protoc {
+        artifact = 'com.google.protobuf:protoc:2.6.1'
     }
-
-    test {
-        java {
-            srcDir 'src/test/java'
-        }
-        resources {
-            srcDir 'src/test/resources'
-        }
-    }
+    generatedFilesBaseDir = "$projectDir/generated"
 }
 
+//=============================================================================
+// Task - Generation Tasks (External Compilers)
+//=============================================================================
+
 task generateMessages {
     description = 'Generates source files from Antlr String types and protobuf'
-    FileTree inputFiles = fileTree(dir: '../wave-proto/build/classes/main/', include: '**/*.class')
+    FileTree inputFiles = fileTree(dir: 'generated/src/main/java', include: '**/*.java')
     inputs.property "files", inputFiles
-    File outputDir = file("src/generated/messages")
+    File outputDir = file("generated/main/java")
     outputs.dir outputDir
     doLast {
         List<String> proto_classes = [
-                "../wave-proto/build/classes/main/org/waveprotocol/box/common/comms/WaveClientRpc.class",
-                "../wave-proto/build/classes/main/org/waveprotocol/box/search/SearchProto.class",
-                "../wave-proto/build/classes/main/org/waveprotocol/box/profile/ProfilesProto.class",
-                "../wave-proto/build/classes/main/org/waveprotocol/box/server/rpc/Rpc.class",
-                "../wave-proto/build/classes/main/org/waveprotocol/box/attachment/AttachmentProto.class",
-                "../wave-proto/build/classes/main/org/waveprotocol/wave/federation/Proto.class",
-                "../wave-proto/build/classes/main/org/waveprotocol/wave/concurrencycontrol/ClientServer.class",
-                "../wave-proto/build/classes/main/org/waveprotocol/wave/diff/Diff.class"
+                "build/classes/proto/org/waveprotocol/box/common/comms/WaveClientRpc.class",
+                "build/classes/proto/org/waveprotocol/box/search/SearchProto.class",
+                "build/classes/proto/org/waveprotocol/box/profile/ProfilesProto.class",
+                "build/classes/proto/org/waveprotocol/box/server/rpc/Rpc.class",
+                "build/classes/proto/org/waveprotocol/box/attachment/AttachmentProto.class",
+                "build/classes/proto/org/waveprotocol/wave/federation/Proto.class",
+                "build/classes/proto/org/waveprotocol/wave/concurrencycontrol/ClientServer.class",
+                "build/classes/proto/org/waveprotocol/wave/diff/Diff.class"
         ]
         List<String> templates = [
                 "src/main/java/org/waveprotocol/pst/templates/api/api.st",
@@ -209,13 +250,13 @@
         ]
         proto_classes.each { proto ->
             javaexec {
-                main = "org.waveprotocol.pst.PstMain"
-                classpath += configurations.compile
+                main = "org.apache.wave.pst.PstMain"
+                classpath += configurations.generateMessages
                 args = [
                         '-s',
                         'pst',
                         '-d',
-                        'src/generated/messages',
+                        'generated/main/java',
                         '-f',
                         proto
                 ]
@@ -225,13 +266,13 @@
     }
 }
 
-generateMessages.dependsOn ":pst:jar"
+generateMessages.dependsOn ":pst:shadowJar", "compileProtoJava"
 
 task generateGXP {
     description = 'Generate source files from GXP prototypes'
     FileTree inputFiles = fileTree(dir: 'src/main/gxp', include: '**/*.gxp')
     inputs.property "files", inputFiles
-    File outputDir = file("src/generated/gxp")
+    File outputDir = file("generated/main/java")
     outputs.dir outputDir
     doLast {
         javaexec {
@@ -239,7 +280,7 @@
             classpath += configurations.generateGXP
             args = [
                     "--dir",
-                    "src/generated/gxp",
+                    "generated/main/java",
                     "--source",
                     "src/main/gxp",
                     "--output_language",
@@ -250,6 +291,9 @@
     }
 }
 
+//=============================================================================
+// Gwt Compilation Options
+//=============================================================================
 task compileGwt {
     description = 'Compiles the GWT sources for production'
     doLast {
@@ -326,14 +370,7 @@
     }
 }
 
-task extractApi(type: Copy) {
-    from (configurations.compile.collect { zipTree(it) }) {
-        //Note: readonly files which get overwritten crash windows.
-        exclude "LICENSE"
-    } into "$buildDir/api"
-}
 
-extractApi.mustRunAfter compileJava
 
 compileJava.dependsOn = [generateMessages, generateGXP]
 
@@ -372,7 +409,9 @@
     }
 }
 
-/* Test Tasks */
+//=============================================================================
+// Tests
+//=============================================================================
 
 test {
     include "**/*Test*"
@@ -460,6 +499,19 @@
 
 ant.importBuild 'config/server-config.xml'
 
+//=============================================================================
+// Custom UberJar Implementation
+// Author Note: this custom implementation should be replaced by the shadow
+//              plugin as shown in the pst project.
+//=============================================================================
+task extractApi(type: Copy) {
+    from (configurations.compile.collect { zipTree(it) }) {
+        //Note: readonly files which get overwritten crash windows.
+        exclude "LICENSE"
+    } into "$buildDir/api"
+}
+
+extractApi.mustRunAfter compileJava
 
 jar {
     manifest {
@@ -506,6 +558,7 @@
     include "org/apache/lucene/**/*"
     include "org/apache/commons/io/**/*"
     include "org/apache/xerces/**/*"
+    include "org/apache/wave/**/*"
     include "org/bson/**/*"
     include "org/dom4j/**/*"
     include "org/eclipse/**/*"
@@ -545,7 +598,11 @@
 
 jar.dependsOn compileJava, compileGwt, extractApi
 
-/* Distribution Tasks */
+//=============================================================================
+// Binary Distribution
+//=============================================================================
+
+def binName = this.group + "-bin"
 
 task createPropertiesFile(type: Copy) {
     from 'src/main/configs'
@@ -557,7 +614,7 @@
 }
 
 task createDistBinZip(type: Zip) {
-    baseName = this.group + "-bin"
+    baseName = binName
     destinationDir = file('../distributions')
     from(jar) {
         into 'apache-wave/bin'
@@ -586,7 +643,9 @@
 }
 
 task createDistBinTar(type: Tar) {
-    baseName = this.group + "-bin"
+    compression = Compression.GZIP
+    extension = 'tar.gz'
+    baseName = binName
     destinationDir = file('../distributions')
     from(jar) {
         into 'apache-wave/bin'
@@ -617,6 +676,9 @@
 createDistBinZip.dependsOn jar, createPropertiesFile
 createDistBinTar.dependsOn jar, createPropertiesFile
 
+//=============================================================================
+// Distribution's
+//=============================================================================
 task createDistBin() {
     doFirst {
         println ''
@@ -633,6 +695,6 @@
 clean {
     delete "war/WEB-INF"
     delete "war/webclient"
-    delete "src/generated"
+    delete "generated/"
     delete "gwt-unitCache"
 }
\ No newline at end of file
diff --git a/wave/src/proto/proto/org/waveprotocol/box/attachment/attachment.proto b/wave/src/proto/proto/org/waveprotocol/box/attachment/attachment.proto
new file mode 100644
index 0000000..78c95cc
--- /dev/null
+++ b/wave/src/proto/proto/org/waveprotocol/box/attachment/attachment.proto
@@ -0,0 +1,50 @@
+// 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.
+//
+// The image attachment metadata.
+//
+// Author: akaplanov@gmail.com (Kaplanov A.)
+
+syntax = "proto2";
+
+package attachment;
+
+option java_package = "org.waveprotocol.box.attachment";
+option java_outer_classname = "AttachmentProto";
+
+message AttachmentsResponse {
+  repeated AttachmentMetadata attachment = 1;
+}
+
+message AttachmentMetadata {
+  required string attachmentId = 1;
+  required string waveRef = 2;
+  required string fileName = 3;
+  required string mimeType = 4;
+  required int64 size = 5;
+  required string creator = 6;
+  required string attachmentUrl = 7;
+  required string thumbnailUrl = 8;
+  optional ImageMetadata imageMetadata = 9;
+  optional ImageMetadata thumbnailMetadata = 10;
+  optional bool malware = 11;
+}
+
+message ImageMetadata {
+  required int32 width = 1;
+  required int32 height = 2;
+}
diff --git a/wave/src/proto/proto/org/waveprotocol/box/common/comms/waveclient-rpc.proto b/wave/src/proto/proto/org/waveprotocol/box/common/comms/waveclient-rpc.proto
new file mode 100644
index 0000000..7b308a8
--- /dev/null
+++ b/wave/src/proto/proto/org/waveprotocol/box/common/comms/waveclient-rpc.proto
@@ -0,0 +1,199 @@
+// 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.
+//
+// The wave view client-server protocol
+//
+// Author: jochen@google.com (Jochen Bekmann)
+// Author: anorth@google.com (Alex North)
+
+syntax = "proto2";
+
+import "org/waveprotocol/box/server/rpc/rpc.proto";
+import "org/waveprotocol/wave/federation/federation.protodevel";
+
+package waveserver;
+
+option java_package = "org.waveprotocol.box.common.comms";
+option java_outer_classname = "WaveClientRpc";
+option java_generic_services = true;
+
+/**
+ * Provides streaming wave views.
+ *
+ * A client requests a possibly filtered view of wavelets in a wave.
+ * The response stream contains first a snapshot for each wavelet
+ * currently in view, and then deltas for those wavelets. The end of
+ * the initial set of snapshots is indicated by a "marker" message.
+ * New wavelets may come into view after the marker, resulting in 
+ * another snapshot.
+ * 
+ * The client may indicate that it already has a snapshot for some wavelets
+ * by providing one or more known versions and signatures. If one matches
+ * the server history the server will not send a snapshot but will instead
+ * begin the stream with an empty delta specifying the resynchronization
+ * version.
+ * 
+ * TODO(anorth):
+ * - make the first response message a channel id only, then no more
+ *   channel ids
+ */
+service ProtocolWaveClientRpc {
+  rpc Open (ProtocolOpenRequest) returns (ProtocolWaveletUpdate) {
+    option (rpc.is_streaming_rpc) = true;
+  };
+  rpc Submit (ProtocolSubmitRequest) returns (ProtocolSubmitResponse);
+  rpc Authenticate (ProtocolAuthenticate) returns (ProtocolAuthenticationResult);
+}
+
+// A workaround for clients which do not support sending cookies over a websocket
+// connection. See: http://code.google.com/p/wave-protocol/issues/detail?id=119
+message ProtocolAuthenticate {
+  required string token = 1;
+}
+
+// RPCs require a return type, although in this case no return data is desired.
+// We don't want to return anything here because clients which implement
+// websockets correctly (and thus don't use ProtocolAuthenticate) cannot
+// recieve the authentication related information.
+// If the client's authentication is not valid, the connection will be closed.
+message ProtocolAuthenticationResult {
+}
+
+/**
+ * A request to open a wave view.
+ */
+message ProtocolOpenRequest {
+  // User making the request.
+  // TODO(anorth): Remove this, replacing it with the implicit logged-in user.
+  required string participant_id = 1;
+  // Wave id to open.
+  required string wave_id = 2;
+  // Wavelet id prefixes by which to filter the view, empty means no filter.
+  repeated string wavelet_id_prefix = 3;
+  // Known wavelet versions for resynchronization.
+  repeated WaveletVersion known_wavelet = 4;
+}
+
+// A pair of (wavelet id, wavelet version)
+message WaveletVersion {
+  required string wavelet_id = 1;
+  required federation.ProtocolHashedVersion hashed_version = 2;
+}
+
+// A document and associated metadata
+message DocumentSnapshot {
+  required string document_id = 1;
+  // This is a document operation that takes the document from zero to its current state.
+  required federation.ProtocolDocumentOperation document_operation = 2;
+
+  // ** Metadata
+  // The participant who submitted the first operation to the document
+  required string author = 3;
+  // All participants who have submitted operations to the document
+  repeated string contributor = 4;
+  // The wavelet version when the document was last modified
+  required int64 last_modified_version = 5;
+  required int64 last_modified_time = 6;
+}
+
+// A wavelet and associated metadata.
+message WaveletSnapshot {
+  required string wavelet_id = 1;
+  // The list of participants of this wavelet.
+  repeated string participant_id = 2;
+  // Snapshots of all the documents in the wavelet.
+  repeated DocumentSnapshot document = 3;
+
+  // ** Metadata
+  // The current version of the wavelet
+  required federation.ProtocolHashedVersion version = 4;
+  // The participant that created the wavelet
+  required int64 last_modified_time = 5;
+  required string creator = 6;
+  required int64 creation_time = 7;
+}
+
+// A snapshot of a user's view of a wave.
+// Contains snapshots of all the wavelets visible to a user
+message WaveViewSnapshot {
+  required string wave_id = 1;
+  repeated WaveletSnapshot wavelet = 2;
+}
+
+/**
+ * Update message for a wave view.
+ * Contains either:
+ * - a channel id (only)
+ * - a marker (only)
+ * - a wavelet name, snapshot, version, and commit version
+ * - a wavelet name, deltas, version
+ * Must contain either one or more applied deltas or a commit notice.
+ *
+ * TODO(anorth): rename to reflect that this is a view update, not wavelet
+ */
+message ProtocolWaveletUpdate {
+  // Specifies the wavelet name in the URI netpath notation.
+  // Set only if there are deltas
+  // TODO(anorth) make optional for channel id, marker updates
+  required string wavelet_name = 1;
+
+  // Zero or more deltas for this wavelet, streamed in order.
+  // If snapshot is set, there should be zero deltas.
+  // TODO(soren): consider using this in the snapshot case for uncommitted deltas.
+  repeated federation.ProtocolWaveletDelta applied_delta = 2;
+
+  // Indicates that the host server has committed the wavelet to disk at the
+  // given version. Mandatory for snapshots.
+  optional federation.ProtocolHashedVersion commit_notice = 3;
+
+  // Resulting version of the wavelet after all deltas have been applied
+  // May only be missing if there are no appliedDeltas
+  // If snapshot is set, this is the version number of the snapshot, and is
+  // mandatory.
+  optional federation.ProtocolHashedVersion resulting_version = 4;
+
+  // An optional snapshot of the wavelet
+  optional WaveletSnapshot snapshot = 5;
+
+  // View open marker, signifies all current snapshots have been sent.
+  optional bool marker = 6 [default=false];
+
+  // Channel id, set only in the first update to a client.
+  // The client includes it in submits.
+  optional string channel_id = 7;
+}
+
+/**
+ * The client requests that the given delta be applied to the wavelet.
+ */
+message ProtocolSubmitRequest {
+  required string wavelet_name = 1;
+  required federation.ProtocolWaveletDelta delta = 2;
+  optional string channel_id = 3;
+}
+
+/**
+ * The result of submitting the delta to the server. If an error occurs
+ * errorMessage will be present, otherwise hashedVersionAfterApplication will be
+ * present. operationsApplied will report the actual number of operations
+ * successfully applied to the wavelet by the server.
+ */
+message ProtocolSubmitResponse {
+  required int32 operations_applied = 1;
+  optional string error_message = 2;
+  optional federation.ProtocolHashedVersion hashed_version_after_application = 3;
+}
diff --git a/wave/src/proto/proto/org/waveprotocol/box/profile/profiles.proto b/wave/src/proto/proto/org/waveprotocol/box/profile/profiles.proto
new file mode 100644
index 0000000..4370ee1
--- /dev/null
+++ b/wave/src/proto/proto/org/waveprotocol/box/profile/profiles.proto
@@ -0,0 +1,51 @@
+// 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.
+//
+// The profile fetch request and response.
+//
+// Author: yurize@apache.org (Yuri Zelikov)
+
+syntax = "proto2";
+
+package profile;
+
+option java_package = "org.waveprotocol.box.profile";
+option java_outer_classname = "ProfilesProto";
+
+
+message ProfileRequest {
+  // The profile addresses in email format.
+  repeated string addresses = 1;
+}
+
+message ProfileResponse {
+  
+  message FetchedProfile {
+    // The profile address in email format.
+    required string address = 1;
+    // The name.
+    required string name = 2;
+    // The image URL.
+    required string imageUrl = 3;
+    // The link to website.
+    optional string profileUrl = 4;
+  }
+  
+  // The fetched profiles.
+  repeated FetchedProfile profiles = 1;
+}
+
diff --git a/wave/src/proto/proto/org/waveprotocol/box/search/search.proto b/wave/src/proto/proto/org/waveprotocol/box/search/search.proto
new file mode 100644
index 0000000..889ee05
--- /dev/null
+++ b/wave/src/proto/proto/org/waveprotocol/box/search/search.proto
@@ -0,0 +1,68 @@
+// 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.
+//
+// The search query and response.
+//
+// Author: vega113@gmail.com (Yuri Z.)
+
+syntax = "proto2";
+
+package search;
+
+option java_package = "org.waveprotocol.box.search";
+option java_outer_classname = "SearchProto";
+
+
+message SearchRequest {
+  // The query to execute.
+  required string query = 1;
+  // The index from which to return results.
+  required int32 index = 2;
+  // The number of results to return.
+  required int32 numResults = 3;
+}
+
+message SearchResponse {
+  // The wave list digest.
+  message Digest {
+    // The wave title.
+	required string title = 1;
+	// The text snippet.
+	required string snippet = 2;
+	// Serialized wave id
+	required string waveId = 3;
+	// Last modified time of the wave.
+	required int64 lastModified = 4;
+	// Unread count for the user.
+	required int32 unreadCount = 5;
+	// Number of blips in the wave.
+ 	required int32 blipCount = 6;
+	// Wave participants.
+	repeated string participants = 7;
+	// The wave author.
+	required string author = 8;
+  }
+
+  // The search query.
+  required string query = 1;
+  // The total number of results to the query (not necessarily all returned).
+  required int32 totalResults = 2;
+  // A list of digests, representing the segment [index, index + result_count] 
+  // from the query parameters.
+  repeated Digest digests = 3;
+}
+
diff --git a/wave/src/proto/proto/org/waveprotocol/box/server/persistence/protos/account-store.proto b/wave/src/proto/proto/org/waveprotocol/box/server/persistence/protos/account-store.proto
new file mode 100644
index 0000000..cd52a51
--- /dev/null
+++ b/wave/src/proto/proto/org/waveprotocol/box/server/persistence/protos/account-store.proto
@@ -0,0 +1,78 @@
+// 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.
+//
+// Account Data data structures. These are used as the on-disk representation of the internal
+// AccountData classes.
+//
+// Author: tad.glines@gmail.com (Tad Glines)
+
+syntax = "proto2";
+
+package protoaccountstore;
+
+option java_package = "org.waveprotocol.box.server.persistence.protos";
+option java_outer_classname = "ProtoAccountStoreData";
+
+// Represents an AccountData instance
+message ProtoAccountData {
+	enum AccountDataType {
+		HUMAN_ACCOUNT = 1;
+		ROBOT_ACCOUNT = 2;
+	}
+
+	required AccountDataType account_type = 1;
+	
+	// The participant id
+	required string account_id = 2;
+
+	// One must be provided depending on the value of account_type.	
+	optional ProtoHumanAccountData human_account_data = 3;
+	optional ProtoRobotAccountData robot_account_data = 4;
+}
+
+// Data specific to a human account
+message ProtoHumanAccountData {
+	optional ProtoPasswordDigest password_digest = 1;
+}
+
+// The values from a PAsswordDigest instance
+message ProtoPasswordDigest {
+	required bytes salt = 1;
+	required bytes digest = 2;
+}
+
+// Data specific to a robot account
+message ProtoRobotAccountData {
+	required string url = 1;
+	required string consumer_secret = 2;
+	optional ProtoRobotCapabilities robot_capabilities = 3;
+	required bool is_verified = 4;
+}
+
+// Data found in a RobotCapabilities instance
+message ProtoRobotCapabilities {
+	required string capabilities_hash = 1;
+	required string protocol_version = 2;
+	repeated ProtoRobotCapability capability = 3;
+}
+
+// Data found in a com.google.api.robot.Capability instance
+message ProtoRobotCapability {
+	required string event_type = 1;
+	repeated string context = 2;
+	required string filter = 3;
+}
diff --git a/wave/src/proto/proto/org/waveprotocol/box/server/persistence/protos/delta-store.proto b/wave/src/proto/proto/org/waveprotocol/box/server/persistence/protos/delta-store.proto
new file mode 100644
index 0000000..09b1adb
--- /dev/null
+++ b/wave/src/proto/proto/org/waveprotocol/box/server/persistence/protos/delta-store.proto
@@ -0,0 +1,38 @@
+
+// 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.
+//
+// Account Data data structures. These are used as the on-disk representation of the internal
+// AccountData classes.
+//
+// Author: tad.glines@gmail.com (Tad Glines)
+
+syntax = "proto2";
+
+import "org/waveprotocol/wave/federation/federation.protodevel";
+
+package protodeltastore;
+
+option java_package = "org.waveprotocol.box.server.persistence.protos";
+option java_outer_classname = "ProtoDeltaStoreData";
+
+message ProtoTransformedWaveletDelta {
+  required string author = 1;
+  required federation.ProtocolHashedVersion resulting_version = 2;
+  required int64 application_timestamp = 3;
+  repeated federation.ProtocolWaveletOperation operation = 4;
+}
diff --git a/wave/src/proto/proto/org/waveprotocol/box/server/rpc/rpc.proto b/wave/src/proto/proto/org/waveprotocol/box/server/rpc/rpc.proto
new file mode 100644
index 0000000..370cd74
--- /dev/null
+++ b/wave/src/proto/proto/org/waveprotocol/box/server/rpc/rpc.proto
@@ -0,0 +1,66 @@
+// 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.
+//
+// Author: thorogood@google.com (Sam Thorogood)
+//
+// Internal protocol buffers used as part of the client-server RPC subsystem.
+// This package also provides options which must be used to better define the
+// way messages are passed between client/server.
+
+syntax = "proto2";
+
+import "google/protobuf/descriptor.proto";
+
+package rpc;
+
+option java_package = "org.waveprotocol.box.server.rpc";
+option java_outer_classname = "Rpc";
+
+extend google.protobuf.MethodOptions {
+  /**
+   * Mark a service method as a streaming RPC. This indicates that the server
+   * end-point of this RPC may return 0-n responses before it is complete.
+   *
+   * Completion of this RPC should be specified by finally passing null as a
+   * result to the callback provided to the interface implementation. Or, by
+   * raising an error condition as normal (through setFailed on the controller).
+   */
+  // TODO: Create a message type for options instead of using a single bool.
+  optional bool is_streaming_rpc = 1003 [default = false];
+}
+
+/**
+ * Used internally by the RPC subsystem.
+ *
+ * Passed from client -> server to indicate that a RPC, streaming or otherwise,
+ * should be cancelled. The server still has a responsibility to finish the RPC
+ * in a standard manner, and this is purely a request.
+ */
+message CancelRpc {
+}
+
+/**
+ * Used internally by the RPC subsystem.
+ *
+ * Passed from server -> client in two cases;
+ *  - a streaming RPC has finished, in which case failed may be true or false
+ *  - a normal RPC has failed, in which case failed must be true
+ */
+message RpcFinished {
+  required bool failed = 1;
+  optional string error_text = 2;
+}
diff --git a/wave/src/proto/proto/org/waveprotocol/wave/concurrencycontrol/clientserver.proto b/wave/src/proto/proto/org/waveprotocol/wave/concurrencycontrol/clientserver.proto
new file mode 100644
index 0000000..17c20a7
--- /dev/null
+++ b/wave/src/proto/proto/org/waveprotocol/wave/concurrencycontrol/clientserver.proto
@@ -0,0 +1,268 @@
+// 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.
+//
+// The wave client-server protocol.
+// See http://www.waveprotocol.org/protocol/design-proposals/clientserver-protocol
+//
+// Author: anorth@google.com (Alex North)
+
+syntax = "proto2";
+
+import "org/waveprotocol/box/server/rpc/rpc.proto";
+import "org/waveprotocol/wave/federation/federation.protodevel";
+
+package concurrencycontrol;
+
+option java_package = "org.waveprotocol.wave.concurrencycontrol";
+option java_outer_classname = "ClientServer";
+
+
+/*
+ *** Fetch service. ***
+ * Provides snapshots describing a client's view of a wave.
+ * As a bandwidth optimization, the client may specify that it already has
+ * snapshots of some wavelets at some version (such as from a previous fetch).
+ * If the server's current version matches the version the client provides
+ * then the snapshot is omitted from the response.
+ */
+service FetchService {
+  rpc Fetch(FetchWaveViewRequest) returns (FetchWaveViewResponse);
+}
+
+message FetchWaveViewRequest {
+  // Wave to open, URI path format.
+  required string waveId = 1;
+  // Wavelet versions the client already knows.
+  // At most one version per wavelet.
+  repeated WaveletVersion knownWavelet = 2;
+}
+
+message FetchWaveViewResponse {
+  required ResponseStatus status = 1;
+  
+  message Wavelet {
+    // The wavelet in view, URI path format.
+    required string waveletId = 1;
+    // Snapshot of the wavelet; omitted if the client already knew it.
+    optional WaveletSnapshot snapshot = 2;
+  }
+  repeated Wavelet wavelet = 2;
+}
+
+/* A wavelet with a known hashed version of that wavelet. */
+message WaveletVersion {
+  // Known wavelet, URI path format.
+  required string waveletId = 1;
+  // Known hashed version of the wavelet.
+  required federation.ProtocolHashedVersion version = 2;
+}
+
+/* A wavelet and associated metadata. */
+message WaveletSnapshot {
+  // Wavelet's id, URI path format.
+  required string waveletId = 1;
+  // Participants of this wavelet.
+  repeated string participant = 2;
+  // Snapshots of all the documents in the wavelet.
+  repeated DocumentSnapshot document = 3;
+
+  //// Metadata ////
+  // Current version and modification timestamp of the wavelet.
+  required federation.ProtocolHashedVersion version = 4;
+  required int64 lastModifiedTime = 5;
+  // Participant and time of creation for the wavelet.
+  required string creator = 6;
+  required int64 creationTime = 7;
+}
+
+/* A document and associated metadata. */
+message DocumentSnapshot {
+  // Id of the document.
+  required string documentId = 1;
+  // Operation that transforms an empty document the document state.
+  required federation.ProtocolDocumentOperation documentOperation = 2;
+
+  //// Metadata ////
+  // Participant who submitted the first operation to the document.
+  required string author = 3;
+  // All participants who have submitted operations to the document.
+  repeated string contributor = 4;
+  // Wavelet version and timestamp when the document was last modified.
+  required int64 lastModifiedVersion = 5;
+  required int64 lastModifiedTime = 6;
+}
+
+/*
+ *** Wavelet channel service. ***
+ * Provides a uni-directional stream of deltas for a single wavelet,
+ * beginning at the delta applying at a client-specified version.
+ * The stream continues until either the client requests the channel
+ * be closed or a terminating message is received. Deltas submitted
+ * with this channel's id are excluded from the stream. There is no
+ * ordering guarantee between this service and responses from the
+ * delta submission service.
+ */
+service WaveletChannelService {
+  rpc Open(OpenWaveletChannelRequest) returns (OpenWaveletChannelStream) {
+      option (rpc.is_streaming_rpc) = true;
+  };
+  rpc Close(CloseWaveletChannelRequest) returns (EmptyResponse);
+}
+
+message OpenWaveletChannelRequest {
+  // Wave id, URI path format.
+  required string waveId = 1;
+  // Wavelet id, URI path format.
+  required string waveletId = 2;
+  // Application version of first delta to return.
+  required federation.ProtocolHashedVersion beginVersion = 3;
+}
+
+/** Repeated message for a wavelet channel. */
+message OpenWaveletChannelStream {
+  // Identifies the channel, provided only in the first message.
+  optional string channelId = 1;
+
+  // Second and subsequent messages contain either or both a delta
+  // and commitVersion.
+  optional WaveletUpdate delta = 2;
+  optional federation.ProtocolHashedVersion commitVersion = 3;
+
+  // Last message contains only a terminator.
+  optional WaveletChannelTerminator terminator = 4;
+}
+
+message CloseWaveletChannelRequest {
+  // Channel to close.
+  required string channelId = 1;
+}
+
+/** A delta applied to a wavelet. */
+message WaveletUpdate {
+  // Transformed delta.
+  required federation.ProtocolWaveletDelta delta = 1;
+  // Wavelet hashed version after the delta.
+  required federation.ProtocolHashedVersion resultingVersion = 2;
+  // Timestamp of delta application.
+  required int64 applicationTimpstamp = 3;
+}
+
+/** Terminates a wavelet stream. */
+message WaveletChannelTerminator {
+  required ResponseStatus status = 1;
+}
+
+
+/*
+ *** Delta submission service. ***
+ * Receives deltas submitted against wavelets.
+ * Deltas are submitted in association with a wavelet channel (see
+ * WaveletChannelService).
+ */
+service DeltaSubmissionService {
+  rpc Submit(SubmitDeltaRequest) returns (SubmitDeltaResponse);
+}
+
+message SubmitDeltaRequest {
+  // Wave to submit to, URI path format.
+  required string waveId = 1;
+  // Wavelet to submit to, URI path format.
+  required string waveletId = 2;
+  // Delta to submit.
+  required federation.ProtocolWaveletDelta delta = 3;
+  // Wavelet channel associated with submission.
+  required string channelId = 4;
+}
+
+message SubmitDeltaResponse {
+  required ResponseStatus status = 1;
+
+  // Number of ops applied from the delta.
+  required int32 operationsApplied = 2;
+  // Wavelet hashed version after the delta.
+  optional federation.ProtocolHashedVersion hashedVersionAfterApplication = 3;
+  // Timestamp of delta application.
+  optional int64 timestampAfterApplication = 4;
+} 
+
+
+/*
+ *** Transport authentication service. ***
+ * Authenticates the underlying transport.
+ * This service is required only to work around a bug in some browsers'
+ * websocket implementations that fail to set cookies containing authentication
+ * tokens.
+ * If the client's authentication is invalid the server should close the
+ * transport.
+ * See: http://code.google.com/p/wave-protocol/issues/detail?id=119
+ */
+service TransportAuthenticationService {
+  rpc Authenticate (TransportAuthenticationRequest) returns (EmptyResponse);
+}
+
+message TransportAuthenticationRequest {
+  // Authentication token.
+  required string token = 1;
+}
+
+
+/*** An empty message for services which have no application-level result. ***/
+message EmptyResponse {
+}
+
+/*** Response status for all services ***/
+message ResponseStatus {
+  enum ResponseCode {
+  // All good.
+  OK = 0;
+
+  // Request was ill-formed.
+  BAD_REQUEST = 1;
+
+  // An unspecified internal error occurred.
+  INTERNAL_ERROR = 2;
+
+  // The request was not authorized.
+  NOT_AUTHORIZED = 3;
+
+  // Hashed version didn't match a point in history.
+  VERSION_ERROR = 4;
+
+  // A delta contained an invalid operation (before or after transformation).
+  INVALID_OPERATION = 5;
+
+  // An operation didn't preserve a document schema.
+  SCHEMA_VIOLATION = 6;
+
+  // A delta is too big or the resulting document count or size is too large.
+  SIZE_LIMIT_EXCEEDED = 7;
+
+  // An operation was rejected by a server policy.
+  POLICY_VIOLATION = 8;
+
+  // An object is unavailable because it has been quarantined.
+  QUARANTINED = 9;
+
+  // A request was made against a version older than the server was willing
+  // to satisfy. Transform and retry.
+  TOO_OLD = 10;
+  }
+  
+  required ResponseCode status = 1;
+  // Reason must be provided if status != OK.
+  optional string failureReason = 2; 
+}
diff --git a/wave/src/proto/proto/org/waveprotocol/wave/diff/build.xml b/wave/src/proto/proto/org/waveprotocol/wave/diff/build.xml
new file mode 100644
index 0000000..0b2ffc0
--- /dev/null
+++ b/wave/src/proto/proto/org/waveprotocol/wave/diff/build.xml
@@ -0,0 +1,28 @@
+<?xml version='1.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.
+
+-->
+<project name="diff">
+  <import file="${build.common.path}"/>
+  <property name="libname" value="diff"/>
+  <patternset id="srcs">
+    <include name="org/waveprotocol/wave/diff/**"/>
+  </patternset>
+</project>
diff --git a/wave/src/proto/proto/org/waveprotocol/wave/diff/diff.proto b/wave/src/proto/proto/org/waveprotocol/wave/diff/diff.proto
new file mode 100644
index 0000000..d75d27a
--- /dev/null
+++ b/wave/src/proto/proto/org/waveprotocol/wave/diff/diff.proto
@@ -0,0 +1,114 @@
+// 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.
+//
+// The wave diff-on-open fetch service.
+//
+// Author: hearnden@google.com (David Hearnden)
+//         anorth@google.com (Alex North)
+
+syntax = "proto2";
+
+import "org/waveprotocol/box/server/rpc/rpc.proto";
+import "org/waveprotocol/wave/concurrencycontrol/clientserver.proto";
+import "org/waveprotocol/wave/federation/federation.protodevel";
+import "org/apache/wave/pst/protobuf/extensions.proto";
+
+package diff;
+
+option java_package = "org.waveprotocol.wave.diff";
+option java_outer_classname = "Diff";
+
+
+/*
+ *** Fetch service. ***
+ * Provides snapshots describing a client's view of a wave, in diff format.
+ * As a bandwidth optimization, the client may specify that it already has
+ * snapshots of some wavelets at some version (such as from a previous fetch).
+ * If the server's current version matches the version the client provides
+ * then the snapshot is omitted from the response.
+ */
+service FetchDiffService {
+  rpc Fetch(FetchDiffRequest) returns (FetchDiffResponse);
+}
+
+message FetchDiffRequest {
+  // Wave to open, URI path format.
+  required string waveId = 1;
+  // Wavelet versions the client already knows.
+  // At most one version per wavelet.
+  repeated concurrencycontrol.WaveletVersion knownWavelet = 2;
+}
+
+message FetchDiffResponse {
+  required concurrencycontrol.ResponseStatus status = 1;
+  
+  message WaveletDiff {
+    // The wavelet in view, URI path format.
+    required string waveletId = 1;
+    // Snapshot of the wavelet; omitted if the client already knew it.
+    optional WaveletDiffSnapshot snapshot = 2;
+  }
+  repeated WaveletDiff wavelet = 2;
+}
+
+/* A wavelet and associated metadata. */
+message WaveletDiffSnapshot {
+  // Wavelet's id, URI path format.
+  required string waveletId = 1;
+
+  // Participants of this wavelet.
+  repeated string participant = 2;
+  // Added participants of this wavelet;
+  repeated string addedParticipant = 21;
+  // Removed participants of this wavelet;
+  repeated string removedParticipant = 22;
+
+  // Snapshots of all the documents in the wavelet.
+  repeated DocumentDiffSnapshot document = 3;
+
+  //// Metadata ////
+  // Current version and modification timestamp of the wavelet.
+  required federation.ProtocolHashedVersion version = 4;
+  required int64 lastModifiedTime = 5 [(int52) = true];
+  // Participant and time of creation for the wavelet.
+  required string creator = 6;
+  required int64 creationTime = 7 [(int52) = true];
+}
+
+/* A document and associated metadata. */
+message DocumentDiffSnapshot {
+  // Id of the document.
+  required string documentId = 1;
+  // Operation that transforms an empty document the last-read document state.
+  optional federation.ProtocolDocumentOperation state = 2;
+
+  // Operation that transforms the last-read document state to the current state.
+  optional federation.ProtocolDocumentOperation diff = 21;
+
+  //// Metadata ////
+  // Participant who submitted the first operation to the document.
+  required string author = 3;
+  // All participants who have submitted operations to the document.
+  repeated string contributor = 4;
+  // Added participants who have submitted operations to the document.
+  repeated string addedContributor = 22;
+  // Removed participants who have submitted operations to the document.
+  repeated string removedContributor = 23;
+  // Wavelet version and timestamp when the document was last modified.
+  required int64 lastModifiedVersion = 5 [(int52) = true];
+  required int64 lastModifiedTime = 6 [(int52) = true];
+}
diff --git a/wave/src/proto/proto/org/waveprotocol/wave/federation/federation.protodevel b/wave/src/proto/proto/org/waveprotocol/wave/federation/federation.protodevel
new file mode 100644
index 0000000..4163a4d
--- /dev/null
+++ b/wave/src/proto/proto/org/waveprotocol/wave/federation/federation.protodevel
@@ -0,0 +1,247 @@
+// 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.
+//
+// Google Wave Federation Protocol data structures.
+//
+// They are intended to be equivalent to the data structures in the
+// draft "Google Wave Federation Protocol Over XMPP" at
+// http://code.google.com/p/wave-protocol/source
+//
+// Author: thorogood@google.com (Sam Thorogood), soren@google.com (Soren Lassen)
+
+syntax = "proto2";
+
+package federation;
+
+import "org/apache/wave/pst/protobuf/extensions.proto";
+
+option java_package = "org.waveprotocol.wave.federation";
+option java_outer_classname = "Proto";
+
+/**
+ * An immutable list of operations for contribution to a wavelet.
+ * Specifies the contributor and the wavelet version that the
+ * operations are intended to be applied to.  The host wave server
+ * may apply the operations to the wavelet at the specified wavelet version
+ * or it may accept them at a later version after operational transformation
+ * against the operations at the intermediate wavelet versions.
+ */
+message ProtocolWaveletDelta {
+  // Wavelet version that the delta is intended to be applied to.
+  required ProtocolHashedVersion hashed_version = 1;
+
+  // Wave address of the contributor. Must be an explicit wavelet participant,
+  // and may be different from the originator of this delta.
+  required string author = 2;
+
+  // Operations included in this delta.
+  repeated ProtocolWaveletOperation operation = 3;
+
+  /*
+   * The nodes on the "overt" path from the originator through the address
+   * access graph leading up to (but excluding) the author. The path excludes
+   * any initial segments of the complete path which come before a WRITE edge
+   * in the graph. This field is empty if the author is either the originator's
+   * entry point into the address graph or is accessed by a WRITE edge.
+   *
+   * For example, "wave-discuss@acmewave.com" may be the explicit participant of
+   * a wavelet, and is set as the author of a delta. However, this group is
+   * being asked to act on behalf of "peter@initech-corp.com", who is a member
+   * of "wave-authors", which is in turn a member of "wave-discuss". In this
+   * example, the delta would be configured as such:
+   *  delta.author = "wave-discuss@acmewave.com"
+   *  delta.addressPath = ["peter@initech-corp.com", "wave-authors@acmewave.com"]
+   */
+  repeated string address_path = 4;
+}
+
+/**
+ * Describes a wavelet version and the wavelet's history hash at that version.
+ */
+message ProtocolHashedVersion {
+  required int64 version = 1 [(int52) = true];
+  required bytes history_hash = 2;
+}
+
+/**
+ * An operation within a delta. Exactly one of the following seven fields must be set
+ * for this operation to be valid.
+ */
+message ProtocolWaveletOperation {
+
+  // A document operation. Mutates the contents of the specified document.
+  message MutateDocument {
+    required string document_id = 1;
+    required ProtocolDocumentOperation document_operation = 2;
+  }
+
+  // Adds a new participant (canonicalized wave address) to the wavelet.
+  optional string add_participant = 1;
+
+  // Removes an existing participant (canonicalized wave address) from the wavelet.
+  optional string remove_participant = 2;
+
+  // Mutates a document.
+  optional MutateDocument mutate_document = 3;
+
+  // Does nothing. True if set.
+  optional bool no_op = 4;
+}
+
+/**
+ * A list of mutation components.
+ */
+message ProtocolDocumentOperation {
+
+  /**
+   * A component of a document operation.  One (and only one) of the component
+   * types must be set.
+   */
+  message Component {
+
+    message KeyValuePair {
+      required string key = 1;
+      required string value = 2;
+    }
+
+    message KeyValueUpdate {
+      required string key = 1;
+      // Absent field means that the attribute was absent/the annotation
+      // was null.
+      optional string old_value = 2;
+      // Absent field means that the attribute should be removed/the annotation
+      // should be set to null.
+      optional string new_value = 3;
+    }
+
+    message ElementStart {
+      required string type = 1;
+      // MUST NOT have two pairs with the same key.
+      repeated KeyValuePair attribute = 2;
+    }
+
+    message ReplaceAttributes {
+      // This field is set to true if and only if both oldAttributes and
+      // newAttributes are empty.  It is needed to ensure that the optional
+      // replaceAttributes component field is not dropped during serialization.
+      optional bool empty = 1;
+      // MUST NOT have two pairs with the same key.
+      repeated KeyValuePair old_attribute = 2;
+      // MUST NOT have two pairs with the same key.
+      repeated KeyValuePair new_attribute = 3;
+    }
+
+    message UpdateAttributes {
+      // This field is set to true if and only if attributeUpdates are empty.
+      // It is needed to ensure that the optional updateAttributes
+      // component field is not dropped during serialization.
+      optional bool empty = 1;
+      // MUST NOT have two updates with the same key.
+      repeated KeyValueUpdate attribute_update = 2;
+    }
+
+    message AnnotationBoundary {
+      // This field is set to true if and only if both ends and changes are
+      // empty.  It is needed to ensure that the optional annotationBoundary
+      // component field is not dropped during serialization.
+      optional bool empty = 1;
+      // MUST NOT have the same string twice.
+      repeated string end = 2;
+      // MUST NOT have two updates with the same key.  MUST NOT
+      // contain any of the strings listed in the 'end' field.
+      repeated KeyValueUpdate change = 3;
+    }
+
+    optional AnnotationBoundary annotation_boundary = 1;
+    optional string characters = 2;
+    optional ElementStart element_start = 3;
+    optional bool element_end = 4;
+    optional int32 retain_item_count = 5;
+    optional string delete_characters = 6;
+    optional ElementStart delete_element_start = 7;
+    optional bool delete_element_end = 8;
+    optional ReplaceAttributes replace_attributes = 9;
+    optional UpdateAttributes update_attributes = 10;
+  }
+
+  repeated Component component = 1;
+}
+
+/**
+ * Information generated about this delta post-applicaton. Used in
+ * ProtocolUpdate and ProtocolHistoryResponse.
+ */
+message ProtocolAppliedWaveletDelta {
+  required ProtocolSignedDelta signed_original_delta = 1;
+  optional ProtocolHashedVersion hashed_version_applied_at = 2;
+  required int32 operations_applied = 3;
+  required int64 application_timestamp = 4 [(int52) = true];
+}
+
+/**
+ * A canonicalised delta signed with a number of domain signatures.
+ */
+message ProtocolSignedDelta {
+  required bytes delta = 1;
+  repeated ProtocolSignature signature = 2;
+}
+
+/**
+ * A signature for a delta. It contains the actual bytes of the signature,
+ * an identifier of the signer (usually the hash of a certificate chain),
+ * and an enum identifying the signature algorithm used.
+ */
+message ProtocolSignature {
+
+  enum SignatureAlgorithm {
+    SHA1_RSA = 1;
+  }
+
+  required bytes signature_bytes = 1;
+  required bytes signer_id = 2;
+  required SignatureAlgorithm signature_algorithm = 3;
+}
+
+/**
+ * A certificate chain that a sender will refer to in subsequent signatures.
+ *
+ * The signer_id field in a ProtocolSignature refers to a ProtocolSignerInfo
+ * as follows: The certificates present in a ProtocolSignerInfo are encoded
+ * in PkiPath format, and then hashed using the hash algorithm indicated in the
+ * ProtocolSignerInfo.
+ */
+message ProtocolSignerInfo {
+
+  enum HashAlgorithm {
+    SHA256 = 1;
+    SHA512 = 2;
+  }
+
+  // The hash algorithm senders will use to generate an id that will refer to
+  // this certificate chain in the future
+  required HashAlgorithm hash_algorithm = 1;
+
+  // The domain that this certificate chain was issued to. Receivers of this
+  // ProtocolSignerInfo SHOULD reject the ProtocolSignerInfo if the target
+  // certificate (the first one in the list) is not issued to this domain.
+  required string domain = 2;
+
+  // The certificate chain. The target certificate (i.e., the certificate issued
+  // to the signer) is first, and the CA certificate (or one issued directly
+  // by the CA) is last.
+  repeated bytes certificate = 3;
+}
diff --git a/wave/src/proto/proto/org/waveprotocol/wave/federation/federation_error.protodevel b/wave/src/proto/proto/org/waveprotocol/wave/federation/federation_error.protodevel
new file mode 100644
index 0000000..f6fbcb7
--- /dev/null
+++ b/wave/src/proto/proto/org/waveprotocol/wave/federation/federation_error.protodevel
@@ -0,0 +1,81 @@
+// 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.
+//
+// Google Wave Federation Protocol error codes.
+//
+// Author: kalman@google.com (Benjamin Kalman)
+
+syntax = "proto2";
+
+option java_package = "org.waveprotocol.wave.federation";
+option java_outer_classname = "FederationErrorProto";
+
+package federation;
+
+/**
+ * Container for a Federation error, containing the error code (as per the
+ * specification) and an optional description for debugging etc.
+ *
+ * The internal enum has codes which must map directly to XMPP error stanzas,
+ * as defined in RFC 3920 (9.3.3).
+ *
+ * TODO(arb): Once the error codes have been audited and standardised, merge into federation.proto.
+ */
+message FederationError {
+  enum Code {
+    // Should only be used for internal success.
+    OK = 0;
+
+    // Response for a completely broken request.
+    BAD_REQUEST = 1;
+
+    // Either the wavelet does not exist, or the request is not authorised and
+    // thus should not reveal the existence of the target wavelet.
+    ITEM_NOT_FOUND = 2;
+
+    // Revealable error conditions; including, but not limited to:
+    //  + submit failed due to invalid delta
+    //  + invalid signer info post
+    NOT_ACCEPTABLE = 3;
+
+    // Signer info not available for delta submit.
+    NOT_AUTHORIZED = 4;
+
+    // Generic 'back-off' message.
+    RESOURCE_CONSTRAINT = 5;
+
+    // Undefined condition. This error will be generated if an error condition
+    // not otherwise contained within this protobuf is received over-the-wire.
+    UNDEFINED_CONDITION = 6;
+
+    // Timeout error condition. Note that this may be generated internally
+    // as well as being valid on-the-wire.
+    REMOTE_SERVER_TIMEOUT = 7;
+
+    // Request unexpected, wait requested. Note that this may be generated
+    // internally, notably if an in-flight ID is re-used.
+    UNEXPECTED_REQUEST = 8;
+
+    // Internal server error, wait requested.
+    INTERNAL_SERVER_ERROR = 9;
+  }
+
+  required Code error_code = 1;
+  optional string error_message = 2;
+
+  // TODO(thorogood): Optional source of message field (i.e. wire/internal) for internal use?
+}