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?
+}