├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── build.sbt ├── project ├── Dependencies.scala ├── build.properties └── plugins.sbt ├── scalabuff-compiler └── src │ ├── main │ └── net │ │ └── sandrogrzicic │ │ └── scalabuff │ │ ├── compiler │ │ ├── BuffedString.scala │ │ ├── Enums.scala │ │ ├── Generator.scala │ │ ├── Nodes.scala │ │ ├── Parser.scala │ │ ├── ScalaBuff.scala │ │ ├── Strings.scala │ │ └── package.scala │ │ └── test │ │ └── UpdateTestResources.scala │ └── test │ ├── resources │ ├── java │ │ ├── Complex.java │ │ └── Extensions.java │ ├── multipleprototests │ │ ├── MultiOne.proto │ │ └── MultiTwo.proto │ ├── parsed │ │ ├── Complex.txt │ │ ├── DataTypes.txt │ │ ├── DhComplex.txt │ │ ├── Empty.txt │ │ ├── Enum.txt │ │ ├── Extensions.txt │ │ ├── Groups.txt │ │ ├── ImportPackages.txt │ │ ├── ImportUseFullname.txt │ │ ├── Imports.txt │ │ ├── InvalidComplex.txt │ │ ├── InvalidMessage.txt │ │ ├── InvalidSimple.txt │ │ ├── Keywords.txt │ │ ├── Message.txt │ │ ├── NestedMessages.txt │ │ ├── Numbers.txt │ │ ├── PackageName.txt │ │ ├── Packed.txt │ │ ├── RemoteProtocol.txt │ │ ├── Simple.txt │ │ ├── SimpleWithComments.txt │ │ └── Singlequoted.txt │ └── proto │ │ ├── Complex.proto │ │ ├── DataTypes.proto │ │ ├── DhComplex.proto │ │ ├── Empty.proto │ │ ├── Enum.proto │ │ ├── Extensions.proto │ │ ├── Groups.proto │ │ ├── ImportPackages.proto │ │ ├── ImportUseFullname.proto │ │ ├── Imports.proto │ │ ├── InvalidComplex.proto │ │ ├── InvalidMessage.proto │ │ ├── InvalidSimple.proto │ │ ├── Keywords.proto │ │ ├── Message.proto │ │ ├── NestedMessages.proto │ │ ├── Numbers.proto │ │ ├── PackageName.proto │ │ ├── Packed.proto │ │ ├── RemoteProtocol.proto │ │ ├── Simple.proto │ │ ├── SimpleWithComments.proto │ │ └── Singlequoted.proto │ └── tests │ ├── BuffedStringTest.scala │ ├── EnumTest.scala │ ├── ExtendedMessageTest.scala │ ├── JavaInteroperabilityTest.scala │ ├── MessageTest.scala │ ├── PerformanceTest.scala │ └── ScalaBuffTest.scala ├── scalabuff-runtime └── src │ └── main │ └── net │ └── sandrogrzicic │ └── scalabuff │ ├── Enum.scala │ ├── ExtendableMessage.scala │ ├── LimitedInputStream.scala │ ├── Message.scala │ ├── MessageBuilder.scala │ └── Parser.scala └── sonatype.sbt /.gitignore: -------------------------------------------------------------------------------- 1 | .cache 2 | *.swp 3 | .idea 4 | .idea_modules 5 | *.iml 6 | 7 | target 8 | bin 9 | doc 10 | 11 | resources/generated 12 | 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | cache: 3 | directories: 4 | - $HOME/.ivy2/cache 5 | - $HOME/.sbt 6 | before_cache: 7 | - find $HOME/.ivy2/cache -name "ivydata-*.properties" -print -delete 8 | - find $HOME/.sbt -name "*.lock" -print -delete 9 | language: scala 10 | script: 11 | - sbt ++$TRAVIS_SCALA_VERSION +test 12 | scala: 2.12.3 13 | jdk: oraclejdk8 14 | 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | ScalaBuff license 2 | 3 | Copyright 2011-2013 Sandro Gržičić 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | 17 | -------------------------------------------------------------------------------- 18 | 19 | Google Protocol Buffers license 20 | 21 | Protocol Buffers - Google's data interchange format 22 | Copyright 2008 Google Inc. All rights reserved. 23 | http://code.google.com/p/protobuf/ 24 | 25 | Redistribution and use in source and binary forms, with or without 26 | modification, are permitted provided that the following conditions are 27 | met: 28 | 29 | * Redistributions of source code must retain the above copyright 30 | notice, this list of conditions and the following disclaimer. 31 | * Redistributions in binary form must reproduce the above 32 | copyright notice, this list of conditions and the following disclaimer 33 | in the documentation and/or other materials provided with the 34 | distribution. 35 | * Neither the name of Google Inc. nor the names of its 36 | contributors may be used to endorse or promote products derived from 37 | this software without specific prior written permission. 38 | 39 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 40 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 41 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 42 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 43 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 44 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 45 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 46 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 47 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 48 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 49 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ScalaBuff is a Scala [Protocol Buffers](https://developers.google.com/protocol-buffers/docs/overview) (protobuf) compiler. It takes .proto files and outputs valid Scala classes that can be used by your code to receive or send protobuf messages. 2 | 3 | Both the ScalaBuff generator and the generated Scala classes depend on Google's Java runtime for Protocol Buffers, which is provided with ScalaBuff. 4 | 5 | If you want to utilize ScalaBuff to generate your Scala classes from .proto sources, you'll need to either [download the source](https://github.com/SandroGrzicic/ScalaBuff/archive/master.zip) or download the packaged JAR for your Scala version from the Sonatype OSS repository. If you download the sources, you can easily run it from SBT or by generating a fat Jar with sbt assembly: 6 | 7 | ```sh 8 | $ sbt assembly 9 | $ java -jar target/scala-2.11/scalabuff-compiler-assembly-1.4.0.jar --proto_path=INPUT_DIR --scala_out=OUTPUT_DIR 10 | ``` 11 | 12 | If you just want to use ScalaBuff-generated classes in your SBT-managed project, here's the dependency to add (located on the Sonatype OSS repository): `"net.sandrogrzicic" %% "scalabuff-runtime" % "[desired_version]"` 13 | The latest release is **1.4.0** with support for Scala 2.10 and 2.11. 14 | 15 | If you'd like to use SBT with ScalaBuff to auto-generate Scala protobuf classes from .proto sources, try the [sbt-scalabuff project](https://github.com/sbt/sbt-scalabuff). 16 | 17 | The [ScalaBuff Wiki](https://github.com/SandroGrzicic/ScalaBuff/wiki) contains more information. For API documentation, see the project [Scaladoc](http://sandrogrzicic.github.com/ScalaBuff/doc/). 18 | 19 | For any questions or general discussion, you can use the [ScalaBuff Google Group](https://groups.google.com/forum/#!forum/scalabuff) but please feel free to [create new issues](https://github.com/SandroGrzicic/ScalaBuff/issues/new) for bug reports or feature requests. Thanks! 20 | 21 | 22 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | import sbt._ 2 | import sbt.Keys._ 3 | import ReleaseTransformations._ 4 | 5 | /** 6 | * ScalaBuff SBT build file. 7 | * 8 | * Useful SBT commands: 9 | * 10 | * run (arguments) Runs ScalaBuff inside SBT with the specified arguments. 11 | * test Runs the tests. 12 | * package Generates the main ScalaBuff compiler .JAR. 13 | * update-test-resources Regenerates the test resources using ScalaBuff. 14 | * 15 | * project scalabuffCompiler Switches to the compiler project (default). 16 | * project scalabuffRuntime Switches to the runtime project. 17 | * 18 | */ 19 | 20 | lazy val buildSettings = Seq( 21 | name := "ScalaBuff", 22 | organization := "net.sandrogrzicic", 23 | version := "1.4.0", 24 | scalaVersion := "2.11.4", 25 | logLevel := Level.Info 26 | ) 27 | 28 | lazy val releaseSettings = Seq( 29 | releaseCrossBuild := true, 30 | releaseProcess := Seq[ReleaseStep]( 31 | checkSnapshotDependencies, 32 | inquireVersions, 33 | runClean, 34 | runTest, 35 | setReleaseVersion, 36 | commitReleaseVersion, 37 | tagRelease, 38 | releaseStepCommand("publishSigned"), 39 | setNextVersion, 40 | commitNextVersion, 41 | releaseStepCommand("sonatypeReleaseAll"), 42 | pushChanges 43 | ) 44 | ) 45 | 46 | lazy val defaultSettings = Seq( 47 | 48 | scalaVersion := "2.12.3", 49 | organization := "uk.me.sandro", 50 | startYear := Option(2011), 51 | // credentials += Credentials(Path.userHome / ".sbt" / ".credentials"), 52 | 53 | resolvers ++= Seq( 54 | "Akka Maven Repository" at "http://akka.io/repository", 55 | "Typesafe Maven Repository" at "http://repo.typesafe.com/typesafe/releases/", 56 | "Sonatype OSS Repository" at "https://oss.sonatype.org/content/groups/public/" 57 | ), 58 | 59 | libraryDependencies ++= Dependencies.common ++ ( 60 | // TODO put this in a separate value 61 | CrossVersion.partialVersion(scalaVersion.value) match { 62 | case Some((2, scalaMajor)) if scalaMajor >= 11 => 63 | Seq("org.scala-lang.modules" %% "scala-parser-combinators" % "1.0.6") 64 | case _ => 65 | Seq() 66 | } 67 | ), 68 | 69 | crossScalaVersions ++= Seq("2.10.6", "2.11.11", "2.12.3"), 70 | 71 | scalacOptions ++= Seq( 72 | "-encoding", "utf8", "-unchecked", "-deprecation", "-feature", 73 | "-Xlog-reflective-calls" 74 | ) ++ (CrossVersion.partialVersion(scalaVersion.value) match { 75 | case Some((2, scalaMajor)) if scalaMajor >= 12 => 76 | Seq("-Xlint:-unused,-missing-interpolator,_", "-Ywarn-unused:-imports") 77 | case _ => 78 | Seq() 79 | }), 80 | javacOptions ++= Seq("-encoding", "utf8", "-Xlint:unchecked", "-Xlint:deprecation"), 81 | 82 | parallelExecution in GlobalScope := true, 83 | 84 | scalaSource in Compile := baseDirectory(_ / "src/main").value, 85 | scalaSource in Test := baseDirectory(_ / "src/test").value, 86 | 87 | javaSource in Compile := baseDirectory(_ / "src/main").value, 88 | javaSource in Test := baseDirectory(_ / "src/test").value, 89 | 90 | compileOrder := CompileOrder.Mixed, 91 | 92 | credentials += Credentials(Path.userHome / ".ivy2" / ".credentials") 93 | ) 94 | 95 | lazy val updateTestResourcesTask = fullRunTask(TaskKey[Unit]("update-test-resources"), Compile, "net.sandrogrzicic.scalabuff.test.UpdateTestResources") 96 | 97 | lazy val compilerProjectSettings = Seq( 98 | mainClass in(Compile, run) := Some("net.sandrogrzicic.scalabuff.compiler.ScalaBuff"), 99 | mainClass in(Compile, packageBin) := Some("net.sandrogrzicic.scalabuff.compiler.ScalaBuff"), 100 | updateTestResourcesTask 101 | ) 102 | 103 | lazy val scalabuffCompiler = project.in(file("scalabuff-compiler")) 104 | .settings(defaultSettings) 105 | .settings(compilerProjectSettings) 106 | .dependsOn(scalabuffRuntime % Test) 107 | 108 | lazy val scalabuffRuntime = project.in(file("scalabuff-runtime")) 109 | .settings(defaultSettings) 110 | 111 | // load the Compiler project at sbt startup 112 | onLoad in Global := (Command.process("project scalabuffCompiler", _: State)) compose (onLoad in Global).value 113 | -------------------------------------------------------------------------------- /project/Dependencies.scala: -------------------------------------------------------------------------------- 1 | import sbt._ 2 | 3 | object Dependencies { 4 | 5 | lazy val common = Seq( 6 | "com.google.protobuf" % "protobuf-java" % "2.5.0", 7 | "org.scalatest" %% "scalatest" % "3.0.1" % "test" 8 | ) 9 | 10 | } 11 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.16 2 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | // https://github.com/sbt/sbt-osgi 2 | addSbtPlugin("com.typesafe.sbt" % "sbt-osgi" % "0.7.0") 3 | 4 | // https://github.com/xerial/sbt-sonatype 5 | addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "2.0") 6 | 7 | // https://github.com/sbt/sbt-release 8 | addSbtPlugin("com.github.gseitz" % "sbt-release" % "1.0.6") 9 | 10 | // https://github.com/sbt/sbt-assembly 11 | addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.5") 12 | -------------------------------------------------------------------------------- /scalabuff-compiler/src/main/net/sandrogrzicic/scalabuff/compiler/BuffedString.scala: -------------------------------------------------------------------------------- 1 | package net.sandrogrzicic.scalabuff.compiler 2 | import scala.util.matching.Regex 3 | 4 | /** 5 | * String extension with some useful methods. 6 | * @author Sandro Gržičić 7 | */ 8 | 9 | class BuffedString(str: String) { 10 | import BuffedString.{camelCaseRegex, scalaReserved} 11 | 12 | /** 13 | * CamelCases this string, with the first letter uppercased. 14 | */ 15 | def camelCase: String = lowerCamelCase.capitalize 16 | 17 | /** 18 | * Adds backticks for reserved keywords or names with characters like spaces, symbols etc. 19 | */ 20 | def quotedIdent(name: String): String = { 21 | if (scalaReserved(name)) '`' + name + '`' 22 | else if (name.matches("[a-zA-Z_][\\w\\d_]*")) name 23 | else '`' + name + '`' 24 | } 25 | 26 | /** 27 | * Generates a valid Scala identifier: 28 | * camelCases this string, leaving the first letter lowercased and wraps it into backticks. 29 | */ 30 | def toScalaIdent: String = quotedIdent(lowerCamelCase) 31 | 32 | /** 33 | * camelCases this string, with the first letter lowercased. 34 | */ 35 | def lowerCamelCase: String = camelCaseRegex.replaceAllIn(str.replace('-', '_'), _.matched.tail.toUpperCase) 36 | 37 | /** 38 | * Generates a valid temporary Scala identifier: 39 | * camelCases this string and prefixes it with two underscores. 40 | */ 41 | def toTemporaryIdent: String = "__" + lowerCamelCase 42 | /** 43 | * Returns the tail of this string, starting at the first character after the last occurence of the specified character. 44 | */ 45 | def dropUntilLast(c: Char): String = str.drop(str.lastIndexOf(c)+1) 46 | 47 | /** 48 | * Returns the tail of this string, starting at the first character after the first occurence of the specified character. 49 | */ 50 | def dropUntilFirst(c: Char): String = str.drop(str.indexOf(c)+1) 51 | 52 | /** 53 | * Returns the head of this string, until the first occurence of the specified character. 54 | */ 55 | def takeUntilFirst(c: Char): String = str.take(str.indexOf(c)) 56 | 57 | /** 58 | * Returns the head of this string, until the last occurence of the specified character. 59 | */ 60 | def takeUntilLast(c: Char): String = str.take(str.lastIndexOf(c)) 61 | 62 | /** 63 | * Returns the substring between the specified characters on the last original string positions. 64 | * If any of the characters isn't found, the returned string is returned fully from the start and/or 65 | * to the end of the original string. 66 | * If the end position is lower than the start position, an empty string is returned. 67 | */ 68 | def betweenLast(from: Char, to: Char): String = { 69 | var fromPos = str.lastIndexOf(from) + 1 70 | var toPos = str.lastIndexOf(to, from) 71 | if (fromPos < 0) fromPos = 0 72 | if (toPos < 0) toPos = str.length 73 | if (fromPos > toPos) "" 74 | else str.substring(fromPos, toPos) 75 | } 76 | 77 | /** 78 | * Returns the substring between the specified characters. 79 | * If any of the characters isn't found, the returned string is returned fully from the start and/or 80 | * to the end of the original string. 81 | * If the end position is lower than the start position, an empty string is returned. 82 | */ 83 | def between(from: Char, to: Char): String = { 84 | var fromPos = str.indexOf(from) + 1 85 | var toPos = str.lastIndexOf(to, from) 86 | if (fromPos < 0) fromPos = 0 87 | if (toPos < 0) toPos = str.length 88 | if (fromPos > toPos) "" 89 | else str.substring(fromPos, toPos) 90 | } 91 | 92 | /** 93 | * Removes leading and trailing double quotes from this string, if any. 94 | */ 95 | def stripQuotes: String = str.stripPrefix("\"").stripSuffix("\"") 96 | } 97 | 98 | object BuffedString { 99 | /** 100 | * Generates as much tabs as there are indent levels. 101 | */ 102 | def indent(indentLevel: Int): String = "\t" * indentLevel 103 | 104 | val camelCaseRegex: Regex = """_(\w)""".r 105 | 106 | /** 107 | * Reserved scala keywords that require backticks 108 | */ 109 | val scalaReserved = 110 | Set("abstract", "case", "catch", "class", "def", "do", "else", 111 | "extends", "false", "final", "finally", "for", "forSome", 112 | "if", "implicit", "import", "lazy", "macro", "match", 113 | "new", "null", "object", "override", "package", "private", 114 | "protected", "return", "sealed", "super", "then", "this", 115 | "throw", "trait", "true", "try", "type", "val", "var", 116 | "while", "with", "yield") 117 | } 118 | -------------------------------------------------------------------------------- /scalabuff-compiler/src/main/net/sandrogrzicic/scalabuff/compiler/Enums.scala: -------------------------------------------------------------------------------- 1 | package net.sandrogrzicic.scalabuff.compiler 2 | 3 | import scala.language.implicitConversions 4 | 5 | /** 6 | * Viktor Klang's Enum 7 | * Source: https://gist.github.com/1057513/ 8 | */ 9 | trait Enum { 10 | 11 | import java.util.concurrent.atomic.AtomicReference 12 | 13 | type EnumVal <: Value 14 | 15 | private val _values = new AtomicReference(Vector[EnumVal]()) 16 | 17 | /** 18 | * Add an EnumVal to our storage, using CCAS to make sure it's thread safe, returns the ordinal. 19 | */ 20 | private final def addEnumVal(newVal: EnumVal): Int = { 21 | import _values.{get, compareAndSet => CAS} 22 | val oldVec = get 23 | val newVec = oldVec :+ newVal 24 | if ((get eq oldVec) && CAS(oldVec, newVec)) newVec.indexWhere(_ eq newVal) else addEnumVal(newVal) 25 | } 26 | 27 | /** 28 | * Get all the enums that exist for this type. 29 | */ 30 | def values: Vector[EnumVal] = _values.get 31 | 32 | protected trait Value { 33 | self: EnumVal => // Enforce that no one mixes in Value in a non-EnumVal type 34 | final val ordinal = addEnumVal(this) // Adds the EnumVal and returns the ordinal 35 | 36 | def name: String 37 | 38 | override def toString: String = name 39 | override def equals(other: Any): Boolean = this eq other.asInstanceOf[AnyRef] 40 | override def hashCode: Int = 31 * (this.getClass.## + name.## + ordinal) 41 | } 42 | 43 | } 44 | 45 | /** 46 | * Field labels. 47 | */ 48 | object FieldLabels extends Enum { 49 | sealed trait EnumVal extends Value 50 | 51 | val REQUIRED: EnumVal = new EnumVal { val name = "required" } 52 | val OPTIONAL: EnumVal = new EnumVal { val name = "optional" } 53 | val REPEATED: EnumVal = new EnumVal { val name = "repeated" } 54 | 55 | def apply(label: String): EnumVal = values.find(label == _.name).getOrElse { 56 | throw new InvalidFieldLabelException(label) 57 | } 58 | 59 | class InvalidFieldLabelException(label: String) extends RuntimeException( 60 | "Invalid field label: " + label 61 | ) 62 | } 63 | 64 | /** 65 | * Field types; both predefined and custom types are an instance of FieldTypes.EnumVal. 66 | */ 67 | object FieldTypes extends Enum { 68 | implicit def buffString(string: String): BuffedString = new BuffedString(string) 69 | 70 | /** 71 | * Represents a predefined field type. 72 | * @param name the field type name 73 | * @param scalaType the output field type name 74 | * @param defaultValue field type default value 75 | * @param wireType the field wire type. 76 | * @param isEnum whether the field is an Enum 77 | * @param isMessage whether the field is a Message 78 | */ 79 | trait EnumVal extends Value { 80 | import com.google.protobuf.WireFormat._ 81 | var name: String 82 | var scalaType: String 83 | var defaultValue: String 84 | var wireType: Int 85 | var isEnum: Boolean = false 86 | var isMessage: Boolean = false 87 | def packable: Boolean = !isMessage && 88 | (isEnum || wireType == WIRETYPE_VARINT || wireType == WIRETYPE_FIXED64 || wireType == WIRETYPE_FIXED32) 89 | } 90 | 91 | /** 92 | * A predefined field type; immutable. 93 | */ 94 | case class PredefinedEnumVal private[FieldTypes] ( 95 | name: String, scalaType: String, defaultValue: String, wireType: Int 96 | ) extends EnumVal { 97 | def name_=(name: String) {} 98 | def scalaType_=(scalaType: String) {} 99 | def defaultValue_=(defaultValue: String) {} 100 | def wireType_=(wireType: Int) {} 101 | } 102 | 103 | import com.google.protobuf.WireFormat._ 104 | 105 | val INT32 = PredefinedEnumVal("Int32", "Int", "0", WIRETYPE_VARINT) 106 | val UINT32 = PredefinedEnumVal("UInt32", "Int", "0", WIRETYPE_VARINT) 107 | val SINT32 = PredefinedEnumVal("SInt32", "Int", "0", WIRETYPE_VARINT) 108 | val FIXED32 = PredefinedEnumVal("Fixed32", "Int", "0", WIRETYPE_FIXED32) 109 | val SFIXED32 = PredefinedEnumVal("SFixed32", "Int", "0", WIRETYPE_FIXED32) 110 | val INT64 = PredefinedEnumVal("Int64", "Long", "0L", WIRETYPE_VARINT) 111 | val UINT64 = PredefinedEnumVal("UInt64", "Long", "0L", WIRETYPE_VARINT) 112 | val SINT64 = PredefinedEnumVal("SInt64", "Long", "0L", WIRETYPE_VARINT) 113 | val FIXED64 = PredefinedEnumVal("Fixed64", "Long", "0L", WIRETYPE_FIXED64) 114 | val SFIXED64 = PredefinedEnumVal("SFixed64", "Long", "0L", WIRETYPE_FIXED64) 115 | val BOOL = PredefinedEnumVal("Bool", "Boolean", "false", WIRETYPE_VARINT) 116 | val FLOAT = PredefinedEnumVal("Float", "Float", "0.0f", WIRETYPE_FIXED32) 117 | val DOUBLE = PredefinedEnumVal("Double", "Double", "0.0", WIRETYPE_FIXED64) 118 | val BYTES = PredefinedEnumVal("Bytes", "com.google.protobuf.ByteString", "com.google.protobuf.ByteString.EMPTY", WIRETYPE_LENGTH_DELIMITED) 119 | val STRING = PredefinedEnumVal("String", "String", "\"\"", WIRETYPE_LENGTH_DELIMITED) 120 | 121 | /** 122 | * A custom field type representing a Message or an Enum. 123 | */ 124 | case class CustomEnumVal private[FieldTypes] ( 125 | var name: String, var scalaType: String, var defaultValue: String, var wireType: Int 126 | ) extends EnumVal 127 | 128 | /** 129 | * Returns an immutable FieldType.PredefinedEnumVal based on the specified proto field type, 130 | * or a new EnumVal with a null default value if it's a custom Message or Enum type. 131 | */ 132 | def apply(fieldType: String): EnumVal = { 133 | values find { fType => 134 | fType.name.toLowerCase == fieldType.toLowerCase && fType.isInstanceOf[PredefinedEnumVal] 135 | } getOrElse CustomEnumVal(fieldType, fieldType, "null", WIRETYPE_LENGTH_DELIMITED) 136 | } 137 | 138 | } 139 | -------------------------------------------------------------------------------- /scalabuff-compiler/src/main/net/sandrogrzicic/scalabuff/compiler/Generator.scala: -------------------------------------------------------------------------------- 1 | package net.sandrogrzicic.scalabuff.compiler 2 | 3 | import annotation.tailrec 4 | import collection.mutable 5 | import net.sandrogrzicic.scalabuff.compiler.FieldTypes._ 6 | import com.google.protobuf._ 7 | import java.io.File 8 | 9 | /** 10 | * Scala class generator. 11 | * @author Sandro Gržičić 12 | */ 13 | 14 | class Generator protected (sourceName: String, importedSymbols: Map[String, ImportedSymbol], generateJsonMethod: Boolean, 15 | targetScalaVersion: Option[String]) { 16 | import Generator._ 17 | 18 | protected val imports = mutable.ListBuffer[String]() 19 | 20 | protected var packageName: String = "" 21 | protected var className: String = sourceName.takeUntilFirst('.').camelCase 22 | 23 | /** 24 | * Whether to optimize the resultant class for speed (true) or for code size (false). True by default. 25 | */ 26 | protected var optimizeForSpeed = true 27 | 28 | protected var lazyGetSerializedSize = false 29 | 30 | /** 31 | * Generates the Scala class code. 32 | */ 33 | protected def generate(tree: List[Node]): ScalaClass = { 34 | 35 | // ******************* 36 | // utility methods 37 | // ******************* 38 | 39 | /** 40 | * Enum generation 41 | */ 42 | def enum(enum: EnumStatement, indentLevel: Int = 0) = { 43 | val indentOuter = BuffedString.indent(indentLevel) 44 | val indent = indentOuter + "\t" 45 | 46 | val out = StringBuilder.newBuilder 47 | out 48 | .append(indentOuter).append("object ").append(enum.name).append(" extends net.sandrogrzicic.scalabuff.Enum {\n") 49 | .append(indent).append("sealed trait EnumVal extends Value\n") 50 | .append(indent).append("val _UNINITIALIZED = new EnumVal { val name = \"UNINITIALIZED ENUM VALUE\"; val id = -1 }\n\n") 51 | 52 | for (enumOption <- enum.options) { 53 | // options? 54 | } 55 | // declaration of constants 56 | for (const <- enum.constants) { 57 | out.append(indent) 58 | .append("val ").append(const.name).append(" = new EnumVal { ") 59 | .append("val name = \"").append(const.name).append("\"; ") 60 | .append("val id = ").append(const.id) 61 | .append(" }\n") 62 | } 63 | out.append("\n") 64 | // constants, as statics 65 | for (const <- enum.constants) { 66 | out.append(indent).append("val ").append(const.name).append("_VALUE = ").append(const.id).append("\n") 67 | } 68 | 69 | // valueOf 70 | out.append("\n").append(indent).append("def valueOf(id: Int) = ") 71 | if (optimizeForSpeed) { // O(1) 72 | // todo: find out why @annotation.switch doesn't work properly 73 | out.append("id match {\n") 74 | for (const <- enum.constants) { 75 | out.append(indent).append("\t") 76 | .append("case ").append(const.id).append(" => ").append(const.name).append("\n") 77 | } 78 | out.append(indent).append("\t").append("case _default => throw new net.sandrogrzicic.scalabuff.UnknownEnumException(_default)\n") 79 | out.append(indent).append("}\n") 80 | } else { // O(n) 81 | out.append("values.find(_.id == id).orNull\n") 82 | } 83 | 84 | // internalGetValueMap 85 | out.append(indent).append("val internalGetValueMap = new com.google.protobuf.Internal.EnumLiteMap[EnumVal] {\n") 86 | .append(indent).append("\tdef findValueByNumber(id: Int): EnumVal = valueOf(id)\n") 87 | .append(indent).append("}\n") 88 | 89 | out.append(indentOuter).append("}\n") 90 | 91 | out.mkString 92 | } 93 | 94 | /** 95 | * Message generation, recurses for nested messages. 96 | * Not tail-recursive, but shouldn't cause stack overflows on sane nesting levels. 97 | */ 98 | def message(name: String, body: MessageBody, indentLevel: Int = 0): String = { 99 | import FieldLabels.{ REQUIRED, OPTIONAL, REPEATED } 100 | import FieldTypes.{ INT32, UINT32, SINT32, FIXED32, SFIXED32, INT64, UINT64, SINT64, FIXED64, SFIXED64, BOOL, FLOAT, DOUBLE, BYTES, STRING } 101 | import WireFormat.{ WIRETYPE_VARINT, WIRETYPE_FIXED32, WIRETYPE_FIXED64, WIRETYPE_LENGTH_DELIMITED, WIRETYPE_START_GROUP, WIRETYPE_END_GROUP } 102 | 103 | val indent0 = BuffedString.indent(indentLevel) 104 | val (indent1, indent2, indent3, indent4) = (indent0 + "\t", indent0 + "\t\t", indent0 + "\t\t\t", indent0 + "\t\t\t\t") 105 | 106 | val fields = body.fields 107 | 108 | /** Whether this message has any extension ranges defined. */ 109 | var hasExtensionRanges = false 110 | 111 | body.options.foreach { 112 | case OptionValue(key, value) => // no options here 113 | } 114 | body.extensionRanges.foreach { 115 | case ExtensionRanges(extensionRanges) => 116 | hasExtensionRanges = true 117 | } 118 | 119 | /** main StringBuilder for the whole message */ 120 | val out = StringBuilder.newBuilder 121 | 122 | // *** case class 123 | out.append(indent0).append("final case class ").append(name).append(" (\n") 124 | // constructor 125 | for (field <- fields) { 126 | out.append(indent1).append(field.name.toScalaIdent).append(": ") 127 | field.label match { 128 | case REQUIRED => 129 | out.append(field.fType.scalaType).append(" = ").append(field.fType.defaultValue).append(",\n") 130 | case OPTIONAL => 131 | out.append("Option[").append(field.fType.scalaType).append("] = ").append(field.defaultValue).append(",\n") 132 | case REPEATED => 133 | out.append("scala.collection.immutable.Seq[").append(field.fType.scalaType).append("] = Vector.empty[").append(field.fType.scalaType).append("],\n") 134 | case _ => // "missing combination " 135 | } 136 | } 137 | if (!fields.isEmpty) out.length -= 2 138 | 139 | out.append('\n').append(indent0).append(") extends com.google.protobuf.") 140 | if (!hasExtensionRanges) { 141 | // normal message 142 | out.append("GeneratedMessageLite") 143 | out.append('\n').append(indent1).append("with com.google.protobuf.MessageLite.Builder") 144 | out.append('\n').append(indent1).append("with net.sandrogrzicic.scalabuff.Message[").append(name).append("]") 145 | } else { 146 | // extendable message 147 | out.append("GeneratedMessageLite.ExtendableMessage[").append(name).append("]") 148 | out.append('\n').append(indent1).append("with net.sandrogrzicic.scalabuff.ExtendableMessage[").append(name).append("]") 149 | } 150 | out.append('\n').append(indent1).append("with net.sandrogrzicic.scalabuff.Parser[").append(name).append("]") 151 | 152 | for (OptionValue(_, value) <- body.options.filter(_.key == "trait")) { 153 | out.append('\n').append(indent1).append("with ").append(value.replace("$name", name).stripQuotes) 154 | } 155 | 156 | out.append(" {\n\n") 157 | 158 | // setters 159 | for (field <- fields) { 160 | field.label match { 161 | case OPTIONAL => out.append(indent1) 162 | .append("def set").append(field.name.camelCase).append("(_f: ").append(field.fType.scalaType) 163 | .append(") = copy(").append(field.name.toScalaIdent).append(" = Some(_f))\n") 164 | case REPEATED => out 165 | .append(indent1).append("def set").append(field.name.camelCase).append("(_i: Int, _v: ").append(field.fType.scalaType) 166 | .append(") = copy(").append(field.name.toScalaIdent).append(" = ").append(field.name.toScalaIdent).append(".updated(_i, _v))\n") 167 | .append(indent1).append("def add").append(field.name.camelCase).append("(_f: ").append(field.fType.scalaType) 168 | .append(") = copy(").append(field.name.toScalaIdent).append(" = ").append(field.name.toScalaIdent).append(" :+ _f)\n") 169 | .append(indent1).append("def addAll").append(field.name.camelCase).append("(_f: ").append(field.fType.scalaType) 170 | .append("*) = copy(").append(field.name.toScalaIdent).append(" = ").append(field.name.toScalaIdent).append(" ++ _f)\n") 171 | .append(indent1).append("def addAll").append(field.name.camelCase).append("(_f: TraversableOnce[").append(field.fType.scalaType) 172 | .append("]) = copy(").append(field.name.toScalaIdent).append(" = ").append(field.name.toScalaIdent).append(" ++ _f)\n") 173 | case _ => // don't generate a setter for REQUIRED fields, as the copy method can be used 174 | } 175 | } 176 | out.append("\n") 177 | 178 | // clearers 179 | for (field <- fields if field.label != REQUIRED) { 180 | out.append(indent1).append("def clear").append(field.name.camelCase).append(" = copy(").append(field.name.toScalaIdent).append(" = ") 181 | field.label match { 182 | case OPTIONAL => out.append("None") 183 | case REPEATED => out.append("Vector.empty[").append(field.fType.scalaType).append("]") 184 | case _ => // don't generate a clearer for REQUIRED fields -> would result in an illegal message 185 | } 186 | out.append(")\n") 187 | } 188 | 189 | // writeTo(CodedOutputStream) 190 | out.append("\n").append(indent1) 191 | .append("def writeTo(output: com.google.protobuf.CodedOutputStream): Unit = {\n") 192 | 193 | fields.foreach { field => 194 | field.label match { 195 | case REQUIRED => out.append(indent2) 196 | .append("output.write").append(field.fType.name).append("(") 197 | .append(field.number).append(", ").append(field.name.toScalaIdent).append(")\n") 198 | case OPTIONAL => out.append(indent2).append("if (") 199 | .append(field.name.toScalaIdent).append(".isDefined) ") 200 | .append("output.write").append(field.fType.name).append("(") 201 | .append(field.number).append(", ").append(field.name.toScalaIdent).append(".get)\n") 202 | case REPEATED => 203 | (field.fType.packable, field.options.filter(value => value.key == "packed" && value.value == "true").headOption) match { 204 | case (true, Some(option)) => 205 | out.append(indent2).append(s"// write field ${field.name} packed \n") 206 | out.append(indent2).append("if (!").append(field.name.toScalaIdent).append(".isEmpty) {\n") 207 | out.append(indent3).append("import com.google.protobuf.CodedOutputStream._\n") 208 | out.append(indent3).append("val dataSize = ").append(field.name.toScalaIdent) 209 | .append(".map(compute").append(field.fType.name).append("SizeNoTag(_)).sum") 210 | .append(" \n") 211 | out.append(indent3).append("output.writeRawVarint32(") 212 | .append((field.number << 3) | WIRETYPE_LENGTH_DELIMITED).append(")").append("\n") 213 | out.append(indent3).append("output.writeRawVarint32(dataSize)").append("\n") 214 | if (optimizeForSpeed) { 215 | createWriteToWhile(false, "", indent3, indent4) 216 | } else { 217 | out.append(indent3).append("for (_v <- ") 218 | .append(field.name.toScalaIdent).append(") ") 219 | .append("output.write").append(field.fType.name).append("NoTag") 220 | .append("(_v)\n") 221 | } 222 | out.append(indent2).append("}\n") 223 | case _ => 224 | if (optimizeForSpeed) { 225 | createWriteToWhile(true, field.number.toString, indent2, indent3) 226 | } else { 227 | out.append(indent2).append("for (_v <- ") 228 | .append(field.name.toScalaIdent).append(") ") 229 | .append("output.write").append(field.fType.name) 230 | out.append("(").append(field.number).append(", _v)\n") 231 | } 232 | } 233 | case _ => // "missing combination " 234 | } 235 | 236 | def createWriteToWhile(useTag: Boolean, tagName: String, i1: String, i2: String): Unit = { 237 | val indexName = s"index_${field.name.toScalaIdent}" 238 | out.append(i1).append(s"var $indexName = 0\n") 239 | out.append(i1).append(s"while (").append(indexName) 240 | .append(" < ").append(field.name.toScalaIdent).append(".length) {\n") 241 | out.append(i2).append("output.write").append(field.fType.name) 242 | 243 | if (!useTag) { 244 | out.append("NoTag") 245 | } 246 | 247 | out.append("(") 248 | if (useTag) { 249 | out.append(field.number).append(", ") 250 | } 251 | 252 | out.append(field.name.toScalaIdent) 253 | .append("(").append(indexName).append("))\n") 254 | out.append(i2).append(indexName).append(" += 1\n") 255 | out.append(i1).append("}\n") 256 | } 257 | } 258 | out.append(indent1).append("}\n") 259 | 260 | // getSerializedSize 261 | val definition = if (lazyGetSerializedSize) "lazy val" else "def" 262 | 263 | out.append("\n").append(indent1).append(definition).append(" getSerializedSize = {\n") 264 | if(fields.nonEmpty) { // prevent Unused import compilation warning when there are no fields for size computing 265 | out.append(indent2).append("import com.google.protobuf.CodedOutputStream._\n") 266 | } 267 | out.append(indent2).append("var __size = 0\n") 268 | for (field <- fields) { 269 | field.label match { 270 | case REQUIRED => out.append(indent2) 271 | .append("__size += compute").append(field.fType.name).append("Size(") 272 | .append(field.number).append(", ").append(field.name.toScalaIdent).append(")\n") 273 | case OPTIONAL => out.append(indent2).append("if (") 274 | .append(field.name.toScalaIdent).append(".isDefined) ") 275 | .append("__size += compute").append(field.fType.name).append("Size(") 276 | .append(field.number).append(", ").append(field.name.toScalaIdent).append(".get)\n") 277 | case REPEATED => 278 | // TODO make this nicer currently code is generated 2 times 279 | (field.fType.packable, field.options.find(value => value.key == "packed" && value.value == "true")) match { 280 | case (true, Some(option)) => 281 | out.append(indent2).append("if (!").append(field.name.toScalaIdent).append(".isEmpty) {\n") 282 | out.append(indent3).append("val dataSize = ").append(field.name.toScalaIdent) 283 | .append(".map(compute").append(field.fType.name).append("SizeNoTag(_)).sum") 284 | .append(" \n") 285 | 286 | val tagSize = CodedOutputStream.computeTagSize(field.number) 287 | out.append(indent3).append(s"__size += $tagSize + computeInt32SizeNoTag(dataSize) + dataSize\n") 288 | out.append(indent2).append("}\n") 289 | case _ => 290 | if (optimizeForSpeed) { 291 | val indexName = s"index_${field.name.toScalaIdent}" 292 | out.append(indent2).append(s"var $indexName = 0\n") 293 | out.append(indent2).append(s"while (").append(indexName) 294 | .append(" < ").append(field.name.toScalaIdent).append(".length) {\n") 295 | out.append(indent3).append("__size += compute") 296 | .append(field.fType.name).append("Size(") 297 | .append(field.number).append(", ").append(field.name.toScalaIdent) 298 | .append("(").append(indexName).append("))\n") 299 | out.append(indent3).append(indexName).append(" += 1\n") 300 | out.append(indent2).append("}\n") 301 | } else { 302 | out.append(indent2).append("for (_v <- ") 303 | .append(field.name.toScalaIdent).append(") ") 304 | .append("__size += compute").append(field.fType.name).append("Size(") 305 | .append(field.number).append(", _v)\n") 306 | } 307 | } 308 | case _ => // "missing combination " 309 | } 310 | } 311 | out.append("\n").append(indent2).append("__size\n") 312 | .append(indent1).append("}\n") 313 | 314 | // mergeFrom(CodedInputStream, ExtensionRegistryLite) 315 | // need to split this into 2 versions: optimize for speed (current code) and for code size (use setters, generating new Messages each time) 316 | out.append("\n").append(indent1) 317 | .append("def mergeFrom(in: com.google.protobuf.CodedInputStream, extensionRegistry: com.google.protobuf.ExtensionRegistryLite): ") 318 | .append(name).append(" = {\n") 319 | .append(indent2).append("val _emptyRegistry = com.google.protobuf.ExtensionRegistryLite.getEmptyRegistry\n") 320 | 321 | for (field <- fields) { 322 | field.label match { 323 | case REQUIRED => out.append(indent2) 324 | .append("var ").append(field.name.toTemporaryIdent).append(": ").append(field.fType.scalaType) 325 | .append(" = ").append(field.fType.defaultValue).append("\n") 326 | case OPTIONAL => out.append(indent2) 327 | .append("var ").append(field.name.toTemporaryIdent).append(": Option[").append(field.fType.scalaType).append("]") 328 | .append(" = ").append(field.name.toScalaIdent).append("\n") 329 | case REPEATED => out.append(indent2) 330 | .append("val ").append(field.name.toTemporaryIdent).append(": scala.collection.mutable.Buffer[").append(field.fType.scalaType).append("]") 331 | .append(" = ").append(field.name.toScalaIdent).append(".toBuffer\n") 332 | case _ => // "missing combination " 333 | } 334 | } 335 | out.append("\n") 336 | .append(indent2).append("def __newMerged = ").append(name).append("(\n") 337 | fields.foreach { field => 338 | out.append(indent3) 339 | if (field.label == REPEATED) out.append("Vector(") 340 | out.append(field.name.toTemporaryIdent) 341 | if (field.label == REPEATED) out.append(": _*)") 342 | out.append(",\n") 343 | } 344 | if (!fields.isEmpty) out.length -= 2 345 | out.append("\n") 346 | out.append(indent2).append(")\n") 347 | .append(indent2).append("while (true) in.readTag match {\n") 348 | .append(indent3).append("case 0 => return __newMerged\n") 349 | var isOptional = false 350 | for (field <- fields) { 351 | isOptional = field.label match { 352 | case OPTIONAL => true 353 | case _ => false 354 | } 355 | out.append(indent3).append("case ").append((field.number << 3) | field.fType.wireType).append(" => ") 356 | out.append(field.name.toTemporaryIdent).append(" ") 357 | 358 | if (field.label == REPEATED) out.append("+") 359 | out.append("= ") 360 | if (isOptional) out.append("Some(") 361 | if (field.fType == WIRETYPE_LENGTH_DELIMITED) out.append("in.readBytes()") 362 | else if (field.fType.isEnum) { 363 | // IFF this is an optional field, AND it has a default (i.e. is not 364 | // None), then fall back to using that default if an exception is 365 | // thrown. 366 | if (isOptional && field.defaultValue != "None") { 367 | out.append("try { ") 368 | out.append(field.fType.scalaType.takeUntilLast('.')).append(".valueOf(in.readEnum())") 369 | out.append(" } catch { case e: Exception => ").append(field.defaultValue).append(".get }") 370 | 371 | } else { 372 | out.append(field.fType.scalaType.takeUntilLast('.')).append(".valueOf(in.readEnum())") 373 | } 374 | } else if (field.fType.isMessage) { 375 | 376 | out.append("readMessage[").append(field.fType.scalaType).append("](in, ") 377 | field.label match { 378 | case REQUIRED => out.append(field.name.toTemporaryIdent) 379 | case OPTIONAL => out 380 | .append(field.name.toTemporaryIdent).append(".orElse({\n") 381 | .append(indent3).append("\t").append(field.name.toTemporaryIdent).append(" = ").append(field.fType.defaultValue).append("\n") 382 | .append(indent3).append("\t").append(field.name.toTemporaryIdent).append("\n") 383 | .append(indent3).append("}).get") 384 | case REPEATED => out 385 | .append(field.fType.defaultValue) 386 | case _ => // "missing combination " 387 | } 388 | out.append(", _emptyRegistry)") 389 | 390 | } else out.append("in.read").append(field.fType.name).append("()") 391 | if(isOptional) out.append(")") 392 | out.append("\n") 393 | 394 | if (field.fType.packable && field.label == REPEATED) { 395 | out.append(indent3).append("case ").append((field.number << 3) | WIRETYPE_LENGTH_DELIMITED).append(" => ") 396 | out.append("\n") 397 | .append(indent4).append("val length = in.readRawVarint32()\n") 398 | .append(indent4).append("val limit = in.pushLimit(length)\n") 399 | .append(indent4).append("while (in.getBytesUntilLimit() > 0) {\n") 400 | .append(indent1).append(indent4).append(field.name.toTemporaryIdent).append(" += ") 401 | if (field.fType.isEnum) 402 | out.append(field.fType.scalaType.takeUntilLast('.')).append(".valueOf(in.readEnum())") 403 | else 404 | out.append("in.read").append(field.fType.name).append("()") 405 | out.append("\n").append(indent4).append("}\n") 406 | out.append(indent4).append("in.popLimit(limit)\n") 407 | } 408 | } 409 | out.append(indent3).append("case default => if (!in.skipField(default)) return __newMerged\n") 410 | out 411 | .append(indent2).append("}\n") 412 | .append(indent2).append("null\n") // compiler needs a return value 413 | .append(indent1).append("}\n") 414 | 415 | // mergeFrom(Message) 416 | out.append("\n").append(indent1) 417 | .append("def mergeFrom(m: ").append(name).append(") = {\n") 418 | .append(indent2).append(name).append("(\n") 419 | for (field <- fields) { 420 | field.label match { 421 | case REQUIRED => out.append(indent3) 422 | .append("m.").append(field.name.toScalaIdent).append(",\n") 423 | case OPTIONAL => out.append(indent3) 424 | .append("m.").append(field.name.toScalaIdent).append(".orElse(") 425 | .append(field.name.toScalaIdent).append("),\n") 426 | case REPEATED => out.append(indent3) 427 | .append(field.name.toScalaIdent).append(" ++ ") 428 | .append("m.").append(field.name.toScalaIdent).append(",\n") 429 | case _ => // "missing combination " 430 | } 431 | } 432 | if (!fields.isEmpty) out.length -= 2 433 | out.append("\n").append(indent2).append(")\n") 434 | out.append(indent1).append("}\n") 435 | 436 | out.append("\n") 437 | .append(indent1).append("def getDefaultInstanceForType = ").append(name).append(".defaultInstance\n") 438 | .append(indent1).append("def clear = getDefaultInstanceForType\n") 439 | .append(indent1).append("def isInitialized = true\n") 440 | .append(indent1).append("def build = this\n") 441 | .append(indent1).append("def buildPartial = this\n") 442 | 443 | out.append(indent1) 444 | .append("def parsePartialFrom(cis: com.google.protobuf.CodedInputStream, er: com.google.protobuf.ExtensionRegistryLite) = ") 445 | .append("mergeFrom(cis, er)\n") 446 | 447 | out.append(indent1).append("override def getParserForType = this\n") 448 | 449 | if (!hasExtensionRanges) { 450 | out 451 | .append(indent1).append("def newBuilderForType = getDefaultInstanceForType\n") 452 | .append(indent1).append("def toBuilder = this\n") 453 | } else { 454 | out 455 | .append(indent1).append("def newBuilderForType = throw new RuntimeException(\"Method not available.\")\n") 456 | .append(indent1).append("def toBuilder = throw new RuntimeException(\"Method not available.\")\n") 457 | } 458 | 459 | // toJson 460 | if (generateJsonMethod) { 461 | out 462 | .append(indent1).append("def toJson(indent: Int = 0): String = {\n") 463 | .append(indent2).append("val indent0 = \"\\n\" + (\"\\t\" * indent)\n") 464 | .append(indent2).append("val (indent1, indent2) = (indent0 + \"\\t\", indent0 + \"\\t\\t\")\n") 465 | .append(indent2).append("val sb = StringBuilder.newBuilder\n") 466 | .append(indent2).append("sb\n") 467 | .append(indent3).append(".append(\"{\")\n") 468 | 469 | for (field <- fields) { 470 | val name = field.name.lowerCamelCase 471 | val (quotesStart, quotesEnd) = if (!field.fType.isMessage) (".append(\"\\\"\")", ".append(\"\\\"\")") else ("", "") 472 | val mapQuotes = if (!field.fType.isMessage) ".map(\"\\\"\" + _ + \"\\\"\")" else "" 473 | val toJson = if (field.fType.isMessage) ".toJson(indent + 1)" else "" 474 | val mapToJson = if (field.fType.isMessage) ".map(_.toJson(indent + 1))" else "" 475 | 476 | field.label match { 477 | case REQUIRED => 478 | out.append(indent3).append("sb.append(indent1).append(\"\\\"").append(name) 479 | .append("\\\": \")").append(quotesStart).append(".append(`").append(name) 480 | .append("`").append(toJson).append(")").append(quotesEnd).append(".append(',')").append('\n') 481 | case OPTIONAL => 482 | out.append(indent3) 483 | .append("if (`").append(name).append("`.isDefined) { ").append("sb.append(indent1).append(\"\\\"").append(name) 484 | .append("\\\": \")").append(quotesStart).append(".append(`").append(name) 485 | .append("`.get").append(toJson).append(")").append(quotesEnd).append(".append(',') }").append('\n') 486 | case REPEATED => 487 | out.append(indent3).append("sb.append(indent1).append(\"\\\"").append(name).append("\\\": [\")") 488 | .append(".append(indent2).append(`").append(name).append("`").append(mapToJson).append(mapQuotes) 489 | .append(".mkString(\", \" + indent2)).append(indent1).append(']').append(',')").append('\n') 490 | case _ => 491 | } 492 | } 493 | out.append(indent2).append("if (sb.last.equals(',')) sb.length -= 1\n") 494 | out.append(indent2).append("sb.append(indent0).append(\"}\")\n") 495 | 496 | out.append(indent2).append("sb.toString()\n") 497 | out.append(indent1).append("}\n\n") 498 | 499 | } else { 500 | out.append(indent1).append("def toJson(indent: Int = 0): String = \"ScalaBuff JSON generation not enabled. Use --generate_json_method to enable.\"\n") 501 | } 502 | 503 | out.append(indent0).append("}\n\n") 504 | 505 | // *** companion object 506 | out.append(indent0).append("object ").append(name).append(" {\n") 507 | .append(indent1) 508 | 509 | out.append("@scala.beans.BeanProperty val defaultInstance = new ") 510 | 511 | out.append(name).append("()\n").append("\n") 512 | 513 | // parseFrom() 514 | out.append(indent1).append("def parseFrom(data: Array[Byte]): ").append(name) 515 | .append(" = defaultInstance.mergeFrom(data)\n") 516 | out.append(indent1).append("def parseFrom(data: Array[Byte], offset: Int, length: Int): ").append(name) 517 | .append(" = defaultInstance.mergeFrom(data, offset, length)\n") 518 | out.append(indent1).append("def parseFrom(byteString: com.google.protobuf.ByteString): ").append(name) 519 | .append(" = defaultInstance.mergeFrom(byteString)\n") 520 | out.append(indent1).append("def parseFrom(stream: java.io.InputStream): ").append(name) 521 | .append(" = defaultInstance.mergeFrom(stream)\n") 522 | out.append(indent1).append("def parseDelimitedFrom(stream: java.io.InputStream): Option[").append(name) 523 | .append("] = defaultInstance.mergeDelimitedFromStream(stream)\n") 524 | out.append("\n") 525 | 526 | // field number integer constants 527 | for (field <- fields) { 528 | out.append(indent1) 529 | .append("val ").append(field.name.toUpperCase) 530 | .append("_FIELD_NUMBER = ").append(field.number).append("\n") 531 | } 532 | 533 | out.append("\n") 534 | 535 | // newBuilder 536 | out 537 | .append(indent1).append("def newBuilder = defaultInstance.newBuilderForType\n") 538 | .append(indent1).append("def newBuilder(prototype: ").append(name).append(") = defaultInstance.mergeFrom(prototype)\n") 539 | 540 | out.append("\n") 541 | 542 | // append any nested enums 543 | for (e <- body.enums) { 544 | out.append(enum(e, indentLevel + 1)).append("\n") 545 | } 546 | 547 | // append any nested groups 548 | body.groups.foreach { 549 | case Group(label, nestedName, id, nestedBody) => // not supported yet (also, deprecated..) 550 | } 551 | // append any nested message extensions 552 | body.extensions.foreach { 553 | case Extension(nestedName, nestedBody) => // not supported yet 554 | } 555 | // append any nested messages (recursive) 556 | body.messages.foreach { 557 | case Message(nestedName, nestedBody) => out.append(message(nestedName, nestedBody, indentLevel + 1)) 558 | } 559 | 560 | // finalize object 561 | out.append(indent0).append("}\n") 562 | 563 | out.mkString 564 | } 565 | 566 | /** 567 | * Recursively traverse the tree. 568 | */ 569 | @tailrec 570 | def traverse(tree: List[Node], output: StringBuilder = StringBuilder.newBuilder): String = { 571 | tree match { 572 | // if the tree is empty, return the generated output 573 | case Nil => output.mkString 574 | // else, build upon the output and call traverse() again with the tree tail 575 | case node :: tail => 576 | node match { 577 | case Message(name, body) => output.append(message(name, body)) 578 | case Extension(name, body) => // very similar to Message 579 | case e: EnumStatement => output.append(enum(e)) 580 | case ImportStatement(name) => imports += name 581 | case PackageStatement(name) => if (packageName.isEmpty) packageName = name 582 | case OptionValue(key, value) => key match { 583 | case "java_package" => packageName = value.stripQuotes 584 | case "java_outer_classname" => className = value.stripQuotes 585 | case "lazy_compute_serialized_size" => value match { 586 | case "true" => lazyGetSerializedSize = true 587 | case "false" => lazyGetSerializedSize = false 588 | case _ => throw new InvalidOptionValueException(key, value) 589 | } 590 | case "optimize_for" => value match { 591 | case "SPEED" => optimizeForSpeed = true 592 | case "CODE_SIZE" => optimizeForSpeed = false 593 | case "LITE_RUNTIME" => optimizeForSpeed = true 594 | case _ => throw new InvalidOptionValueException(key, value) 595 | } 596 | case _ => // ignore options which aren't recognized 597 | } 598 | case _ => throw new UnexpectedNodeException(node) 599 | } 600 | traverse(tail, output) 601 | } 602 | } 603 | 604 | // ********************** 605 | // additional tree passes 606 | // ********************** 607 | 608 | recognizeCustomTypes(tree, importedSymbols) 609 | prependParentClassNames(tree, getAllNestedMessageTypes(tree)) 610 | setDefaultsForOptionalFields(tree) 611 | fullySpecifyImportedSymbols(tree, importedSymbols) 612 | 613 | // final tree pass: traverse the tree, so we can get class/package names, options, etc. 614 | val generatedOutput = traverse(tree) 615 | 616 | // Now that we have processed all of the options, make sure that the class name doesn't 617 | // match one of the top level type names. 618 | val matchingTypeName = tree.exists { 619 | case msg: Message => msg.name == className 620 | case e: EnumStatement => e.name == className 621 | case _ => false 622 | } 623 | 624 | if (matchingTypeName) { 625 | throw new GenerationFailureException("Cannot generate valid Scala output because the class name, '%s' for the extension registry class matches the name of one of the messages or enums declared in the .proto. Please either rename the type or use the java_outer_classname option to specify a different class name for the .proto file.".format(className)) 626 | } 627 | 628 | val output = StringBuilder.newBuilder 629 | 630 | // header 631 | output 632 | .append("// Generated by ScalaBuff, the Scala Protocol Buffers compiler. DO NOT EDIT!\n") 633 | .append("// source: ") 634 | .append(sourceName) 635 | .append("\n\n") 636 | // package 637 | if (!packageName.isEmpty) 638 | output.append("package ").append(packageName).append("\n\n") 639 | 640 | // imports 641 | imports.foreach { i => 642 | output.append("//import ").append(i).append("\n") 643 | } 644 | if (imports.size > 0) output.append("\n") 645 | imports.clear() 646 | 647 | // generated output 648 | output.append(generatedOutput).append("\n") 649 | 650 | val messageNames = tree.collect { case m:Message => m }.map(_.name) 651 | // outer object 652 | output 653 | .append("object ").append(className).append(" {\n") 654 | .append("\tdef registerAllExtensions(registry: com.google.protobuf.ExtensionRegistryLite): Unit = {\n") 655 | .append("\t}\n\n") 656 | 657 | .append("\tprivate val fromBinaryHintMap = collection.immutable.HashMap[String, Array[Byte] ⇒ com.google.protobuf.GeneratedMessageLite](").append("\n") 658 | .append(messageNames.map { m => "\t\t" + s""" "$m" -> (bytes ⇒ $m.parseFrom(bytes))""" } mkString ",\n") 659 | .append("\n\t)").append("\n") 660 | 661 | .append("\n") 662 | .append("\tdef deserializePayload(payload: Array[Byte], payloadType: String): com.google.protobuf.GeneratedMessageLite = {").append("\n") 663 | .append("\t\tfromBinaryHintMap.get(payloadType) match {").append("\n") 664 | .append("\t\t\tcase Some(f) ⇒ f(payload)").append("\n") 665 | .append("\t\t\tcase None ⇒ throw new IllegalArgumentException(s\"unimplemented deserialization of message payload of type [${payloadType}]\")").append("\n") 666 | .append("\t\t}").append("\n") 667 | .append("\t}").append("\n") 668 | 669 | .append("}\n") 670 | 671 | ScalaClass(output.mkString, packageName.replace('.', File.separatorChar) + File.separatorChar, className) 672 | } 673 | 674 | } 675 | 676 | object Generator { 677 | /** 678 | * Returns a valid Scala class. 679 | */ 680 | def apply(tree: List[Node], sourceName: String, importedSymbols: Map[String, ImportedSymbol], generateJsonMethod: Boolean, 681 | targetScalaVersion: Option[String]): ScalaClass = { 682 | new Generator(sourceName, importedSymbols, generateJsonMethod, targetScalaVersion).generate(tree) 683 | } 684 | 685 | /** 686 | * Modifies some fields of Message and Enum types so that they can be used properly. 687 | * Discovers whether each field type is a Message or an Enum. 688 | */ 689 | protected def recognizeCustomTypes(tree: List[Node], importedSymbols: Map[String, ImportedSymbol]) { 690 | val (enumNames, customFieldTypes) = getEnumNames(tree) 691 | fixCustomTypes(tree, enumNames, customFieldTypes, importedSymbols) 692 | } 693 | 694 | /** Return all enum names and custom field types found in the specified tree. */ 695 | protected def getEnumNames( 696 | tree: List[Node], 697 | enumNames: mutable.HashSet[String] = mutable.HashSet.empty[String], 698 | customFieldTypes: mutable.ArrayBuffer[FieldTypes.EnumVal] = mutable.ArrayBuffer.empty[FieldTypes.EnumVal] 699 | ): (mutable.HashSet[String], mutable.ArrayBuffer[FieldTypes.EnumVal]) = { 700 | 701 | for (node <- tree) { 702 | node match { 703 | case Message(name, body) => 704 | enumNames ++= body.enums.map(_.name) 705 | customFieldTypes ++= body.fields.map(_.fType) collect { case t: CustomEnumVal => t } 706 | getEnumNames(body.messages, enumNames, customFieldTypes) 707 | case EnumStatement(name, constants, options) => enumNames += name 708 | case _ => 709 | } 710 | } 711 | (enumNames, customFieldTypes) 712 | } 713 | 714 | /** Update fields which have custom types. */ 715 | protected def fixCustomTypes(tree: List[Node], enumNames: mutable.Set[String], customFieldTypes: mutable.Buffer[EnumVal], importedSymbols: Map[String, ImportedSymbol]) { 716 | for (fType <- customFieldTypes if !fType.isMessage && !fType.isEnum) { 717 | if (enumNames.contains(fType.name.dropUntilLast('.')) || importedSymbols.get(fType.scalaType).exists(_.isEnum)) { 718 | fType.isEnum = true 719 | fType.name = "Enum" 720 | fType.defaultValue = fType.scalaType + "._UNINITIALIZED" 721 | fType.scalaType += ".EnumVal" 722 | fType.wireType = WireFormat.WIRETYPE_VARINT 723 | } else { 724 | fType.isMessage = true 725 | fType.name = "Message" 726 | fType.defaultValue = fType.scalaType + ".defaultInstance" 727 | } 728 | } 729 | } 730 | 731 | /** Returns all message types of nested messages. */ 732 | protected def getAllNestedMessageTypes(tree: List[Node]): Map[Message, List[String]] = { 733 | val nestedMessages = new mutable.HashMap[Message, List[String]] 734 | for (node <- tree) node match { 735 | case m @ Message(name, body) => 736 | for (innerMessage <- body.messages) { 737 | nestedMessages.put(m, innerMessage.name :: nestedMessages.getOrElse(m, Nil)) 738 | } 739 | nestedMessages ++= getAllNestedMessageTypes(body.messages) 740 | case _ => Nil 741 | } 742 | nestedMessages.toMap 743 | } 744 | 745 | /** Prepend parent class names to all nested custom field types. */ 746 | val processedFieldTypes = new mutable.HashSet[FieldTypes.EnumVal]() 747 | val processedEnums = new mutable.HashSet[FieldTypes.EnumVal]() 748 | protected def prependParentClassNames(tree: List[Node], nestedMessageTypes: Map[Message, List[String]]) { 749 | for (node <- tree) node match { 750 | case parent @ Message(parentName, parentBody) => 751 | // prepend parent class names to messages 752 | parentBody.messages.foreach { 753 | case child @ Message(_, nestedMessage) => 754 | val filteredFields = parentBody.fields.withFilter(f => f.fType.isMessage && !processedFieldTypes(f.fType)) 755 | for (field <- filteredFields) { 756 | val fType = field.fType 757 | // prepend only if the mesage type is a child of the parent message 758 | if (nestedMessageTypes(parent).contains(fType.scalaType)) { 759 | fType.scalaType = parentName + "." + fType.scalaType 760 | fType.defaultValue = parentName + "." + fType.defaultValue 761 | processedFieldTypes += fType 762 | } 763 | } 764 | // recurse for any nested messages 765 | prependParentClassNames(child :: nestedMessage.messages, nestedMessageTypes) 766 | } 767 | // prepend parent class names to all nested enums 768 | parentBody.enums.foreach { 769 | case EnumStatement(eName, eConstants, eOptions) => 770 | for (field <- parentBody.fields.withFilter { f => f.fType.isEnum && !processedEnums(f.fType) }) { 771 | val fType = field.fType 772 | processedEnums += fType 773 | fType.scalaType = parentName + "." + fType.scalaType 774 | fType.defaultValue = fType.scalaType.replace(".EnumVal", "") + "._UNINITIALIZED" 775 | } 776 | } 777 | case _ => 778 | } 779 | } 780 | 781 | protected def setDefaultsForOptionalFields(tree: List[Node]) { 782 | for (node <- tree) node match { 783 | case Message(_, body) => 784 | for (field <- body.fields) { 785 | field.defaultValue = { 786 | if (field.label == FieldLabels.OPTIONAL) { 787 | field.options.find(_.key == "default") match { 788 | case Some(option) => "Some(" + { 789 | val qualifiedType = field.fType.scalaType.takeUntilLast('.') 790 | if (qualifiedType.isEmpty) option.value 791 | else qualifiedType + "." + option.value 792 | } + { 793 | if (field.fType.scalaType == "Float") "f" else "" 794 | } + 795 | ")" 796 | 797 | case None => "None" 798 | } 799 | } else { 800 | field.fType.defaultValue 801 | } 802 | } 803 | } 804 | // recurse for any nested messages 805 | setDefaultsForOptionalFields(body.messages) 806 | case _ => 807 | } 808 | } 809 | 810 | protected def fullySpecifyImportedSymbols(tree: List[Node], importedSymbols: Map[String, ImportedSymbol]) { 811 | def apply(node: Node) { 812 | node match { 813 | case Message(_, body) => 814 | body.messages.foreach(apply) 815 | body.fields.foreach { field => 816 | val scalaType = if (field.fType.scalaType endsWith ".EnumVal") 817 | field.fType.scalaType.split("\\.")(0) 818 | else 819 | field.fType.scalaType 820 | importedSymbols.filter { 821 | case (name, symbol) => 822 | val shortName = scalaType.stripPrefix(symbol.protoPackage + ".") 823 | name == shortName || shortName.startsWith(name + ".") 824 | }.foreach { 825 | case (name, symbol) => 826 | // namespaces might be empty for imported message types 827 | val namespacePrefix = if (symbol.packageName.isEmpty) "" else symbol.packageName + "." 828 | val protoPkgPrefix = if (symbol.protoPackage.isEmpty) "" else symbol.protoPackage + "." 829 | field.fType.scalaType = namespacePrefix + field.fType.scalaType.stripPrefix(protoPkgPrefix) 830 | field.fType.defaultValue = namespacePrefix + field.fType.defaultValue.stripPrefix(protoPkgPrefix) 831 | } 832 | } 833 | case _ => 834 | } 835 | } 836 | tree.foreach(apply) 837 | } 838 | } 839 | 840 | case class ImportedSymbol(packageName: String, isEnum: Boolean, protoPackage: String = "") 841 | 842 | /** 843 | * A generated Scala class. The path is relative. 844 | */ 845 | case class ScalaClass(body: String, path: String, file: String) { 846 | assert(path.endsWith(File.separator), "path must end with a " + File.separator) 847 | assert(!file.contains(File.separator), "file name must not contain a " + File.separator) 848 | } 849 | 850 | /** 851 | * Thrown when a valid Scala class cannot be generated using the the tree returned from the Parser. 852 | */ 853 | class GenerationFailureException(message: String) extends RuntimeException(message) 854 | 855 | /** 856 | * Thrown when a Node occurs in an unexpected location in the tree. 857 | */ 858 | class UnexpectedNodeException(node: Node, parentNode: Node = null) extends GenerationFailureException( 859 | "Unexpected child node " + node + (if (parentNode ne null) "found in " + parentNode else "") 860 | ) 861 | 862 | class InvalidOptionValueException(key: String, value: String) extends GenerationFailureException( 863 | "Invalid option value " + value + " for key " + key 864 | ) 865 | -------------------------------------------------------------------------------- /scalabuff-compiler/src/main/net/sandrogrzicic/scalabuff/compiler/Nodes.scala: -------------------------------------------------------------------------------- 1 | package net.sandrogrzicic.scalabuff.compiler 2 | 3 | /** 4 | * Nodes produced by the Parser. 5 | * @author Sandro Gržičić 6 | */ 7 | 8 | /** 9 | * AST node. 10 | */ 11 | sealed abstract class Node 12 | 13 | case class ImportStatement(packageName: String) extends Node 14 | 15 | case class PackageStatement(packageName: String) extends Node 16 | 17 | case class OptionValue(key: String, value: String) extends Node 18 | 19 | case class MessageBody( 20 | fields: List[Field], enums: List[EnumStatement], messages: List[Message], 21 | extensionRanges: List[ExtensionRanges], extensions: List[Extension], 22 | groups: List[Group], options: List[OptionValue] 23 | ) 24 | 25 | case class Message(name: String, body: MessageBody) extends Node 26 | 27 | case class Extension(name: String, body: MessageBody) extends Node 28 | 29 | case class ExtensionRanges(list: List[ExtensionRange]) extends Node 30 | 31 | case class ExtensionRange(from: Int, to: Int = -1) extends Node 32 | 33 | case class Group(label: FieldLabels.EnumVal, name: String, number: Int, body: MessageBody) extends Node 34 | 35 | case class Field( 36 | label: FieldLabels.EnumVal, fType: FieldTypes.EnumVal, name: String, 37 | number: Int, options: List[OptionValue], var defaultValue: String = "" 38 | ) extends Node 39 | 40 | case class EnumStatement(name: String, constants: List[EnumConstant], options: List[OptionValue]) extends Node 41 | 42 | case class EnumConstant(name: String, id: Int) extends Node 43 | 44 | -------------------------------------------------------------------------------- /scalabuff-compiler/src/main/net/sandrogrzicic/scalabuff/compiler/Parser.scala: -------------------------------------------------------------------------------- 1 | package net.sandrogrzicic.scalabuff.compiler 2 | 3 | import util.parsing.combinator._ 4 | import util.parsing.input.{ PagedSeqReader, CharSequenceReader } 5 | import collection.immutable.PagedSeq 6 | import collection.mutable.ListBuffer 7 | import scala.language.postfixOps 8 | 9 | /** 10 | * Main Protobuf parser. 11 | * @author Sandro Gržičić 12 | */ 13 | class Parser(val inputName: String) extends RegexParsers with PackratParsers { 14 | 15 | // skip C/C++ style comments and whitespace. 16 | override protected val whiteSpace = """((/\*(?:.|\r|\n)*?\*/)|//.*|\s+)+""".r 17 | 18 | lazy val protoParser: PackratParser[List[Node]] = ((message | extension | enumP | importP | packageP | option) *) 19 | 20 | lazy val message: PackratParser[Message] = "message" ~> identifier ~ messageBody ^^ { 21 | case name ~ body => Message(name, body) 22 | } 23 | 24 | lazy val messageBody: PackratParser[MessageBody] = ("{" ~> ((field | enumP | message | extension | extensionRanges | group | option) *) <~ "}") ^^ { 25 | body => parseBody(body) 26 | } 27 | 28 | lazy val extension: PackratParser[Extension] = ("extend" ~> userType ~ ("{" ~> ((field | group) *) <~ "}")) ^^ { 29 | case name ~ body => Extension(name, parseBody(body.filter(_.isInstanceOf[Node]))) 30 | } 31 | 32 | lazy val enumP: PackratParser[EnumStatement] = ("enum" ~> identifier <~ "{") ~ ((option | enumField | ";") *) <~ "}" <~ (";" ?) ^^ { 33 | case name ~ values => { 34 | val constants = values.view.collect { case constant: EnumConstant => constant } 35 | val options = values.view.collect { case option: OptionValue => option } 36 | EnumStatement(name, constants.toList, options.toList) 37 | } 38 | } 39 | lazy val enumField: PackratParser[EnumConstant] = (identifier <~ "=") ~ integerConstant <~ ";" ^^ { 40 | case name ~ id => EnumConstant(name, id.toInt) 41 | } 42 | 43 | lazy val importP: PackratParser[ImportStatement] = "import" ~> quotedStringConstant <~ ";" ^^ { 44 | importedPackage => ImportStatement(importedPackage) 45 | } 46 | 47 | lazy val packageP: PackratParser[PackageStatement] = "package" ~> (identifier ~ (("." ~ identifier) *)) <~ ";" ^^ { 48 | case ident ~ idents => PackageStatement(ident + idents.map(i => i._1 + i._2).mkString.stripQuotes) 49 | } 50 | 51 | lazy val option: PackratParser[OptionValue] = "option" ~> optionBody <~ ";" 52 | 53 | lazy val optionBody: PackratParser[OptionValue] = (("(" ?) ~> (identifier <~ (")" ?)) ~ (("." ~ identifier) *)) ~ ("=" ~> constant) ^^ { 54 | case ident ~ idents ~ value => OptionValue(ident + idents.map(i => i._1 + i._2).mkString, value) 55 | } 56 | 57 | lazy val group: PackratParser[Group] = (label <~ "group") ~ (camelCaseIdentifier <~ "=") ~ integerConstant ~ messageBody ^^ { 58 | case gLabel ~ name ~ number ~ body => Group(gLabel, name, number.toInt, body) 59 | } 60 | 61 | lazy val field: PackratParser[Field] = label ~ fieldType ~ (identifier <~ "=") ~ fieldId ~ 62 | (("[" ~> optionBody ~ (("," ~ optionBody) *) <~ "]") ?) <~ ";" ^^ { 63 | case fLabel ~ fType ~ name ~ number ~ options => { 64 | val optionList = options match { 65 | case Some(fOpt ~ fOpts) => List(fOpt) ++ fOpts.map(e => e._2) 66 | case None => List[OptionValue]() 67 | } 68 | 69 | Field(fLabel, fType, name, number.toInt, optionList) 70 | } 71 | } 72 | 73 | lazy val label: PackratParser[FieldLabels.EnumVal] = ("required" | "optional" | "repeated") ^^ { 74 | fLabel => FieldLabels(fLabel) 75 | } 76 | 77 | lazy val fieldType: PackratParser[FieldTypes.EnumVal] = userType ^^ { 78 | fType => FieldTypes(fType) 79 | } 80 | 81 | lazy val userType: PackratParser[String] = (("." ?) ~ identifier ~ (("." ~ identifier) *)) ^^ { 82 | case dot ~ ident ~ idents => dot.getOrElse("") + ident + idents.map(i => i._1 + i._2).mkString 83 | } 84 | 85 | lazy val extensionRanges: PackratParser[ExtensionRanges] = "extensions" ~> extensionRange ~ (("," ~ extensionRange) *) <~ ";" ^^ { 86 | case ext ~ exts => ExtensionRanges(List(ext) ++ exts.map(e => e._2)) 87 | } 88 | lazy val extensionRange: PackratParser[ExtensionRange] = integerConstant ~ (("to" ~> (integerConstant | "max")) ?) ^^ { 89 | case from ~ to => to match { 90 | case Some(int) => int match { 91 | case "max" => ExtensionRange(from.toInt) 92 | case i => ExtensionRange(from.toInt, i.toInt) 93 | } 94 | case None => ExtensionRange(from.toInt, from.toInt) 95 | } 96 | } 97 | 98 | lazy val constant: PackratParser[String] = identifier | floatConstant | integerConstant | quotedStringConstant | stringConstant | booleanConstant 99 | 100 | lazy val identifier: PackratParser[String] = memo("""[A-Za-z_][\w_]*""".r) 101 | 102 | lazy val camelCaseIdentifier: PackratParser[String] = memo("""[A-Z][\w_]*""".r) 103 | 104 | lazy val fieldId: PackratParser[String] = hexadecimalInteger | octalInteger | positiveInteger 105 | 106 | lazy val integerConstant: PackratParser[String] = hexadecimalInteger | octalInteger | decimalInteger 107 | lazy val decimalInteger: PackratParser[String] = memo("""-?\d+""".r) 108 | lazy val positiveInteger: PackratParser[String] = memo("""\d+""".r) 109 | lazy val hexadecimalInteger: PackratParser[String] = memo("""(0[xX])[A-Fa-f0-9]+""".r) ^^ { 110 | hexStr => Integer.parseInt(hexStr.drop(2), 16).toString 111 | } 112 | lazy val octalInteger: PackratParser[String] = memo("""0[0-7]+""".r) ^^ { 113 | octStr => Integer.parseInt(octStr, 8).toString 114 | } 115 | lazy val floatConstant: PackratParser[String] = memo("""-?\d+\.\d+([Ee][+-]\d+)?""".r) 116 | lazy val booleanConstant: PackratParser[String] = "true" | "false" 117 | lazy val stringConstant: PackratParser[String] = ((hexEscape | octEscape | charEscape | stringCharacter) *) ^^ { 118 | string: List[String] => string.mkString 119 | } 120 | lazy val quotedStringConstant: PackratParser[String] = quotationMarks ~> ((hexEscape | octEscape | charEscape | stringCharacter) *) <~ quotationMarks ^^ { 121 | string: List[String] => "\"" + string.mkString + "\"" 122 | } 123 | lazy val stringCharacter: PackratParser[String] = memo("""[^"\n']""".r) 124 | lazy val quotationMarks: PackratParser[String] = memo("""["']""".r) 125 | lazy val hexEscape: PackratParser[String] = memo("""\\0[Xx][A-Fa-f0-9]{1,2}""".r) 126 | lazy val octEscape: PackratParser[String] = memo("""\\0?[0-7]{1,3}""".r) 127 | lazy val charEscape: PackratParser[String] = memo("""\\[abfnrtv\\\?'"]""".r) 128 | 129 | 130 | /** 131 | * Parsing helper, parses the body of a Message or Extension. 132 | */ 133 | def parseBody(body: List[Node]): MessageBody = { 134 | 135 | val fields = ListBuffer[Field]() 136 | val enums = ListBuffer[EnumStatement]() 137 | val messages = ListBuffer[Message]() 138 | val groups = ListBuffer[Group]() 139 | val extensionRanges = ListBuffer[ExtensionRanges]() 140 | val options = ListBuffer[OptionValue]() 141 | val extensions = ListBuffer[Extension]() 142 | 143 | for (node <- body) node match { 144 | case n: Field => fields += n 145 | case n: EnumStatement => enums += n 146 | case n: Message => messages += n 147 | case n: ExtensionRanges => extensionRanges += n 148 | case n: Extension => extensions += n 149 | case n: Group => groups += n 150 | case n: OptionValue => options += n 151 | case _ => require(false, "Impossible node type found.") 152 | } 153 | 154 | MessageBody( 155 | fields.toList, enums.toList, messages.toList, 156 | extensionRanges.toList, extensions.toList, groups.toList, options.toList 157 | ) 158 | } 159 | 160 | 161 | /** 162 | * Parse the given input as a .proto file. 163 | */ 164 | def protoParse(input: Input): List[Node] = { 165 | phrase(protoParser)(input) match { 166 | case Success(tree, _) => tree 167 | case NoSuccess(error, element) => throw new ParsingFailureException(parsingError(error, element)) 168 | } 169 | } 170 | 171 | /** 172 | * Returns the parsing failure details. 173 | */ 174 | def parsingError(error: String, element: Input): String = { 175 | inputName + ":" + element.pos.line + ":" + element.pos.column + ": " + error + "\n" + 176 | element.pos.longString 177 | } 178 | } 179 | 180 | object Parser { 181 | /** 182 | * Parse the given Reader input as a .proto file and return the resulting parse tree. 183 | */ 184 | def apply(input: java.io.Reader, name: String): List[Node] = { 185 | new Parser(name).protoParse(new PagedSeqReader(PagedSeq.fromReader(input))) 186 | } 187 | /** 188 | * Parse the given Reader input as a .proto file and return the resulting parse tree. 189 | */ 190 | def apply(input: java.io.Reader): List[Node] = apply(input, Strings.UNKNOWN_INPUT) 191 | 192 | /** 193 | * Parse the given InputStream input as a .proto file and return the resulting parse tree. 194 | */ 195 | def apply(input: java.io.InputStream, name: String, encoding: String): List[Node] = { 196 | apply(new java.io.BufferedReader(new java.io.InputStreamReader(input, encoding)), name) 197 | } 198 | /** 199 | * Parse the given InputStream input as a .proto file and return the resulting parse tree. 200 | */ 201 | def apply(input: java.io.InputStream, encoding: String = "utf-8"): List[Node] = apply(input, Strings.UNKNOWN_INPUT, "utf-8") 202 | 203 | /** 204 | * Parse the given File input as a .proto file and return the resulting parse tree. 205 | */ 206 | def apply(input: java.io.File): List[Node] = { 207 | val fis = new java.io.FileInputStream(input) 208 | try { apply(fis, input.getName) } finally { fis.close() } 209 | } 210 | 211 | /** 212 | * Parse the given String input as a .proto file and return the resulting parse tree. 213 | */ 214 | def apply(input: String, name: String): List[Node] = 215 | new Parser(name).protoParse(new CharSequenceReader(input)) 216 | 217 | /** 218 | * Parse the given String input as a .proto file and return the resulting parse tree. 219 | */ 220 | def apply(input: String): List[Node] = apply(input, Strings.UNKNOWN_INPUT) 221 | 222 | } 223 | 224 | /** 225 | * Thrown when an input .proto file cannot be parsed successfully by the Parser. 226 | */ 227 | class ParsingFailureException(message: String) extends RuntimeException(message) 228 | -------------------------------------------------------------------------------- /scalabuff-compiler/src/main/net/sandrogrzicic/scalabuff/compiler/ScalaBuff.scala: -------------------------------------------------------------------------------- 1 | package net.sandrogrzicic.scalabuff.compiler 2 | 3 | import java.io._ 4 | import java.nio.charset.Charset 5 | import java.util.concurrent.atomic.AtomicBoolean 6 | 7 | /** 8 | * ScalaBuff runtime. 9 | * @author Sandro Gržičić 10 | */ 11 | object ScalaBuff { 12 | 13 | val defaultCharset: Charset = if (Charset.isSupported("utf-8")) Charset.forName("utf-8") else Charset.defaultCharset() 14 | 15 | case class Settings( 16 | outputDirectory: File = new File("." + File.separatorChar), 17 | importDirectories: Seq[File] = List(new File(".")), 18 | stdout: Boolean = false, 19 | inputEncoding: Charset = defaultCharset, 20 | outputEncoding: Charset = defaultCharset, 21 | verbose: Boolean = false, 22 | extraVerbose: Boolean = false, 23 | generateJsonMethod: Boolean = false, 24 | targetScalaVersion: Option[String] = None 25 | ) 26 | 27 | val defaultSettings = Settings() 28 | 29 | /** 30 | * Runs ScalaBuff on the specified file and returns the resulting Scala class. 31 | * If the encoding is not specified, it defaults to either UTF-8 (if available) or the platform default charset. 32 | */ 33 | def apply(file: File)(implicit settings: Settings = defaultSettings): ScalaClass = { 34 | val tree = parse(file) 35 | val symbols = processImportSymbols(tree, file.getParentFile) 36 | Generator(tree, file.getName, symbols, settings.generateJsonMethod, settings.targetScalaVersion) 37 | } 38 | 39 | /** 40 | * Runs ScalaBuff on the specified input String and returns the output Scala class. 41 | */ 42 | def fromString(input: String, generateJsonMethod: Boolean = false, targetScalaVersion: Option[String] = None): ScalaClass = { 43 | Generator(Parser(input), "", Map(), generateJsonMethod, targetScalaVersion) 44 | } 45 | 46 | /** 47 | * Parse a protobuf file into Nodes. 48 | */ 49 | def parse(file: File)(implicit settings: Settings = defaultSettings): List[Node] = { 50 | val reader = read(file) 51 | try { 52 | Parser(reader) 53 | } finally { 54 | reader.close() 55 | } 56 | } 57 | 58 | /** 59 | * Process "import" statements in a protobuf AST by scanning the imported 60 | * files and building a map of their exported symbols. 61 | */ 62 | def processImportSymbols(tree: List[Node], parentFolder: File)(implicit settings: Settings = defaultSettings): Map[String, ImportedSymbol] = { 63 | implicit val searchInFirst: Option[File] = Some(parentFolder) 64 | 65 | def dig(name: String): List[(String, ImportedSymbol)] = { 66 | val tree = parse(searchPath(name).getOrElse { throw new IOException("Unable to import: " + name) }) 67 | val packageName = tree.collectFirst { 68 | case OptionValue(key, value) if key == "java_package" => value.stripQuotes 69 | }.getOrElse("") 70 | val protoPackage = tree.collectFirst { 71 | case PackageStatement(name) => name 72 | }.getOrElse("") 73 | tree.collect { 74 | case Message(name, _) => (name, ImportedSymbol(packageName, false, protoPackage)) 75 | case EnumStatement(name, _, _) => (name, ImportedSymbol(packageName, true, protoPackage)) 76 | } 77 | } 78 | tree.collect { 79 | case ImportStatement(name) => dig(name.stripQuotes) 80 | }.flatten.toMap 81 | } 82 | 83 | val protoFileFilter: FileFilter = new FileFilter() { 84 | override def accept(file: File) = file.getName.endsWith(".proto") 85 | } 86 | 87 | def findFiles(startAt: File): Seq[File] = { 88 | def recurse(src: File, seq: Seq[File] = Seq[File]()): Seq[File] = { 89 | src match { 90 | case e if !e.exists() => println(Strings.INVALID_IMPORT_DIRECTORY + e); seq 91 | case f if f.isFile => seq :+ src 92 | case d => seq ++ src.listFiles(protoFileFilter).toSeq.map(recurse(_)).foldLeft(Seq[File]())(_ ++ _) 93 | } 94 | } 95 | 96 | recurse(startAt) 97 | } 98 | 99 | def searchPath(filename: String)(implicit settings: Settings = defaultSettings, searchInFirst: Option[File] = None): Option[File] = { 100 | val file = new File(filename) 101 | 102 | if (file.isAbsolute) { 103 | Option(file).filter(_.exists) 104 | } else { 105 | (searchInFirst.toSeq ++ settings.importDirectories).view.map { folder => 106 | new File(folder, filename) 107 | }.find(_.exists) 108 | } 109 | } 110 | 111 | def verbosePrintln(msg: String)(implicit settings: Settings) { if (settings.verbose) println(msg) } 112 | 113 | 114 | /** 115 | * ScalaBuff entry point. If any errors occur, calls System.exit(1). 116 | */ 117 | def main(args: Array[String]) { 118 | val success = run(args) 119 | 120 | if (!success) { 121 | System.exit(1) 122 | } 123 | } 124 | 125 | /** 126 | * Runner: Runs ScalaBuff on the specified resource path(s). 127 | * 128 | * @return success: if true, no errors were encountered. 129 | */ 130 | def run(args: Array[String]): Boolean = { 131 | args match { 132 | case noArguments if noArguments.isEmpty => println(Strings.HELP); true 133 | 134 | case arguments: Array[String] => 135 | val (rawSettings: Array[String], paths: Array[String]) = arguments.partition(_.startsWith("-")) 136 | 137 | implicit val parsedSettings: Settings = rawSettings.foldLeft(Settings()) { 138 | case (settings, setting) => parseSetting(setting, settings) match { 139 | case Left(message) => println(message); settings 140 | case Right(newSettings) => newSettings 141 | } 142 | } 143 | 144 | if (parsedSettings.extraVerbose) { 145 | println("Parameters: " + rawSettings.mkString(" ")) 146 | println("Paths: " + paths.mkString(" ")) 147 | } 148 | 149 | /* 150 | * Find all protobuf files under directory if none specified 151 | */ 152 | val protoFiles: Seq[File] = paths match { 153 | case empty if empty.isEmpty => 154 | parsedSettings.importDirectories.foldLeft(Seq[File]()) { 155 | case (seq, dir) => seq ++ findFiles(dir) 156 | } 157 | case files => 158 | files.flatMap(searchPath) 159 | } 160 | 161 | val success = new AtomicBoolean(true) 162 | 163 | for (file <- protoFiles.par) { 164 | verbosePrintln("Processing: " + file.getAbsolutePath) 165 | try { 166 | val scalaClass = apply(file) 167 | try { 168 | write(scalaClass) 169 | } catch { 170 | // just print the error and continue processing 171 | case io: IOException => 172 | success.set(false) 173 | println(Strings.CANNOT_WRITE_FILE + scalaClass.path + scalaClass.file + ".scala") 174 | } 175 | } catch { 176 | // just print the error and continue processing 177 | case pf: ParsingFailureException => 178 | success.set(false) 179 | println(pf.getMessage) 180 | case io: IOException => 181 | success.set(false) 182 | println(Strings.CANNOT_ACCESS_RESOURCE + file.getAbsolutePath) 183 | } 184 | } 185 | success.get() 186 | } 187 | } 188 | 189 | /** 190 | * Parse the provided setting, return either an error message or a new Settings. 191 | */ 192 | protected def parseSetting(setting: String, settings: Settings): Either[String, Settings] = 193 | (setting match { 194 | case "-h" | "--help" => 195 | Strings.HELP 196 | 197 | case s if s startsWith "-I" => 198 | val dir = s.substring("-I".length) 199 | val importDir = new File(dir) 200 | if (!importDir.isDirectory) Strings.INVALID_IMPORT_DIRECTORY + dir 201 | else settings.copy(importDirectories = settings.importDirectories :+ importDir) 202 | 203 | case s if s startsWith "--proto_path=" => 204 | val dir = s.substring("--proto_path=".length) 205 | val importDir = new File(dir) 206 | if (!importDir.isDirectory) Strings.INVALID_IMPORT_DIRECTORY + dir 207 | else settings.copy(importDirectories = settings.importDirectories :+ importDir) 208 | 209 | case s if s startsWith "--scala_out=" => 210 | val dir = s.substring("--scala_out=".length) 211 | val outputDir = new File(dir) 212 | if (!outputDir.isDirectory) Strings.INVALID_OUTPUT_DIRECTORY + dir 213 | else settings.copy(outputDirectory = outputDir) 214 | 215 | case "--stdout" => 216 | settings.copy(stdout = true) 217 | 218 | case "-v" | "--verbose" if !settings.verbose => 219 | settings.copy(verbose = true) 220 | 221 | case "-v" | "--verbose" if settings.verbose => 222 | settings.copy(extraVerbose = true) 223 | 224 | case s if s startsWith "--proto_encoding=" => 225 | val inputEncoding = s.substring("--proto_encoding=".length) 226 | try { 227 | settings.copy(inputEncoding = Charset.forName(inputEncoding)) 228 | } catch { 229 | case ue: UnsupportedEncodingException => Strings.UNSUPPORTED_INPUT_ENCODING + inputEncoding 230 | } 231 | 232 | case s if s startsWith "--out_encoding=" => 233 | val outputEncoding = s.substring("--out_encoding=".length) 234 | try { 235 | settings.copy(outputEncoding = Charset.forName(outputEncoding)) 236 | } catch { 237 | case ue: UnsupportedEncodingException => Strings.UNSUPPORTED_OUTPUT_ENCODING + outputEncoding 238 | } 239 | 240 | case s if s startsWith "--target=" => 241 | val targetVersion = s.substring("--target=".length) 242 | settings.copy(targetScalaVersion = Some(targetVersion)) 243 | 244 | case "--generate_json_method" => 245 | settings.copy(generateJsonMethod = true) 246 | 247 | case unknown => 248 | Strings.UNKNOWN_ARGUMENT + unknown 249 | 250 | }) match { 251 | case s: String => Left(s) 252 | case s: Settings => Right(s) 253 | } 254 | 255 | /** 256 | * Returns a new Reader based on the specified File and Charset. 257 | */ 258 | protected def read(file: File)(implicit settings: Settings = defaultSettings): Reader = 259 | new BufferedReader(new InputStreamReader(new FileInputStream(file), defaultSettings.inputEncoding)) 260 | 261 | /** 262 | * Write the specified ScalaClass to a file, or to stdout, depending on the Settings. 263 | */ 264 | protected def write(generated: ScalaClass)(implicit settings: Settings) { 265 | if (settings.stdout) { 266 | println(generated) 267 | } else { 268 | 269 | val targetDir = new File(settings.outputDirectory + File.separator + generated.path) 270 | 271 | // generate all the directories between outputDirectory and generated.path 272 | // target directory exists because the passed option is checked in option() 273 | targetDir.mkdirs() 274 | 275 | val targetFile = new File(targetDir, generated.file.camelCase + ".scala") 276 | 277 | if (targetFile.exists()) targetFile.delete() 278 | 279 | val writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(targetFile), settings.outputEncoding)) 280 | 281 | try { 282 | writer.write(generated.body) 283 | } finally { 284 | writer.close() 285 | } 286 | } 287 | } 288 | 289 | } 290 | 291 | /** 292 | * The root ScalaBuff RuntimeException. 293 | */ 294 | class ScalaBuffException(message: String) extends RuntimeException(message) 295 | -------------------------------------------------------------------------------- /scalabuff-compiler/src/main/net/sandrogrzicic/scalabuff/compiler/Strings.scala: -------------------------------------------------------------------------------- 1 | package net.sandrogrzicic.scalabuff.compiler 2 | 3 | /** 4 | * Text strings. 5 | * @author Sandro Gržičić 6 | */ 7 | 8 | object Strings { 9 | val CANNOT_ACCESS_RESOURCE = "* Error: Cannot access specified resource: " 10 | val CANNOT_WRITE_FILE = "* Error: Unable to write output file: " 11 | val INVALID_IMPORT_DIRECTORY = "* Error: Invalid import directory: " 12 | val INVALID_OUTPUT_DIRECTORY = "* Error: Invalid output directory: " 13 | val UNSUPPORTED_INPUT_ENCODING = "* Error: Unsupported proto encoding: " 14 | val UNSUPPORTED_OUTPUT_ENCODING = "* Error: Unsupported output encoding: " 15 | val UNKNOWN_ARGUMENT = "* Error: Unknown argument: " 16 | val HELP = """Scala protocol buffers compiler. 17 | Usage: scalabuff [options] protoFiles 18 | Parse protocol buffer files and generate output based on the options given: 19 | -IPATH, --proto_path=PATH Specify the directory in which to search for 20 | imports. May be specified multiple times; 21 | directories will be searched in order. If not 22 | given, the current working directory is used. 23 | --scala_out=OUTPUT_DIR Generate Scala source files in this directory 24 | (if not specified, current directory is used). 25 | --stdout Output all results to stdout, do not write any 26 | files. 27 | --generate_json_method Generate a toJson method for each case class. 28 | --proto_encoding=ENC Use ENC as the encoding of the input files. 29 | --out_encoding=ENC Use ENC as the encoding of the output files. 30 | -h, --help Show this help text and exit. 31 | """ 32 | 33 | val UNKNOWN_INPUT = "" 34 | 35 | 36 | } 37 | -------------------------------------------------------------------------------- /scalabuff-compiler/src/main/net/sandrogrzicic/scalabuff/compiler/package.scala: -------------------------------------------------------------------------------- 1 | package net.sandrogrzicic.scalabuff 2 | 3 | import scala.language.implicitConversions 4 | 5 | /** 6 | * Useful things for this package. 7 | */ 8 | package object compiler { 9 | 10 | implicit def buffString(string: String): BuffedString = new BuffedString(string) 11 | 12 | } 13 | 14 | 15 | -------------------------------------------------------------------------------- /scalabuff-compiler/src/main/net/sandrogrzicic/scalabuff/test/UpdateTestResources.scala: -------------------------------------------------------------------------------- 1 | package net.sandrogrzicic.scalabuff.test 2 | 3 | import java.io._ 4 | import java.io.File.{separator => /} 5 | 6 | import net.sandrogrzicic.scalabuff.compiler._ 7 | 8 | /** 9 | * A small program which updates the test resources (ScalaBuff outputs). 10 | * 11 | * @author Sandro Gržičić 12 | */ 13 | object UpdateTestResources extends App { 14 | // Set this to true for debugging resource generation 15 | val verbose = false 16 | 17 | update() 18 | 19 | /** 20 | * Update the output test resources. 21 | */ 22 | def update() { 23 | val protoExtension = ".proto" 24 | val parsedExtension = ".txt" 25 | 26 | val protoFileFilter = new FileFilter { 27 | def accept(filtered: File) = filtered.getName.endsWith(protoExtension) 28 | } 29 | 30 | val testDir = "scalabuff-compiler" + / + "src" + / + "test" + / 31 | 32 | val parsedDir = testDir + "resources" + / + "parsed" + / 33 | 34 | val protoDirFile = new File(testDir + "resources" + / + "proto" + /) 35 | 36 | Option(protoDirFile.listFiles(protoFileFilter)) match { 37 | case None => 38 | println(s"Cannot find directory ($protoDirFile)!") 39 | 40 | case Some(protoFiles) => 41 | println(s"Processing files in directory ($protoDirFile)...\n") 42 | 43 | for (file <- protoFiles) { 44 | val fileName = file.getName.dropRight(protoExtension.length).camelCase 45 | val generatedParsedFile = new File(parsedDir + fileName + parsedExtension) 46 | generatedParsedFile.delete() 47 | 48 | val generatedParsed = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(generatedParsedFile), "utf-8")) 49 | 50 | var parsedOption: Option[List[Node]] = None 51 | var output: String = null 52 | 53 | try { 54 | parsedOption = Some(Parser(file)) 55 | } catch { 56 | // in case of a parsing error, write it to the output file 57 | case e: Throwable => 58 | if (verbose) println(s"Error parsing ${file}: ${e}") 59 | output = e.getMessage 60 | } 61 | try { 62 | generatedParsed.write(parsedOption.map(_.toString + "\n").getOrElse(output)) 63 | } catch { 64 | case e: Throwable => 65 | if (verbose) println(s"Error parsing ${file}: ${e}") 66 | e.printStackTrace() 67 | 68 | } finally { 69 | generatedParsed.close() 70 | } 71 | 72 | parsedOption.foreach { parsed => 73 | // if we have a valid parsing tree, generate a Scala proto class. 74 | 75 | // for now, this is hard-coded. 76 | val importedSymbols = Map("PackageTest" -> ImportedSymbol("nested", isEnum = false)) 77 | 78 | val generated = Generator(parsed, file.getName, importedSymbols, generateJsonMethod = true, None) 79 | val generatedPath = testDir + generated.path + generated.file + ".scala" 80 | 81 | new File(testDir + generated.path).mkdirs() 82 | 83 | if (verbose) println(s"# Deleting $generatedPath") 84 | new File(generatedPath).delete() 85 | 86 | val generatedClass = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(generatedPath), "utf-8")) 87 | 88 | try { 89 | generatedClass.write(generated.body) 90 | 91 | } catch { 92 | case e: Throwable => 93 | if (verbose) println(s"Error parsing $file: $e") 94 | e.printStackTrace() 95 | 96 | } finally { 97 | generatedClass.close() 98 | } 99 | 100 | if (verbose) println(s"# Wrote $generatedPath") 101 | } 102 | println(fileName) 103 | } 104 | 105 | println(s"\nFinished processing (${protoFiles.length}) files.") 106 | } 107 | 108 | 109 | } 110 | 111 | } 112 | -------------------------------------------------------------------------------- /scalabuff-compiler/src/test/resources/java/Extensions.java: -------------------------------------------------------------------------------- 1 | // Generated by the protocol buffer compiler. DO NOT EDIT! 2 | // source: test/resources/proto/extensions.proto 3 | 4 | package resources.java; 5 | 6 | public final class Extensions { 7 | private Extensions() {} 8 | public static void registerAllExtensions( 9 | com.google.protobuf.ExtensionRegistryLite registry) { 10 | registry.add(resources.java.Extensions.fooExtended); 11 | } 12 | public interface ExtensionsTestOrBuilder extends 13 | com.google.protobuf.GeneratedMessageLite. 14 | ExtendableMessageOrBuilder { 15 | 16 | // required int32 foo = 1; 17 | boolean hasFoo(); 18 | int getFoo(); 19 | } 20 | public static final class ExtensionsTest extends 21 | com.google.protobuf.GeneratedMessageLite.ExtendableMessage< 22 | ExtensionsTest> implements ExtensionsTestOrBuilder { 23 | // Use ExtensionsTest.newBuilder() to construct. 24 | private ExtensionsTest(Builder builder) { 25 | super(builder); 26 | } 27 | private ExtensionsTest(boolean noInit) {} 28 | 29 | private static final ExtensionsTest defaultInstance; 30 | public static ExtensionsTest getDefaultInstance() { 31 | return defaultInstance; 32 | } 33 | 34 | public ExtensionsTest getDefaultInstanceForType() { 35 | return defaultInstance; 36 | } 37 | 38 | private int bitField0_; 39 | // required int32 foo = 1; 40 | public static final int FOO_FIELD_NUMBER = 1; 41 | private int foo_; 42 | public boolean hasFoo() { 43 | return ((bitField0_ & 0x00000001) == 0x00000001); 44 | } 45 | public int getFoo() { 46 | return foo_; 47 | } 48 | 49 | private void initFields() { 50 | foo_ = 0; 51 | } 52 | private byte memoizedIsInitialized = -1; 53 | public final boolean isInitialized() { 54 | byte isInitialized = memoizedIsInitialized; 55 | if (isInitialized != -1) return isInitialized == 1; 56 | 57 | if (!hasFoo()) { 58 | memoizedIsInitialized = 0; 59 | return false; 60 | } 61 | if (!extensionsAreInitialized()) { 62 | memoizedIsInitialized = 0; 63 | return false; 64 | } 65 | memoizedIsInitialized = 1; 66 | return true; 67 | } 68 | 69 | public void writeTo(com.google.protobuf.CodedOutputStream output) 70 | throws java.io.IOException { 71 | getSerializedSize(); 72 | com.google.protobuf.GeneratedMessageLite 73 | .ExtendableMessage.ExtensionWriter extensionWriter = 74 | newExtensionWriter(); 75 | if (((bitField0_ & 0x00000001) == 0x00000001)) { 76 | output.writeInt32(1, foo_); 77 | } 78 | extensionWriter.writeUntil(201, output); 79 | } 80 | 81 | private int memoizedSerializedSize = -1; 82 | public int getSerializedSize() { 83 | int size = memoizedSerializedSize; 84 | if (size != -1) return size; 85 | 86 | size = 0; 87 | if (((bitField0_ & 0x00000001) == 0x00000001)) { 88 | size += com.google.protobuf.CodedOutputStream 89 | .computeInt32Size(1, foo_); 90 | } 91 | size += extensionsSerializedSize(); 92 | memoizedSerializedSize = size; 93 | return size; 94 | } 95 | 96 | private static final long serialVersionUID = 0L; 97 | @java.lang.Override 98 | protected java.lang.Object writeReplace() 99 | throws java.io.ObjectStreamException { 100 | return super.writeReplace(); 101 | } 102 | 103 | public static resources.java.Extensions.ExtensionsTest parseFrom( 104 | com.google.protobuf.ByteString data) 105 | throws com.google.protobuf.InvalidProtocolBufferException { 106 | return newBuilder().mergeFrom(data).buildParsed(); 107 | } 108 | public static resources.java.Extensions.ExtensionsTest parseFrom( 109 | com.google.protobuf.ByteString data, 110 | com.google.protobuf.ExtensionRegistryLite extensionRegistry) 111 | throws com.google.protobuf.InvalidProtocolBufferException { 112 | return newBuilder().mergeFrom(data, extensionRegistry) 113 | .buildParsed(); 114 | } 115 | public static resources.java.Extensions.ExtensionsTest parseFrom(byte[] data) 116 | throws com.google.protobuf.InvalidProtocolBufferException { 117 | return newBuilder().mergeFrom(data).buildParsed(); 118 | } 119 | public static resources.java.Extensions.ExtensionsTest parseFrom( 120 | byte[] data, 121 | com.google.protobuf.ExtensionRegistryLite extensionRegistry) 122 | throws com.google.protobuf.InvalidProtocolBufferException { 123 | return newBuilder().mergeFrom(data, extensionRegistry) 124 | .buildParsed(); 125 | } 126 | public static resources.java.Extensions.ExtensionsTest parseFrom(java.io.InputStream input) 127 | throws java.io.IOException { 128 | return newBuilder().mergeFrom(input).buildParsed(); 129 | } 130 | public static resources.java.Extensions.ExtensionsTest parseFrom( 131 | java.io.InputStream input, 132 | com.google.protobuf.ExtensionRegistryLite extensionRegistry) 133 | throws java.io.IOException { 134 | return newBuilder().mergeFrom(input, extensionRegistry) 135 | .buildParsed(); 136 | } 137 | public static resources.java.Extensions.ExtensionsTest parseDelimitedFrom(java.io.InputStream input) 138 | throws java.io.IOException { 139 | Builder builder = newBuilder(); 140 | if (builder.mergeDelimitedFrom(input)) { 141 | return builder.buildParsed(); 142 | } else { 143 | return null; 144 | } 145 | } 146 | public static resources.java.Extensions.ExtensionsTest parseDelimitedFrom( 147 | java.io.InputStream input, 148 | com.google.protobuf.ExtensionRegistryLite extensionRegistry) 149 | throws java.io.IOException { 150 | Builder builder = newBuilder(); 151 | if (builder.mergeDelimitedFrom(input, extensionRegistry)) { 152 | return builder.buildParsed(); 153 | } else { 154 | return null; 155 | } 156 | } 157 | public static resources.java.Extensions.ExtensionsTest parseFrom( 158 | com.google.protobuf.CodedInputStream input) 159 | throws java.io.IOException { 160 | return newBuilder().mergeFrom(input).buildParsed(); 161 | } 162 | public static resources.java.Extensions.ExtensionsTest parseFrom( 163 | com.google.protobuf.CodedInputStream input, 164 | com.google.protobuf.ExtensionRegistryLite extensionRegistry) 165 | throws java.io.IOException { 166 | return newBuilder().mergeFrom(input, extensionRegistry) 167 | .buildParsed(); 168 | } 169 | 170 | public static Builder newBuilder() { return Builder.create(); } 171 | public Builder newBuilderForType() { return newBuilder(); } 172 | public static Builder newBuilder(resources.java.Extensions.ExtensionsTest prototype) { 173 | return newBuilder().mergeFrom(prototype); 174 | } 175 | public Builder toBuilder() { return newBuilder(this); } 176 | 177 | public static final class Builder extends 178 | com.google.protobuf.GeneratedMessageLite.ExtendableBuilder< 179 | resources.java.Extensions.ExtensionsTest, Builder> implements resources.java.Extensions.ExtensionsTestOrBuilder { 180 | // Construct using resources.generated.Extensions.ExtensionsTest.newBuilder() 181 | private Builder() { 182 | maybeForceBuilderInitialization(); 183 | } 184 | 185 | private void maybeForceBuilderInitialization() { 186 | } 187 | private static Builder create() { 188 | return new Builder(); 189 | } 190 | 191 | public Builder clear() { 192 | super.clear(); 193 | foo_ = 0; 194 | bitField0_ = (bitField0_ & ~0x00000001); 195 | return this; 196 | } 197 | 198 | public Builder clone() { 199 | return create().mergeFrom(buildPartial()); 200 | } 201 | 202 | public resources.java.Extensions.ExtensionsTest getDefaultInstanceForType() { 203 | return resources.java.Extensions.ExtensionsTest.getDefaultInstance(); 204 | } 205 | 206 | public resources.java.Extensions.ExtensionsTest build() { 207 | resources.java.Extensions.ExtensionsTest result = buildPartial(); 208 | if (!result.isInitialized()) { 209 | throw newUninitializedMessageException(result); 210 | } 211 | return result; 212 | } 213 | 214 | private resources.java.Extensions.ExtensionsTest buildParsed() 215 | throws com.google.protobuf.InvalidProtocolBufferException { 216 | resources.java.Extensions.ExtensionsTest result = buildPartial(); 217 | if (!result.isInitialized()) { 218 | throw newUninitializedMessageException( 219 | result).asInvalidProtocolBufferException(); 220 | } 221 | return result; 222 | } 223 | 224 | public resources.java.Extensions.ExtensionsTest buildPartial() { 225 | resources.java.Extensions.ExtensionsTest result = new resources.java.Extensions.ExtensionsTest(this); 226 | int from_bitField0_ = bitField0_; 227 | int to_bitField0_ = 0; 228 | if (((from_bitField0_ & 0x00000001) == 0x00000001)) { 229 | to_bitField0_ |= 0x00000001; 230 | } 231 | result.foo_ = foo_; 232 | result.bitField0_ = to_bitField0_; 233 | return result; 234 | } 235 | 236 | public Builder mergeFrom(resources.java.Extensions.ExtensionsTest other) { 237 | if (other == resources.java.Extensions.ExtensionsTest.getDefaultInstance()) return this; 238 | if (other.hasFoo()) { 239 | setFoo(other.getFoo()); 240 | } 241 | this.mergeExtensionFields(other); 242 | return this; 243 | } 244 | 245 | public final boolean isInitialized() { 246 | if (!hasFoo()) { 247 | 248 | return false; 249 | } 250 | if (!extensionsAreInitialized()) { 251 | 252 | return false; 253 | } 254 | return true; 255 | } 256 | 257 | public Builder mergeFrom( 258 | com.google.protobuf.CodedInputStream input, 259 | com.google.protobuf.ExtensionRegistryLite extensionRegistry) 260 | throws java.io.IOException { 261 | while (true) { 262 | int tag = input.readTag(); 263 | switch (tag) { 264 | case 0: 265 | 266 | return this; 267 | default: { 268 | if (!parseUnknownField(input, extensionRegistry, tag)) { 269 | 270 | return this; 271 | } 272 | break; 273 | } 274 | case 8: { 275 | bitField0_ |= 0x00000001; 276 | foo_ = input.readInt32(); 277 | break; 278 | } 279 | } 280 | } 281 | } 282 | 283 | private int bitField0_; 284 | 285 | // required int32 foo = 1; 286 | private int foo_ ; 287 | public boolean hasFoo() { 288 | return ((bitField0_ & 0x00000001) == 0x00000001); 289 | } 290 | public int getFoo() { 291 | return foo_; 292 | } 293 | public Builder setFoo(int value) { 294 | bitField0_ |= 0x00000001; 295 | foo_ = value; 296 | 297 | return this; 298 | } 299 | public Builder clearFoo() { 300 | bitField0_ = (bitField0_ & ~0x00000001); 301 | foo_ = 0; 302 | 303 | return this; 304 | } 305 | 306 | // @@protoc_insertion_point(builder_scope:resources.generated.ExtensionsTest) 307 | } 308 | 309 | static { 310 | defaultInstance = new ExtensionsTest(true); 311 | defaultInstance.initFields(); 312 | } 313 | 314 | // @@protoc_insertion_point(class_scope:resources.generated.ExtensionsTest) 315 | } 316 | 317 | public static final int FOOEXTENDED_FIELD_NUMBER = 100; 318 | public static final 319 | com.google.protobuf.GeneratedMessageLite.GeneratedExtension< 320 | resources.java.Extensions.ExtensionsTest, 321 | java.lang.Integer> fooExtended = com.google.protobuf.GeneratedMessageLite 322 | .newSingularGeneratedExtension( 323 | resources.java.Extensions.ExtensionsTest.getDefaultInstance(), 324 | 5, 325 | null, 326 | null, 327 | 100, 328 | com.google.protobuf.WireFormat.FieldType.INT32); 329 | 330 | static { 331 | } 332 | 333 | // @@protoc_insertion_point(outer_class_scope) 334 | } 335 | -------------------------------------------------------------------------------- /scalabuff-compiler/src/test/resources/multipleprototests/MultiOne.proto: -------------------------------------------------------------------------------- 1 | package resources.generated; 2 | 3 | option optimize_for = LITE_RUNTIME; 4 | 5 | message MutiMessageOne { 6 | } 7 | -------------------------------------------------------------------------------- /scalabuff-compiler/src/test/resources/multipleprototests/MultiTwo.proto: -------------------------------------------------------------------------------- 1 | package resources.generated; 2 | 3 | option optimize_for = LITE_RUNTIME; 4 | 5 | message MultiMessageTwo { 6 | required int32 required_field = 1; 7 | optional float optional_field = 2; 8 | repeated string repeated_field = 3; 9 | 10 | optional int32 type = 4 [default=100]; 11 | optional int32 int32Default = 5 [default=100]; 12 | optional string stringDefault = 6 [default="some string"]; 13 | } 14 | -------------------------------------------------------------------------------- /scalabuff-compiler/src/test/resources/parsed/Complex.txt: -------------------------------------------------------------------------------- 1 | List(PackageStatement(resources.generated), OptionValue(optimize_for,LITE_RUNTIME), Message(ComplexMessage,MessageBody(List(Field(required,Bytes,first_field,1,List(),), Field(optional,String,second_field,2,List(OptionValue(default,"defaultValueForSecondField")),), Field(optional,Nested,nested_outer_field,3,List(),), Field(repeated,SimpleEnum,simple_enum_field,4,List(),), Field(repeated,String,repeated_string_field,5,List(),), Field(repeated,Bytes,repeated_bytes_field,6,List(),)),List(EnumStatement(SimpleEnum,List(EnumConstant(KEY_NAME,1)),List())),List(Message(Nested,MessageBody(List(Field(required,String,nested_field,1,List(),), Field(optional,SimpleEnum,nested_enum,2,List(),)),List(),List(),List(),List(),List(),List()))),List(),List(),List(),List())), Message(AnotherMessage,MessageBody(List(Field(required,ComplexMessage.Nested,field_nested,1,List(),), Field(required,ComplexMessage.SimpleEnum,field_enum,2,List(),)),List(),List(),List(),List(),List(),List()))) 2 | -------------------------------------------------------------------------------- /scalabuff-compiler/src/test/resources/parsed/DataTypes.txt: -------------------------------------------------------------------------------- 1 | List(PackageStatement(resources.generated), OptionValue(java_outer_classname,"DataTypesTest"), Message(DataTypes,MessageBody(List(Field(required,Int32,varint1,1,List(),), Field(optional,Int64,varint2,2,List(),), Field(optional,UInt32,varint3,3,List(),), Field(required,UInt64,varint4,4,List(),), Field(optional,SInt32,varint5,5,List(),), Field(optional,SInt64,varint6,6,List(),), Field(optional,Bool,varint7,7,List(),), Field(optional,Fixed64,f64bit1,100,List(),), Field(optional,SFixed64,f64bit2,101,List(),), Field(optional,Double,f64bit3,102,List(),), Field(optional,String,length_delim1,200,List(),), Field(optional,Bytes,length_delim2,201,List(),), Field(optional,Varint8Enum,length_delim3,202,List(),), Field(repeated,Int32,length_delim4,204,List(),), Field(repeated,Int32,length_delim5,203,List(OptionValue(packed,true)),), Field(optional,Fixed32,f32bit1,500,List(),), Field(optional,SFixed32,f32bit2,501,List(),), Field(optional,Float,f32bit3,502,List(),)),List(EnumStatement(Varint8Enum,List(EnumConstant(ENUM_ZERO,0), EnumConstant(ENUM_ONE,1)),List())),List(),List(),List(),List(),List()))) 2 | -------------------------------------------------------------------------------- /scalabuff-compiler/src/test/resources/parsed/DhComplex.txt: -------------------------------------------------------------------------------- 1 | List(PackageStatement(resources.generated), Message(Response,MessageBody(List(Field(repeated,VideoResult,response,1,List(),)),List(),List(Message(Rendition,MessageBody(List(Field(optional,String,profile_key,1,List(),), Field(optional,String,data,2,List(),), Field(repeated,Property,property,3,List(),)),List(),List(Message(Property,MessageBody(List(Field(required,Key,key,1,List(),), Field(required,String,value,2,List(),)),List(EnumStatement(Key,List(EnumConstant(UNDEFINED,0), EnumConstant(BANDWIDTH,1), EnumConstant(RESOLUTION,2), EnumConstant(CODECS,3)),List())),List(),List(),List(),List(),List()))),List(),List(),List(),List())), Message(Video,MessageBody(List(Field(optional,String,identifier,1,List(),), Field(optional,String,asset_key,2,List(),), Field(optional,Float,duration,3,List(),), Field(repeated,Rendition,renditions,4,List(),)),List(),List(),List(),List(),List(),List())), Message(VideoFailure,MessageBody(List(Field(optional,String,asset_key,1,List(),), Field(repeated,String,reason,2,List(),), Field(optional,Cause,cause,3,List(),)),List(EnumStatement(Cause,List(EnumConstant(UNEXPECTED,1), EnumConstant(ASSET_NOT_FOUND,2), EnumConstant(UNKNOWN_ACCOUNT,3), EnumConstant(TIMEOUT,4), EnumConstant(DATABASE_ERROR,5)),List())),List(),List(),List(),List(),List())), Message(VideoResult,MessageBody(List(Field(optional,Video,success,1,List(),), Field(optional,VideoFailure,failure,2,List(),)),List(),List(),List(),List(),List(),List()))),List(),List(),List(),List()))) 2 | -------------------------------------------------------------------------------- /scalabuff-compiler/src/test/resources/parsed/Empty.txt: -------------------------------------------------------------------------------- 1 | List(PackageStatement(resources.generated), OptionValue(java_outer_classname,"EmptyMessageTest"), OptionValue(optimize_for,LITE_RUNTIME)) 2 | -------------------------------------------------------------------------------- /scalabuff-compiler/src/test/resources/parsed/Enum.txt: -------------------------------------------------------------------------------- 1 | List(PackageStatement(resources.generated), OptionValue(java_outer_classname,"EnumTest"), EnumStatement(ComputerPeripherals,List(EnumConstant(KEYBOARD,1), EnumConstant(MOUSE,2)),List()), Message(MyPeripherals,MessageBody(List(Field(optional,ComputerPeripherals,primary_peripheral,1,List(OptionValue(default,KEYBOARD)),), Field(optional,ComputerPeripherals,secondary_peripheral,2,List(),)),List(),List(),List(),List(),List(),List())), Message(Outer,MessageBody(List(Field(required,Inner,inner_required,1,List(),), Field(optional,Inner,inner_optional,2,List(OptionValue(default,FIRST)),), Field(repeated,Inner,inner_repeated,3,List(),)),List(EnumStatement(Inner,List(EnumConstant(FIRST,1), EnumConstant(SECOND,2)),List())),List(),List(),List(),List(),List())), Message(OuterDuplicate,MessageBody(List(Field(required,Inner,inner_required,1,List(),), Field(optional,Inner,inner_optional,2,List(OptionValue(default,SECOND)),), Field(repeated,Inner,inner_repeated,3,List(),)),List(EnumStatement(Inner,List(EnumConstant(FIRST,1), EnumConstant(SECOND,2)),List())),List(),List(),List(),List(),List())), Message(OuterEnumContainer,MessageBody(List(Field(required,InnerEnumContainer,inner_message,1,List(),)),List(),List(Message(InnerEnumContainer,MessageBody(List(Field(required,SomeEnum,some_enum,1,List(),)),List(EnumStatement(SomeEnum,List(EnumConstant(VALUE_1,0), EnumConstant(VALUE_2,1), EnumConstant(VALUE_3,2)),List())),List(),List(),List(),List(),List()))),List(),List(),List(),List()))) 2 | -------------------------------------------------------------------------------- /scalabuff-compiler/src/test/resources/parsed/Extensions.txt: -------------------------------------------------------------------------------- 1 | List(PackageStatement(resources.generated), OptionValue(optimize_for,LITE_RUNTIME), Message(ExtensionsTest,MessageBody(List(Field(required,Int32,foo,1,List(),)),List(),List(),List(ExtensionRanges(List(ExtensionRange(100,200)))),List(),List(),List())), Extension(ExtensionsTest,MessageBody(List(Field(optional,Int32,fooExtended,100,List(OptionValue(default,5)),)),List(),List(),List(),List(),List(),List()))) 2 | -------------------------------------------------------------------------------- /scalabuff-compiler/src/test/resources/parsed/Groups.txt: -------------------------------------------------------------------------------- 1 | List(PackageStatement(resources.generated), OptionValue(java_outer_classname,"GroupsTest"), Message(Groups,MessageBody(List(),List(),List(),List(),List(),List(Group(repeated,Group,1,MessageBody(List(Field(required,String,name,2,List(),)),List(),List(),List(),List(),List(),List()))),List()))) 2 | -------------------------------------------------------------------------------- /scalabuff-compiler/src/test/resources/parsed/ImportPackages.txt: -------------------------------------------------------------------------------- 1 | List(PackageStatement(resources.generated), ImportStatement("package_name.proto"), OptionValue(optimize_for,LITE_RUNTIME), Message(UsesImportPackage,MessageBody(List(Field(required,PackageTest,package_test,1,List(),)),List(),List(),List(),List(),List(),List()))) 2 | -------------------------------------------------------------------------------- /scalabuff-compiler/src/test/resources/parsed/ImportUseFullname.txt: -------------------------------------------------------------------------------- 1 | List(PackageStatement(resources.generated), ImportStatement("package_name.proto"), OptionValue(optimize_for,LITE_RUNTIME), Message(UseFullImportedName,MessageBody(List(Field(required,resources.generated.nested.PackageTest,fullname_test,1,List(),)),List(),List(),List(),List(),List(),List()))) 2 | -------------------------------------------------------------------------------- /scalabuff-compiler/src/test/resources/parsed/Imports.txt: -------------------------------------------------------------------------------- 1 | List(PackageStatement(resources.generated), ImportStatement("simple.proto"), OptionValue(optimize_for,LITE_RUNTIME), Message(UsesImport,MessageBody(List(Field(required,SimpleTest,simple_test,1,List(),)),List(),List(),List(),List(),List(),List()))) 2 | -------------------------------------------------------------------------------- /scalabuff-compiler/src/test/resources/parsed/InvalidComplex.txt: -------------------------------------------------------------------------------- 1 | :5:11: `=' expected but `6' found 2 | optional 6int640 invalid_field_user_type = 2; // invalid field user type 3 | ^ -------------------------------------------------------------------------------- /scalabuff-compiler/src/test/resources/parsed/InvalidMessage.txt: -------------------------------------------------------------------------------- 1 | :3:17: `{' expected but `M' found 2 | message Invalid Message { } 3 | ^ -------------------------------------------------------------------------------- /scalabuff-compiler/src/test/resources/parsed/InvalidSimple.txt: -------------------------------------------------------------------------------- 1 | :3:1: `option' expected but `i' found 2 | invalidSimple { } 3 | ^ -------------------------------------------------------------------------------- /scalabuff-compiler/src/test/resources/parsed/Keywords.txt: -------------------------------------------------------------------------------- 1 | List(PackageStatement(resources.generated), OptionValue(optimize_for,LITE_RUNTIME), Message(KeywordsTest,MessageBody(List(Field(required,Int64,size,1,List(),), Field(required,Int32,case,2,List(),), Field(required,Int32,val,3,List(),), Field(required,Int32,var,4,List(),), Field(required,Int32,def,5,List(),), Field(required,Int32,object,6,List(),), Field(required,Int32,class,7,List(),), Field(required,Int32,lazy,8,List(),), Field(required,Int32,type,9,List(),)),List(),List(),List(),List(),List(),List()))) 2 | -------------------------------------------------------------------------------- /scalabuff-compiler/src/test/resources/parsed/Message.txt: -------------------------------------------------------------------------------- 1 | List(PackageStatement(resources.generated), OptionValue(java_outer_classname,"ScalaBuffMessageTest"), OptionValue(optimize_for,LITE_RUNTIME), Message(EmptyMessage,MessageBody(List(),List(),List(),List(),List(),List(),List()))) 2 | -------------------------------------------------------------------------------- /scalabuff-compiler/src/test/resources/parsed/NestedMessages.txt: -------------------------------------------------------------------------------- 1 | List(PackageStatement(resources.generated), Message(TopLevel,MessageBody(List(Field(required,UInt32,id_toplevel,1,List(),)),List(),List(Message(Inner,MessageBody(List(Field(required,UInt32,id_inner,1,List(),)),List(),List(),List(),List(),List(),List()))),List(),List(),List(),List())), Message(Foobar,MessageBody(List(Field(required,Foo,reqFoo,1,List(),), Field(optional,Foo,optFoo,2,List(),), Field(optional,Bar,optBar,3,List(),), Field(repeated,Foo,repFoo,4,List(),), Field(repeated,Bar,repBar,5,List(),), Field(repeated,FooBar,rep_foo_bar,6,List(),), Field(required,TopLevel,top_level_req,7,List(),), Field(optional,TopLevel,top_level_opt,8,List(),), Field(required,TopLevel.Inner,top_level_inner_req,9,List(),)),List(),List(Message(Foo,MessageBody(List(Field(optional,UInt64,id,1,List(),)),List(),List(),List(),List(),List(),List())), Message(Bar,MessageBody(List(Field(optional,UInt64,id,1,List(),)),List(),List(),List(),List(),List(),List())), Message(FooBar,MessageBody(List(Field(optional,UInt64,id,1,List(),)),List(),List(),List(),List(),List(),List()))),List(),List(),List(),List()))) 2 | -------------------------------------------------------------------------------- /scalabuff-compiler/src/test/resources/parsed/Numbers.txt: -------------------------------------------------------------------------------- 1 | List(PackageStatement(resources.generated), OptionValue(optimize_for,LITE_RUNTIME), Message(NumbersTest1,MessageBody(List(Field(optional,Int32,some_hex_number,1,List(OptionValue(default,430689775)),), Field(optional,Int32,some_oct_number,2,List(OptionValue(default,342391)),)),List(),List(),List(),List(),List(),List())), EnumStatement(NumbersTest2,List(EnumConstant(SOME_HEX_NUMBER,430689775), EnumConstant(SOME_OCT_NUMBER,342391)),List())) 2 | -------------------------------------------------------------------------------- /scalabuff-compiler/src/test/resources/parsed/PackageName.txt: -------------------------------------------------------------------------------- 1 | List(PackageStatement(resources.generated.nested), OptionValue(java_package,"resources.generated.nested"), Message(PackageTest,MessageBody(List(Field(required,Int32,required_field,1,List(),)),List(),List(),List(),List(),List(),List()))) 2 | -------------------------------------------------------------------------------- /scalabuff-compiler/src/test/resources/parsed/Packed.txt: -------------------------------------------------------------------------------- 1 | List(PackageStatement(resources.generated), OptionValue(optimize_for,LITE_RUNTIME), Message(PackedTest,MessageBody(List(Field(required,Int32,required_field,1,List(),), Field(optional,Float,optional_field,2,List(),), Field(repeated,Int32,repeated_packed_field,3,List(OptionValue(packed,true)),)),List(),List(),List(),List(),List(),List()))) 2 | -------------------------------------------------------------------------------- /scalabuff-compiler/src/test/resources/parsed/RemoteProtocol.txt: -------------------------------------------------------------------------------- 1 | List(OptionValue(java_package,"resources.generated"), OptionValue(optimize_for,SPEED), Message(AkkaRemoteProtocol,MessageBody(List(Field(optional,RemoteMessageProtocol,message,1,List(),), Field(optional,RemoteControlProtocol,instruction,2,List(),)),List(),List(),List(),List(),List(),List())), Message(RemoteMessageProtocol,MessageBody(List(Field(required,ActorRefProtocol,recipient,1,List(),), Field(required,MessageProtocol,message,2,List(),), Field(optional,ActorRefProtocol,sender,4,List(),), Field(repeated,MetadataEntryProtocol,metadata,5,List(),)),List(),List(),List(),List(),List(),List())), Message(RemoteControlProtocol,MessageBody(List(Field(required,CommandType,commandType,1,List(),), Field(optional,String,cookie,2,List(),), Field(optional,AddressProtocol,origin,3,List(),)),List(),List(),List(),List(),List(),List())), EnumStatement(CommandType,List(EnumConstant(CONNECT,1), EnumConstant(SHUTDOWN,2), EnumConstant(HEARTBEAT,3)),List()), Message(ActorRefProtocol,MessageBody(List(Field(required,String,path,1,List(),)),List(),List(),List(),List(),List(),List())), Message(MessageProtocol,MessageBody(List(Field(required,Bytes,message,1,List(),), Field(required,Int32,serializerId,2,List(),), Field(optional,Bytes,messageManifest,3,List(),)),List(),List(),List(),List(),List(),List())), Message(MetadataEntryProtocol,MessageBody(List(Field(required,String,key,1,List(),), Field(required,Bytes,value,2,List(),)),List(),List(),List(),List(),List(),List())), Message(AddressProtocol,MessageBody(List(Field(required,String,system,1,List(),), Field(required,String,hostname,2,List(),), Field(required,UInt32,port,3,List(),)),List(),List(),List(),List(),List(),List())), Message(DaemonMsgCreateProtocol,MessageBody(List(Field(required,PropsProtocol,props,1,List(),), Field(required,DeployProtocol,deploy,2,List(),), Field(required,String,path,3,List(),), Field(required,ActorRefProtocol,supervisor,4,List(),)),List(),List(),List(),List(),List(),List())), Message(PropsProtocol,MessageBody(List(Field(required,String,dispatcher,1,List(),), Field(required,DeployProtocol,deploy,2,List(),), Field(optional,String,fromClassCreator,3,List(),), Field(optional,Bytes,creator,4,List(),), Field(optional,Bytes,routerConfig,5,List(),)),List(),List(),List(),List(),List(),List())), Message(DeployProtocol,MessageBody(List(Field(required,String,path,1,List(),), Field(optional,Bytes,config,2,List(),), Field(optional,Bytes,routerConfig,3,List(),), Field(optional,Bytes,scope,4,List(),)),List(),List(),List(),List(),List(),List()))) 2 | -------------------------------------------------------------------------------- /scalabuff-compiler/src/test/resources/parsed/Simple.txt: -------------------------------------------------------------------------------- 1 | List(PackageStatement(resources.generated), OptionValue(optimize_for,LITE_RUNTIME), Message(SimpleTest,MessageBody(List(Field(required,Int32,required_field,1,List(),), Field(optional,Float,optional_field,2,List(),), Field(repeated,String,repeated_field,3,List(),), Field(optional,Int32,type,4,List(OptionValue(default,100)),), Field(optional,Int32,int32Default,5,List(OptionValue(default,100)),), Field(optional,Int32,int32Negative,6,List(OptionValue(default,-1)),), Field(optional,String,stringDefault,7,List(OptionValue(default,"somestring")),), Field(optional,Float,floatDefault,8,List(OptionValue(default,1.0)),), Field(optional,Float,floatNegative,9,List(OptionValue(default,-1.0)),)),List(),List(),List(),List(),List(),List()))) 2 | -------------------------------------------------------------------------------- /scalabuff-compiler/src/test/resources/parsed/SimpleWithComments.txt: -------------------------------------------------------------------------------- 1 | List(PackageStatement(resources.generated), Message(SimpleRequest,MessageBody(List(Field(required,String,query,1,List(),), Field(optional,Int32,page_number,2,List(),), Field(optional,Int32,results_per_page,3,List(),)),List(),List(),List(),List(),List(),List()))) 2 | -------------------------------------------------------------------------------- /scalabuff-compiler/src/test/resources/parsed/Singlequoted.txt: -------------------------------------------------------------------------------- 1 | List(PackageStatement(resources.generated), OptionValue(optimize_for,LITE_RUNTIME), Message(SingleQuote,MessageBody(List(Field(optional,String,single_quoted_field,1,List(OptionValue(default,"NA")),)),List(),List(),List(),List(),List(),List()))) 2 | -------------------------------------------------------------------------------- /scalabuff-compiler/src/test/resources/proto/Complex.proto: -------------------------------------------------------------------------------- 1 | package resources.generated; 2 | 3 | option optimize_for = LITE_RUNTIME; 4 | 5 | message ComplexMessage { 6 | required bytes first_field = 1; 7 | enum SimpleEnum { 8 | KEY_NAME = 1; 9 | } 10 | message Nested { 11 | required string nested_field = 1; 12 | optional SimpleEnum nested_enum = 2; 13 | } 14 | optional string second_field = 2 [default = "defaultValueForSecondField"]; 15 | optional Nested nested_outer_field = 3; 16 | repeated SimpleEnum simple_enum_field = 4; 17 | repeated string repeated_string_field = 5; 18 | repeated bytes repeated_bytes_field = 6; 19 | } 20 | 21 | message AnotherMessage { 22 | required ComplexMessage.Nested field_nested = 1; 23 | required ComplexMessage.SimpleEnum field_enum = 2; 24 | } 25 | -------------------------------------------------------------------------------- /scalabuff-compiler/src/test/resources/proto/DataTypes.proto: -------------------------------------------------------------------------------- 1 | package resources.generated; 2 | 3 | option java_outer_classname = "DataTypesTest"; 4 | 5 | message DataTypes { 6 | required int32 varint1 = 1; 7 | optional int64 varint2 = 2; 8 | optional uint32 varint3 = 3; 9 | required uint64 varint4 = 4; 10 | optional sint32 varint5 = 5; 11 | optional sint64 varint6 = 6; 12 | optional bool varint7 = 7; 13 | enum Varint8Enum { 14 | ENUM_ZERO = 0; 15 | ENUM_ONE = 1; 16 | } 17 | 18 | optional fixed64 f64bit1 = 100; 19 | optional sfixed64 f64bit2 = 101; 20 | optional double f64bit3 = 102; 21 | 22 | optional string length_delim1 = 200; 23 | optional bytes length_delim2 = 201; 24 | optional Varint8Enum length_delim3 = 202; 25 | repeated int32 length_delim4 = 204; 26 | repeated int32 length_delim5 = 203 [packed=true]; 27 | 28 | optional fixed32 f32bit1 = 500; 29 | optional sfixed32 f32bit2 = 501; 30 | optional float f32bit3 = 502; 31 | } -------------------------------------------------------------------------------- /scalabuff-compiler/src/test/resources/proto/DhComplex.proto: -------------------------------------------------------------------------------- 1 | /** 2 | * author: David Harcombe 3 | * https://github.com/mDialog/ScalaBuff/commit/bbb648d6ac1773199b0ae96e45846f3ad1328f3e 4 | */ 5 | 6 | package resources.generated; 7 | 8 | message Response { 9 | message Rendition { 10 | message Property { 11 | enum Key { 12 | UNDEFINED = 0; 13 | BANDWIDTH = 1; 14 | RESOLUTION = 2; 15 | CODECS = 3; 16 | } 17 | 18 | required Key key = 1; 19 | required string value = 2; 20 | } 21 | 22 | optional string profile_key = 1; 23 | optional string data = 2; 24 | repeated Property property = 3; 25 | } 26 | 27 | message Video { 28 | optional string identifier = 1; 29 | optional string asset_key = 2; 30 | optional float duration = 3; 31 | repeated Rendition renditions = 4; 32 | } 33 | 34 | message VideoFailure { 35 | enum Cause { 36 | UNEXPECTED = 1; 37 | ASSET_NOT_FOUND = 2; 38 | UNKNOWN_ACCOUNT = 3; 39 | TIMEOUT = 4; 40 | DATABASE_ERROR = 5; 41 | } 42 | 43 | optional string asset_key = 1; 44 | repeated string reason = 2; 45 | optional Cause cause = 3; 46 | } 47 | 48 | message VideoResult { 49 | optional Video success = 1; 50 | optional VideoFailure failure = 2; 51 | } 52 | 53 | repeated VideoResult response = 1; 54 | } 55 | -------------------------------------------------------------------------------- /scalabuff-compiler/src/test/resources/proto/Empty.proto: -------------------------------------------------------------------------------- 1 | package resources.generated; 2 | 3 | option java_outer_classname = "EmptyMessageTest"; 4 | 5 | option optimize_for = LITE_RUNTIME; 6 | 7 | // empty .proto file; 8 | // contains only comments -------------------------------------------------------------------------------- /scalabuff-compiler/src/test/resources/proto/Enum.proto: -------------------------------------------------------------------------------- 1 | package resources.generated; 2 | 3 | option java_outer_classname = "EnumTest"; 4 | 5 | enum ComputerPeripherals { 6 | KEYBOARD = 1; 7 | MOUSE = 2; 8 | } 9 | 10 | message MyPeripherals { 11 | optional ComputerPeripherals primary_peripheral = 1 [default = KEYBOARD]; 12 | optional ComputerPeripherals secondary_peripheral = 2; 13 | } 14 | 15 | message Outer { 16 | enum Inner { 17 | FIRST = 1; 18 | SECOND = 2; 19 | } 20 | required Inner inner_required = 1; 21 | optional Inner inner_optional = 2 [default = FIRST]; 22 | repeated Inner inner_repeated = 3; 23 | } 24 | 25 | message OuterDuplicate { 26 | enum Inner { 27 | FIRST = 1; 28 | SECOND = 2; 29 | } 30 | required Inner inner_required = 1; 31 | optional Inner inner_optional = 2 [default = SECOND]; 32 | repeated Inner inner_repeated = 3; 33 | } 34 | 35 | message OuterEnumContainer { 36 | message InnerEnumContainer { 37 | enum SomeEnum { 38 | VALUE_1 = 0; 39 | VALUE_2 = 1; 40 | VALUE_3 = 2; 41 | } 42 | required SomeEnum some_enum = 1; 43 | } 44 | required InnerEnumContainer inner_message = 1; 45 | } 46 | -------------------------------------------------------------------------------- /scalabuff-compiler/src/test/resources/proto/Extensions.proto: -------------------------------------------------------------------------------- 1 | package resources.generated; 2 | 3 | option optimize_for = LITE_RUNTIME; 4 | 5 | message ExtensionsTest { 6 | required int32 foo = 1; 7 | 8 | extensions 100 to 200; 9 | } 10 | 11 | extend ExtensionsTest { 12 | optional int32 fooExtended = 100 [default = 5]; 13 | } 14 | -------------------------------------------------------------------------------- /scalabuff-compiler/src/test/resources/proto/Groups.proto: -------------------------------------------------------------------------------- 1 | package resources.generated; 2 | 3 | option java_outer_classname = "GroupsTest"; 4 | 5 | message Groups { 6 | repeated group Group = 1 { 7 | required string name = 2; 8 | } 9 | } -------------------------------------------------------------------------------- /scalabuff-compiler/src/test/resources/proto/ImportPackages.proto: -------------------------------------------------------------------------------- 1 | package resources.generated; 2 | 3 | import "package_name.proto"; 4 | 5 | option optimize_for = LITE_RUNTIME; 6 | 7 | message UsesImportPackage { 8 | required PackageTest package_test = 1; 9 | } 10 | -------------------------------------------------------------------------------- /scalabuff-compiler/src/test/resources/proto/ImportUseFullname.proto: -------------------------------------------------------------------------------- 1 | package resources.generated; 2 | 3 | import "package_name.proto"; 4 | 5 | option optimize_for = LITE_RUNTIME; 6 | 7 | message UseFullImportedName { 8 | required resources.generated.nested.PackageTest fullname_test = 1; 9 | } -------------------------------------------------------------------------------- /scalabuff-compiler/src/test/resources/proto/Imports.proto: -------------------------------------------------------------------------------- 1 | package resources.generated; 2 | 3 | import "simple.proto"; 4 | 5 | option optimize_for = LITE_RUNTIME; 6 | 7 | message UsesImport { 8 | required SimpleTest simple_test = 1; 9 | } -------------------------------------------------------------------------------- /scalabuff-compiler/src/test/resources/proto/InvalidComplex.proto: -------------------------------------------------------------------------------- 1 | package resources.generated; 2 | 3 | message InvalidComplex { // correct 4 | optional int32 varint1 = 1; // correct 5 | optional 6int640 invalid_field_user_type = 2; // invalid field user type 6 | invalidLabel int32 invalid_label = 3; // invalid label 7 | optional bool 1invalid_field_name = 7; // invalid field name 8 | optional fixed64 invalid_tag_number = f; // invalid tag number 9 | optional string missing_tag_number = ; // missing tag number 10 | repeated packed invalid_option - 203 [packed-true]; // - instead of = 11 | optional float floating floated = 502; // "floated" extra 12 | 13 | } -------------------------------------------------------------------------------- /scalabuff-compiler/src/test/resources/proto/InvalidMessage.proto: -------------------------------------------------------------------------------- 1 | package resources.generated; 2 | 3 | message Invalid Message { } -------------------------------------------------------------------------------- /scalabuff-compiler/src/test/resources/proto/InvalidSimple.proto: -------------------------------------------------------------------------------- 1 | package resources.generated; 2 | 3 | invalidSimple { } -------------------------------------------------------------------------------- /scalabuff-compiler/src/test/resources/proto/Keywords.proto: -------------------------------------------------------------------------------- 1 | package resources.generated; 2 | 3 | option optimize_for = LITE_RUNTIME; 4 | 5 | message KeywordsTest { 6 | required int64 size = 1; 7 | required int32 case = 2; 8 | required int32 val = 3; 9 | required int32 var = 4; 10 | required int32 def = 5; 11 | required int32 object = 6; 12 | required int32 class = 7; 13 | required int32 lazy = 8; 14 | required int32 type = 9; 15 | } 16 | -------------------------------------------------------------------------------- /scalabuff-compiler/src/test/resources/proto/Message.proto: -------------------------------------------------------------------------------- 1 | package resources.generated; 2 | 3 | option java_outer_classname = "ScalaBuffMessageTest"; 4 | 5 | option optimize_for = LITE_RUNTIME; 6 | 7 | message EmptyMessage { 8 | } -------------------------------------------------------------------------------- /scalabuff-compiler/src/test/resources/proto/NestedMessages.proto: -------------------------------------------------------------------------------- 1 | package resources.generated; 2 | 3 | message TopLevel { 4 | required uint32 id_toplevel = 1; 5 | 6 | message Inner { 7 | required uint32 id_inner = 1; 8 | } 9 | } 10 | 11 | message Foobar { 12 | message Foo { 13 | optional uint64 id = 1; 14 | } 15 | message Bar { 16 | optional uint64 id = 1; 17 | } 18 | message FooBar { 19 | optional uint64 id = 1; 20 | } 21 | required Foo reqFoo = 1; 22 | 23 | optional Foo optFoo = 2; 24 | optional Bar optBar = 3; 25 | 26 | repeated Foo repFoo = 4; 27 | repeated Bar repBar = 5; 28 | repeated FooBar rep_foo_bar = 6; 29 | 30 | required TopLevel top_level_req = 7; 31 | optional TopLevel top_level_opt = 8; 32 | 33 | required TopLevel.Inner top_level_inner_req = 9; 34 | } 35 | -------------------------------------------------------------------------------- /scalabuff-compiler/src/test/resources/proto/Numbers.proto: -------------------------------------------------------------------------------- 1 | package resources.generated; 2 | 3 | option optimize_for = LITE_RUNTIME; 4 | 5 | message NumbersTest1 { 6 | optional int32 some_hex_number = 1 [default=0x19ABCDEF]; 7 | optional int32 some_oct_number = 2 [default=01234567]; 8 | } 9 | 10 | enum NumbersTest2 { 11 | SOME_HEX_NUMBER = 0x19ABCDEF; 12 | SOME_OCT_NUMBER = 01234567; 13 | } 14 | 15 | -------------------------------------------------------------------------------- /scalabuff-compiler/src/test/resources/proto/PackageName.proto: -------------------------------------------------------------------------------- 1 | package resources.generated.nested; 2 | 3 | option java_package = "resources.generated.nested"; 4 | 5 | message PackageTest { 6 | required int32 required_field = 1; 7 | } 8 | -------------------------------------------------------------------------------- /scalabuff-compiler/src/test/resources/proto/Packed.proto: -------------------------------------------------------------------------------- 1 | package resources.generated; 2 | 3 | option optimize_for = LITE_RUNTIME; 4 | 5 | message PackedTest { 6 | required int32 required_field = 1; 7 | optional float optional_field = 2; 8 | repeated int32 repeated_packed_field = 3 [packed=true]; 9 | // has data size 2 10 | // repeated int32 repeated_packed_field_33 = 33 [packed=true]; 11 | } 12 | -------------------------------------------------------------------------------- /scalabuff-compiler/src/test/resources/proto/RemoteProtocol.proto: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2009-2012 Typesafe Inc. 3 | */ 4 | 5 | option java_package = "resources.generated"; 6 | option optimize_for = SPEED; 7 | 8 | /****************************************** 9 | Compile with: 10 | cd ./akka-remote/src/main/protocol 11 | protoc RemoteProtocol.proto --java_out ../java 12 | cd ../../../.. 13 | ./scripts/fix-protobuf.sh 14 | *******************************************/ 15 | 16 | message AkkaRemoteProtocol { 17 | optional RemoteMessageProtocol message = 1; 18 | optional RemoteControlProtocol instruction = 2; 19 | } 20 | 21 | /** 22 | * Defines a remote message. 23 | */ 24 | message RemoteMessageProtocol { 25 | required ActorRefProtocol recipient = 1; 26 | required MessageProtocol message = 2; 27 | optional ActorRefProtocol sender = 4; 28 | repeated MetadataEntryProtocol metadata = 5; 29 | } 30 | 31 | /** 32 | * Defines some control messages for the remoting 33 | */ 34 | message RemoteControlProtocol { 35 | required CommandType commandType = 1; 36 | optional string cookie = 2; 37 | optional AddressProtocol origin = 3; 38 | } 39 | 40 | /** 41 | * Defines the type of the RemoteControlProtocol command type 42 | */ 43 | enum CommandType { 44 | CONNECT = 1; 45 | SHUTDOWN = 2; 46 | HEARTBEAT = 3; 47 | } 48 | 49 | /** 50 | * Defines a remote ActorRef that "remembers" and uses its original Actor instance 51 | * on the original node. 52 | */ 53 | message ActorRefProtocol { 54 | required string path = 1; 55 | } 56 | 57 | /** 58 | * Defines a message. 59 | */ 60 | message MessageProtocol { 61 | required bytes message = 1; 62 | required int32 serializerId = 2; 63 | optional bytes messageManifest = 3; 64 | } 65 | 66 | /** 67 | * Defines a meta data entry. 68 | */ 69 | message MetadataEntryProtocol { 70 | required string key = 1; 71 | required bytes value = 2; 72 | } 73 | 74 | /** 75 | * Defines a remote address. 76 | */ 77 | message AddressProtocol { 78 | required string system = 1; 79 | required string hostname = 2; 80 | required uint32 port = 3; 81 | } 82 | 83 | /** 84 | * Defines akka.remote.DaemonMsgCreate 85 | */ 86 | message DaemonMsgCreateProtocol { 87 | required PropsProtocol props = 1; 88 | required DeployProtocol deploy = 2; 89 | required string path = 3; 90 | required ActorRefProtocol supervisor = 4; 91 | } 92 | 93 | /** 94 | * Serialization of akka.actor.Props 95 | */ 96 | message PropsProtocol { 97 | required string dispatcher = 1; 98 | required DeployProtocol deploy = 2; 99 | optional string fromClassCreator = 3; 100 | optional bytes creator = 4; 101 | optional bytes routerConfig = 5; 102 | } 103 | 104 | /** 105 | * Serialization of akka.actor.Deploy 106 | */ 107 | message DeployProtocol { 108 | required string path = 1; 109 | optional bytes config = 2; 110 | optional bytes routerConfig = 3; 111 | optional bytes scope = 4; 112 | } 113 | -------------------------------------------------------------------------------- /scalabuff-compiler/src/test/resources/proto/Simple.proto: -------------------------------------------------------------------------------- 1 | package resources.generated; 2 | 3 | option optimize_for = LITE_RUNTIME; 4 | 5 | message SimpleTest { 6 | required int32 required_field = 1; 7 | optional float optional_field = 2; 8 | repeated string repeated_field = 3; 9 | 10 | optional int32 type = 4 [default=100]; 11 | optional int32 int32Default = 5 [default=100]; 12 | optional int32 int32Negative = 6 [default=-1]; 13 | optional string stringDefault = 7 [default="some string"]; 14 | optional float floatDefault = 8 [default=1.0]; 15 | optional float floatNegative = 9 [default=-1.0]; 16 | } 17 | -------------------------------------------------------------------------------- /scalabuff-compiler/src/test/resources/proto/SimpleWithComments.proto: -------------------------------------------------------------------------------- 1 | package resources.generated; 2 | 3 | /* simpleWithComments.proto */ 4 | 5 | /* 6 | * A simple request. 7 | */ 8 | 9 | message SimpleRequest { // start message 10 | required string query = 1; // query 11 | optional int32 page_number = 2; // page number 12 | optional int32 results_per_page = 3; // number of results per page 13 | } // end simple request. 14 | 15 | // random comment -------------------------------------------------------------------------------- /scalabuff-compiler/src/test/resources/proto/Singlequoted.proto: -------------------------------------------------------------------------------- 1 | package resources.generated; 2 | 3 | option optimize_for = LITE_RUNTIME; 4 | 5 | message SingleQuote { 6 | optional string single_quoted_field = 1 [default = 'NA']; 7 | } 8 | -------------------------------------------------------------------------------- /scalabuff-compiler/src/test/tests/BuffedStringTest.scala: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import org.scalatest.{FunSuite, Matchers} 4 | import net.sandrogrzicic.scalabuff.compiler.BuffedString 5 | import scala.language.implicitConversions 6 | 7 | /** 8 | * Tests for the BuffedString string helper class. 9 | * @author Sandro Gržičić 10 | */ 11 | 12 | class BuffedStringTest extends FunSuite with Matchers { 13 | 14 | implicit def buffString(string: String): BuffedString = new BuffedString(string) 15 | 16 | test("camelCase") { 17 | "very_long_name_in_c_style_001".camelCase should equal ("VeryLongNameInCStyle001") 18 | } 19 | 20 | val testPath = "root/dots.in.path/file.name.extension" 21 | 22 | test("dropUntilFirst") { 23 | testPath.dropUntilFirst('/') should equal ("dots.in.path/file.name.extension") 24 | } 25 | test("dropUntilLast") { 26 | testPath.dropUntilLast('/') should equal ("file.name.extension") 27 | } 28 | 29 | test("takeUntilFirst") { 30 | testPath.takeUntilFirst('.') should equal ("root/dots") 31 | } 32 | test("takeUntilLast") { 33 | testPath.takeUntilLast('.') should equal ("root/dots.in.path/file.name") 34 | } 35 | 36 | test("betweenLast") { 37 | testPath.betweenLast('/', '.') should equal ("file.name") 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /scalabuff-compiler/src/test/tests/EnumTest.scala: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import com.google.protobuf._ 4 | import java.io.{ByteArrayInputStream, ByteArrayOutputStream} 5 | import org.scalatest.{FunSuite, Matchers} 6 | import resources.generated._ 7 | import scala.collection._ 8 | 9 | /** 10 | * Tests whether generated Scala classes function correctly. 11 | * @author Sandro Gržičić 12 | */ 13 | 14 | class EnumTest extends FunSuite with Matchers { 15 | 16 | // This is copied from: 17 | // https://code.google.com/p/protobuf/source/browse/trunk/java/src/main/java/com/google/protobuf/WireFormat.java?r=425#69 18 | // because Google has seen fit NOT to make these accessible. Grr! 19 | 20 | private val TAG_TYPE_BITS = 3 21 | 22 | private def makeTag(fieldNumber: Int, wireType: Int): Byte = { 23 | val tag = (fieldNumber << TAG_TYPE_BITS) | wireType 24 | tag.toByte 25 | } 26 | 27 | test("enum.parseFrom: valid enum ID") { 28 | val message = MyPeripherals(Some(ComputerPeripherals.MOUSE)) 29 | 30 | MyPeripherals.parseFrom(message.toByteArray) should equal (message) 31 | MyPeripherals.parseFrom(message.toByteString) should equal (message) 32 | 33 | val os = new ByteArrayOutputStream 34 | message.writeTo(os) 35 | 36 | MyPeripherals.parseFrom(new ByteArrayInputStream(os.toByteArray)) should equal (message) 37 | 38 | // Write out the enum in wire format 39 | val tag = makeTag(1, WireFormat.WIRETYPE_VARINT) 40 | val rawMessage = ByteString.copyFrom(Array[Byte](tag, 2)) 41 | 42 | MyPeripherals.parseFrom(rawMessage.toByteArray) should equal (message) 43 | } 44 | 45 | test("enum.parseFrom: unknown enum ID without default") { 46 | val tag = makeTag(2, WireFormat.WIRETYPE_VARINT) 47 | val message = ByteString.copyFrom(Array[Byte](tag, 7)) 48 | 49 | val thrown = intercept[Exception] { 50 | MyPeripherals.parseFrom(new ByteArrayInputStream(message.toByteArray)) should equal (message) 51 | } 52 | 53 | thrown.getMessage.contains("Unknown enum ID") should be(true) 54 | } 55 | 56 | test("enum.parseFrom: unknown enum ID with default") { 57 | val tag = makeTag(1, WireFormat.WIRETYPE_VARINT) 58 | val message = ByteString.copyFrom(Array[Byte](tag, 7)) 59 | val defaultMessage = MyPeripherals(Some(ComputerPeripherals.KEYBOARD)) 60 | 61 | MyPeripherals.parseFrom(new ByteArrayInputStream(message.toByteArray)) should equal (defaultMessage) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /scalabuff-compiler/src/test/tests/ExtendedMessageTest.scala: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import org.scalatest.{FunSuite, Matchers} 4 | import resources.generated._ 5 | 6 | /** 7 | * Tests whether generated Scala classes function correctly. 8 | * @author Sandro Gržičić 9 | */ 10 | 11 | class ExtendedMessageTest extends FunSuite with Matchers { 12 | 13 | test("ExtendableMessage") { 14 | val foo = 0 15 | 16 | val sent = ExtensionsTest(foo) 17 | 18 | sent.foo should equal (foo) 19 | 20 | val received = ExtensionsTest.defaultInstance.mergeFrom(sent.toByteArray) 21 | 22 | received.foo should equal (sent.foo) 23 | 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /scalabuff-compiler/src/test/tests/JavaInteroperabilityTest.scala: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import org.scalatest.{FunSuite, Matchers} 4 | import resources.generated._ 5 | import com.google.protobuf._ 6 | import java.util.Arrays 7 | 8 | /** 9 | * Tests whether generated Scala classes are interoperable with corresponding Java classes. 10 | * @author Sandro Gržičić 11 | */ 12 | 13 | class JavaInteroperabilityTest extends FunSuite with Matchers { 14 | 15 | test("ComplexMessage") { 16 | import resources.java.{Complex => JComplex} 17 | 18 | val first = ByteString.copyFromUtf8("Sandro Gržičić") 19 | val second = "Sandro Grzicic" 20 | val nestedNested = "Nested String" 21 | 22 | val repeatedStringArray = Array("net", "sandrogrzicic", "scalabuff") 23 | val repeatedBytesArray = Array(ByteString.copyFrom(Array[Byte](1, 2, 3)), ByteString.copyFrom(Array[Byte](4, 5, 6))) 24 | 25 | val scala = ComplexMessage( 26 | first, 27 | Some(second), 28 | Some(ComplexMessage.Nested( 29 | nestedNested, 30 | Some(ComplexMessage.SimpleEnum.KEY_NAME) 31 | )), 32 | Vector(ComplexMessage.SimpleEnum.KEY_NAME), 33 | Vector(repeatedStringArray: _*), 34 | Vector(repeatedBytesArray: _*) 35 | ) 36 | 37 | val javaNested = JComplex.ComplexMessage.Nested 38 | .newBuilder() 39 | .setNestedField(nestedNested) 40 | .setNestedEnum(JComplex.ComplexMessage.SimpleEnum.KEY_NAME) 41 | .build() 42 | 43 | val java = JComplex.ComplexMessage 44 | .newBuilder() 45 | .setFirstField(first) 46 | .setSecondField(second) 47 | .setNestedOuterField(javaNested) 48 | .addAllSimpleEnumField(Arrays.asList(JComplex.ComplexMessage.SimpleEnum.KEY_NAME)) 49 | .addAllRepeatedStringField(Arrays.asList(repeatedStringArray: _*)) 50 | .addAllRepeatedBytesField(Arrays.asList(repeatedBytesArray: _*)) 51 | .build() 52 | 53 | Arrays.hashCode(scala.toByteArray) should equal (Arrays.hashCode(java.toByteArray)) 54 | 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /scalabuff-compiler/src/test/tests/MessageTest.scala: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import org.scalatest.{FunSuite, Matchers} 4 | import resources.generated._ 5 | import com.google.protobuf._ 6 | import scala.collection._ 7 | import java.io.{ByteArrayInputStream, ByteArrayOutputStream} 8 | 9 | /** 10 | * Tests whether generated Scala classes function correctly. 11 | * @author Sandro Gržičić 12 | */ 13 | 14 | class MessageTest extends FunSuite with Matchers { 15 | 16 | test("ComplexMessage") { 17 | val nestedNested = "Nested String" 18 | val nestedEnum = ComplexMessage.SimpleEnum.KEY_NAME 19 | 20 | val first = ByteString.copyFromUtf8("Sandro Gržičić") 21 | val second = "Sandro Grzicic" 22 | val nestedOuter = ComplexMessage.Nested(nestedNested, Some(nestedEnum)) 23 | val simpleEnum = immutable.Seq(ComplexMessage.SimpleEnum.KEY_NAME) 24 | val repeatedString = List("net", "sandrogrzicic", "scalabuff") 25 | val repeatedBytes = Vector(ByteString.copyFrom(Array[Byte](1, 2, 3)), ByteString.copyFrom(Array[Byte](4, 5, 6))) 26 | 27 | val sent = ComplexMessage(first, Some(second), Some(nestedOuter), simpleEnum, repeatedString, repeatedBytes) 28 | 29 | sent.firstField should equal(first) 30 | sent.secondField.get should equal(second) 31 | sent.nestedOuterField.get should equal(nestedOuter) 32 | sent.simpleEnumField should equal(simpleEnum) 33 | sent.repeatedStringField should equal(repeatedString) 34 | sent.repeatedBytesField should equal(repeatedBytes) 35 | 36 | val received = ComplexMessage.defaultInstance.mergeFrom(sent.toByteArray) 37 | 38 | received.firstField should equal (sent.firstField) 39 | received.secondField should equal (sent.secondField) 40 | received.nestedOuterField should equal (sent.nestedOuterField) 41 | received.simpleEnumField should equal (sent.simpleEnumField) 42 | received.repeatedStringField should equal (sent.repeatedStringField) 43 | received.repeatedBytesField should equal (sent.repeatedBytesField) 44 | } 45 | 46 | test("object.parseFrom") { 47 | val message = ComplexMessage(ByteString.copyFromUtf8("Sandro Gržičić")) 48 | 49 | ComplexMessage.parseFrom(message.toByteArray) should equal (message) 50 | ComplexMessage.parseFrom(message.toByteString) should equal (message) 51 | 52 | val os = new ByteArrayOutputStream() 53 | message.writeTo(os) 54 | 55 | ComplexMessage.parseFrom(new ByteArrayInputStream(os.toByteArray)) should equal (message) 56 | } 57 | 58 | test("Keywords") { 59 | val message = KeywordsTest(123456789L) 60 | message.toByteArray.size should equal (21) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /scalabuff-compiler/src/test/tests/PerformanceTest.scala: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import org.scalatest.{FunSuite, Matchers} 4 | import net.sandrogrzicic.scalabuff.compiler._ 5 | import java.io.{FileFilter, PrintStream, ByteArrayOutputStream, File} 6 | import File.{separator => /} 7 | 8 | /** 9 | * ScalaBuff performance test. 10 | * @author Sandro Gržičić 11 | */ 12 | 13 | class PerformanceTest extends FunSuite with Matchers { 14 | 15 | val WARMUP_COUNT = 20 16 | val REPEAT_COUNT = 20 17 | 18 | val protoDir = "scalabuff-compiler" + / + "src" + / + "test" + / + "resources" + / + "proto" + / 19 | val protoDirFile = new File(protoDir) 20 | 21 | val protoFileFilter = new FileFilter { 22 | def accept(filtered: File) = filtered.getName.endsWith(".proto") 23 | } 24 | 25 | val outputStream = new ByteArrayOutputStream() 26 | val printStream = new PrintStream(outputStream) 27 | 28 | test("performance test") { 29 | 30 | val inputFiles = protoDirFile.listFiles(protoFileFilter).map(_.getName) 31 | val scalaBuffArguments = Array("--stdout", "--verbose", "--proto_path=" + protoDir) ++ inputFiles 32 | 33 | def doRun() { 34 | outputStream.reset() 35 | Console.withOut(printStream) { 36 | ScalaBuff.run(scalaBuffArguments) 37 | } 38 | } 39 | 40 | // JVM warmup 41 | var warmupRun: Int = 0 42 | while (warmupRun < WARMUP_COUNT) { 43 | doRun() 44 | warmupRun += 1 45 | } 46 | 47 | System.gc() 48 | val start = System.currentTimeMillis() 49 | var run = 0 50 | while (run < REPEAT_COUNT) { 51 | doRun() 52 | run += 1 53 | } 54 | val time = (System.currentTimeMillis() - start) / REPEAT_COUNT.toDouble 55 | 56 | info("Time per run: " + time + " ms") 57 | } 58 | } 59 | 60 | -------------------------------------------------------------------------------- /scalabuff-compiler/src/test/tests/ScalaBuffTest.scala: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import org.scalatest.{FunSuite, Matchers} 4 | import net.sandrogrzicic.scalabuff.compiler._ 5 | import net.sandrogrzicic.scalabuff.compiler.{Strings, ScalaBuff, ScalaClass} 6 | import java.io.{PrintStream, ByteArrayOutputStream, File} 7 | import File.{separator => /} 8 | 9 | /** 10 | * ScalaBuff CLI runner test. 11 | * @author Sandro Gržičić 12 | */ 13 | class ScalaBuffTest extends FunSuite with Matchers { 14 | 15 | val NEWLINE = System.getProperty("line.separator") 16 | 17 | val parsedExtension = ".txt" 18 | 19 | val outputDir = "scalabuff-compiler" + / + "src" + / + "test" + / 20 | 21 | val protoDir = outputDir + "resources" + / + "proto" + / 22 | val multiProtoDir = outputDir + "resources" + / + "multipleprototests" + / 23 | val parsedDir = outputDir + "resources" + / + "parsed" + / 24 | val resourcesGeneratedDir = "resources" + / + "generated" + / 25 | val generatedDir = outputDir + resourcesGeneratedDir 26 | 27 | val testProto = "Simple" 28 | val testProtoParsed = io.Source.fromFile(new File(parsedDir + testProto + parsedExtension), "UTF-8").mkString 29 | val testProtoGenerated = io.Source.fromFile(new File(generatedDir + testProto.capitalize + ".scala"), "UTF-8").mkString 30 | 31 | val testProtoMulti = "MultiOne" 32 | 33 | val testProtoPacked = "Packed" 34 | 35 | test("apply: simple .proto file") { 36 | val settings = ScalaBuff.Settings(generateJsonMethod = true) 37 | val scalaClass: ScalaClass = ScalaBuff(new File(protoDir + testProto + ".proto"))(settings) 38 | scalaClass.body should equal(testProtoGenerated) 39 | scalaClass.file should equal("Simple") 40 | scalaClass.path should equal(resourcesGeneratedDir) 41 | } 42 | 43 | test("run: no arguments") { 44 | val outputStream = new ByteArrayOutputStream() 45 | Console.withOut(new PrintStream(outputStream)) { 46 | ScalaBuff.run(Array()) 47 | outputStream.toString("utf-8") should equal(Strings.HELP + NEWLINE) 48 | } 49 | } 50 | 51 | test("run: simple .proto file without a specified output directory") { 52 | val resourcesDirectory = new File("scalabuff-compiler" + / + "resources") 53 | val resourcesGeneratedDirectory = new File("scalabuff-compiler" + / + resourcesGeneratedDir) 54 | // don't attempt to modify an existing root folder 55 | if (!(resourcesDirectory.exists() && resourcesDirectory.isDirectory || 56 | resourcesGeneratedDirectory.exists() && resourcesGeneratedDirectory.isDirectory) 57 | ) { 58 | val outputStream = new ByteArrayOutputStream() 59 | val simpleProto = protoDir + testProto + ".proto" 60 | Console.withOut(new PrintStream(outputStream)) { 61 | ScalaBuff.run(Array("--generate_json_method", simpleProto)) 62 | outputStream.toString("utf-8") should be('empty) 63 | } 64 | val outputFile = new File(resourcesGeneratedDir + testProto + ".scala") 65 | outputFile should be('exists) 66 | outputFile.deleteOnExit() 67 | val outputFileSource = io.Source.fromFile(outputFile, "UTF-8") 68 | outputFileSource.mkString should equal(testProtoGenerated) 69 | outputFileSource.close() 70 | outputFile.delete() 71 | new File("resources" + / + "generated").delete() 72 | new File("resources").delete() 73 | } 74 | } 75 | 76 | test("run: simple .proto file with a specified output directory") { 77 | val outputStream = new ByteArrayOutputStream() 78 | val simpleProto = protoDir + testProto + ".proto" 79 | Console.withOut(new PrintStream(outputStream)) { 80 | ScalaBuff.run(Array("-v", "-v", "--generate_json_method", "--scala_out=" + outputDir, simpleProto)) 81 | val output = outputStream.toString.split("\r?\n") 82 | output.length should be (2) 83 | output(0) should startWith("Parameters: ") 84 | output(1) should startWith("Paths: ") 85 | } 86 | val outputFile = new File(outputDir + / + resourcesGeneratedDir + testProto + ".scala") 87 | outputFile should be('exists) 88 | val outputFileSource = io.Source.fromFile(outputFile, "UTF-8") 89 | outputFileSource.mkString should equal(testProtoGenerated) 90 | outputFileSource.close() 91 | } 92 | 93 | test("run: input directory only") { 94 | val protoFiles = Seq("MultiOne", "MultiTwo") 95 | 96 | val outputStream = new ByteArrayOutputStream() 97 | Console.withOut(new PrintStream(outputStream)) { 98 | ScalaBuff.run(Array("-v", "-v", "--scala_out=" + outputDir, "--proto_path=" + multiProtoDir)) 99 | outputStream.toString("utf-8").split("\n").size should be(2) 100 | } 101 | 102 | for (proto <- protoFiles) { 103 | val outputFile = new File(outputDir + / + resourcesGeneratedDir + proto + ".scala") 104 | outputFile should be('exists) 105 | val outputFileSource = io.Source.fromFile(outputFile, "UTF-8") 106 | val exampleProtoGenerated = io.Source.fromFile(new File(generatedDir + proto + ".scala"), "UTF-8").mkString 107 | outputFileSource.mkString should equal(exampleProtoGenerated) 108 | outputFileSource.close() 109 | } 110 | } 111 | 112 | test("run: multiple input directories, with file") { 113 | val outputStream = new ByteArrayOutputStream() 114 | Console.withOut(new PrintStream(outputStream)) { 115 | ScalaBuff.run(Array("--scala_out=" + outputDir, 116 | "--proto_path=" + parsedDir, // no proto files here, but we want to make sure MultiOne.proto is found 117 | "--proto_path=" + multiProtoDir, 118 | "--verbose", 119 | testProtoMulti + ".proto")) 120 | outputStream.toString("utf-8").split("\n").size should be(1) 121 | } 122 | 123 | val outputFile = new File(outputDir + / + resourcesGeneratedDir + testProtoMulti + ".scala") 124 | outputFile should be('exists) 125 | val outputFileSource = io.Source.fromFile(outputFile, "UTF-8") 126 | val exampleProtoGenerated = io.Source.fromFile(new File(generatedDir + testProtoMulti + ".scala"), "UTF-8").mkString 127 | outputFileSource.mkString should equal(exampleProtoGenerated) 128 | outputFileSource.close() 129 | } 130 | 131 | test("run: import across packages") { 132 | def compile(filename: String, subFolder: Option[String]) { 133 | val protoFile = filename + ".proto" 134 | val scalaFile = filename + ".scala" 135 | val outputStream = new ByteArrayOutputStream() 136 | Console.withOut(new PrintStream(outputStream)) { 137 | ScalaBuff.run(Array("--scala_out=" + outputDir, 138 | "--proto_path=" + protoDir, 139 | "--verbose", 140 | protoFile)) 141 | outputStream.toString("utf-8").split("\n").size should be(1) 142 | } 143 | 144 | val outputFile = 145 | new File(outputDir + / + resourcesGeneratedDir + 146 | subFolder.map(_ + /).getOrElse("") + scalaFile) 147 | outputFile should be('exists) 148 | val outputFileSource = io.Source.fromFile(outputFile, "UTF-8") 149 | val exampleProtoGenerated = 150 | io.Source.fromFile(new File(generatedDir + subFolder.map(_ + /).getOrElse("") + scalaFile), "UTF-8").mkString 151 | outputFileSource.mkString should equal(exampleProtoGenerated) 152 | outputFileSource.close() 153 | } 154 | 155 | compile("PackageName", Some("nested")) 156 | compile("ImportPackages", None) 157 | compile("ImportUseFullname", None) 158 | } 159 | 160 | test("run: unknown option") { 161 | val outputStream = new ByteArrayOutputStream() 162 | Console.withOut(new PrintStream(outputStream)) { 163 | val unsupportedOption = "--unsupported-option" 164 | ScalaBuff.run(Array(unsupportedOption)) 165 | outputStream.toString("utf-8") should be(Strings.UNKNOWN_ARGUMENT + unsupportedOption + NEWLINE) 166 | } 167 | } 168 | 169 | test("run: invalid output directory") { 170 | val outputStream = new ByteArrayOutputStream() 171 | Console.withOut(new PrintStream(outputStream)) { 172 | val invalidOutputDirectory = "()/%$#:;" 173 | ScalaBuff.run(Array("--scala_out=" + invalidOutputDirectory)) 174 | outputStream.toString("utf-8") should be(Strings.INVALID_OUTPUT_DIRECTORY + invalidOutputDirectory + NEWLINE) 175 | } 176 | } 177 | 178 | test("apply: packed .proto file") { 179 | 180 | val settings = ScalaBuff.Settings(generateJsonMethod = true) 181 | val scalaClass: ScalaClass = ScalaBuff(new File(protoDir + testProtoPacked + ".proto"))(settings) 182 | // TODO matches 183 | // println(scalaClass) 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /scalabuff-runtime/src/main/net/sandrogrzicic/scalabuff/Enum.scala: -------------------------------------------------------------------------------- 1 | package net.sandrogrzicic.scalabuff 2 | 3 | import scala.language.implicitConversions 4 | 5 | /** 6 | * Viktor Klang's Enum, modified for ScalaBuff for protobuf usage 7 | * Source: https://gist.github.com/1057513/ 8 | */ 9 | trait Enum { 10 | 11 | import java.util.concurrent.atomic.AtomicReference 12 | 13 | type EnumVal <: Value 14 | 15 | implicit def _enumToInt(_e: EnumVal): Int = _e.id 16 | 17 | private val _values = new AtomicReference(Vector[EnumVal]()) 18 | 19 | /** 20 | * Add an EnumVal to our storage, using CCAS to make sure it's thread safe, returns the ordinal. 21 | */ 22 | private final def addEnumVal(newVal: EnumVal): Int = { 23 | import _values.{get, compareAndSet => CAS} 24 | val oldVec = get 25 | val newVec = oldVec :+ newVal 26 | if ((get eq oldVec) && CAS(oldVec, newVec)) newVec.indexWhere(_ eq newVal) else addEnumVal(newVal) 27 | } 28 | 29 | /** 30 | * Get all the enums that exist for this type. 31 | */ 32 | def values: Vector[EnumVal] = _values.get 33 | 34 | protected trait Value extends com.google.protobuf.Internal.EnumLite { 35 | self: EnumVal => // Enforce that no one mixes in Value in a non-EnumVal type 36 | final val ordinal = addEnumVal(this) // Adds the EnumVal and returns the ordinal 37 | 38 | // proto enum value 39 | val id: Int 40 | // proto enum name 41 | val name: String 42 | 43 | def getNumber = id 44 | 45 | override def toString = name 46 | /** 47 | * Enum Values with identical values are equal. 48 | */ 49 | override def equals(other: Any) = other.isInstanceOf[Value] && this.id == other.asInstanceOf[Value].id 50 | /** 51 | * Enum Values with identical values return the same hashCode. 52 | */ 53 | override def hashCode = 31 * (this.getClass.## + name.## + id) 54 | } 55 | 56 | } 57 | 58 | 59 | /** 60 | * Thrown when an unknown enum number is passed to the valueOf method of an Enum. 61 | */ 62 | class UnknownEnumException(enumID: Int) extends RuntimeException("Unknown enum ID: " + enumID) 63 | 64 | /** 65 | * Thrown when a required field with enum type is uninitialized on access attempt. 66 | */ 67 | class UninitializedEnumException[T](name: String) extends RuntimeException("Enum not initialized: " + name) 68 | -------------------------------------------------------------------------------- /scalabuff-runtime/src/main/net/sandrogrzicic/scalabuff/ExtendableMessage.scala: -------------------------------------------------------------------------------- 1 | package net.sandrogrzicic.scalabuff 2 | 3 | import com.google.protobuf._ 4 | 5 | /** 6 | * Message trait for extendable messages generated with ScalaBuff. 7 | * Ordinarily Messages would have GeneratedMessageLite.Builder mixed in, but since it's a Java class, we can't do that. 8 | * Contains methods implementing the MessageLite.Builder Java interface, similar to ones in GeneratedMessageLite.Builder. 9 | * 10 | * @todo WORK IN PROGRESS 11 | * @author Sandro Gržičić 12 | */ 13 | trait ExtendableMessage[ 14 | MessageType <: GeneratedMessageLite.ExtendableMessage[MessageType]] extends MessageBuilder[MessageType] 15 | // extends GeneratedMessageLite.ExtendableBuilder[MessageType, MessageType] { 16 | { 17 | 18 | 19 | 20 | } 21 | 22 | -------------------------------------------------------------------------------- /scalabuff-runtime/src/main/net/sandrogrzicic/scalabuff/LimitedInputStream.scala: -------------------------------------------------------------------------------- 1 | package net.sandrogrzicic.scalabuff 2 | 3 | import java.io.{FilterInputStream, InputStream} 4 | 5 | /** 6 | * See {@link com.google.protobuf.AbstractMessageLite.Builder#LimitedInputStream}. 7 | */ 8 | final class LimitedInputStream( 9 | val inputStream: InputStream, private var limit: Int 10 | ) extends FilterInputStream(inputStream) { 11 | 12 | override def available = scala.math.min(super.available, limit) 13 | 14 | override def read = { 15 | if (limit > 0) { 16 | val result = super.read 17 | if (result >= 0) { 18 | limit -= 1 19 | } 20 | result 21 | } else { 22 | -1 23 | } 24 | } 25 | 26 | override def read(bytes: Array[Byte], offset: Int, length: Int) = { 27 | if (limit > 0) { 28 | val limitedLength = scala.math.min(length, limit) 29 | val result = super.read(bytes, offset, limitedLength) 30 | if (result >= 0) { 31 | limit -= result 32 | } 33 | result 34 | } else { 35 | -1 36 | } 37 | } 38 | 39 | override def skip(n: Long) = { 40 | val result = super.skip(scala.math.min(n, limit)) 41 | if (result >= 0) { 42 | limit = (limit - result).toInt 43 | } 44 | result 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /scalabuff-runtime/src/main/net/sandrogrzicic/scalabuff/Message.scala: -------------------------------------------------------------------------------- 1 | package net.sandrogrzicic.scalabuff 2 | 3 | import com.google.protobuf._ 4 | 5 | /** 6 | * Message trait for messages generated with ScalaBuff. 7 | * Ordinarily Messages would have GeneratedMessageLite.Builder mixed in, but since it's a Java class, we can't do that. 8 | * Contains methods implementing the MessageLite.Builder Java interface, similar to ones in GeneratedMessageLite.Builder. 9 | * 10 | * @author Sandro Gržičić 11 | */ 12 | trait Message[MessageType <: MessageLite with MessageLite.Builder] 13 | extends MessageLite.Builder with MessageBuilder[MessageType] { 14 | 15 | 16 | } 17 | 18 | -------------------------------------------------------------------------------- /scalabuff-runtime/src/main/net/sandrogrzicic/scalabuff/MessageBuilder.scala: -------------------------------------------------------------------------------- 1 | package net.sandrogrzicic.scalabuff 2 | 3 | import com.google.protobuf.{ExtensionRegistryLite, CodedInputStream, ByteString} 4 | import java.io.InputStream 5 | import scala.language.implicitConversions 6 | 7 | /** 8 | * Message trait for messages generated with ScalaBuff. 9 | * Ordinarily Messages would have GeneratedMessageLite.Builder mixed in, but since it's a Java class, we can't do that. 10 | * Contains methods implementing the MessageLite.Builder Java interface, similar to ones in GeneratedMessageLite.Builder. 11 | * 12 | * @author Sandro Gržičić 13 | */ 14 | 15 | trait MessageBuilder[MessageType] { 16 | implicit def _anyToOption[T](any: T): Option[T] = Option[T](any) 17 | 18 | implicit def _stringToByteString(string: String): ByteString = ByteString.copyFromUtf8(string) 19 | 20 | def mergeFrom(message: MessageType): MessageType 21 | 22 | def isInitialized: Boolean 23 | 24 | def mergeFrom(input: CodedInputStream, extensionRegistry: ExtensionRegistryLite): MessageType 25 | 26 | def mergeFrom(input: CodedInputStream): MessageType = mergeFrom(input, ExtensionRegistryLite.getEmptyRegistry) 27 | 28 | def mergeFrom(data: ByteString): MessageType = { 29 | val input = data.newCodedInput 30 | val merged = mergeFrom(input) 31 | input.checkLastTagWas(0) 32 | merged 33 | } 34 | 35 | def mergeFrom(data: ByteString, extensionRegistry: ExtensionRegistryLite): MessageType = { 36 | val input = data.newCodedInput 37 | val merged = mergeFrom(input, extensionRegistry) 38 | input.checkLastTagWas(0) 39 | merged 40 | } 41 | 42 | def mergeFrom(data: Array[Byte]): MessageType = mergeFrom(data, 0, data.length) 43 | 44 | def mergeFrom(data: Array[Byte], offset: Int, length: Int): MessageType = { 45 | val input = CodedInputStream.newInstance(data, offset, length) 46 | val merged = mergeFrom(input) 47 | input.checkLastTagWas(0) 48 | merged 49 | } 50 | 51 | def mergeFrom(data: Array[Byte], extensionRegistry: ExtensionRegistryLite): MessageType = mergeFrom(data, 0, data.length, extensionRegistry) 52 | 53 | def mergeFrom(data: Array[Byte], off: Int, len: Int, extensionRegistry: ExtensionRegistryLite): MessageType = { 54 | val input = CodedInputStream.newInstance(data, off, len) 55 | val merged = mergeFrom(input, extensionRegistry) 56 | input.checkLastTagWas(0) 57 | merged 58 | } 59 | 60 | def mergeFrom(input: InputStream): MessageType = { 61 | val codedInput = CodedInputStream.newInstance(input) 62 | val merged = mergeFrom(codedInput) 63 | codedInput.checkLastTagWas(0) 64 | merged 65 | } 66 | 67 | def mergeFrom(input: InputStream, extensionRegistry: ExtensionRegistryLite): MessageType = { 68 | val codedInput = CodedInputStream.newInstance(input) 69 | val merged = mergeFrom(codedInput, extensionRegistry) 70 | codedInput.checkLastTagWas(0) 71 | merged 72 | } 73 | 74 | /** 75 | * NOTE: Due to Java Protocol Buffers library compatibility, this method is useless in most cases. 76 | * @see Message#mergeDelimitedFromStream() 77 | */ 78 | def mergeDelimitedFrom(input: InputStream, extensionRegistry: ExtensionRegistryLite) = { 79 | val firstByte = input.read 80 | if (firstByte != -1) { 81 | val size = CodedInputStream.readRawVarint32(firstByte, input) 82 | val limitedInput = new LimitedInputStream(input, size) 83 | mergeFrom(limitedInput, extensionRegistry) 84 | true 85 | } else { 86 | false 87 | } 88 | } 89 | 90 | /** 91 | * NOTE: Due to Java Protocol Buffers library compatibility, this method is useless in most cases. 92 | * @see Message#mergeDelimitedFromStream() 93 | */ 94 | def mergeDelimitedFrom(input: InputStream): Boolean = { 95 | mergeDelimitedFrom(input, ExtensionRegistryLite.getEmptyRegistry) 96 | } 97 | 98 | def mergeDelimitedFromStream(input: InputStream, extensionRegistry: ExtensionRegistryLite): Option[MessageType] = { 99 | val firstByte = input.read 100 | if (firstByte != -1) { 101 | val size = CodedInputStream.readRawVarint32(firstByte, input) 102 | val limitedInput = new LimitedInputStream(input, size) 103 | Some(mergeFrom(limitedInput, extensionRegistry)) 104 | } else { 105 | None 106 | } 107 | } 108 | 109 | def mergeDelimitedFromStream(input: InputStream): Option[MessageType] = { 110 | mergeDelimitedFromStream(input, ExtensionRegistryLite.getEmptyRegistry) 111 | } 112 | 113 | 114 | /** 115 | * See {@link com.google.protobuf.CodedInputStream#readMessage}. 116 | * 117 | * CodedInputStream#readMessage attempts to mutate the passed Builder and discards the returned value, 118 | * which we need, since our "Builders" (Messages) return a new instance whenever a mutation is performed. 119 | */ 120 | def readMessage[ReadMessageType <: MessageBuilder[ReadMessageType]](in: CodedInputStream, message: ReadMessageType, extensionRegistry: ExtensionRegistryLite) = { 121 | val length = in.readRawVarint32() 122 | val oldLimit = in.pushLimit(length) 123 | 124 | val newMessage = message.mergeFrom(in, extensionRegistry) 125 | 126 | in.checkLastTagWas(0) 127 | in.popLimit(oldLimit) 128 | 129 | newMessage 130 | } 131 | 132 | def toByteArray(): Array[Byte] 133 | 134 | def toByteBuffer = java.nio.ByteBuffer.wrap(toByteArray()) 135 | 136 | 137 | } 138 | -------------------------------------------------------------------------------- /scalabuff-runtime/src/main/net/sandrogrzicic/scalabuff/Parser.scala: -------------------------------------------------------------------------------- 1 | package net.sandrogrzicic.scalabuff 2 | 3 | import com.google.protobuf._ 4 | import java.io.IOException 5 | import java.io.InputStream 6 | 7 | /** 8 | * Trait which implements most of the com.google.protobuf.Parser interface methods. 9 | * 10 | * @author Sandro Gržičić 11 | */ 12 | trait Parser[MessageType <: MessageLite] extends com.google.protobuf.Parser[MessageType] { 13 | import Parser.EMPTY_REGISTRY 14 | 15 | private def checkMessageInitialized(message: MessageType): MessageType = { 16 | if (message != null && !message.isInitialized) { 17 | throw new UninitializedMessageException(message) 18 | .asInvalidProtocolBufferException 19 | .setUnfinishedMessage(message) 20 | } 21 | message 22 | } 23 | 24 | def parsePartialFrom(input: CodedInputStream): MessageType = { 25 | parsePartialFrom(input, EMPTY_REGISTRY) 26 | } 27 | 28 | def parseFrom(input: CodedInputStream, extensionRegistry: ExtensionRegistryLite): MessageType = { 29 | checkMessageInitialized(parsePartialFrom(input, extensionRegistry)) 30 | } 31 | 32 | def parseFrom(input: CodedInputStream): MessageType = { 33 | parseFrom(input, EMPTY_REGISTRY) 34 | } 35 | 36 | def parsePartialFrom(data: ByteString, extensionRegistry: ExtensionRegistryLite): MessageType = { 37 | val input = data.newCodedInput 38 | val message = parsePartialFrom(input, extensionRegistry) 39 | try { 40 | input.checkLastTagWas(0) 41 | } 42 | catch { 43 | case e: InvalidProtocolBufferException => { 44 | throw e.setUnfinishedMessage(message) 45 | } 46 | } 47 | message 48 | } 49 | 50 | def parsePartialFrom(data: ByteString): MessageType = { 51 | parsePartialFrom(data, EMPTY_REGISTRY) 52 | } 53 | 54 | def parseFrom(data: ByteString, extensionRegistry: ExtensionRegistryLite): MessageType = { 55 | checkMessageInitialized(parsePartialFrom(data, extensionRegistry)) 56 | } 57 | 58 | def parseFrom(data: ByteString): MessageType = { 59 | parseFrom(data, EMPTY_REGISTRY) 60 | } 61 | 62 | def parsePartialFrom(data: Array[Byte], off: Int, len: Int, extensionRegistry: ExtensionRegistryLite): MessageType = { 63 | val input = CodedInputStream.newInstance(data, off, len) 64 | val message = parsePartialFrom(input, extensionRegistry) 65 | try { 66 | input.checkLastTagWas(0) 67 | } catch { 68 | case e: InvalidProtocolBufferException => { 69 | throw e.setUnfinishedMessage(message) 70 | } 71 | } 72 | message 73 | } 74 | 75 | def parsePartialFrom(data: Array[Byte], off: Int, len: Int): MessageType = { 76 | parsePartialFrom(data, off, len, EMPTY_REGISTRY) 77 | } 78 | 79 | def parsePartialFrom(data: Array[Byte], extensionRegistry: ExtensionRegistryLite): MessageType = { 80 | parsePartialFrom(data, 0, data.length, extensionRegistry) 81 | } 82 | 83 | def parsePartialFrom(data: Array[Byte]): MessageType = { 84 | parsePartialFrom(data, 0, data.length, EMPTY_REGISTRY) 85 | } 86 | 87 | def parseFrom(data: Array[Byte], off: Int, len: Int, extensionRegistry: ExtensionRegistryLite): MessageType = { 88 | checkMessageInitialized(parsePartialFrom(data, off, len, extensionRegistry)) 89 | } 90 | 91 | def parseFrom(data: Array[Byte], off: Int, len: Int): MessageType = { 92 | parseFrom(data, off, len, EMPTY_REGISTRY) 93 | } 94 | 95 | def parseFrom(data: Array[Byte], extensionRegistry: ExtensionRegistryLite): MessageType = { 96 | parseFrom(data, 0, data.length, extensionRegistry) 97 | } 98 | 99 | def parseFrom(data: Array[Byte]): MessageType = { 100 | parseFrom(data, EMPTY_REGISTRY) 101 | } 102 | 103 | def parsePartialFrom(input: InputStream, extensionRegistry: ExtensionRegistryLite): MessageType = { 104 | val codedInput = CodedInputStream.newInstance(input) 105 | val message = parsePartialFrom(codedInput, extensionRegistry) 106 | try { 107 | codedInput.checkLastTagWas(0) 108 | } catch { 109 | case e: InvalidProtocolBufferException => { 110 | throw e.setUnfinishedMessage(message) 111 | } 112 | } 113 | 114 | message 115 | } 116 | 117 | def parsePartialFrom(input: InputStream): MessageType = { 118 | parsePartialFrom(input, EMPTY_REGISTRY) 119 | } 120 | 121 | def parseFrom(input: InputStream, extensionRegistry: ExtensionRegistryLite): MessageType = { 122 | checkMessageInitialized(parsePartialFrom(input, extensionRegistry)) 123 | } 124 | 125 | def parseFrom(input: InputStream): MessageType = { 126 | parseFrom(input, EMPTY_REGISTRY) 127 | } 128 | 129 | def parsePartialDelimitedFrom(input: InputStream, extensionRegistry: ExtensionRegistryLite): MessageType = { 130 | var size = 0 131 | try { 132 | val firstByte = input.read 133 | if (firstByte == -1) { 134 | null 135 | } 136 | size = CodedInputStream.readRawVarint32(firstByte, input) 137 | } 138 | catch { 139 | case e: IOException => { 140 | throw new InvalidProtocolBufferException(e.getMessage) 141 | } 142 | } 143 | val limitedInput = new LimitedInputStream(input, size) 144 | parsePartialFrom(limitedInput, extensionRegistry) 145 | } 146 | 147 | def parsePartialDelimitedFrom(input: InputStream): MessageType = { 148 | parsePartialDelimitedFrom(input, EMPTY_REGISTRY) 149 | } 150 | 151 | def parseDelimitedFrom(input: InputStream, extensionRegistry: ExtensionRegistryLite): MessageType = { 152 | checkMessageInitialized(parsePartialDelimitedFrom(input, extensionRegistry)) 153 | } 154 | 155 | def parseDelimitedFrom(input: InputStream): MessageType = { 156 | parseDelimitedFrom(input, EMPTY_REGISTRY) 157 | } 158 | } 159 | 160 | object Parser { 161 | final val EMPTY_REGISTRY = ExtensionRegistryLite.getEmptyRegistry 162 | } -------------------------------------------------------------------------------- /sonatype.sbt: -------------------------------------------------------------------------------- 1 | publishMavenStyle := true 2 | 3 | licenses := Seq("Apache" -> url("http://www.apache.org/licenses/LICENSE-2.0")) 4 | homepage := Some(url("https://github.com/SandroGrzicic/ScalaBuff/")) 5 | scmInfo := Some( 6 | ScmInfo( 7 | url("https://github.com/SandroGrzicic/ScalaBuff"), 8 | "scm:git@github.com:SandroGrzicic/ScalaBuff.git" 9 | ) 10 | ) 11 | developers := List( 12 | Developer(id = "sandrogrzicic", name = "Sandro Grzicic", email = "scalabuff@sandro.me.uk", url = url("https://github.com/SandroGrzicic/")) 13 | ) 14 | 15 | publishTo := { 16 | val nexus = "https://oss.sonatype.org/" 17 | if (isSnapshot.value) 18 | Some("snapshots" at nexus + "content/repositories/snapshots") 19 | else 20 | Some("releases" at nexus + "service/local/staging/deploy/maven2") 21 | } 22 | 23 | publishArtifact in Test := false 24 | --------------------------------------------------------------------------------