commit
2b22b611a8
566 changed files with 52070 additions and 16490 deletions
@ -0,0 +1,69 @@ |
|||||||
|
|
||||||
|
benchmarks_protoc_inputs = \
|
||||||
|
benchmarks.proto \
|
||||||
|
benchmark_messages_proto3.proto
|
||||||
|
|
||||||
|
benchmarks_protoc_inputs_proto2 = \
|
||||||
|
benchmark_messages_proto2.proto
|
||||||
|
|
||||||
|
benchmarks_protoc_outputs = \
|
||||||
|
benchmarks.pb.cc \
|
||||||
|
benchmarks.pb.h \
|
||||||
|
benchmark_messages_proto3.pb.cc \
|
||||||
|
benchmark_messages_proto3.pb.h
|
||||||
|
|
||||||
|
benchmarks_protoc_outputs_proto2 = \
|
||||||
|
benchmark_messages_proto2.pb.cc \
|
||||||
|
benchmark_messages_proto2.pb.h
|
||||||
|
|
||||||
|
bin_PROGRAMS = generate-datasets
|
||||||
|
|
||||||
|
generate_datasets_LDADD = $(top_srcdir)/src/libprotobuf.la
|
||||||
|
generate_datasets_SOURCES = generate_datasets.cc
|
||||||
|
generate_datasets_CPPFLAGS = -I$(top_srcdir)/src -I$(srcdir)
|
||||||
|
nodist_generate_datasets_SOURCES = \
|
||||||
|
$(benchmarks_protoc_outputs) \
|
||||||
|
$(benchmarks_protoc_outputs_proto2)
|
||||||
|
|
||||||
|
# Explicit deps because BUILT_SOURCES are only done before a "make all/check"
|
||||||
|
# so a direct "make test_cpp" could fail if parallel enough.
|
||||||
|
# See: https://www.gnu.org/software/automake/manual/html_node/Built-Sources-Example.html#Recording-Dependencies-manually
|
||||||
|
generate_datasets-generate_datasets.$(OBJEXT): benchmarks.pb.h |
||||||
|
|
||||||
|
$(benchmarks_protoc_outputs): protoc_middleman |
||||||
|
$(benchmarks_protoc_outputs_proto2): protoc_middleman2 |
||||||
|
|
||||||
|
CLEANFILES = \
|
||||||
|
$(benchmarks_protoc_outputs) \
|
||||||
|
$(benchmarks_protoc_outputs_proto2) \
|
||||||
|
protoc_middleman \
|
||||||
|
protoc_middleman2 \
|
||||||
|
dataset.*
|
||||||
|
|
||||||
|
MAINTAINERCLEANFILES = \
|
||||||
|
Makefile.in
|
||||||
|
|
||||||
|
if USE_EXTERNAL_PROTOC |
||||||
|
|
||||||
|
protoc_middleman: $(benchmarks_protoc_inputs) |
||||||
|
$(PROTOC) -I$(srcdir) -I$(top_srcdir) --cpp_out=. $(benchmarks_protoc_inputs)
|
||||||
|
touch protoc_middleman
|
||||||
|
|
||||||
|
protoc_middleman2: $(benchmarks_protoc_inputs_proto2) |
||||||
|
$(PROTOC) -I$(srcdir) -I$(top_srcdir) --cpp_out=. $(benchmarks_protoc_inputs_proto2)
|
||||||
|
touch protoc_middleman2
|
||||||
|
|
||||||
|
else |
||||||
|
|
||||||
|
# We have to cd to $(srcdir) before executing protoc because $(protoc_inputs) is
|
||||||
|
# relative to srcdir, which may not be the same as the current directory when
|
||||||
|
# building out-of-tree.
|
||||||
|
protoc_middleman: $(top_srcdir)/src/protoc$(EXEEXT) $(benchmarks_protoc_inputs) $(well_known_type_protoc_inputs) |
||||||
|
oldpwd=`pwd` && ( cd $(srcdir) && $$oldpwd/../src/protoc$(EXEEXT) -I. -I$(top_srcdir)/src --cpp_out=$$oldpwd $(benchmarks_protoc_inputs) )
|
||||||
|
touch protoc_middleman
|
||||||
|
|
||||||
|
protoc_middleman2: $(top_srcdir)/src/protoc$(EXEEXT) $(benchmarks_protoc_inputs_proto2) $(well_known_type_protoc_inputs) |
||||||
|
oldpwd=`pwd` && ( cd $(srcdir) && $$oldpwd/../src/protoc$(EXEEXT) -I. -I$(top_srcdir)/src --cpp_out=$$oldpwd $(benchmarks_protoc_inputs_proto2) )
|
||||||
|
touch protoc_middleman
|
||||||
|
|
||||||
|
endif |
@ -0,0 +1,28 @@ |
|||||||
|
|
||||||
|
# Protocol Buffers Benchmarks |
||||||
|
|
||||||
|
This directory contains benchmarking schemas and data sets that you |
||||||
|
can use to test a variety of performance scenarios against your |
||||||
|
protobuf language runtime. |
||||||
|
|
||||||
|
The schema for the datasets is described in `benchmarks.proto`. |
||||||
|
|
||||||
|
Generate the data sets like so: |
||||||
|
|
||||||
|
``` |
||||||
|
$ make |
||||||
|
$ ./generate-datasets |
||||||
|
Wrote dataset: dataset.google_message1_proto3.pb |
||||||
|
Wrote dataset: dataset.google_message1_proto2.pb |
||||||
|
Wrote dataset: dataset.google_message2.pb |
||||||
|
$ |
||||||
|
``` |
||||||
|
|
||||||
|
Each data set will be written to its own file. Benchmarks will |
||||||
|
likely want to run several benchmarks against each data set (parse, |
||||||
|
serialize, possibly JSON, possibly using different APIs, etc). |
||||||
|
|
||||||
|
We would like to add more data sets. In general we will favor data sets |
||||||
|
that make the overall suite diverse without being too large or having |
||||||
|
too many similar tests. Ideally everyone can run through the entire |
||||||
|
suite without the test run getting too long. |
@ -0,0 +1,76 @@ |
|||||||
|
// Benchmark messages for proto3. |
||||||
|
|
||||||
|
syntax = "proto3"; |
||||||
|
|
||||||
|
package benchmarks.proto3; |
||||||
|
option java_package = "com.google.protobuf.benchmarks"; |
||||||
|
|
||||||
|
// This is the default, but we specify it here explicitly. |
||||||
|
option optimize_for = SPEED; |
||||||
|
|
||||||
|
message GoogleMessage1 { |
||||||
|
string field1 = 1; |
||||||
|
string field9 = 9; |
||||||
|
string field18 = 18; |
||||||
|
bool field80 = 80; |
||||||
|
bool field81 = 81; |
||||||
|
int32 field2 = 2; |
||||||
|
int32 field3 = 3; |
||||||
|
int32 field280 = 280; |
||||||
|
int32 field6 = 6; |
||||||
|
int64 field22 = 22; |
||||||
|
string field4 = 4; |
||||||
|
repeated fixed64 field5 = 5; |
||||||
|
bool field59 = 59; |
||||||
|
string field7 = 7; |
||||||
|
int32 field16 = 16; |
||||||
|
int32 field130 = 130; |
||||||
|
bool field12 = 12; |
||||||
|
bool field17 = 17; |
||||||
|
bool field13 = 13; |
||||||
|
bool field14 = 14; |
||||||
|
int32 field104 = 104; |
||||||
|
int32 field100 = 100; |
||||||
|
int32 field101 = 101; |
||||||
|
string field102 = 102; |
||||||
|
string field103 = 103; |
||||||
|
int32 field29 = 29; |
||||||
|
bool field30 = 30; |
||||||
|
int32 field60 = 60; |
||||||
|
int32 field271 = 271; |
||||||
|
int32 field272 = 272; |
||||||
|
int32 field150 = 150; |
||||||
|
int32 field23 = 23; |
||||||
|
bool field24 = 24; |
||||||
|
int32 field25 = 25; |
||||||
|
GoogleMessage1SubMessage field15 = 15; |
||||||
|
bool field78 = 78; |
||||||
|
int32 field67 = 67; |
||||||
|
int32 field68 = 68; |
||||||
|
int32 field128 = 128; |
||||||
|
string field129 = 129; |
||||||
|
int32 field131 = 131; |
||||||
|
} |
||||||
|
|
||||||
|
message GoogleMessage1SubMessage { |
||||||
|
int32 field1 = 1; |
||||||
|
int32 field2 = 2; |
||||||
|
int32 field3 = 3; |
||||||
|
string field15 = 15; |
||||||
|
bool field12 = 12; |
||||||
|
int64 field13 = 13; |
||||||
|
int64 field14 = 14; |
||||||
|
int32 field16 = 16; |
||||||
|
int32 field19 = 19; |
||||||
|
bool field20 = 20; |
||||||
|
bool field28 = 28; |
||||||
|
fixed64 field21 = 21; |
||||||
|
int32 field22 = 22; |
||||||
|
bool field23 = 23; |
||||||
|
bool field206 = 206; |
||||||
|
fixed32 field203 = 203; |
||||||
|
int32 field204 = 204; |
||||||
|
string field205 = 205; |
||||||
|
uint64 field207 = 207; |
||||||
|
uint64 field300 = 300; |
||||||
|
} |
@ -0,0 +1,63 @@ |
|||||||
|
// Protocol Buffers - Google's data interchange format |
||||||
|
// Copyright 2008 Google Inc. All rights reserved. |
||||||
|
// https://developers.google.com/protocol-buffers/ |
||||||
|
// |
||||||
|
// Redistribution and use in source and binary forms, with or without |
||||||
|
// modification, are permitted provided that the following conditions are |
||||||
|
// met: |
||||||
|
// |
||||||
|
// * Redistributions of source code must retain the above copyright |
||||||
|
// notice, this list of conditions and the following disclaimer. |
||||||
|
// * Redistributions in binary form must reproduce the above |
||||||
|
// copyright notice, this list of conditions and the following disclaimer |
||||||
|
// in the documentation and/or other materials provided with the |
||||||
|
// distribution. |
||||||
|
// * Neither the name of Google Inc. nor the names of its |
||||||
|
// contributors may be used to endorse or promote products derived from |
||||||
|
// this software without specific prior written permission. |
||||||
|
// |
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||||
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||||
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||||
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||||
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||||
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||||
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||||
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||||
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||||
|
|
||||||
|
syntax = "proto3"; |
||||||
|
package benchmarks; |
||||||
|
option java_package = "com.google.protobuf.benchmarks"; |
||||||
|
|
||||||
|
message BenchmarkDataset { |
||||||
|
// Name of the benchmark dataset. This should be unique across all datasets. |
||||||
|
// Should only contain word characters: [a-zA-Z0-9_] |
||||||
|
string name = 1; |
||||||
|
|
||||||
|
// Fully-qualified name of the protobuf message for this dataset. |
||||||
|
// It will be one of the messages defined benchmark_messages_proto2.proto |
||||||
|
// or benchmark_messages_proto3.proto. |
||||||
|
// |
||||||
|
// Implementations that do not support reflection can implement this with |
||||||
|
// an explicit "if/else" chain that lists every known message defined |
||||||
|
// in those files. |
||||||
|
string message_name = 2; |
||||||
|
|
||||||
|
// The payload(s) for this dataset. They should be parsed or serialized |
||||||
|
// in sequence, in a loop, ie. |
||||||
|
// |
||||||
|
// while (!benchmarkDone) { // Benchmark runner decides when to exit. |
||||||
|
// for (i = 0; i < benchmark.payload.length; i++) { |
||||||
|
// parse(benchmark.payload[i]) |
||||||
|
// } |
||||||
|
// } |
||||||
|
// |
||||||
|
// This is intended to let datasets include a variety of data to provide |
||||||
|
// potentially more realistic results than just parsing the same message |
||||||
|
// over and over. A single message parsed repeatedly could yield unusually |
||||||
|
// good branch prediction performance. |
||||||
|
repeated bytes payload = 3; |
||||||
|
} |
@ -0,0 +1,117 @@ |
|||||||
|
// Protocol Buffers - Google's data interchange format
|
||||||
|
// Copyright 2008 Google Inc. All rights reserved.
|
||||||
|
// https://developers.google.com/protocol-buffers/
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * Redistributions in binary form must reproduce the above
|
||||||
|
// copyright notice, this list of conditions and the following disclaimer
|
||||||
|
// in the documentation and/or other materials provided with the
|
||||||
|
// distribution.
|
||||||
|
// * Neither the name of Google Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
#include <fstream> |
||||||
|
#include <iostream> |
||||||
|
#include "benchmarks.pb.h" |
||||||
|
|
||||||
|
using benchmarks::BenchmarkDataset; |
||||||
|
using google::protobuf::Descriptor; |
||||||
|
using google::protobuf::DescriptorPool; |
||||||
|
using google::protobuf::Message; |
||||||
|
using google::protobuf::MessageFactory; |
||||||
|
|
||||||
|
std::set<std::string> names; |
||||||
|
|
||||||
|
const char *file_prefix = "dataset."; |
||||||
|
const char *file_suffix = ".pb"; |
||||||
|
|
||||||
|
void WriteFileWithPayloads(const std::string& name, |
||||||
|
const std::string& message_name, |
||||||
|
const std::vector<std::string>& payload) { |
||||||
|
if (!names.insert(name).second) { |
||||||
|
std::cerr << "Duplicate test name: " << name << "\n"; |
||||||
|
abort(); |
||||||
|
} |
||||||
|
|
||||||
|
// First verify that this message name exists in our set of benchmark messages
|
||||||
|
// and that these payloads are valid for the given message.
|
||||||
|
const Descriptor* d = |
||||||
|
DescriptorPool::generated_pool()->FindMessageTypeByName(message_name); |
||||||
|
|
||||||
|
if (!d) { |
||||||
|
std::cerr << "For dataset " << name << ", no such message: " |
||||||
|
<< message_name << "\n"; |
||||||
|
abort(); |
||||||
|
} |
||||||
|
|
||||||
|
Message* m = MessageFactory::generated_factory()->GetPrototype(d)->New(); |
||||||
|
|
||||||
|
for (size_t i = 0; i < payload.size(); i++) { |
||||||
|
if (!m->ParseFromString(payload[i])) { |
||||||
|
std::cerr << "For dataset " << name << ", payload[" << i << "] fails " |
||||||
|
<< "to parse\n"; |
||||||
|
abort(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
BenchmarkDataset dataset; |
||||||
|
dataset.set_name(name); |
||||||
|
dataset.set_message_name(message_name); |
||||||
|
for (size_t i = 0; i < payload.size(); i++) { |
||||||
|
dataset.add_payload()->assign(payload[i]); |
||||||
|
} |
||||||
|
|
||||||
|
std::ofstream writer; |
||||||
|
std::string fname = file_prefix + name + file_suffix; |
||||||
|
writer.open(fname.c_str()); |
||||||
|
dataset.SerializeToOstream(&writer); |
||||||
|
writer.close(); |
||||||
|
|
||||||
|
std::cerr << "Wrote dataset: " << fname << "\n"; |
||||||
|
} |
||||||
|
|
||||||
|
void WriteFile(const std::string& name, const std::string& message_name, |
||||||
|
const std::string& payload) { |
||||||
|
std::vector<std::string> payloads; |
||||||
|
payloads.push_back(payload); |
||||||
|
WriteFileWithPayloads(name, message_name, payloads); |
||||||
|
} |
||||||
|
|
||||||
|
std::string ReadFile(const std::string& name) { |
||||||
|
std::ifstream file(name.c_str()); |
||||||
|
GOOGLE_CHECK(file.is_open()) << "Couldn't find file '" << name << |
||||||
|
"', please make sure you are running " |
||||||
|
"this command from the benchmarks/ " |
||||||
|
"directory.\n"; |
||||||
|
return std::string((std::istreambuf_iterator<char>(file)), |
||||||
|
std::istreambuf_iterator<char>()); |
||||||
|
} |
||||||
|
|
||||||
|
int main() { |
||||||
|
WriteFile("google_message1_proto3", "benchmarks.proto3.GoogleMessage1", |
||||||
|
ReadFile("google_message1.dat")); |
||||||
|
WriteFile("google_message1_proto2", "benchmarks.proto2.GoogleMessage1", |
||||||
|
ReadFile("google_message1.dat")); |
||||||
|
|
||||||
|
// Not in proto3 because it has a group, which is not supported.
|
||||||
|
WriteFile("google_message2", "benchmarks.proto2.GoogleMessage2", |
||||||
|
ReadFile("google_message2.dat")); |
||||||
|
} |
@ -0,0 +1,125 @@ |
|||||||
|
|
||||||
|
import com.google.protobuf.conformance.Conformance; |
||||||
|
import com.google.protobuf.InvalidProtocolBufferException; |
||||||
|
|
||||||
|
class ConformanceJavaLite { |
||||||
|
private int testCount = 0; |
||||||
|
|
||||||
|
private boolean readFromStdin(byte[] buf, int len) throws Exception { |
||||||
|
int ofs = 0; |
||||||
|
while (len > 0) { |
||||||
|
int read = System.in.read(buf, ofs, len); |
||||||
|
if (read == -1) { |
||||||
|
return false; // EOF
|
||||||
|
} |
||||||
|
ofs += read; |
||||||
|
len -= read; |
||||||
|
} |
||||||
|
|
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
private void writeToStdout(byte[] buf) throws Exception { |
||||||
|
System.out.write(buf); |
||||||
|
} |
||||||
|
|
||||||
|
// Returns -1 on EOF (the actual values will always be positive).
|
||||||
|
private int readLittleEndianIntFromStdin() throws Exception { |
||||||
|
byte[] buf = new byte[4]; |
||||||
|
if (!readFromStdin(buf, 4)) { |
||||||
|
return -1; |
||||||
|
} |
||||||
|
return (buf[0] & 0xff) |
||||||
|
| ((buf[1] & 0xff) << 8) |
||||||
|
| ((buf[2] & 0xff) << 16) |
||||||
|
| ((buf[3] & 0xff) << 24); |
||||||
|
} |
||||||
|
|
||||||
|
private void writeLittleEndianIntToStdout(int val) throws Exception { |
||||||
|
byte[] buf = new byte[4]; |
||||||
|
buf[0] = (byte)val; |
||||||
|
buf[1] = (byte)(val >> 8); |
||||||
|
buf[2] = (byte)(val >> 16); |
||||||
|
buf[3] = (byte)(val >> 24); |
||||||
|
writeToStdout(buf); |
||||||
|
} |
||||||
|
|
||||||
|
private Conformance.ConformanceResponse doTest(Conformance.ConformanceRequest request) { |
||||||
|
Conformance.TestAllTypes testMessage; |
||||||
|
|
||||||
|
switch (request.getPayloadCase()) { |
||||||
|
case PROTOBUF_PAYLOAD: { |
||||||
|
try { |
||||||
|
testMessage = Conformance.TestAllTypes.parseFrom(request.getProtobufPayload()); |
||||||
|
} catch (InvalidProtocolBufferException e) { |
||||||
|
return Conformance.ConformanceResponse.newBuilder().setParseError(e.getMessage()).build(); |
||||||
|
} |
||||||
|
break; |
||||||
|
} |
||||||
|
case JSON_PAYLOAD: { |
||||||
|
return Conformance.ConformanceResponse.newBuilder().setSkipped( |
||||||
|
"Lite runtime does not suport Json Formant.").build(); |
||||||
|
} |
||||||
|
case PAYLOAD_NOT_SET: { |
||||||
|
throw new RuntimeException("Request didn't have payload."); |
||||||
|
} |
||||||
|
|
||||||
|
default: { |
||||||
|
throw new RuntimeException("Unexpected payload case."); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
switch (request.getRequestedOutputFormat()) { |
||||||
|
case UNSPECIFIED: |
||||||
|
throw new RuntimeException("Unspecified output format."); |
||||||
|
|
||||||
|
case PROTOBUF: |
||||||
|
return Conformance.ConformanceResponse.newBuilder().setProtobufPayload(testMessage.toByteString()).build(); |
||||||
|
|
||||||
|
case JSON: |
||||||
|
return Conformance.ConformanceResponse.newBuilder().setSkipped( |
||||||
|
"Lite runtime does not suport Json Formant.").build(); |
||||||
|
|
||||||
|
default: { |
||||||
|
throw new RuntimeException("Unexpected request output."); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private boolean doTestIo() throws Exception { |
||||||
|
int bytes = readLittleEndianIntFromStdin(); |
||||||
|
|
||||||
|
if (bytes == -1) { |
||||||
|
return false; // EOF
|
||||||
|
} |
||||||
|
|
||||||
|
byte[] serializedInput = new byte[bytes]; |
||||||
|
|
||||||
|
if (!readFromStdin(serializedInput, bytes)) { |
||||||
|
throw new RuntimeException("Unexpected EOF from test program."); |
||||||
|
} |
||||||
|
|
||||||
|
Conformance.ConformanceRequest request = |
||||||
|
Conformance.ConformanceRequest.parseFrom(serializedInput); |
||||||
|
Conformance.ConformanceResponse response = doTest(request); |
||||||
|
byte[] serializedOutput = response.toByteArray(); |
||||||
|
|
||||||
|
writeLittleEndianIntToStdout(serializedOutput.length); |
||||||
|
writeToStdout(serializedOutput); |
||||||
|
|
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
public void run() throws Exception { |
||||||
|
while (doTestIo()) { |
||||||
|
this.testCount++; |
||||||
|
} |
||||||
|
|
||||||
|
System.err.println("ConformanceJavaLite: received EOF from test runner after " + |
||||||
|
this.testCount + " tests"); |
||||||
|
} |
||||||
|
|
||||||
|
public static void main(String[] args) throws Exception { |
||||||
|
new ConformanceJavaLite().run(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,674 @@ |
|||||||
|
# Protocol Buffers in Swift |
||||||
|
|
||||||
|
## Objective |
||||||
|
|
||||||
|
This document describes the user-facing API and internal implementation of |
||||||
|
proto2 and proto3 messages in Apple’s Swift programming language. |
||||||
|
|
||||||
|
One of the key goals of protobufs is to provide idiomatic APIs for each |
||||||
|
language. In that vein, **interoperability with Objective-C is a non-goal of |
||||||
|
this proposal.** Protobuf users who need to pass messages between Objective-C |
||||||
|
and Swift code in the same application should use the existing Objective-C proto |
||||||
|
library. The goal of the effort described here is to provide an API for protobuf |
||||||
|
messages that uses features specific to Swift—optional types, algebraic |
||||||
|
enumerated types, value types, and so forth—in a natural way that will delight, |
||||||
|
rather than surprise, users of the language. |
||||||
|
|
||||||
|
## Naming |
||||||
|
|
||||||
|
* By convention, both typical protobuf message names and Swift structs/classes |
||||||
|
are `UpperCamelCase`, so for most messages, the name of a message can be the |
||||||
|
same as the name of its generated type. (However, see the discussion below |
||||||
|
about prefixes under [Packages](#packages).) |
||||||
|
|
||||||
|
* Enum cases in protobufs typically are `UPPERCASE_WITH_UNDERSCORES`, whereas |
||||||
|
in Swift they are `lowerCamelCase` (as of the Swift 3 API design |
||||||
|
guidelines). We will transform the names to match Swift convention, using |
||||||
|
a whitelist similar to the Objective-C compiler plugin to handle commonly |
||||||
|
used acronyms. |
||||||
|
|
||||||
|
* Typical fields in proto messages are `lowercase_with_underscores`, while in |
||||||
|
Swift they are `lowerCamelCase`. We will transform the names to match |
||||||
|
Swift convention by removing the underscores and uppercasing the subsequent |
||||||
|
letter. |
||||||
|
|
||||||
|
## Swift reserved words |
||||||
|
|
||||||
|
Swift has a large set of reserved words—some always reserved and some |
||||||
|
contextually reserved (that is, they can be used as identifiers in contexts |
||||||
|
where they would not be confused). As of Swift 2.2, the set of always-reserved |
||||||
|
words is: |
||||||
|
|
||||||
|
``` |
||||||
|
_, #available, #column, #else, #elseif, #endif, #file, #function, #if, #line, |
||||||
|
#selector, as, associatedtype, break, case, catch, class, continue, default, |
||||||
|
defer, deinit, do, dynamicType, else, enum, extension, fallthrough, false, for, |
||||||
|
func, guard, if, import, in, init, inout, internal, is, let, nil, operator, |
||||||
|
private, protocol, public, repeat, rethrows, return, self, Self, static, |
||||||
|
struct, subscript, super, switch, throw, throws, true, try, typealias, var, |
||||||
|
where, while |
||||||
|
``` |
||||||
|
|
||||||
|
The set of contextually reserved words is: |
||||||
|
|
||||||
|
``` |
||||||
|
associativity, convenience, dynamic, didSet, final, get, infix, indirect, |
||||||
|
lazy, left, mutating, none, nonmutating, optional, override, postfix, |
||||||
|
precedence, prefix, Protocol, required, right, set, Type, unowned, weak, |
||||||
|
willSet |
||||||
|
``` |
||||||
|
|
||||||
|
It is possible to use any reserved word as an identifier by escaping it with |
||||||
|
backticks (for example, ``let `class` = 5``). Other name-mangling schemes would |
||||||
|
require us to transform the names themselves (for example, by appending an |
||||||
|
underscore), which requires us to then ensure that the new name does not collide |
||||||
|
with something else in the same namespace. |
||||||
|
|
||||||
|
While the backtick feature may not be widely known by all Swift developers, a |
||||||
|
small amount of user education can address this and it seems like the best |
||||||
|
approach. We can unconditionally surround all property names with backticks to |
||||||
|
simplify generation. |
||||||
|
|
||||||
|
Some remapping will still be required, though, to avoid collisions between |
||||||
|
generated properties and the names of methods and properties defined in the base |
||||||
|
protocol/implementation of messages. |
||||||
|
|
||||||
|
# Features of Protocol Buffers |
||||||
|
|
||||||
|
This section describes how the features of the protocol buffer syntaxes (proto2 |
||||||
|
and proto3) map to features in Swift—what the code generated from a proto will |
||||||
|
look like, and how it will be implemented in the underlying library. |
||||||
|
|
||||||
|
## Packages |
||||||
|
|
||||||
|
Modules are the main form of namespacing in Swift, but they are not declared |
||||||
|
using syntactic constructs like namespaces in C++ or packages in Java. Instead, |
||||||
|
they are tied to build targets in Xcode (or, in the future with open-source |
||||||
|
Swift, declarations in a Swift Package Manager manifest). They also do not |
||||||
|
easily support nesting submodules (Clang module maps support this, but pure |
||||||
|
Swift does not yet provide a way to define submodules). |
||||||
|
|
||||||
|
We will generate types with fully-qualified underscore-delimited names. For |
||||||
|
example, a message `Baz` in package `foo.bar` would generate a struct named |
||||||
|
`Foo_Bar_Baz`. For each fully-qualified proto message, there will be exactly one |
||||||
|
unique type symbol emitted in the generated binary. |
||||||
|
|
||||||
|
Users are likely to balk at the ugliness of underscore-delimited names for every |
||||||
|
generated type. To improve upon this situation, we will add a new string file |
||||||
|
level option, `swift_package_typealias`, that can be added to `.proto` files. |
||||||
|
When present, this will cause `typealias`es to be added to the generated Swift |
||||||
|
messages that replace the package name prefix with the provided string. For |
||||||
|
example, the following `.proto` file: |
||||||
|
|
||||||
|
```protobuf |
||||||
|
option swift_package_typealias = "FBP"; |
||||||
|
package foo.bar; |
||||||
|
|
||||||
|
message Baz { |
||||||
|
// Message fields |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
would generate the following Swift source: |
||||||
|
|
||||||
|
```swift |
||||||
|
public struct Foo_Bar_Baz { |
||||||
|
// Message fields and other methods |
||||||
|
} |
||||||
|
|
||||||
|
typealias FBPBaz = Foo_Bar_Baz |
||||||
|
``` |
||||||
|
|
||||||
|
It should be noted that this type alias is recorded in the generated |
||||||
|
`.swiftmodule` so that code importing the module can refer to it, but it does |
||||||
|
not cause a new symbol to be generated in the compiled binary (i.e., we do not |
||||||
|
risk compiled size bloat by adding `typealias`es for every type). |
||||||
|
|
||||||
|
Other strategies to handle packages that were considered and rejected can be |
||||||
|
found in [Appendix A](#appendix-a-rejected-strategies-to-handle-packages). |
||||||
|
|
||||||
|
## Messages |
||||||
|
|
||||||
|
Proto messages are natural value types and we will generate messages as structs |
||||||
|
instead of classes. Users will benefit from Swift’s built-in behavior with |
||||||
|
regard to mutability. We will define a `ProtoMessage` protocol that defines the |
||||||
|
common methods and properties for all messages (such as serialization) and also |
||||||
|
lets users treat messages polymorphically. Any shared method implementations |
||||||
|
that do not differ between individual messages can be implemented in a protocol |
||||||
|
extension. |
||||||
|
|
||||||
|
The backing storage itself for fields of a message will be managed by a |
||||||
|
`ProtoFieldStorage` type that uses an internal dictionary keyed by field number, |
||||||
|
and whose values are the value of the field with that number (up-cast to Swift’s |
||||||
|
`Any` type). This class will provide type-safe getters and setters so that |
||||||
|
generated messages can manipulate this storage, and core serialization logic |
||||||
|
will live here as well. Furthermore, factoring the storage out into a separate |
||||||
|
type, rather than inlining the fields as stored properties in the message |
||||||
|
itself, lets us implement copy-on-write efficiently to support passing around |
||||||
|
large messages. (Furthermore, because the messages themselves are value types, |
||||||
|
inlining fields is not possible if the fields are submessages of the same type, |
||||||
|
or a type that eventually includes a submessage of the same type.) |
||||||
|
|
||||||
|
### Required fields (proto2 only) |
||||||
|
|
||||||
|
Required fields in proto2 messages seem like they could be naturally represented |
||||||
|
by non-optional properties in Swift, but this presents some problems/concerns. |
||||||
|
|
||||||
|
Serialization APIs permit partial serialization, which allows required fields to |
||||||
|
remain unset. Furthermore, other language APIs still provide `has*` and `clear*` |
||||||
|
methods for required fields, and knowing whether a property has a value when the |
||||||
|
message is in memory is still useful. |
||||||
|
|
||||||
|
For example, an e-mail draft message may have the “to” address required on the |
||||||
|
wire, but when the user constructs it in memory, it doesn’t make sense to force |
||||||
|
a value until they provide one. We only want to force a value to be present when |
||||||
|
the message is serialized to the wire. Using non-optional properties prevents |
||||||
|
this use case, and makes client usage awkward because the user would be forced |
||||||
|
to select a sentinel or placeholder value for any required fields at the time |
||||||
|
the message was created. |
||||||
|
|
||||||
|
### Default values |
||||||
|
|
||||||
|
In proto2, fields can have a default value specified that may be a value other |
||||||
|
than the default value for its corresponding language type (for example, a |
||||||
|
default value of 5 instead of 0 for an integer). When reading a field that is |
||||||
|
not explicitly set, the user expects to get that value. This makes Swift |
||||||
|
optionals (i.e., `Foo?`) unsuitable for fields in general. Unfortunately, we |
||||||
|
cannot implement our own “enhanced optional” type without severely complicating |
||||||
|
usage (Swift’s use of type inference and its lack of implicit conversions would |
||||||
|
require manual unwrapping of every property value). |
||||||
|
|
||||||
|
Instead, we can use **implicitly unwrapped optionals.** For example, a property |
||||||
|
generated for a field of type `int32` would have Swift type `Int32!`. These |
||||||
|
properties would behave with the following characteristics, which mirror the |
||||||
|
nil-resettable properties used elsewhere in Apple’s SDKs (for example, |
||||||
|
`UIView.tintColor`): |
||||||
|
|
||||||
|
* Assigning a non-nil value to a property sets the field to that value. |
||||||
|
* Assigning nil to a property clears the field (its internal representation is |
||||||
|
nilled out). |
||||||
|
* Reading the value of a property returns its value if it is set, or returns |
||||||
|
its default value if it is not set. Reading a property never returns nil. |
||||||
|
|
||||||
|
The final point in the list above implies that the optional cannot be checked to |
||||||
|
determine if the field is set to a value other than its default: it will never |
||||||
|
be nil. Instead, we must provide `has*` methods for each field to allow the user |
||||||
|
to check this. These methods will be public in proto2. In proto3, these methods |
||||||
|
will be private (if generated at all), since the user can test the returned |
||||||
|
value against the zero value for that type. |
||||||
|
|
||||||
|
### Autocreation of nested messages |
||||||
|
|
||||||
|
For convenience, dotting into an unset field representing a nested message will |
||||||
|
return an instance of that message with default values. As in the Objective-C |
||||||
|
implementation, this does not actually cause the field to be set until the |
||||||
|
returned message is mutated. Fortunately, thanks to the way mutability of value |
||||||
|
types is implemented in Swift, the language automatically handles the |
||||||
|
reassignment-on-mutation for us. A static singleton instance containing default |
||||||
|
values can be associated with each message that can be returned when reading, so |
||||||
|
copies are only made by the Swift runtime when mutation occurs. For example, |
||||||
|
given the following proto: |
||||||
|
|
||||||
|
```protobuf |
||||||
|
message Node { |
||||||
|
Node child = 1; |
||||||
|
string value = 2 [default = "foo"]; |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
The following Swift code would act as commented, where setting deeply nested |
||||||
|
properties causes the copies and mutations to occur as the assignment statement |
||||||
|
is unwound: |
||||||
|
|
||||||
|
```swift |
||||||
|
var node = Node() |
||||||
|
|
||||||
|
let s = node.child.child.value |
||||||
|
// 1. node.child returns the "default Node". |
||||||
|
// 2. Reading .child on the result of (1) returns the same default Node. |
||||||
|
// 3. Reading .value on the result of (2) returns the default value "foo". |
||||||
|
|
||||||
|
node.child.child.value = "bar" |
||||||
|
// 4. Setting .value on the default Node causes a copy to be made and sets |
||||||
|
// the property on that copy. Subsequently, the language updates the |
||||||
|
// value of "node.child.child" to point to that copy. |
||||||
|
// 5. Updating "node.child.child" in (4) requires another copy, because |
||||||
|
// "node.child" was also the instance of the default node. The copy is |
||||||
|
// assigned back to "node.child". |
||||||
|
// 6. Setting "node.child" in (5) is a simple value reassignment, since |
||||||
|
// "node" is a mutable var. |
||||||
|
``` |
||||||
|
|
||||||
|
In other words, the generated messages do not internally have to manage parental |
||||||
|
relationships to backfill the appropriate properties on mutation. Swift provides |
||||||
|
this for free. |
||||||
|
|
||||||
|
## Scalar value fields |
||||||
|
|
||||||
|
Proto scalar value fields will map to Swift types in the following way: |
||||||
|
|
||||||
|
.proto Type | Swift Type |
||||||
|
----------- | ------------------- |
||||||
|
`double` | `Double` |
||||||
|
`float` | `Float` |
||||||
|
`int32` | `Int32` |
||||||
|
`int64` | `Int64` |
||||||
|
`uint32` | `UInt32` |
||||||
|
`uint64` | `UInt64` |
||||||
|
`sint32` | `Int32` |
||||||
|
`sint64` | `Int64` |
||||||
|
`fixed32` | `UInt32` |
||||||
|
`fixed64` | `UInt64` |
||||||
|
`sfixed32` | `Int32` |
||||||
|
`sfixed64` | `Int64` |
||||||
|
`bool` | `Bool` |
||||||
|
`string` | `String` |
||||||
|
`bytes` | `Foundation.NSData` |
||||||
|
|
||||||
|
The proto spec defines a number of integral types that map to the same Swift |
||||||
|
type; for example, `intXX`, `sintXX`, and `sfixedXX` are all signed integers, |
||||||
|
and `uintXX` and `fixedXX` are both unsigned integers. No other language |
||||||
|
implementation distinguishes these further, so we do not do so either. The |
||||||
|
rationale is that the various types only serve to distinguish how the value is |
||||||
|
**encoded on the wire**; once loaded in memory, the user is not concerned about |
||||||
|
these variations. |
||||||
|
|
||||||
|
Swift’s lack of implicit conversions among types will make it slightly annoying |
||||||
|
to use these types in a context expecting an `Int`, or vice-versa, but since |
||||||
|
this is a data-interchange format with explicitly-sized fields, we should not |
||||||
|
hide that information from the user. Users will have to explicitly write |
||||||
|
`Int(message.myField)`, for example. |
||||||
|
|
||||||
|
## Embedded message fields |
||||||
|
|
||||||
|
Embedded message fields can be represented using an optional variable of the |
||||||
|
generated message type. Thus, the message |
||||||
|
|
||||||
|
```protobuf |
||||||
|
message Foo { |
||||||
|
Bar bar = 1; |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
would be represented in Swift as |
||||||
|
|
||||||
|
```swift |
||||||
|
public struct Foo: ProtoMessage { |
||||||
|
public var bar: Bar! { |
||||||
|
get { ... } |
||||||
|
set { ... } |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
If the user explicitly sets `bar` to nil, or if it was never set when read from |
||||||
|
the wire, retrieving the value of `bar` would return a default, statically |
||||||
|
allocated instance of `Bar` containing default values for its fields. This |
||||||
|
achieves the desired behavior for default values in the same way that scalar |
||||||
|
fields are designed, and also allows users to deep-drill into complex object |
||||||
|
graphs to get or set fields without checking for nil at each step. |
||||||
|
|
||||||
|
## Enum fields |
||||||
|
|
||||||
|
The design and implementation of enum fields will differ somewhat drastically |
||||||
|
depending on whether the message being generated is a proto2 or proto3 message. |
||||||
|
|
||||||
|
### proto2 enums |
||||||
|
|
||||||
|
For proto2, we do not need to be concerned about unknown enum values, so we can |
||||||
|
use the simple raw-value enum syntax provided by Swift. So the following enum in |
||||||
|
proto2: |
||||||
|
|
||||||
|
```protobuf |
||||||
|
enum ContentType { |
||||||
|
TEXT = 0; |
||||||
|
IMAGE = 1; |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
would become this Swift enum: |
||||||
|
|
||||||
|
```swift |
||||||
|
public enum ContentType: Int32, NilLiteralConvertible { |
||||||
|
case text = 0 |
||||||
|
case image = 1 |
||||||
|
|
||||||
|
public init(nilLiteral: ()) { |
||||||
|
self = .text |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
See below for the discussion about `NilLiteralConvertible`. |
||||||
|
|
||||||
|
### proto3 enums |
||||||
|
|
||||||
|
For proto3, we need to be able to preserve unknown enum values that may come |
||||||
|
across the wire so that they can be written back if unmodified. We can |
||||||
|
accomplish this in Swift by using a case with an associated value for unknowns. |
||||||
|
So the following enum in proto3: |
||||||
|
|
||||||
|
```protobuf |
||||||
|
enum ContentType { |
||||||
|
TEXT = 0; |
||||||
|
IMAGE = 1; |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
would become this Swift enum: |
||||||
|
|
||||||
|
```swift |
||||||
|
public enum ContentType: RawRepresentable, NilLiteralConvertible { |
||||||
|
case text |
||||||
|
case image |
||||||
|
case UNKNOWN_VALUE(Int32) |
||||||
|
|
||||||
|
public typealias RawValue = Int32 |
||||||
|
|
||||||
|
public init(nilLiteral: ()) { |
||||||
|
self = .text |
||||||
|
} |
||||||
|
|
||||||
|
public init(rawValue: RawValue) { |
||||||
|
switch rawValue { |
||||||
|
case 0: self = .text |
||||||
|
case 1: self = .image |
||||||
|
default: self = .UNKNOWN_VALUE(rawValue) |
||||||
|
} |
||||||
|
|
||||||
|
public var rawValue: RawValue { |
||||||
|
switch self { |
||||||
|
case .text: return 0 |
||||||
|
case .image: return 1 |
||||||
|
case .UNKNOWN_VALUE(let value): return value |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
Note that the use of a parameterized case prevents us from inheriting from the |
||||||
|
raw `Int32` type; Swift does not allow an enum with a raw type to have cases |
||||||
|
with arguments. Instead, we must implement the raw value initializer and |
||||||
|
computed property manually. The `UNKNOWN_VALUE` case is explicitly chosen to be |
||||||
|
"ugly" so that it stands out and does not conflict with other possible case |
||||||
|
names. |
||||||
|
|
||||||
|
Using this approach, proto3 consumers must always have a default case or handle |
||||||
|
the `.UNKNOWN_VALUE` case to satisfy case exhaustion in a switch statement; the |
||||||
|
Swift compiler considers it an error if switch statements are not exhaustive. |
||||||
|
|
||||||
|
### NilLiteralConvertible conformance |
||||||
|
|
||||||
|
This is required to clean up the usage of enum-typed properties in switch |
||||||
|
statements. Unlike other field types, enum properties cannot be |
||||||
|
implicitly-unwrapped optionals without requiring that uses in switch statements |
||||||
|
be explicitly unwrapped. For example, if we consider a message with the enum |
||||||
|
above, this usage will fail to compile: |
||||||
|
|
||||||
|
```swift |
||||||
|
// Without NilLiteralConvertible conformance on ContentType |
||||||
|
public struct SomeMessage: ProtoMessage { |
||||||
|
public var contentType: ContentType! { ... } |
||||||
|
} |
||||||
|
|
||||||
|
// ERROR: no case named text or image |
||||||
|
switch someMessage.contentType { |
||||||
|
case .text: { ... } |
||||||
|
case .image: { ... } |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
Even though our implementation guarantees that `contentType` will never be nil, |
||||||
|
if it is an optional type, its cases would be `some` and `none`, not the cases |
||||||
|
of the underlying enum type. In order to use it in this context, the user must |
||||||
|
write `someMessage.contentType!` in their switch statement. |
||||||
|
|
||||||
|
Making the enum itself `NilLiteralConvertible` permits us to make the property |
||||||
|
non-optional, so the user can still set it to nil to clear it (i.e., reset it to |
||||||
|
its default value), while eliminating the need to explicitly unwrap it in a |
||||||
|
switch statement. |
||||||
|
|
||||||
|
```swift |
||||||
|
// With NilLiteralConvertible conformance on ContentType |
||||||
|
public struct SomeMessage: ProtoMessage { |
||||||
|
// Note that the property type is no longer optional |
||||||
|
public var contentType: ContentType { ... } |
||||||
|
} |
||||||
|
|
||||||
|
// OK: Compiles and runs as expected |
||||||
|
switch someMessage.contentType { |
||||||
|
case .text: { ... } |
||||||
|
case .image: { ... } |
||||||
|
} |
||||||
|
|
||||||
|
// The enum can be reset to its default value this way |
||||||
|
someMessage.contentType = nil |
||||||
|
``` |
||||||
|
|
||||||
|
One minor oddity with this approach is that nil will be auto-converted to the |
||||||
|
default value of the enum in any context, not just field assignment. In other |
||||||
|
words, this is valid: |
||||||
|
|
||||||
|
```swift |
||||||
|
func foo(contentType: ContentType) { ... } |
||||||
|
foo(nil) // Inside foo, contentType == .text |
||||||
|
``` |
||||||
|
|
||||||
|
That being said, the advantage of being able to simultaneously support |
||||||
|
nil-resettability and switch-without-unwrapping outweighs this side effect, |
||||||
|
especially if appropriately documented. It is our hope that a new form of |
||||||
|
resettable properties will be added to Swift that eliminates this inconsistency. |
||||||
|
Some community members have already drafted or sent proposals for review that |
||||||
|
would benefit our designs: |
||||||
|
|
||||||
|
* [SE-0030: Property Behaviors] |
||||||
|
(https://github.com/apple/swift-evolution/blob/master/proposals/0030-property-behavior-decls.md) |
||||||
|
* [Drafted: Resettable Properties] |
||||||
|
(https://github.com/patters/swift-evolution/blob/master/proposals/0000-resettable-properties.md) |
||||||
|
|
||||||
|
### Enum aliases |
||||||
|
|
||||||
|
The `allow_alias` option in protobuf slightly complicates the use of Swift enums |
||||||
|
to represent that type, because raw values of cases in an enum must be unique. |
||||||
|
Swift lets us define static variables in an enum that alias actual cases. For |
||||||
|
example, the following protobuf enum: |
||||||
|
|
||||||
|
```protobuf |
||||||
|
enum Foo { |
||||||
|
option allow_alias = true; |
||||||
|
BAR = 0; |
||||||
|
BAZ = 0; |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
will be represented in Swift as: |
||||||
|
|
||||||
|
```swift |
||||||
|
public enum Foo: Int32, NilLiteralConvertible { |
||||||
|
case bar = 0 |
||||||
|
static public let baz = bar |
||||||
|
|
||||||
|
// ... etc. |
||||||
|
} |
||||||
|
|
||||||
|
// Can still use .baz shorthand to reference the alias in contexts |
||||||
|
// where the type is inferred |
||||||
|
``` |
||||||
|
|
||||||
|
That is, we use the first name as the actual case and use static variables for |
||||||
|
the other aliases. One drawback to this approach is that the static aliases |
||||||
|
cannot be used as cases in a switch statement (the compiler emits the error |
||||||
|
*“Enum case ‘baz’ not found in type ‘Foo’”*). However, in our own code bases, |
||||||
|
there are only a few places where enum aliases are not mere renamings of an |
||||||
|
older value, but they also don’t appear to be the type of value that one would |
||||||
|
expect to switch on (for example, a group of named constants representing |
||||||
|
metrics rather than a set of options), so this restriction is not significant. |
||||||
|
|
||||||
|
This strategy also implies that changing the name of an enum and adding the old |
||||||
|
name as an alias below the new name will be a breaking change in the generated |
||||||
|
Swift code. |
||||||
|
|
||||||
|
## Oneof types |
||||||
|
|
||||||
|
The `oneof` feature represents a “variant/union” data type that maps nicely to |
||||||
|
Swift enums with associated values (algebraic types). These fields can also be |
||||||
|
accessed independently though, and, specifically in the case of proto2, it’s |
||||||
|
reasonable to expect access to default values when accessing a field that is not |
||||||
|
explicitly set. |
||||||
|
|
||||||
|
Taking all this into account, we can represent a `oneof` in Swift with two sets |
||||||
|
of constructs: |
||||||
|
|
||||||
|
* Properties in the message that correspond to the `oneof` fields. |
||||||
|
* A nested enum named after the `oneof` and which provides the corresponding |
||||||
|
field values as case arguments. |
||||||
|
|
||||||
|
This approach fulfills the needs of proto consumers by providing a |
||||||
|
Swift-idiomatic way of simultaneously checking which field is set and accessing |
||||||
|
its value, providing individual properties to access the default values |
||||||
|
(important for proto2), and safely allows a field to be moved into a `oneof` |
||||||
|
without breaking clients. |
||||||
|
|
||||||
|
Consider the following proto: |
||||||
|
|
||||||
|
```protobuf |
||||||
|
message MyMessage { |
||||||
|
oneof record { |
||||||
|
string name = 1 [default = "unnamed"]; |
||||||
|
int32 id_number = 2 [default = 0]; |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
In Swift, we would generate an enum, a property for that enum, and properties |
||||||
|
for the fields themselves: |
||||||
|
|
||||||
|
```swift |
||||||
|
public struct MyMessage: ProtoMessage { |
||||||
|
public enum Record: NilLiteralConvertible { |
||||||
|
case name(String) |
||||||
|
case idNumber(Int32) |
||||||
|
case NOT_SET |
||||||
|
|
||||||
|
public init(nilLiteral: ()) { self = .NOT_SET } |
||||||
|
} |
||||||
|
|
||||||
|
// This is the "Swifty" way of accessing the value |
||||||
|
public var record: Record { ... } |
||||||
|
|
||||||
|
// Direct access to the underlying fields |
||||||
|
public var name: String! { ... } |
||||||
|
public var idNumber: Int32! { ... } |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
This makes both usage patterns possible: |
||||||
|
|
||||||
|
```swift |
||||||
|
// Usage 1: Case-based dispatch |
||||||
|
switch message.record { |
||||||
|
case .name(let name): |
||||||
|
// Do something with name if it was explicitly set |
||||||
|
case .idNumber(let id): |
||||||
|
// Do something with id_number if it was explicitly set |
||||||
|
case .NOT_SET: |
||||||
|
// Do something if it’s not set |
||||||
|
} |
||||||
|
|
||||||
|
// Usage 2: Direct access for default value fallback |
||||||
|
// Sets the label text to the name if it was explicitly set, or to |
||||||
|
// "unnamed" (the default value for the field) if id_number was set |
||||||
|
// instead |
||||||
|
let myLabel = UILabel() |
||||||
|
myLabel.text = message.name |
||||||
|
``` |
||||||
|
|
||||||
|
As with proto enums, the generated `oneof` enum conforms to |
||||||
|
`NilLiteralConvertible` to avoid switch statement issues. Setting the property |
||||||
|
to nil will clear it (i.e., reset it to `NOT_SET`). |
||||||
|
|
||||||
|
## Unknown Fields (proto2 only) |
||||||
|
|
||||||
|
To be written. |
||||||
|
|
||||||
|
## Extensions (proto2 only) |
||||||
|
|
||||||
|
To be written. |
||||||
|
|
||||||
|
## Reflection and Descriptors |
||||||
|
|
||||||
|
We will not include reflection or descriptors in the first version of the Swift |
||||||
|
library. The use cases for reflection on mobile are not as strong and the static |
||||||
|
data to represent the descriptors would add bloat when we wish to keep the code |
||||||
|
size small. |
||||||
|
|
||||||
|
In the future, we will investigate whether they can be included as extensions |
||||||
|
which might be able to be excluded from a build and/or automatically dead |
||||||
|
stripped by the compiler if they are not used. |
||||||
|
|
||||||
|
## Appendix A: Rejected strategies to handle packages |
||||||
|
|
||||||
|
### Each package is its own Swift module |
||||||
|
|
||||||
|
Each proto package could be declared as its own Swift module, replacing dots |
||||||
|
with underscores (e.g., package `foo.bar` becomes module `Foo_Bar`). Then, users |
||||||
|
would simply import modules containing whatever proto modules they want to use |
||||||
|
and refer to the generated types by their short names. |
||||||
|
|
||||||
|
**This solution is simply not possible, however.** Swift modules cannot |
||||||
|
circularly reference each other, but there is no restriction against proto |
||||||
|
packages doing so. Circular imports are forbidden (e.g., `foo.proto` importing |
||||||
|
`bar.proto` importing `foo.proto`), but nothing prevents package `foo` from |
||||||
|
using a type in package `bar` which uses a different type in package `foo`, as |
||||||
|
long as there is no import cycle. If these packages were generated as Swift |
||||||
|
modules, then `Foo` would contain an `import Bar` statement and `Bar` would |
||||||
|
contain an `import Foo` statement, and there is no way to compile this. |
||||||
|
|
||||||
|
### Ad hoc namespacing with structs |
||||||
|
|
||||||
|
We can “fake” namespaces in Swift by declaring empty structs with private |
||||||
|
initializers. Since modules are constructed based on compiler arguments, not by |
||||||
|
syntactic constructs, and because there is no pure Swift way to define |
||||||
|
submodules (even though Clang module maps support this), there is no |
||||||
|
source-drive way to group generated code into namespaces aside from this |
||||||
|
approach. |
||||||
|
|
||||||
|
Types can be added to those intermediate package structs using Swift extensions. |
||||||
|
For example, a message `Baz` in package `foo.bar` could be represented in Swift |
||||||
|
as follows: |
||||||
|
|
||||||
|
```swift |
||||||
|
public struct Foo { |
||||||
|
private init() {} |
||||||
|
} |
||||||
|
|
||||||
|
public extension Foo { |
||||||
|
public struct Bar { |
||||||
|
private init() {} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public extension Foo.Bar { |
||||||
|
public struct Baz { |
||||||
|
// Message fields and other methods |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
let baz = Foo.Bar.Baz() |
||||||
|
``` |
||||||
|
|
||||||
|
Each of these constructs would actually be defined in a separate file; Swift |
||||||
|
lets us keep them separate and add multiple structs to a single “namespace” |
||||||
|
through extensions. |
||||||
|
|
||||||
|
Unfortunately, these intermediate structs generate symbols of their own |
||||||
|
(metatype information in the data segment). This becomes problematic if multiple |
||||||
|
build targets contain Swift sources generated from different messages in the |
||||||
|
same package. At link time, these symbols would collide, resulting in multiple |
||||||
|
definition errors. |
||||||
|
|
||||||
|
This approach also has the disadvantage that there is no automatic “short” way |
||||||
|
to refer to the generated messages at the deepest nesting levels; since this use |
||||||
|
of structs is a hack around the lack of namespaces, there is no equivalent to |
||||||
|
import (Java) or using (C++) to simplify this. Users would have to declare type |
||||||
|
aliases to make this cleaner, or we would have to generate them for users. |
@ -0,0 +1,147 @@ |
|||||||
|
# Third-Party Add-ons for Protocol Buffers |
||||||
|
|
||||||
|
This page lists code related to Protocol Buffers which is developed and maintained by third parties. You may find this code useful, but note that **these projects are not affiliated with or endorsed by Google (unless explicitly marked)**; try them at your own risk. Also note that many projects here are in the early stages of development and not production-ready. |
||||||
|
|
||||||
|
If you have a project that should be listed here, please [send us a pull request](https://github.com/google/protobuf/pulls) to update this page. |
||||||
|
|
||||||
|
## Programming Languages |
||||||
|
|
||||||
|
These are projects we know about implementing Protocol Buffers for other programming languages: |
||||||
|
* Action Script: http://code.google.com/p/protobuf-actionscript3/ |
||||||
|
* Action Script: https://code.google.com/p/protoc-gen-as3/ |
||||||
|
* Action Script: https://github.com/matrix3d/JProtoc |
||||||
|
* C: https://github.com/protobuf-c/protobuf-c |
||||||
|
* C: http://koti.kapsi.fi/jpa/nanopb/ |
||||||
|
* C: https://github.com/cloudwu/pbc/ |
||||||
|
* C: https://github.com/haberman/upb/wiki |
||||||
|
* C: https://github.com/squidfunk/protobluff |
||||||
|
* C++: https://github.com/google/protobuf (Google-official implementation) |
||||||
|
* C/C++: http://spbc.sf.net/ |
||||||
|
* C#: http://code.google.com/p/protobuf-csharp-port |
||||||
|
* C#: http://code.google.com/p/protosharp/ |
||||||
|
* C#: https://silentorbit.com/protobuf/ |
||||||
|
* C#/.NET/WCF/VB: http://code.google.com/p/protobuf-net/ |
||||||
|
* Clojure: http://github.com/ninjudd/clojure-protobuf |
||||||
|
* Common Lisp: http://www.prism.gatech.edu/~ndantam3/docs/s-protobuf/ |
||||||
|
* Common Lisp: http://github.com/brown/protobuf |
||||||
|
* D: https://github.com/msoucy/dproto |
||||||
|
* D: http://256.makerslocal.org/wiki/index.php/ProtocolBuffer |
||||||
|
* D: https://github.com/opticron/ProtocolBuffer |
||||||
|
* Dart: https://github.com/dart-lang/dart-protobuf (runtime) https://github.com/dart-lang/dart-protoc-plugin (code generator) |
||||||
|
* Delphi: http://sourceforge.net/projects/protobuf-delphi/ |
||||||
|
* Delphi: http://fundementals.sourceforge.net/dl.html |
||||||
|
* Elixir: https://github.com/jeremyong/exprotoc |
||||||
|
* Erlang: http://github.com/ngerakines/erlang_protobuffs/tree/master |
||||||
|
* Erlang: http://piqi.org/ |
||||||
|
* Erlang: https://code.google.com/p/protoc-gen-erl/ |
||||||
|
* Erlang: https://github.com/basho/erlang_protobuffs |
||||||
|
* Go: https://github.com/golang/protobuf (Google-official implementation) |
||||||
|
* Go: http://code.google.com/p/goprotobuf/ |
||||||
|
* Go: https://github.com/akunspy/gopbuf |
||||||
|
* Haskell: http://hackage.haskell.org/package/hprotoc |
||||||
|
* Haxe: https://github.com/Atry/protoc-gen-haxe |
||||||
|
* Java: https://github.com/google/protobuf (Google-official implementation) |
||||||
|
* Java/Android: https://github.com/square/wire |
||||||
|
* Java ME: http://code.google.com/p/protobuf-javame/ |
||||||
|
* Java ME: http://swingme.sourceforge.net/encode.shtml |
||||||
|
* Java ME: http://github.com/ponderingpanda/protobuf-j2me |
||||||
|
* Java ME: http://code.google.com/p/protobuf-j2me/ |
||||||
|
* Javascript: http://code.google.com/p/protobuf-js/ |
||||||
|
* Javascript: http://github.com/sirikata/protojs |
||||||
|
* Javascript: https://github.com/dcodeIO/ProtoBuf.js |
||||||
|
* Javascript: http://code.google.com/p/protobuf-for-node/ |
||||||
|
* Javascript: http://code.google.com/p/protostuff/ |
||||||
|
* Julia: https://github.com/tanmaykm/ProtoBuf.jl |
||||||
|
* Lua: http://code.google.com/p/protoc-gen-lua/ |
||||||
|
* Lua: http://github.com/indygreg/lua-protobuf |
||||||
|
* Lua: https://github.com/Neopallium/lua-pb |
||||||
|
* Matlab: http://code.google.com/p/protobuf-matlab/ |
||||||
|
* Mercury: http://code.google.com/p/protobuf-mercury/ |
||||||
|
* Objective C: http://code.google.com/p/protobuf-objc/ |
||||||
|
* Objective C: https://github.com/alexeyxo/protobuf-objc |
||||||
|
* OCaml: http://piqi.org/ |
||||||
|
* Perl: http://groups.google.com/group/protobuf-perl |
||||||
|
* Perl: http://search.cpan.org/perldoc?Google::ProtocolBuffers |
||||||
|
* Perl/XS: http://code.google.com/p/protobuf-perlxs/ |
||||||
|
* PHP: http://code.google.com/p/pb4php/ |
||||||
|
* PHP: https://github.com/allegro/php-protobuf/ |
||||||
|
* PHP: https://github.com/chobie/php-protocolbuffers |
||||||
|
* PHP: http://drslump.github.com/Protobuf-PHP |
||||||
|
* Prolog: http://www.swi-prolog.org/pldoc/package/protobufs.html |
||||||
|
* Python: https://github.com/google/protobuf (Google-official implementation) |
||||||
|
* Python: http://eigenein.github.com/protobuf/ |
||||||
|
* R: http://cran.r-project.org/package=RProtoBuf |
||||||
|
* Ruby: http://code.google.com/p/ruby-protobuf/ |
||||||
|
* Ruby: http://github.com/mozy/ruby-protocol-buffers |
||||||
|
* Ruby: https://github.com/bmizerany/beefcake/tree/master/lib/beefcake |
||||||
|
* Ruby: https://github.com/localshred/protobuf |
||||||
|
* Rust: https://github.com/stepancheg/rust-protobuf/ |
||||||
|
* Scala: http://github.com/jeffplaisance/scala-protobuf |
||||||
|
* Scala: http://code.google.com/p/protobuf-scala |
||||||
|
* Scala: https://github.com/SandroGrzicic/ScalaBuff |
||||||
|
* Scala: http://trueaccord.github.io/ScalaPB/ |
||||||
|
* Swift: https://github.com/alexeyxo/protobuf-swift |
||||||
|
* Vala: https://launchpad.net/protobuf-vala |
||||||
|
* Visual Basic: http://code.google.com/p/protobuf-net/ |
||||||
|
|
||||||
|
## RPC Implementations |
||||||
|
|
||||||
|
GRPC (http://www.grpc.io/) is Google's RPC implementation for Protocol Buffers. There are other third-party RPC implementations as well. Some of these actually work with Protocol Buffers service definitions (defined using the `service` keyword in `.proto` files) while others just use Protocol Buffers message objects. |
||||||
|
|
||||||
|
* https://github.com/grpc/grpc (C++, Node.js, Python, Ruby, Objective-C, PHP, C#, Google-official implementation) |
||||||
|
* http://zeroc.com/ice.html (Multiple languages) |
||||||
|
* http://code.google.com/p/protobuf-net/ (C#/.NET/WCF/VB) |
||||||
|
* https://launchpad.net/txprotobuf/ (Python) |
||||||
|
* https://github.com/modeswitch/protobuf-rpc (Python) |
||||||
|
* http://code.google.com/p/protobuf-socket-rpc/ (Java, Python) |
||||||
|
* http://code.google.com/p/proto-streamer/ (Java) |
||||||
|
* http://code.google.com/p/server1/ (C++) |
||||||
|
* http://deltavsoft.com/RcfUserGuide/Protobufs (C++) |
||||||
|
* http://code.google.com/p/protobuf-mina-rpc/ (Python client, Java server) |
||||||
|
* http://code.google.com/p/casocklib/ (C++) |
||||||
|
* http://code.google.com/p/cxf-protobuf/ (Java) |
||||||
|
* http://code.google.com/p/protobuf-remote/ (C++/C#) |
||||||
|
* http://code.google.com/p/protobuf-rpc-pro/ (Java) |
||||||
|
* https://code.google.com/p/protorpc/ (Go/C++) |
||||||
|
* https://code.google.com/p/eneter-protobuf-serializer/ (Java/.NET) |
||||||
|
* http://www.deltavsoft.com/RCFProto.html (C++/Java/Python/C#) |
||||||
|
* https://github.com/robbinfan/claire-protorpc (C++) |
||||||
|
* https://github.com/BaiduPS/sofa-pbrpc (C++) |
||||||
|
* https://github.com/ebencheung/arab (C++) |
||||||
|
* http://code.google.com/p/protobuf-csharp-rpc/ (C#) |
||||||
|
* https://github.com/thesamet/rpcz (C++/Python, based on ZeroMQ) |
||||||
|
* https://github.com/w359405949/libmaid (C++, Python) |
||||||
|
* https://github.com/madwyn/libpbrpc (C++) |
||||||
|
|
||||||
|
## Other Utilities |
||||||
|
|
||||||
|
There are miscellaneous other things you may find useful as a Protocol Buffers developer. |
||||||
|
|
||||||
|
* [NetBeans IDE plugin](http://code.google.com/p/protobuf-netbeans-plugin/) |
||||||
|
* [Wireshark/Ethereal packet sniffer plugin](http://code.google.com/p/protobuf-wireshark/) |
||||||
|
* [Alternate encodings (JSON, XML, HTML) for Java protobufs](http://code.google.com/p/protobuf-java-format/) |
||||||
|
* [Another JSON encoder/decoder for Java](https://github.com/sijuv/protobuf-codec) |
||||||
|
* [Editor for serialized protobufs](http://code.google.com/p/protobufeditor/) |
||||||
|
* [Intellij IDEA plugin](http://github.com/nnmatveev/idea-plugin-protobuf) |
||||||
|
* [TextMate syntax highlighting](http://github.com/michaeledgar/protobuf-tmbundle) |
||||||
|
* [Oracle PL SQL plugin](http://code.google.com/p/protocol-buffer-plsql/) |
||||||
|
* [Eclipse editor for protobuf (from Google)](http://code.google.com/p/protobuf-dt/) |
||||||
|
* [C++ Builder compatible protobuf](https://github.com/saadware/protobuf-cppbuilder) |
||||||
|
* Maven Protocol Compiler Plugin |
||||||
|
* https://github.com/sergei-ivanov/maven-protoc-plugin/ |
||||||
|
* http://igor-petruk.github.com/protobuf-maven-plugin/ |
||||||
|
* http://code.google.com/p/maven-protoc-plugin/ |
||||||
|
* https://github.com/os72/protoc-jar-maven-plugin |
||||||
|
* [Documentation generator plugin (Markdown/HTML/DocBook/...)](https://github.com/estan/protoc-gen-doc) |
||||||
|
* [DocBook generator for .proto files](http://code.google.com/p/protoc-gen-docbook/) |
||||||
|
* [Protobuf for nginx module](https://github.com/dbcode/protobuf-nginx/) |
||||||
|
* [RSpec matchers and Cucumber step defs for testing Protocol Buffers](https://github.com/connamara/protobuf_spec) |
||||||
|
* [Sbt plugin for Protocol Buffers](https://github.com/Atry/sbt-cppp) |
||||||
|
* [Gradle Protobuf Plugin](https://github.com/aantono/gradle-plugin-protobuf) |
||||||
|
* [Multi-platform executable JAR and Java API for protoc](https://github.com/os72/protoc-jar) |
||||||
|
* [Python scripts to convert between Protocol Buffers and JSON](https://github.com/NextTuesday/py-pb-converters) |
||||||
|
* [Visual Studio Language Service support for Protocol Buffers](http://visualstudiogallery.msdn.microsoft.com/4bc0f38c-b058-4e05-ae38-155e053c19c5) |
||||||
|
* [C++ library for serialization/de-serialization between Protocol Buffers and JSON.](https://github.com/yinqiwen/pbjson) |
||||||
|
* [ProtoBuf with Java EE7 Expression Language 3.0; pure Java ProtoBuf Parser and Builder.](https://github.com/protobufel/protobuf-el) |
||||||
|
* [Notepad++ Syntax Highlighting for .proto files](https://github.com/chai2010/notepadplus-protobuf) |
||||||
|
* [Linter for .proto files](https://github.com/ckaznocha/protoc-gen-lint) |
@ -0,0 +1,145 @@ |
|||||||
|
// Protocol Buffers - Google's data interchange format
|
||||||
|
// Copyright 2008 Google Inc. All rights reserved.
|
||||||
|
// https://developers.google.com/protocol-buffers/
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * Redistributions in binary form must reproduce the above
|
||||||
|
// copyright notice, this list of conditions and the following disclaimer
|
||||||
|
// in the documentation and/or other materials provided with the
|
||||||
|
// distribution.
|
||||||
|
// * Neither the name of Google Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
package com.google.protobuf; |
||||||
|
|
||||||
|
import static java.lang.Math.max; |
||||||
|
import static java.lang.Math.min; |
||||||
|
|
||||||
|
import java.io.FileOutputStream; |
||||||
|
import java.io.IOException; |
||||||
|
import java.io.OutputStream; |
||||||
|
import java.lang.ref.SoftReference; |
||||||
|
import java.nio.ByteBuffer; |
||||||
|
|
||||||
|
/** |
||||||
|
* Utility class to provide efficient writing of {@link ByteBuffer}s to {@link OutputStream}s. |
||||||
|
*/ |
||||||
|
final class ByteBufferWriter { |
||||||
|
private ByteBufferWriter() {} |
||||||
|
|
||||||
|
/** |
||||||
|
* Minimum size for a cached buffer. This prevents us from allocating buffers that are too |
||||||
|
* small to be easily reused. |
||||||
|
*/ |
||||||
|
// TODO(nathanmittler): tune this property or allow configuration?
|
||||||
|
private static final int MIN_CACHED_BUFFER_SIZE = 1024; |
||||||
|
|
||||||
|
/** |
||||||
|
* Maximum size for a cached buffer. If a larger buffer is required, it will be allocated |
||||||
|
* but not cached. |
||||||
|
*/ |
||||||
|
// TODO(nathanmittler): tune this property or allow configuration?
|
||||||
|
private static final int MAX_CACHED_BUFFER_SIZE = 16 * 1024; |
||||||
|
|
||||||
|
/** |
||||||
|
* The fraction of the requested buffer size under which the buffer will be reallocated. |
||||||
|
*/ |
||||||
|
// TODO(nathanmittler): tune this property or allow configuration?
|
||||||
|
private static final float BUFFER_REALLOCATION_THRESHOLD = 0.5f; |
||||||
|
|
||||||
|
/** |
||||||
|
* Keeping a soft reference to a thread-local buffer. This buffer is used for writing a |
||||||
|
* {@link ByteBuffer} to an {@link OutputStream} when no zero-copy alternative was available. |
||||||
|
* Using a "soft" reference since VMs may keep this reference around longer than "weak" |
||||||
|
* (e.g. HotSpot will maintain soft references until memory pressure warrants collection). |
||||||
|
*/ |
||||||
|
private static final ThreadLocal<SoftReference<byte[]>> BUFFER = |
||||||
|
new ThreadLocal<SoftReference<byte[]>>(); |
||||||
|
|
||||||
|
/** |
||||||
|
* For testing purposes only. Clears the cached buffer to force a new allocation on the next |
||||||
|
* invocation. |
||||||
|
*/ |
||||||
|
static void clearCachedBuffer() { |
||||||
|
BUFFER.set(null); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Writes the remaining content of the buffer to the given stream. The buffer {@code position} |
||||||
|
* will remain unchanged by this method. |
||||||
|
*/ |
||||||
|
static void write(ByteBuffer buffer, OutputStream output) throws IOException { |
||||||
|
final int initialPos = buffer.position(); |
||||||
|
try { |
||||||
|
if (buffer.hasArray()) { |
||||||
|
// Optimized write for array-backed buffers.
|
||||||
|
// Note that we're taking the risk that a malicious OutputStream could modify the array.
|
||||||
|
output.write(buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.remaining()); |
||||||
|
} else if (output instanceof FileOutputStream) { |
||||||
|
// Use a channel to write out the ByteBuffer. This will automatically empty the buffer.
|
||||||
|
((FileOutputStream) output).getChannel().write(buffer); |
||||||
|
} else { |
||||||
|
// Read all of the data from the buffer to an array.
|
||||||
|
// TODO(nathanmittler): Consider performance improvements for other "known" stream types.
|
||||||
|
final byte[] array = getOrCreateBuffer(buffer.remaining()); |
||||||
|
while (buffer.hasRemaining()) { |
||||||
|
int length = min(buffer.remaining(), array.length); |
||||||
|
buffer.get(array, 0, length); |
||||||
|
output.write(array, 0, length); |
||||||
|
} |
||||||
|
} |
||||||
|
} finally { |
||||||
|
// Restore the initial position.
|
||||||
|
buffer.position(initialPos); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private static byte[] getOrCreateBuffer(int requestedSize) { |
||||||
|
requestedSize = max(requestedSize, MIN_CACHED_BUFFER_SIZE); |
||||||
|
|
||||||
|
byte[] buffer = getBuffer(); |
||||||
|
// Only allocate if we need to.
|
||||||
|
if (buffer == null || needToReallocate(requestedSize, buffer.length)) { |
||||||
|
buffer = new byte[requestedSize]; |
||||||
|
|
||||||
|
// Only cache the buffer if it's not too big.
|
||||||
|
if (requestedSize <= MAX_CACHED_BUFFER_SIZE) { |
||||||
|
setBuffer(buffer); |
||||||
|
} |
||||||
|
} |
||||||
|
return buffer; |
||||||
|
} |
||||||
|
|
||||||
|
private static boolean needToReallocate(int requestedSize, int bufferLength) { |
||||||
|
// First check against just the requested length to avoid the multiply.
|
||||||
|
return bufferLength < requestedSize |
||||||
|
&& bufferLength < requestedSize * BUFFER_REALLOCATION_THRESHOLD; |
||||||
|
} |
||||||
|
|
||||||
|
private static byte[] getBuffer() { |
||||||
|
SoftReference<byte[]> sr = BUFFER.get(); |
||||||
|
return sr == null ? null : sr.get(); |
||||||
|
} |
||||||
|
|
||||||
|
private static void setBuffer(byte[] value) { |
||||||
|
BUFFER.set(new SoftReference<byte[]>(value)); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,116 @@ |
|||||||
|
// Protocol Buffers - Google's data interchange format
|
||||||
|
// Copyright 2008 Google Inc. All rights reserved.
|
||||||
|
// https://developers.google.com/protocol-buffers/
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * Redistributions in binary form must reproduce the above
|
||||||
|
// copyright notice, this list of conditions and the following disclaimer
|
||||||
|
// in the documentation and/or other materials provided with the
|
||||||
|
// distribution.
|
||||||
|
// * Neither the name of Google Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
package com.google.protobuf; |
||||||
|
|
||||||
|
import java.io.IOException; |
||||||
|
import java.nio.ByteBuffer; |
||||||
|
|
||||||
|
/** |
||||||
|
* An output target for raw bytes. This interface provides semantics that support two types of |
||||||
|
* writing: |
||||||
|
* |
||||||
|
* <p/><b>Traditional write operations:</b> |
||||||
|
* (as defined by {@link java.io.OutputStream}) where the target method is responsible for either |
||||||
|
* copying the data or completing the write before returning from the method call. |
||||||
|
* |
||||||
|
* <p/><b>Lazy write operations:</b> where the caller guarantees that it will never modify the |
||||||
|
* provided buffer and it can therefore be considered immutable. The target method is free to |
||||||
|
* maintain a reference to the buffer beyond the scope of the method call (e.g. until the write |
||||||
|
* operation completes). |
||||||
|
*/ |
||||||
|
@ExperimentalApi |
||||||
|
public abstract class ByteOutput { |
||||||
|
/** |
||||||
|
* Writes a single byte. |
||||||
|
* |
||||||
|
* @param value the byte to be written |
||||||
|
* @throws IOException thrown if an error occurred while writing |
||||||
|
*/ |
||||||
|
public abstract void write(byte value) throws IOException; |
||||||
|
|
||||||
|
/** |
||||||
|
* Writes a sequence of bytes. The {@link ByteOutput} must copy {@code value} if it will |
||||||
|
* not be processed prior to the return of this method call, since {@code value} may be |
||||||
|
* reused/altered by the caller. |
||||||
|
* |
||||||
|
* <p>NOTE: This method <strong>MUST NOT</strong> modify the {@code value}. Doing so is a |
||||||
|
* programming error and will lead to data corruption which will be difficult to debug. |
||||||
|
* |
||||||
|
* @param value the bytes to be written |
||||||
|
* @param offset the offset of the start of the writable range |
||||||
|
* @param length the number of bytes to write starting from {@code offset} |
||||||
|
* @throws IOException thrown if an error occurred while writing |
||||||
|
*/ |
||||||
|
public abstract void write(byte[] value, int offset, int length) throws IOException; |
||||||
|
|
||||||
|
/** |
||||||
|
* Writes a sequence of bytes. The {@link ByteOutput} is free to retain a reference to the value |
||||||
|
* beyond the scope of this method call (e.g. write later) since it is considered immutable and is |
||||||
|
* guaranteed not to change by the caller. |
||||||
|
* |
||||||
|
* <p>NOTE: This method <strong>MUST NOT</strong> modify the {@code value}. Doing so is a |
||||||
|
* programming error and will lead to data corruption which will be difficult to debug. |
||||||
|
* |
||||||
|
* @param value the bytes to be written |
||||||
|
* @param offset the offset of the start of the writable range |
||||||
|
* @param length the number of bytes to write starting from {@code offset} |
||||||
|
* @throws IOException thrown if an error occurred while writing |
||||||
|
*/ |
||||||
|
public abstract void writeLazy(byte[] value, int offset, int length) throws IOException; |
||||||
|
|
||||||
|
/** |
||||||
|
* Writes a sequence of bytes. The {@link ByteOutput} must copy {@code value} if it will |
||||||
|
* not be processed prior to the return of this method call, since {@code value} may be |
||||||
|
* reused/altered by the caller. |
||||||
|
* |
||||||
|
* <p>NOTE: This method <strong>MUST NOT</strong> modify the {@code value}. Doing so is a |
||||||
|
* programming error and will lead to data corruption which will be difficult to debug. |
||||||
|
* |
||||||
|
* @param value the bytes to be written. Upon returning from this call, the {@code position} of |
||||||
|
* this buffer will be set to the {@code limit} |
||||||
|
* @throws IOException thrown if an error occurred while writing |
||||||
|
*/ |
||||||
|
public abstract void write(ByteBuffer value) throws IOException; |
||||||
|
|
||||||
|
/** |
||||||
|
* Writes a sequence of bytes. The {@link ByteOutput} is free to retain a reference to the value |
||||||
|
* beyond the scope of this method call (e.g. write later) since it is considered immutable and is |
||||||
|
* guaranteed not to change by the caller. |
||||||
|
* |
||||||
|
* <p>NOTE: This method <strong>MUST NOT</strong> modify the {@code value}. Doing so is a |
||||||
|
* programming error and will lead to data corruption which will be difficult to debug. |
||||||
|
* |
||||||
|
* @param value the bytes to be written. Upon returning from this call, the {@code position} of |
||||||
|
* this buffer will be set to the {@code limit} |
||||||
|
* @throws IOException thrown if an error occurred while writing |
||||||
|
*/ |
||||||
|
public abstract void writeLazy(ByteBuffer value) throws IOException; |
||||||
|
} |
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,239 @@ |
|||||||
|
// Protocol Buffers - Google's data interchange format
|
||||||
|
// Copyright 2008 Google Inc. All rights reserved.
|
||||||
|
// https://developers.google.com/protocol-buffers/
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * Redistributions in binary form must reproduce the above
|
||||||
|
// copyright notice, this list of conditions and the following disclaimer
|
||||||
|
// in the documentation and/or other materials provided with the
|
||||||
|
// distribution.
|
||||||
|
// * Neither the name of Google Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
package com.google.protobuf; |
||||||
|
|
||||||
|
import java.lang.reflect.Method; |
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.Iterator; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.Set; |
||||||
|
import java.util.TreeSet; |
||||||
|
|
||||||
|
/** |
||||||
|
* Helps generate {@link String} representations of {@link MessageLite} protos. |
||||||
|
*/ |
||||||
|
// TODO(dweis): Fix map fields.
|
||||||
|
final class MessageLiteToString { |
||||||
|
|
||||||
|
private static final String LIST_SUFFIX = "List"; |
||||||
|
private static final String BUILDER_LIST_SUFFIX = "OrBuilderList"; |
||||||
|
private static final String BYTES_SUFFIX = "Bytes"; |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns a {@link String} representation of the {@link MessageLite} object. The first line of |
||||||
|
* the {@code String} representation representation includes a comment string to uniquely identify |
||||||
|
* the objcet instance. This acts as an indicator that this should not be relied on for |
||||||
|
* comparisons. |
||||||
|
* |
||||||
|
* <p>For use by generated code only. |
||||||
|
*/ |
||||||
|
static String toString(MessageLite messageLite, String commentString) { |
||||||
|
StringBuilder buffer = new StringBuilder(); |
||||||
|
buffer.append("# ").append(commentString); |
||||||
|
reflectivePrintWithIndent(messageLite, buffer, 0); |
||||||
|
return buffer.toString(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Reflectively prints the {@link MessageLite} to the buffer at given {@code indent} level. |
||||||
|
* |
||||||
|
* @param buffer the buffer to write to |
||||||
|
* @param indent the number of spaces to indent the proto by |
||||||
|
*/ |
||||||
|
private static void reflectivePrintWithIndent( |
||||||
|
MessageLite messageLite, StringBuilder buffer, int indent) { |
||||||
|
// Build a map of method name to method. We're looking for methods like getFoo(), hasFoo(), and
|
||||||
|
// getFooList() which might be useful for building an object's string representation.
|
||||||
|
Map<String, Method> nameToNoArgMethod = new HashMap<String, Method>(); |
||||||
|
Map<String, Method> nameToMethod = new HashMap<String, Method>(); |
||||||
|
Set<String> getters = new TreeSet<String>(); |
||||||
|
for (Method method : messageLite.getClass().getDeclaredMethods()) { |
||||||
|
nameToMethod.put(method.getName(), method); |
||||||
|
if (method.getParameterTypes().length == 0) { |
||||||
|
nameToNoArgMethod.put(method.getName(), method); |
||||||
|
|
||||||
|
if (method.getName().startsWith("get")) { |
||||||
|
getters.add(method.getName()); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
for (String getter : getters) { |
||||||
|
String suffix = getter.replaceFirst("get", ""); |
||||||
|
if (suffix.endsWith(LIST_SUFFIX) && !suffix.endsWith(BUILDER_LIST_SUFFIX)) { |
||||||
|
String camelCase = suffix.substring(0, 1).toLowerCase() |
||||||
|
+ suffix.substring(1, suffix.length() - LIST_SUFFIX.length()); |
||||||
|
// Try to reflectively get the value and toString() the field as if it were repeated. This
|
||||||
|
// only works if the method names have not be proguarded out or renamed.
|
||||||
|
Method listMethod = nameToNoArgMethod.get("get" + suffix); |
||||||
|
if (listMethod != null) { |
||||||
|
printField( |
||||||
|
buffer, |
||||||
|
indent, |
||||||
|
camelCaseToSnakeCase(camelCase), |
||||||
|
GeneratedMessageLite.invokeOrDie(listMethod, messageLite)); |
||||||
|
continue; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
Method setter = nameToMethod.get("set" + suffix); |
||||||
|
if (setter == null) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
if (suffix.endsWith(BYTES_SUFFIX) |
||||||
|
&& nameToNoArgMethod.containsKey( |
||||||
|
"get" + suffix.substring(0, suffix.length() - "Bytes".length()))) { |
||||||
|
// Heuristic to skip bytes based accessors for string fields.
|
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
String camelCase = suffix.substring(0, 1).toLowerCase() + suffix.substring(1); |
||||||
|
|
||||||
|
// Try to reflectively get the value and toString() the field as if it were optional. This
|
||||||
|
// only works if the method names have not be proguarded out or renamed.
|
||||||
|
Method getMethod = nameToNoArgMethod.get("get" + suffix); |
||||||
|
Method hasMethod = nameToNoArgMethod.get("has" + suffix); |
||||||
|
// TODO(dweis): Fix proto3 semantics.
|
||||||
|
if (getMethod != null) { |
||||||
|
Object value = GeneratedMessageLite.invokeOrDie(getMethod, messageLite); |
||||||
|
final boolean hasValue = hasMethod == null |
||||||
|
? !isDefaultValue(value) |
||||||
|
: (Boolean) GeneratedMessageLite.invokeOrDie(hasMethod, messageLite); |
||||||
|
// TODO(dweis): This doesn't stop printing oneof case twice: value and enum style.
|
||||||
|
if (hasValue) { |
||||||
|
printField( |
||||||
|
buffer, |
||||||
|
indent, |
||||||
|
camelCaseToSnakeCase(camelCase), |
||||||
|
value); |
||||||
|
} |
||||||
|
continue; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (messageLite instanceof GeneratedMessageLite.ExtendableMessage) { |
||||||
|
Iterator<Map.Entry<GeneratedMessageLite.ExtensionDescriptor, Object>> iter = |
||||||
|
((GeneratedMessageLite.ExtendableMessage<?, ?>) messageLite).extensions.iterator(); |
||||||
|
while (iter.hasNext()) { |
||||||
|
Map.Entry<GeneratedMessageLite.ExtensionDescriptor, Object> entry = iter.next(); |
||||||
|
printField(buffer, indent, "[" + entry.getKey().getNumber() + "]", entry.getValue()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (((GeneratedMessageLite<?, ?>) messageLite).unknownFields != null) { |
||||||
|
((GeneratedMessageLite<?, ?>) messageLite).unknownFields.printWithIndent(buffer, indent); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private static boolean isDefaultValue(Object o) { |
||||||
|
if (o instanceof Boolean) { |
||||||
|
return !((Boolean) o); |
||||||
|
} |
||||||
|
if (o instanceof Integer) { |
||||||
|
return ((Integer) o) == 0; |
||||||
|
} |
||||||
|
if (o instanceof Float) { |
||||||
|
return ((Float) o) == 0f; |
||||||
|
} |
||||||
|
if (o instanceof Double) { |
||||||
|
return ((Double) o) == 0d; |
||||||
|
} |
||||||
|
if (o instanceof String) { |
||||||
|
return o.equals(""); |
||||||
|
} |
||||||
|
if (o instanceof ByteString) { |
||||||
|
return o.equals(ByteString.EMPTY); |
||||||
|
} |
||||||
|
if (o instanceof MessageLite) { // Can happen in oneofs.
|
||||||
|
return o == ((MessageLite) o).getDefaultInstanceForType(); |
||||||
|
} |
||||||
|
if (o instanceof java.lang.Enum<?>) { // Catches oneof enums.
|
||||||
|
return ((java.lang.Enum<?>) o).ordinal() == 0; |
||||||
|
} |
||||||
|
|
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Formats a text proto field. |
||||||
|
* |
||||||
|
* <p>For use by generated code only. |
||||||
|
* |
||||||
|
* @param buffer the buffer to write to |
||||||
|
* @param indent the number of spaces the proto should be indented by |
||||||
|
* @param name the field name (in lower underscore case) |
||||||
|
* @param object the object value of the field |
||||||
|
*/ |
||||||
|
static final void printField(StringBuilder buffer, int indent, String name, Object object) { |
||||||
|
if (object instanceof List<?>) { |
||||||
|
List<?> list = (List<?>) object; |
||||||
|
for (Object entry : list) { |
||||||
|
printField(buffer, indent, name, entry); |
||||||
|
} |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
buffer.append('\n'); |
||||||
|
for (int i = 0; i < indent; i++) { |
||||||
|
buffer.append(' '); |
||||||
|
} |
||||||
|
buffer.append(name); |
||||||
|
|
||||||
|
if (object instanceof String) { |
||||||
|
buffer.append(": \"").append(TextFormatEscaper.escapeText((String) object)).append('"'); |
||||||
|
} else if (object instanceof ByteString) { |
||||||
|
buffer.append(": \"").append(TextFormatEscaper.escapeBytes((ByteString) object)).append('"'); |
||||||
|
} else if (object instanceof GeneratedMessageLite) { |
||||||
|
buffer.append(" {"); |
||||||
|
reflectivePrintWithIndent((GeneratedMessageLite<?, ?>) object, buffer, indent + 2); |
||||||
|
buffer.append("\n"); |
||||||
|
for (int i = 0; i < indent; i++) { |
||||||
|
buffer.append(' '); |
||||||
|
} |
||||||
|
buffer.append("}"); |
||||||
|
} else { |
||||||
|
buffer.append(": ").append(object.toString()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private static final String camelCaseToSnakeCase(String camelCase) { |
||||||
|
StringBuilder builder = new StringBuilder(); |
||||||
|
for (int i = 0; i < camelCase.length(); i++) { |
||||||
|
char ch = camelCase.charAt(i); |
||||||
|
if (Character.isUpperCase(ch)) { |
||||||
|
builder.append("_"); |
||||||
|
} |
||||||
|
builder.append(Character.toLowerCase(ch)); |
||||||
|
} |
||||||
|
return builder.toString(); |
||||||
|
} |
||||||
|
} |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue