Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in / Register
Toggle navigation
P
protobuf
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Packages
Packages
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
submodule
protobuf
Commits
fbb3ef28
Commit
fbb3ef28
authored
Aug 25, 2015
by
Feng Xiao
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Merge Java util package to github.
parent
839b180d
Show whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
4736 additions
and
0 deletions
+4736
-0
pom.xml
java/util/pom.xml
+202
-0
FieldMaskTree.java
...src/main/java/com/google/protobuf/util/FieldMaskTree.java
+259
-0
FieldMaskUtil.java
...src/main/java/com/google/protobuf/util/FieldMaskUtil.java
+222
-0
JsonFormat.java
...il/src/main/java/com/google/protobuf/util/JsonFormat.java
+1571
-0
TimeUtil.java
...util/src/main/java/com/google/protobuf/util/TimeUtil.java
+545
-0
FieldMaskTreeTest.java
...test/java/com/google/protobuf/util/FieldMaskTreeTest.java
+229
-0
FieldMaskUtilTest.java
...test/java/com/google/protobuf/util/FieldMaskUtilTest.java
+135
-0
JsonFormatTest.java
...rc/test/java/com/google/protobuf/util/JsonFormatTest.java
+976
-0
TimeUtilTest.java
.../src/test/java/com/google/protobuf/util/TimeUtilTest.java
+439
-0
json_test.proto
...il/src/test/java/com/google/protobuf/util/json_test.proto
+158
-0
No files found.
java/util/pom.xml
0 → 100644
View file @
fbb3ef28
<?xml version="1.0" encoding="UTF-8"?>
<project
xmlns=
"http://maven.apache.org/POM/4.0.0"
xmlns:xsi=
"http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation=
"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"
>
<modelVersion>
4.0.0
</modelVersion>
<parent>
<groupId>
com.google
</groupId>
<artifactId>
google
</artifactId>
<version>
1
</version>
</parent>
<groupId>
com.google.protobuf
</groupId>
<artifactId>
protobuf-java-util
</artifactId>
<version>
3.0.0-alpha-4-pre
</version>
<packaging>
bundle
</packaging>
<name>
Protocol Buffer Java API
</name>
<description>
Protocol Buffers are a way of encoding structured data in an efficient yet
extensible format.
</description>
<inceptionYear>
2008
</inceptionYear>
<url>
https://developers.google.com/protocol-buffers/
</url>
<licenses>
<license>
<name>
New BSD license
</name>
<url>
http://www.opensource.org/licenses/bsd-license.php
</url>
<distribution>
repo
</distribution>
</license>
</licenses>
<scm>
<url>
https://github.com/google/protobuf
</url>
<connection>
scm:git:https://github.com/google/protobuf.git
</connection>
</scm>
<dependencies>
<dependency>
<groupId>
com.google.protobuf
</groupId>
<artifactId>
protobuf-java
</artifactId>
<version>
3.0.0-alpha-4-pre
</version>
<scope>
compile
</scope>
</dependency>
<dependency>
<groupId>
com.google.guava
</groupId>
<artifactId>
guava
</artifactId>
<version>
18.0
</version>
<scope>
compile
</scope>
</dependency>
<dependency>
<groupId>
com.google.code.gson
</groupId>
<artifactId>
gson
</artifactId>
<version>
2.3
</version>
<scope>
compile
</scope>
</dependency>
<dependency>
<groupId>
junit
</groupId>
<artifactId>
junit
</artifactId>
<version>
4.4
</version>
<scope>
test
</scope>
</dependency>
<dependency>
<groupId>
org.easymock
</groupId>
<artifactId>
easymock
</artifactId>
<version>
2.2
</version>
<scope>
test
</scope>
</dependency>
<dependency>
<groupId>
org.easymock
</groupId>
<artifactId>
easymockclassextension
</artifactId>
<version>
2.2.1
</version>
<scope>
test
</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>
maven-compiler-plugin
</artifactId>
<configuration>
<source>
1.5
</source>
<target>
1.5
</target>
</configuration>
</plugin>
<plugin>
<artifactId>
maven-surefire-plugin
</artifactId>
<configuration>
<includes>
<include>
**/*Test.java
</include>
<include>
../src/main/java/com/google/protobuf/TestUtil.java
</include>
</includes>
</configuration>
</plugin>
<plugin>
<artifactId>
maven-antrun-plugin
</artifactId>
<executions>
<execution>
<id>
generate-test-sources
</id>
<phase>
generate-test-sources
</phase>
<configuration>
<tasks>
<mkdir
dir=
"target/generated-test-sources"
/>
<exec
executable=
"../../src/protoc"
>
<arg
value=
"--java_out=target/generated-test-sources"
/>
<arg
value=
"--proto_path=../../src"
/>
<arg
value=
"--proto_path=src/test/java"
/>
<arg
value=
"../../src/google/protobuf/unittest.proto"
/>
<arg
value=
"../../src/google/protobuf/unittest_import.proto"
/>
<arg
value=
"../../src/google/protobuf/unittest_import_public.proto"
/>
<arg
value=
"src/test/java/com/google/protobuf/util/json_test.proto"
/>
</exec>
</tasks>
<testSourceRoot>
target/generated-test-sources
</testSourceRoot>
</configuration>
<goals>
<goal>
run
</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>
org.apache.felix
</groupId>
<artifactId>
maven-bundle-plugin
</artifactId>
<extensions>
true
</extensions>
<configuration>
<instructions>
<Bundle-DocURL>
https://developers.google.com/protocol-buffers/
</Bundle-DocURL>
<Bundle-SymbolicName>
com.google.protobuf.util
</Bundle-SymbolicName>
<Export-Package>
com.google.protobuf.util;version=3.0.0-alpha-3
</Export-Package>
</instructions>
</configuration>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>
release
</id>
<distributionManagement>
<snapshotRepository>
<id>
sonatype-nexus-staging
</id>
<url>
https://oss.sonatype.org/content/repositories/snapshots
</url>
</snapshotRepository>
<repository>
<id>
sonatype-nexus-staging
</id>
<url>
https://oss.sonatype.org/service/local/staging/deploy/maven2/
</url>
</repository>
</distributionManagement>
<build>
<plugins>
<plugin>
<groupId>
org.apache.maven.plugins
</groupId>
<artifactId>
maven-source-plugin
</artifactId>
<version>
2.2.1
</version>
<executions>
<execution>
<id>
attach-sources
</id>
<goals>
<goal>
jar-no-fork
</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>
org.apache.maven.plugins
</groupId>
<artifactId>
maven-javadoc-plugin
</artifactId>
<version>
2.9.1
</version>
<executions>
<execution>
<id>
attach-javadocs
</id>
<goals>
<goal>
jar
</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>
org.apache.maven.plugins
</groupId>
<artifactId>
maven-gpg-plugin
</artifactId>
<version>
1.5
</version>
<executions>
<execution>
<id>
sign-artifacts
</id>
<phase>
verify
</phase>
<goals>
<goal>
sign
</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>
org.sonatype.plugins
</groupId>
<artifactId>
nexus-staging-maven-plugin
</artifactId>
<version>
1.6.3
</version>
<extensions>
true
</extensions>
<configuration>
<serverId>
sonatype-nexus-staging
</serverId>
<nexusUrl>
https://oss.sonatype.org/
</nexusUrl>
<autoReleaseAfterClose>
false
</autoReleaseAfterClose>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>
java/util/src/main/java/com/google/protobuf/util/FieldMaskTree.java
0 → 100644
View file @
fbb3ef28
// 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
.
util
;
import
com.google.protobuf.Descriptors.Descriptor
;
import
com.google.protobuf.Descriptors.FieldDescriptor
;
import
com.google.protobuf.FieldMask
;
import
com.google.protobuf.Message
;
import
java.util.ArrayList
;
import
java.util.List
;
import
java.util.Map.Entry
;
import
java.util.TreeMap
;
import
java.util.logging.Logger
;
/**
* A tree representation of a FieldMask. Each leaf node in this tree represent
* a field path in the FieldMask.
*
* <p>For example, FieldMask "foo.bar,foo.baz,bar.baz" as a tree will be:
* <pre>
* [root] -+- foo -+- bar
* | |
* | +- baz
* |
* +- bar --- baz
* </pre>
*
* <p>By representing FieldMasks with this tree structure we can easily convert
* a FieldMask to a canonical form, merge two FieldMasks, calculate the
* intersection to two FieldMasks and traverse all fields specified by the
* FieldMask in a message tree.
*/
class
FieldMaskTree
{
private
static
final
Logger
logger
=
Logger
.
getLogger
(
FieldMaskTree
.
class
.
getName
());
private
static
final
String
FIELD_PATH_SEPARATOR_REGEX
=
"\\."
;
private
static
class
Node
{
public
TreeMap
<
String
,
Node
>
children
=
new
TreeMap
<
String
,
Node
>();
}
private
final
Node
root
=
new
Node
();
/** Creates an empty FieldMaskTree. */
public
FieldMaskTree
()
{}
/** Creates a FieldMaskTree for a given FieldMask. */
public
FieldMaskTree
(
FieldMask
mask
)
{
mergeFromFieldMask
(
mask
);
}
@Override
public
String
toString
()
{
return
FieldMaskUtil
.
toString
(
toFieldMask
());
}
/**
* Adds a field path to the tree. In a FieldMask, every field path matches the
* specified field as well as all its sub-fields. For example, a field path
* "foo.bar" matches field "foo.bar" and also "foo.bar.baz", etc. When adding
* a field path to the tree, redundant sub-paths will be removed. That is,
* after adding "foo.bar" to the tree, "foo.bar.baz" will be removed if it
* exists, which will turn the tree node for "foo.bar" to a leaf node.
* Likewise, if the field path to add is a sub-path of an existing leaf node,
* nothing will be changed in the tree.
*/
public
FieldMaskTree
addFieldPath
(
String
path
)
{
String
[]
parts
=
path
.
split
(
FIELD_PATH_SEPARATOR_REGEX
);
if
(
parts
.
length
==
0
)
{
return
this
;
}
Node
node
=
root
;
boolean
createNewBranch
=
false
;
// Find the matching node in the tree.
for
(
String
part
:
parts
)
{
// Check whether the path matches an existing leaf node.
if
(!
createNewBranch
&&
node
!=
root
&&
node
.
children
.
isEmpty
())
{
// The path to add is a sub-path of an existing leaf node.
return
this
;
}
if
(
node
.
children
.
containsKey
(
part
))
{
node
=
node
.
children
.
get
(
part
);
}
else
{
createNewBranch
=
true
;
Node
tmp
=
new
Node
();
node
.
children
.
put
(
part
,
tmp
);
node
=
tmp
;
}
}
// Turn the matching node into a leaf node (i.e., remove sub-paths).
node
.
children
.
clear
();
return
this
;
}
/**
* Merges all field paths in a FieldMask into this tree.
*/
public
FieldMaskTree
mergeFromFieldMask
(
FieldMask
mask
)
{
for
(
String
path
:
mask
.
getPathsList
())
{
addFieldPath
(
path
);
}
return
this
;
}
/** Converts this tree to a FieldMask. */
public
FieldMask
toFieldMask
()
{
if
(
root
.
children
.
isEmpty
())
{
return
FieldMask
.
getDefaultInstance
();
}
List
<
String
>
paths
=
new
ArrayList
<
String
>();
getFieldPaths
(
root
,
""
,
paths
);
return
FieldMask
.
newBuilder
().
addAllPaths
(
paths
).
build
();
}
/** Gathers all field paths in a sub-tree. */
private
void
getFieldPaths
(
Node
node
,
String
path
,
List
<
String
>
paths
)
{
if
(
node
.
children
.
isEmpty
())
{
paths
.
add
(
path
);
return
;
}
for
(
Entry
<
String
,
Node
>
entry
:
node
.
children
.
entrySet
())
{
String
childPath
=
path
.
isEmpty
()
?
entry
.
getKey
()
:
path
+
"."
+
entry
.
getKey
();
getFieldPaths
(
entry
.
getValue
(),
childPath
,
paths
);
}
}
/**
* Adds the intersection of this tree with the given {@code path} to
* {@code output}.
*/
public
void
intersectFieldPath
(
String
path
,
FieldMaskTree
output
)
{
if
(
root
.
children
.
isEmpty
())
{
return
;
}
String
[]
parts
=
path
.
split
(
FIELD_PATH_SEPARATOR_REGEX
);
if
(
parts
.
length
==
0
)
{
return
;
}
Node
node
=
root
;
for
(
String
part
:
parts
)
{
if
(
node
!=
root
&&
node
.
children
.
isEmpty
())
{
// The given path is a sub-path of an existing leaf node in the tree.
output
.
addFieldPath
(
path
);
return
;
}
if
(
node
.
children
.
containsKey
(
part
))
{
node
=
node
.
children
.
get
(
part
);
}
else
{
return
;
}
}
// We found a matching node for the path. All leaf children of this matching
// node is in the intersection.
List
<
String
>
paths
=
new
ArrayList
<
String
>();
getFieldPaths
(
node
,
path
,
paths
);
for
(
String
value
:
paths
)
{
output
.
addFieldPath
(
value
);
}
}
/**
* Merges all fields specified by this FieldMaskTree from {@code source} to
* {@code destination}.
*/
public
void
merge
(
Message
source
,
Message
.
Builder
destination
,
FieldMaskUtil
.
MergeOptions
options
)
{
if
(
source
.
getDescriptorForType
()
!=
destination
.
getDescriptorForType
())
{
throw
new
IllegalArgumentException
(
"Cannot merge messages of different types."
);
}
if
(
root
.
children
.
isEmpty
())
{
return
;
}
merge
(
root
,
""
,
source
,
destination
,
options
);
}
/** Merges all fields specified by a sub-tree from {@code source} to
* {@code destination}.
*/
private
void
merge
(
Node
node
,
String
path
,
Message
source
,
Message
.
Builder
destination
,
FieldMaskUtil
.
MergeOptions
options
)
{
assert
source
.
getDescriptorForType
()
==
destination
.
getDescriptorForType
();
Descriptor
descriptor
=
source
.
getDescriptorForType
();
for
(
Entry
<
String
,
Node
>
entry
:
node
.
children
.
entrySet
())
{
FieldDescriptor
field
=
descriptor
.
findFieldByName
(
entry
.
getKey
());
if
(
field
==
null
)
{
logger
.
warning
(
"Cannot find field \""
+
entry
.
getKey
()
+
"\" in message type "
+
descriptor
.
getFullName
());
continue
;
}
if
(!
entry
.
getValue
().
children
.
isEmpty
())
{
if
(
field
.
isRepeated
()
||
field
.
getJavaType
()
!=
FieldDescriptor
.
JavaType
.
MESSAGE
)
{
logger
.
warning
(
"Field \""
+
field
.
getFullName
()
+
"\" is not a "
+
"singluar message field and cannot have sub-fields."
);
continue
;
}
String
childPath
=
path
.
isEmpty
()
?
entry
.
getKey
()
:
path
+
"."
+
entry
.
getKey
();
merge
(
entry
.
getValue
(),
childPath
,
(
Message
)
source
.
getField
(
field
),
destination
.
getFieldBuilder
(
field
),
options
);
continue
;
}
if
(
field
.
isRepeated
())
{
if
(
options
.
replaceRepeatedFields
())
{
destination
.
setField
(
field
,
source
.
getField
(
field
));
}
else
{
for
(
Object
element
:
(
List
)
source
.
getField
(
field
))
{
destination
.
addRepeatedField
(
field
,
element
);
}
}
}
else
{
if
(
field
.
getJavaType
()
==
FieldDescriptor
.
JavaType
.
MESSAGE
)
{
if
(
options
.
replaceMessageFields
())
{
destination
.
setField
(
field
,
source
.
getField
(
field
));
}
else
{
destination
.
getFieldBuilder
(
field
).
mergeFrom
(
(
Message
)
source
.
getField
(
field
));
}
}
else
{
destination
.
setField
(
field
,
source
.
getField
(
field
));
}
}
}
}
}
java/util/src/main/java/com/google/protobuf/util/FieldMaskUtil.java
0 → 100644
View file @
fbb3ef28
// 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
.
util
;
import
com.google.protobuf.Descriptors.Descriptor
;
import
com.google.protobuf.Descriptors.FieldDescriptor
;
import
com.google.protobuf.FieldMask
;
import
com.google.protobuf.Internal
;
import
com.google.protobuf.Message
;
import
java.util.Arrays
;
import
java.util.List
;
/**
* Utility helper functions to work with {@link com.google.protobuf.FieldMask}.
*/
public
class
FieldMaskUtil
{
private
static
final
String
FIELD_PATH_SEPARATOR
=
","
;
private
static
final
String
FIELD_PATH_SEPARATOR_REGEX
=
","
;
private
static
final
String
FIELD_SEPARATOR_REGEX
=
"\\."
;
private
FieldMaskUtil
()
{}
/**
* Converts a FieldMask to a string.
*/
public
static
String
toString
(
FieldMask
fieldMask
)
{
StringBuilder
result
=
new
StringBuilder
();
boolean
first
=
true
;
for
(
String
value
:
fieldMask
.
getPathsList
())
{
if
(
value
.
isEmpty
())
{
// Ignore empty paths.
continue
;
}
if
(
first
)
{
first
=
false
;
}
else
{
result
.
append
(
FIELD_PATH_SEPARATOR
);
}
result
.
append
(
value
);
}
return
result
.
toString
();
}
/**
* Parses from a string to a FieldMask.
*/
public
static
FieldMask
fromString
(
String
value
)
{
return
fromStringList
(
null
,
Arrays
.
asList
(
value
.
split
(
FIELD_PATH_SEPARATOR_REGEX
)));
}
/**
* Parses from a string to a FieldMask and validates all field paths.
*
* @throws IllegalArgumentException if any of the field path is invalid.
*/
public
static
FieldMask
fromString
(
Class
<?
extends
Message
>
type
,
String
value
)
throws
IllegalArgumentException
{
return
fromStringList
(
type
,
Arrays
.
asList
(
value
.
split
(
FIELD_PATH_SEPARATOR_REGEX
)));
}
/**
* Constructs a FieldMask for a list of field paths in a certain type.
*
* @throws IllegalArgumentException if any of the field path is not valid.
*/
public
static
FieldMask
fromStringList
(
Class
<?
extends
Message
>
type
,
List
<
String
>
paths
)
throws
IllegalArgumentException
{
FieldMask
.
Builder
builder
=
FieldMask
.
newBuilder
();
for
(
String
path
:
paths
)
{
if
(
path
.
isEmpty
())
{
// Ignore empty field paths.
continue
;
}
if
(
type
!=
null
&&
!
isValid
(
type
,
path
))
{
throw
new
IllegalArgumentException
(
path
+
" is not a valid path for "
+
type
);
}
builder
.
addPaths
(
path
);
}
return
builder
.
build
();
}
/**
* Checks whether a given field path is valid.
*/
public
static
boolean
isValid
(
Class
<?
extends
Message
>
type
,
String
path
)
{
String
[]
parts
=
path
.
split
(
FIELD_SEPARATOR_REGEX
);
if
(
parts
.
length
==
0
)
{
return
false
;
}
Descriptor
descriptor
=
Internal
.
getDefaultInstance
(
type
).
getDescriptorForType
();
for
(
String
name
:
parts
)
{
if
(
descriptor
==
null
)
{
return
false
;
}
FieldDescriptor
field
=
descriptor
.
findFieldByName
(
name
);
if
(
field
==
null
)
{
return
false
;
}
if
(!
field
.
isRepeated
()
&&
field
.
getJavaType
()
==
FieldDescriptor
.
JavaType
.
MESSAGE
)
{
descriptor
=
field
.
getMessageType
();
}
else
{
descriptor
=
null
;
}
}
return
true
;
}
/**
* Converts a FieldMask to its canonical form. In the canonical form of a
* FieldMask, all field paths are sorted alphabetically and redundant field
* paths are moved.
*/
public
static
FieldMask
normalize
(
FieldMask
mask
)
{
return
new
FieldMaskTree
(
mask
).
toFieldMask
();
}
/**
* Creates an union of two FieldMasks.
*/
public
static
FieldMask
union
(
FieldMask
mask1
,
FieldMask
mask2
)
{
return
new
FieldMaskTree
(
mask1
).
mergeFromFieldMask
(
mask2
).
toFieldMask
();
}
/**
* Calculates the intersection of two FieldMasks.
*/
public
static
FieldMask
intersection
(
FieldMask
mask1
,
FieldMask
mask2
)
{
FieldMaskTree
tree
=
new
FieldMaskTree
(
mask1
);
FieldMaskTree
result
=
new
FieldMaskTree
();
for
(
String
path
:
mask2
.
getPathsList
())
{
tree
.
intersectFieldPath
(
path
,
result
);
}
return
result
.
toFieldMask
();
}
/**
* Options to customize merging behavior.
*/
public
static
class
MergeOptions
{
private
boolean
replaceMessageFields
=
false
;
private
boolean
replaceRepeatedFields
=
false
;
/**
* Whether to replace message fields (i.e., discard existing content in
* destination message fields) when merging.
* Default behavior is to merge the source message field into the
* destination message field.
*/
public
boolean
replaceMessageFields
()
{
return
replaceMessageFields
;
}
/**
* Whether to replace repeated fields (i.e., discard existing content in
* destination repeated fields) when merging.
* Default behavior is to append elements from source repeated field to the
* destination repeated field.
*/
public
boolean
replaceRepeatedFields
()
{
return
replaceRepeatedFields
;
}
public
void
setReplaceMessageFields
(
boolean
value
)
{
replaceMessageFields
=
value
;
}
public
void
setReplaceRepeatedFields
(
boolean
value
)
{
replaceRepeatedFields
=
value
;
}
}
/**
* Merges fields specified by a FieldMask from one message to another.
*/
public
static
void
merge
(
FieldMask
mask
,
Message
source
,
Message
.
Builder
destination
,
MergeOptions
options
)
{
new
FieldMaskTree
(
mask
).
merge
(
source
,
destination
,
options
);
}
/**
* Merges fields specified by a FieldMask from one message to another.
*/
public
static
void
merge
(
FieldMask
mask
,
Message
source
,
Message
.
Builder
destination
)
{
merge
(
mask
,
source
,
destination
,
new
MergeOptions
());
}
}
java/util/src/main/java/com/google/protobuf/util/JsonFormat.java
0 → 100644
View file @
fbb3ef28
// 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
.
util
;
import
com.google.common.io.BaseEncoding
;
import
com.google.gson.Gson
;
import
com.google.gson.JsonArray
;
import
com.google.gson.JsonElement
;
import
com.google.gson.JsonNull
;
import
com.google.gson.JsonObject
;
import
com.google.gson.JsonParser
;
import
com.google.gson.JsonPrimitive
;
import
com.google.gson.stream.JsonReader
;
import
com.google.protobuf.Any
;
import
com.google.protobuf.BoolValue
;
import
com.google.protobuf.ByteString
;
import
com.google.protobuf.BytesValue
;
import
com.google.protobuf.Descriptors.Descriptor
;
import
com.google.protobuf.Descriptors.EnumDescriptor
;
import
com.google.protobuf.Descriptors.EnumValueDescriptor
;
import
com.google.protobuf.Descriptors.FieldDescriptor
;
import
com.google.protobuf.Descriptors.FileDescriptor
;
import
com.google.protobuf.DoubleValue
;
import
com.google.protobuf.Duration
;
import
com.google.protobuf.DynamicMessage
;
import
com.google.protobuf.FieldMask
;
import
com.google.protobuf.FloatValue
;
import
com.google.protobuf.Int32Value
;
import
com.google.protobuf.Int64Value
;
import
com.google.protobuf.InvalidProtocolBufferException
;
import
com.google.protobuf.ListValue
;
import
com.google.protobuf.Message
;
import
com.google.protobuf.MessageOrBuilder
;
import
com.google.protobuf.StringValue
;
import
com.google.protobuf.Struct
;
import
com.google.protobuf.Timestamp
;
import
com.google.protobuf.UInt32Value
;
import
com.google.protobuf.UInt64Value
;
import
com.google.protobuf.Value
;
import
java.io.IOException
;
import
java.io.Reader
;
import
java.io.StringReader
;
import
java.math.BigDecimal
;
import
java.math.BigInteger
;
import
java.text.ParseException
;
import
java.util.Collections
;
import
java.util.HashMap
;
import
java.util.HashSet
;
import
java.util.List
;
import
java.util.Map
;
import
java.util.Set
;
import
java.util.logging.Logger
;
/**
* Utility classes to convert protobuf messages to/from JSON format. The JSON
* format follows Proto3 JSON specification and only proto3 features are
* supported. Proto2 only features (e.g., extensions and unknown fields) will
* be discarded in the conversion. That is, when converting proto2 messages
* to JSON format, extensions and unknown fields will be treated as if they
* do not exist. This applies to proto2 messages embedded in proto3 messages
* as well.
*/
public
class
JsonFormat
{
private
static
final
Logger
logger
=
Logger
.
getLogger
(
JsonFormat
.
class
.
getName
());
private
JsonFormat
()
{}
/**
* Creates a {@link Printer} with default configurations.
*/
public
static
Printer
printer
()
{
return
new
Printer
(
TypeRegistry
.
getEmptyTypeRegistry
());
}
/**
* A Printer converts protobuf message to JSON format.
*/
public
static
class
Printer
{
private
final
TypeRegistry
registry
;
private
Printer
(
TypeRegistry
registry
)
{
this
.
registry
=
registry
;
}
/**
* Creates a new {@link Printer} using the given registry. The new Printer
* clones all other configurations from the current {@link Printer}.
*
* @throws IllegalArgumentException if a registry is already set.
*/
public
Printer
usingTypeRegistry
(
TypeRegistry
registry
)
{
if
(
this
.
registry
!=
TypeRegistry
.
getEmptyTypeRegistry
())
{
throw
new
IllegalArgumentException
(
"Only one registry is allowed."
);
}
return
new
Printer
(
registry
);
}
/**
* Converts a protobuf message to JSON format.
*
* @throws InvalidProtocolBufferException if the message contains Any types
* that can't be resolved.
* @throws IOException if writing to the output fails.
*/
public
void
appendTo
(
MessageOrBuilder
message
,
Appendable
output
)
throws
IOException
{
// TODO(xiaofeng): Investigate the allocation overhead and optimize for
// mobile.
new
PrinterImpl
(
registry
,
output
).
print
(
message
);
}
/**
* Converts a protobuf message to JSON format. Throws exceptions if there
* are unknown Any types in the message.
*/
public
String
print
(
MessageOrBuilder
message
)
throws
InvalidProtocolBufferException
{
try
{
StringBuilder
builder
=
new
StringBuilder
();
appendTo
(
message
,
builder
);
return
builder
.
toString
();
}
catch
(
InvalidProtocolBufferException
e
)
{
throw
e
;
}
catch
(
IOException
e
)
{
// Unexpected IOException.
throw
new
IllegalStateException
(
e
);
}
}
}
/**
* Creates a {@link Parser} with default configuration.
*/
public
static
Parser
parser
()
{
return
new
Parser
(
TypeRegistry
.
getEmptyTypeRegistry
());
}
/**
* A Parser parses JSON to protobuf message.
*/
public
static
class
Parser
{
private
final
TypeRegistry
registry
;
private
Parser
(
TypeRegistry
registry
)
{
this
.
registry
=
registry
;
}
/**
* Creates a new {@link Parser} using the given registry. The new Parser
* clones all other configurations from this Parser.
*
* @throws IllegalArgumentException if a registry is already set.
*/
public
Parser
usingTypeRegistry
(
TypeRegistry
registry
)
{
if
(
this
.
registry
!=
TypeRegistry
.
getEmptyTypeRegistry
())
{
throw
new
IllegalArgumentException
(
"Only one registry is allowed."
);
}
return
new
Parser
(
registry
);
}
/**
* Parses from JSON into a protobuf message.
*
* @throws InvalidProtocolBufferException if the input is not valid JSON
* format or there are unknown fields in the input.
*/
public
void
merge
(
String
json
,
Message
.
Builder
builder
)
throws
InvalidProtocolBufferException
{
// TODO(xiaofeng): Investigate the allocation overhead and optimize for
// mobile.
new
ParserImpl
(
registry
).
merge
(
json
,
builder
);
}
/**
* Parses from JSON into a protobuf message.
*
* @throws InvalidProtocolBufferException if the input is not valid JSON
* format or there are unknown fields in the input.
* @throws IOException if reading from the input throws.
*/
public
void
merge
(
Reader
json
,
Message
.
Builder
builder
)
throws
IOException
{
// TODO(xiaofeng): Investigate the allocation overhead and optimize for
// mobile.
new
ParserImpl
(
registry
).
merge
(
json
,
builder
);
}
}
/**
* A TypeRegistry is used to resolve Any messages in the JSON conversion.
* You must provide a TypeRegistry containing all message types used in
* Any message fields, or the JSON conversion will fail because data
* in Any message fields is unrecognizable. You don't need to supply a
* TypeRegistry if you don't use Any message fields.
*/
public
static
class
TypeRegistry
{
private
static
class
EmptyTypeRegistryHolder
{
private
static
final
TypeRegistry
EMPTY
=
new
TypeRegistry
(
Collections
.<
String
,
Descriptor
>
emptyMap
());
}
public
static
TypeRegistry
getEmptyTypeRegistry
()
{
return
EmptyTypeRegistryHolder
.
EMPTY
;
}
public
static
Builder
newBuilder
()
{
return
new
Builder
();
}
/**
* Find a type by its full name. Returns null if it cannot be found in
* this {@link TypeRegistry}.
*/
public
Descriptor
find
(
String
name
)
{
return
types
.
get
(
name
);
}
private
final
Map
<
String
,
Descriptor
>
types
;
private
TypeRegistry
(
Map
<
String
,
Descriptor
>
types
)
{
this
.
types
=
types
;
}
/**
* A Builder is used to build {@link TypeRegistry}.
*/
public
static
class
Builder
{
private
Builder
()
{}
/**
* Adds a message type and all types defined in the same .proto file as
* well as all transitively imported .proto files to this {@link Builder}.
*/
public
Builder
add
(
Descriptor
messageType
)
{
if
(
types
==
null
)
{
throw
new
IllegalStateException
(
"A TypeRegistry.Builer can only be used once."
);
}
addFile
(
messageType
.
getFile
());
return
this
;
}
/**
* Adds message types and all types defined in the same .proto file as
* well as all transitively imported .proto files to this {@link Builder}.
*/
public
Builder
add
(
Iterable
<
Descriptor
>
messageTypes
)
{
if
(
types
==
null
)
{
throw
new
IllegalStateException
(
"A TypeRegistry.Builer can only be used once."
);
}
for
(
Descriptor
type
:
messageTypes
)
{
addFile
(
type
.
getFile
());
}
return
this
;
}
/**
* Builds a {@link TypeRegistry}. This method can only be called once for
* one Builder.
*/
public
TypeRegistry
build
()
{
TypeRegistry
result
=
new
TypeRegistry
(
types
);
// Make sure the built {@link TypeRegistry} is immutable.
types
=
null
;
return
result
;
}
private
void
addFile
(
FileDescriptor
file
)
{
// Skip the file if it's already added.
if
(
files
.
contains
(
file
.
getName
()))
{
return
;
}
for
(
FileDescriptor
dependency
:
file
.
getDependencies
())
{
addFile
(
dependency
);
}
for
(
Descriptor
message
:
file
.
getMessageTypes
())
{
addMessage
(
message
);
}
}
private
void
addMessage
(
Descriptor
message
)
{
for
(
Descriptor
nestedType
:
message
.
getNestedTypes
())
{
addMessage
(
nestedType
);
}
if
(
types
.
containsKey
(
message
.
getFullName
()))
{
logger
.
warning
(
"Type "
+
message
.
getFullName
()
+
" is added multiple times."
);
return
;
}
types
.
put
(
message
.
getFullName
(),
message
);
}
private
final
Set
<
String
>
files
=
new
HashSet
<
String
>();
private
Map
<
String
,
Descriptor
>
types
=
new
HashMap
<
String
,
Descriptor
>();
}
}
/**
* A TextGenerator adds indentation when writing formatted text.
*/
private
static
final
class
TextGenerator
{
private
final
Appendable
output
;
private
final
StringBuilder
indent
=
new
StringBuilder
();
private
boolean
atStartOfLine
=
true
;
private
TextGenerator
(
final
Appendable
output
)
{
this
.
output
=
output
;
}
/**
* Indent text by two spaces. After calling Indent(), two spaces will be
* inserted at the beginning of each line of text. Indent() may be called
* multiple times to produce deeper indents.
*/
public
void
indent
()
{
indent
.
append
(
" "
);
}
/**
* Reduces the current indent level by two spaces, or crashes if the indent
* level is zero.
*/
public
void
outdent
()
{
final
int
length
=
indent
.
length
();
if
(
length
<
2
)
{
throw
new
IllegalArgumentException
(
" Outdent() without matching Indent()."
);
}
indent
.
delete
(
length
-
2
,
length
);
}
/**
* Print text to the output stream.
*/
public
void
print
(
final
CharSequence
text
)
throws
IOException
{
final
int
size
=
text
.
length
();
int
pos
=
0
;
for
(
int
i
=
0
;
i
<
size
;
i
++)
{
if
(
text
.
charAt
(
i
)
==
'\n'
)
{
write
(
text
.
subSequence
(
pos
,
i
+
1
));
pos
=
i
+
1
;
atStartOfLine
=
true
;
}
}
write
(
text
.
subSequence
(
pos
,
size
));
}
private
void
write
(
final
CharSequence
data
)
throws
IOException
{
if
(
data
.
length
()
==
0
)
{
return
;
}
if
(
atStartOfLine
)
{
atStartOfLine
=
false
;
output
.
append
(
indent
);
}
output
.
append
(
data
);
}
}
/**
* A Printer converts protobuf messages to JSON format.
*/
private
static
final
class
PrinterImpl
{
private
final
TypeRegistry
registry
;
private
final
TextGenerator
generator
;
// We use Gson to help handle string escapes.
private
final
Gson
gson
;
private
static
class
GsonHolder
{
private
static
final
Gson
DEFAULT_GSON
=
new
Gson
();
}
PrinterImpl
(
TypeRegistry
registry
,
Appendable
jsonOutput
)
{
this
.
registry
=
registry
;
this
.
generator
=
new
TextGenerator
(
jsonOutput
);
this
.
gson
=
GsonHolder
.
DEFAULT_GSON
;
}
void
print
(
MessageOrBuilder
message
)
throws
IOException
{
WellKnownTypePrinter
specialPrinter
=
wellKnownTypePrinters
.
get
(
message
.
getDescriptorForType
().
getFullName
());
if
(
specialPrinter
!=
null
)
{
specialPrinter
.
print
(
this
,
message
);
return
;
}
print
(
message
,
null
);
}
private
interface
WellKnownTypePrinter
{
void
print
(
PrinterImpl
printer
,
MessageOrBuilder
message
)
throws
IOException
;
}
private
static
final
Map
<
String
,
WellKnownTypePrinter
>
wellKnownTypePrinters
=
buildWellKnownTypePrinters
();
private
static
Map
<
String
,
WellKnownTypePrinter
>
buildWellKnownTypePrinters
()
{
Map
<
String
,
WellKnownTypePrinter
>
printers
=
new
HashMap
<
String
,
WellKnownTypePrinter
>();
// Special-case Any.
printers
.
put
(
Any
.
getDescriptor
().
getFullName
(),
new
WellKnownTypePrinter
()
{
@Override
public
void
print
(
PrinterImpl
printer
,
MessageOrBuilder
message
)
throws
IOException
{
printer
.
printAny
(
message
);
}
});
// Special-case wrapper types.
WellKnownTypePrinter
wrappersPrinter
=
new
WellKnownTypePrinter
()
{
@Override
public
void
print
(
PrinterImpl
printer
,
MessageOrBuilder
message
)
throws
IOException
{
printer
.
printWrapper
(
message
);
}
};
printers
.
put
(
BoolValue
.
getDescriptor
().
getFullName
(),
wrappersPrinter
);
printers
.
put
(
Int32Value
.
getDescriptor
().
getFullName
(),
wrappersPrinter
);
printers
.
put
(
UInt32Value
.
getDescriptor
().
getFullName
(),
wrappersPrinter
);
printers
.
put
(
Int64Value
.
getDescriptor
().
getFullName
(),
wrappersPrinter
);
printers
.
put
(
UInt64Value
.
getDescriptor
().
getFullName
(),
wrappersPrinter
);
printers
.
put
(
StringValue
.
getDescriptor
().
getFullName
(),
wrappersPrinter
);
printers
.
put
(
BytesValue
.
getDescriptor
().
getFullName
(),
wrappersPrinter
);
printers
.
put
(
FloatValue
.
getDescriptor
().
getFullName
(),
wrappersPrinter
);
printers
.
put
(
DoubleValue
.
getDescriptor
().
getFullName
(),
wrappersPrinter
);
// Special-case Timestamp.
printers
.
put
(
Timestamp
.
getDescriptor
().
getFullName
(),
new
WellKnownTypePrinter
()
{
@Override
public
void
print
(
PrinterImpl
printer
,
MessageOrBuilder
message
)
throws
IOException
{
printer
.
printTimestamp
(
message
);
}
});
// Special-case Duration.
printers
.
put
(
Duration
.
getDescriptor
().
getFullName
(),
new
WellKnownTypePrinter
()
{
@Override
public
void
print
(
PrinterImpl
printer
,
MessageOrBuilder
message
)
throws
IOException
{
printer
.
printDuration
(
message
);
}
});
// Special-case FieldMask.
printers
.
put
(
FieldMask
.
getDescriptor
().
getFullName
(),
new
WellKnownTypePrinter
()
{
@Override
public
void
print
(
PrinterImpl
printer
,
MessageOrBuilder
message
)
throws
IOException
{
printer
.
printFieldMask
(
message
);
}
});
// Special-case Struct.
printers
.
put
(
Struct
.
getDescriptor
().
getFullName
(),
new
WellKnownTypePrinter
()
{
@Override
public
void
print
(
PrinterImpl
printer
,
MessageOrBuilder
message
)
throws
IOException
{
printer
.
printStruct
(
message
);
}
});
// Special-case Value.
printers
.
put
(
Value
.
getDescriptor
().
getFullName
(),
new
WellKnownTypePrinter
()
{
@Override
public
void
print
(
PrinterImpl
printer
,
MessageOrBuilder
message
)
throws
IOException
{
printer
.
printValue
(
message
);
}
});
// Special-case ListValue.
printers
.
put
(
ListValue
.
getDescriptor
().
getFullName
(),
new
WellKnownTypePrinter
()
{
@Override
public
void
print
(
PrinterImpl
printer
,
MessageOrBuilder
message
)
throws
IOException
{
printer
.
printListValue
(
message
);
}
});
return
printers
;
}
/** Prints google.protobuf.Any */
private
void
printAny
(
MessageOrBuilder
message
)
throws
IOException
{
Descriptor
descriptor
=
message
.
getDescriptorForType
();
FieldDescriptor
typeUrlField
=
descriptor
.
findFieldByName
(
"type_url"
);
FieldDescriptor
valueField
=
descriptor
.
findFieldByName
(
"value"
);
// Validates type of the message. Note that we can't just cast the message
// to com.google.protobuf.Any because it might be a DynamicMessage.
if
(
typeUrlField
==
null
||
valueField
==
null
||
typeUrlField
.
getType
()
!=
FieldDescriptor
.
Type
.
STRING
||
valueField
.
getType
()
!=
FieldDescriptor
.
Type
.
BYTES
)
{
throw
new
InvalidProtocolBufferException
(
"Invalid Any type."
);
}
String
typeUrl
=
(
String
)
message
.
getField
(
typeUrlField
);
String
typeName
=
getTypeName
(
typeUrl
);
Descriptor
type
=
registry
.
find
(
typeName
);
if
(
type
==
null
)
{
throw
new
InvalidProtocolBufferException
(
"Cannot find type for url: "
+
typeUrl
);
}
ByteString
content
=
(
ByteString
)
message
.
getField
(
valueField
);
Message
contentMessage
=
DynamicMessage
.
getDefaultInstance
(
type
)
.
getParserForType
().
parseFrom
(
content
);
WellKnownTypePrinter
printer
=
wellKnownTypePrinters
.
get
(
typeName
);
if
(
printer
!=
null
)
{
// If the type is one of the well-known types, we use a special
// formatting.
generator
.
print
(
"{\n"
);
generator
.
indent
();
generator
.
print
(
"\"@type\": "
+
gson
.
toJson
(
typeUrl
)
+
",\n"
);
generator
.
print
(
"\"value\": "
);
printer
.
print
(
this
,
contentMessage
);
generator
.
print
(
"\n"
);
generator
.
outdent
();
generator
.
print
(
"}"
);
}
else
{
// Print the content message instead (with a "@type" field added).
print
(
contentMessage
,
typeUrl
);
}
}
/** Prints wrapper types (e.g., google.protobuf.Int32Value) */
private
void
printWrapper
(
MessageOrBuilder
message
)
throws
IOException
{
Descriptor
descriptor
=
message
.
getDescriptorForType
();
FieldDescriptor
valueField
=
descriptor
.
findFieldByName
(
"value"
);
if
(
valueField
==
null
)
{
throw
new
InvalidProtocolBufferException
(
"Invalid Wrapper type."
);
}
// When formatting wrapper types, we just print its value field instead of
// the whole message.
printSingleFieldValue
(
valueField
,
message
.
getField
(
valueField
));
}
private
ByteString
toByteString
(
MessageOrBuilder
message
)
{
if
(
message
instanceof
Message
)
{
return
((
Message
)
message
).
toByteString
();
}
else
{
return
((
Message
.
Builder
)
message
).
build
().
toByteString
();
}
}
/** Prints google.protobuf.Timestamp */
private
void
printTimestamp
(
MessageOrBuilder
message
)
throws
IOException
{
Timestamp
value
=
Timestamp
.
parseFrom
(
toByteString
(
message
));
generator
.
print
(
"\""
+
TimeUtil
.
toString
(
value
)
+
"\""
);
}
/** Prints google.protobuf.Duration */
private
void
printDuration
(
MessageOrBuilder
message
)
throws
IOException
{
Duration
value
=
Duration
.
parseFrom
(
toByteString
(
message
));
generator
.
print
(
"\""
+
TimeUtil
.
toString
(
value
)
+
"\""
);
}
/** Prints google.protobuf.FieldMask */
private
void
printFieldMask
(
MessageOrBuilder
message
)
throws
IOException
{
FieldMask
value
=
FieldMask
.
parseFrom
(
toByteString
(
message
));
generator
.
print
(
"\""
+
FieldMaskUtil
.
toString
(
value
)
+
"\""
);
}
/** Prints google.protobuf.Struct */
private
void
printStruct
(
MessageOrBuilder
message
)
throws
IOException
{
Descriptor
descriptor
=
message
.
getDescriptorForType
();
FieldDescriptor
field
=
descriptor
.
findFieldByName
(
"fields"
);
if
(
field
==
null
)
{
throw
new
InvalidProtocolBufferException
(
"Invalid Struct type."
);
}
// Struct is formatted as a map object.
printMapFieldValue
(
field
,
message
.
getField
(
field
));
}
/** Prints google.protobuf.Value */
private
void
printValue
(
MessageOrBuilder
message
)
throws
IOException
{
// For a Value message, only the value of the field is formatted.
Map
<
FieldDescriptor
,
Object
>
fields
=
message
.
getAllFields
();
if
(
fields
.
isEmpty
())
{
// No value set.
generator
.
print
(
"null"
);
return
;
}
// A Value message can only have at most one field set (it only contains
// an oneof).
if
(
fields
.
size
()
!=
1
)
{
throw
new
InvalidProtocolBufferException
(
"Invalid Value type."
);
}
for
(
Map
.
Entry
<
FieldDescriptor
,
Object
>
entry
:
fields
.
entrySet
())
{
printSingleFieldValue
(
entry
.
getKey
(),
entry
.
getValue
());
}
}
/** Prints google.protobuf.ListValue */
private
void
printListValue
(
MessageOrBuilder
message
)
throws
IOException
{
Descriptor
descriptor
=
message
.
getDescriptorForType
();
FieldDescriptor
field
=
descriptor
.
findFieldByName
(
"values"
);
if
(
field
==
null
)
{
throw
new
InvalidProtocolBufferException
(
"Invalid ListValue type."
);
}
printRepeatedFieldValue
(
field
,
message
.
getField
(
field
));
}
/** Prints a regular message with an optional type URL. */
private
void
print
(
MessageOrBuilder
message
,
String
typeUrl
)
throws
IOException
{
generator
.
print
(
"{\n"
);
generator
.
indent
();
boolean
printedField
=
false
;
if
(
typeUrl
!=
null
)
{
generator
.
print
(
"\"@type\": "
+
gson
.
toJson
(
typeUrl
));
printedField
=
true
;
}
for
(
Map
.
Entry
<
FieldDescriptor
,
Object
>
field
:
message
.
getAllFields
().
entrySet
())
{
// Skip unknown enum fields.
if
(
field
.
getValue
()
instanceof
EnumValueDescriptor
&&
((
EnumValueDescriptor
)
field
.
getValue
()).
getIndex
()
==
-
1
)
{
continue
;
}
if
(
printedField
)
{
// Add line-endings for the previous field.
generator
.
print
(
",\n"
);
}
else
{
printedField
=
true
;
}
printField
(
field
.
getKey
(),
field
.
getValue
());
}
// Add line-endings for the last field.
if
(
printedField
)
{
generator
.
print
(
"\n"
);
}
generator
.
outdent
();
generator
.
print
(
"}"
);
}
private
void
printField
(
FieldDescriptor
field
,
Object
value
)
throws
IOException
{
generator
.
print
(
"\""
+
fieldNameToCamelName
(
field
.
getName
())
+
"\": "
);
if
(
field
.
isMapField
())
{
printMapFieldValue
(
field
,
value
);
}
else
if
(
field
.
isRepeated
())
{
printRepeatedFieldValue
(
field
,
value
);
}
else
{
printSingleFieldValue
(
field
,
value
);
}
}
@SuppressWarnings
(
"rawtypes"
)
private
void
printRepeatedFieldValue
(
FieldDescriptor
field
,
Object
value
)
throws
IOException
{
generator
.
print
(
"["
);
boolean
printedElement
=
false
;
for
(
Object
element
:
(
List
)
value
)
{
// Skip unknown enum entries.
if
(
element
instanceof
EnumValueDescriptor
&&
((
EnumValueDescriptor
)
element
).
getIndex
()
==
-
1
)
{
continue
;
}
if
(
printedElement
)
{
generator
.
print
(
", "
);
}
else
{
printedElement
=
true
;
}
printSingleFieldValue
(
field
,
element
);
}
generator
.
print
(
"]"
);
}
@SuppressWarnings
(
"rawtypes"
)
private
void
printMapFieldValue
(
FieldDescriptor
field
,
Object
value
)
throws
IOException
{
Descriptor
type
=
field
.
getMessageType
();
FieldDescriptor
keyField
=
type
.
findFieldByName
(
"key"
);
FieldDescriptor
valueField
=
type
.
findFieldByName
(
"value"
);
if
(
keyField
==
null
||
valueField
==
null
)
{
throw
new
InvalidProtocolBufferException
(
"Invalid map field."
);
}
generator
.
print
(
"{\n"
);
generator
.
indent
();
boolean
printedElement
=
false
;
for
(
Object
element
:
(
List
)
value
)
{
Message
entry
=
(
Message
)
element
;
Object
entryKey
=
entry
.
getField
(
keyField
);
Object
entryValue
=
entry
.
getField
(
valueField
);
// Skip unknown enum entries.
if
(
entryValue
instanceof
EnumValueDescriptor
&&
((
EnumValueDescriptor
)
entryValue
).
getIndex
()
==
-
1
)
{
continue
;
}
if
(
printedElement
)
{
generator
.
print
(
",\n"
);
}
else
{
printedElement
=
true
;
}
// Key fields are always double-quoted.
printSingleFieldValue
(
keyField
,
entryKey
,
true
);
generator
.
print
(
": "
);
printSingleFieldValue
(
valueField
,
entryValue
);
}
if
(
printedElement
)
{
generator
.
print
(
"\n"
);
}
generator
.
outdent
();
generator
.
print
(
"}"
);
}
private
void
printSingleFieldValue
(
FieldDescriptor
field
,
Object
value
)
throws
IOException
{
printSingleFieldValue
(
field
,
value
,
false
);
}
/**
* Prints a field's value in JSON format.
*
* @param alwaysWithQuotes whether to always add double-quotes to primitive
* types.
*/
private
void
printSingleFieldValue
(
final
FieldDescriptor
field
,
final
Object
value
,
boolean
alwaysWithQuotes
)
throws
IOException
{
switch
(
field
.
getType
())
{
case
INT32:
case
SINT32:
case
SFIXED32:
if
(
alwaysWithQuotes
)
{
generator
.
print
(
"\""
);
}
generator
.
print
(((
Integer
)
value
).
toString
());
if
(
alwaysWithQuotes
)
{
generator
.
print
(
"\""
);
}
break
;
case
INT64:
case
SINT64:
case
SFIXED64:
generator
.
print
(
"\""
+
((
Long
)
value
).
toString
()
+
"\""
);
break
;
case
BOOL:
if
(
alwaysWithQuotes
)
{
generator
.
print
(
"\""
);
}
if
(((
Boolean
)
value
).
booleanValue
())
{
generator
.
print
(
"true"
);
}
else
{
generator
.
print
(
"false"
);
}
if
(
alwaysWithQuotes
)
{
generator
.
print
(
"\""
);
}
break
;
case
FLOAT:
Float
floatValue
=
(
Float
)
value
;
if
(
floatValue
.
isNaN
())
{
generator
.
print
(
"\"NaN\""
);
}
else
if
(
floatValue
.
isInfinite
())
{
if
(
floatValue
<
0
)
{
generator
.
print
(
"\"-Infinity\""
);
}
else
{
generator
.
print
(
"\"Infinity\""
);
}
}
else
{
if
(
alwaysWithQuotes
)
{
generator
.
print
(
"\""
);
}
generator
.
print
(
floatValue
.
toString
());
if
(
alwaysWithQuotes
)
{
generator
.
print
(
"\""
);
}
}
break
;
case
DOUBLE:
Double
doubleValue
=
(
Double
)
value
;
if
(
doubleValue
.
isNaN
())
{
generator
.
print
(
"\"NaN\""
);
}
else
if
(
doubleValue
.
isInfinite
())
{
if
(
doubleValue
<
0
)
{
generator
.
print
(
"\"-Infinity\""
);
}
else
{
generator
.
print
(
"\"Infinity\""
);
}
}
else
{
if
(
alwaysWithQuotes
)
{
generator
.
print
(
"\""
);
}
generator
.
print
(
doubleValue
.
toString
());
if
(
alwaysWithQuotes
)
{
generator
.
print
(
"\""
);
}
}
break
;
case
UINT32:
case
FIXED32:
if
(
alwaysWithQuotes
)
{
generator
.
print
(
"\""
);
}
generator
.
print
(
unsignedToString
((
Integer
)
value
));
if
(
alwaysWithQuotes
)
{
generator
.
print
(
"\""
);
}
break
;
case
UINT64:
case
FIXED64:
generator
.
print
(
"\""
+
unsignedToString
((
Long
)
value
)
+
"\""
);
break
;
case
STRING:
generator
.
print
(
gson
.
toJson
(
value
));
break
;
case
BYTES:
generator
.
print
(
"\""
);
generator
.
print
(
BaseEncoding
.
base64
().
encode
(((
ByteString
)
value
).
toByteArray
()));
generator
.
print
(
"\""
);
break
;
case
ENUM:
// Special-case google.protobuf.NullValue (it's an Enum).
if
(
field
.
getEnumType
().
getFullName
().
equals
(
"google.protobuf.NullValue"
))
{
// No matter what value it contains, we always print it as "null".
if
(
alwaysWithQuotes
)
{
generator
.
print
(
"\""
);
}
generator
.
print
(
"null"
);
if
(
alwaysWithQuotes
)
{
generator
.
print
(
"\""
);
}
}
else
{
generator
.
print
(
"\""
+
((
EnumValueDescriptor
)
value
).
getName
()
+
"\""
);
}
break
;
case
MESSAGE:
case
GROUP:
print
((
Message
)
value
);
break
;
}
}
}
/** Convert an unsigned 32-bit integer to a string. */
private
static
String
unsignedToString
(
final
int
value
)
{
if
(
value
>=
0
)
{
return
Integer
.
toString
(
value
);
}
else
{
return
Long
.
toString
(
value
&
0x00000000FFFFFFFF
L
);
}
}
/** Convert an unsigned 64-bit integer to a string. */
private
static
String
unsignedToString
(
final
long
value
)
{
if
(
value
>=
0
)
{
return
Long
.
toString
(
value
);
}
else
{
// Pull off the most-significant bit so that BigInteger doesn't think
// the number is negative, then set it again using setBit().
return
BigInteger
.
valueOf
(
value
&
Long
.
MAX_VALUE
)
.
setBit
(
Long
.
SIZE
-
1
).
toString
();
}
}
private
static
final
String
TYPE_URL_PREFIX
=
"type.googleapis.com"
;
private
static
String
getTypeName
(
String
typeUrl
)
throws
InvalidProtocolBufferException
{
String
[]
parts
=
typeUrl
.
split
(
"/"
);
if
(
parts
.
length
!=
2
||
!
parts
[
0
].
equals
(
TYPE_URL_PREFIX
))
{
throw
new
InvalidProtocolBufferException
(
"Invalid type url found: "
+
typeUrl
);
}
return
parts
[
1
];
}
private
static
String
fieldNameToCamelName
(
String
name
)
{
StringBuilder
result
=
new
StringBuilder
(
name
.
length
());
boolean
isNextUpperCase
=
false
;
for
(
int
i
=
0
;
i
<
name
.
length
();
i
++)
{
Character
ch
=
name
.
charAt
(
i
);
if
(
Character
.
isLowerCase
(
ch
))
{
if
(
isNextUpperCase
)
{
result
.
append
(
Character
.
toUpperCase
(
ch
));
}
else
{
result
.
append
(
ch
);
}
isNextUpperCase
=
false
;
}
else
if
(
Character
.
isUpperCase
(
ch
))
{
if
(
i
==
0
&&
!
isNextUpperCase
)
{
// Force first letter to lower-case unless explicitly told to
// capitalize it.
result
.
append
(
Character
.
toLowerCase
(
ch
));
}
else
{
// Capital letters after the first are left as-is.
result
.
append
(
ch
);
}
isNextUpperCase
=
false
;
}
else
if
(
Character
.
isDigit
(
ch
))
{
result
.
append
(
ch
);
isNextUpperCase
=
true
;
}
else
{
isNextUpperCase
=
true
;
}
}
return
result
.
toString
();
}
private
static
class
ParserImpl
{
private
final
TypeRegistry
registry
;
private
final
JsonParser
jsonParser
;
ParserImpl
(
TypeRegistry
registry
)
{
this
.
registry
=
registry
;
this
.
jsonParser
=
new
JsonParser
();
}
void
merge
(
Reader
json
,
Message
.
Builder
builder
)
throws
IOException
{
JsonReader
reader
=
new
JsonReader
(
json
);
reader
.
setLenient
(
false
);
merge
(
jsonParser
.
parse
(
reader
),
builder
);
}
void
merge
(
String
json
,
Message
.
Builder
builder
)
throws
InvalidProtocolBufferException
{
try
{
JsonReader
reader
=
new
JsonReader
(
new
StringReader
(
json
));
reader
.
setLenient
(
false
);
merge
(
jsonParser
.
parse
(
reader
),
builder
);
}
catch
(
InvalidProtocolBufferException
e
)
{
throw
e
;
}
catch
(
Exception
e
)
{
// We convert all exceptions from JSON parsing to our own exceptions.
throw
new
InvalidProtocolBufferException
(
e
.
getMessage
());
}
}
private
interface
WellKnownTypeParser
{
void
merge
(
ParserImpl
parser
,
JsonElement
json
,
Message
.
Builder
builder
)
throws
InvalidProtocolBufferException
;
}
private
static
final
Map
<
String
,
WellKnownTypeParser
>
wellKnownTypeParsers
=
buildWellKnownTypeParsers
();
private
static
Map
<
String
,
WellKnownTypeParser
>
buildWellKnownTypeParsers
()
{
Map
<
String
,
WellKnownTypeParser
>
parsers
=
new
HashMap
<
String
,
WellKnownTypeParser
>();
// Special-case Any.
parsers
.
put
(
Any
.
getDescriptor
().
getFullName
(),
new
WellKnownTypeParser
()
{
@Override
public
void
merge
(
ParserImpl
parser
,
JsonElement
json
,
Message
.
Builder
builder
)
throws
InvalidProtocolBufferException
{
parser
.
mergeAny
(
json
,
builder
);
}
});
// Special-case wrapper types.
WellKnownTypeParser
wrappersPrinter
=
new
WellKnownTypeParser
()
{
@Override
public
void
merge
(
ParserImpl
parser
,
JsonElement
json
,
Message
.
Builder
builder
)
throws
InvalidProtocolBufferException
{
parser
.
mergeWrapper
(
json
,
builder
);
}
};
parsers
.
put
(
BoolValue
.
getDescriptor
().
getFullName
(),
wrappersPrinter
);
parsers
.
put
(
Int32Value
.
getDescriptor
().
getFullName
(),
wrappersPrinter
);
parsers
.
put
(
UInt32Value
.
getDescriptor
().
getFullName
(),
wrappersPrinter
);
parsers
.
put
(
Int64Value
.
getDescriptor
().
getFullName
(),
wrappersPrinter
);
parsers
.
put
(
UInt64Value
.
getDescriptor
().
getFullName
(),
wrappersPrinter
);
parsers
.
put
(
StringValue
.
getDescriptor
().
getFullName
(),
wrappersPrinter
);
parsers
.
put
(
BytesValue
.
getDescriptor
().
getFullName
(),
wrappersPrinter
);
parsers
.
put
(
FloatValue
.
getDescriptor
().
getFullName
(),
wrappersPrinter
);
parsers
.
put
(
DoubleValue
.
getDescriptor
().
getFullName
(),
wrappersPrinter
);
// Special-case Timestamp.
parsers
.
put
(
Timestamp
.
getDescriptor
().
getFullName
(),
new
WellKnownTypeParser
()
{
@Override
public
void
merge
(
ParserImpl
parser
,
JsonElement
json
,
Message
.
Builder
builder
)
throws
InvalidProtocolBufferException
{
parser
.
mergeTimestamp
(
json
,
builder
);
}
});
// Special-case Duration.
parsers
.
put
(
Duration
.
getDescriptor
().
getFullName
(),
new
WellKnownTypeParser
()
{
@Override
public
void
merge
(
ParserImpl
parser
,
JsonElement
json
,
Message
.
Builder
builder
)
throws
InvalidProtocolBufferException
{
parser
.
mergeDuration
(
json
,
builder
);
}
});
// Special-case FieldMask.
parsers
.
put
(
FieldMask
.
getDescriptor
().
getFullName
(),
new
WellKnownTypeParser
()
{
@Override
public
void
merge
(
ParserImpl
parser
,
JsonElement
json
,
Message
.
Builder
builder
)
throws
InvalidProtocolBufferException
{
parser
.
mergeFieldMask
(
json
,
builder
);
}
});
// Special-case Struct.
parsers
.
put
(
Struct
.
getDescriptor
().
getFullName
(),
new
WellKnownTypeParser
()
{
@Override
public
void
merge
(
ParserImpl
parser
,
JsonElement
json
,
Message
.
Builder
builder
)
throws
InvalidProtocolBufferException
{
parser
.
mergeStruct
(
json
,
builder
);
}
});
// Special-case Value.
parsers
.
put
(
Value
.
getDescriptor
().
getFullName
(),
new
WellKnownTypeParser
()
{
@Override
public
void
merge
(
ParserImpl
parser
,
JsonElement
json
,
Message
.
Builder
builder
)
throws
InvalidProtocolBufferException
{
parser
.
mergeValue
(
json
,
builder
);
}
});
return
parsers
;
}
private
void
merge
(
JsonElement
json
,
Message
.
Builder
builder
)
throws
InvalidProtocolBufferException
{
WellKnownTypeParser
specialParser
=
wellKnownTypeParsers
.
get
(
builder
.
getDescriptorForType
().
getFullName
());
if
(
specialParser
!=
null
)
{
specialParser
.
merge
(
this
,
json
,
builder
);
return
;
}
mergeMessage
(
json
,
builder
,
false
);
}
// Maps from camel-case field names to FieldDescriptor.
private
final
Map
<
Descriptor
,
Map
<
String
,
FieldDescriptor
>>
fieldNameMaps
=
new
HashMap
<
Descriptor
,
Map
<
String
,
FieldDescriptor
>>();
private
Map
<
String
,
FieldDescriptor
>
getFieldNameMap
(
Descriptor
descriptor
)
{
if
(!
fieldNameMaps
.
containsKey
(
descriptor
))
{
Map
<
String
,
FieldDescriptor
>
fieldNameMap
=
new
HashMap
<
String
,
FieldDescriptor
>();
for
(
FieldDescriptor
field
:
descriptor
.
getFields
())
{
fieldNameMap
.
put
(
fieldNameToCamelName
(
field
.
getName
()),
field
);
}
fieldNameMaps
.
put
(
descriptor
,
fieldNameMap
);
return
fieldNameMap
;
}
return
fieldNameMaps
.
get
(
descriptor
);
}
private
void
mergeMessage
(
JsonElement
json
,
Message
.
Builder
builder
,
boolean
skipTypeUrl
)
throws
InvalidProtocolBufferException
{
if
(!(
json
instanceof
JsonObject
))
{
throw
new
InvalidProtocolBufferException
(
"Expect message object but got: "
+
json
);
}
JsonObject
object
=
(
JsonObject
)
json
;
Map
<
String
,
FieldDescriptor
>
fieldNameMap
=
getFieldNameMap
(
builder
.
getDescriptorForType
());
for
(
Map
.
Entry
<
String
,
JsonElement
>
entry
:
object
.
entrySet
())
{
if
(
skipTypeUrl
&&
entry
.
getKey
().
equals
(
"@type"
))
{
continue
;
}
FieldDescriptor
field
=
fieldNameMap
.
get
(
entry
.
getKey
());
if
(
field
==
null
)
{
throw
new
InvalidProtocolBufferException
(
"Cannot find field: "
+
entry
.
getKey
()
+
" in message "
+
builder
.
getDescriptorForType
().
getFullName
());
}
mergeField
(
field
,
entry
.
getValue
(),
builder
);
}
}
private
void
mergeAny
(
JsonElement
json
,
Message
.
Builder
builder
)
throws
InvalidProtocolBufferException
{
Descriptor
descriptor
=
builder
.
getDescriptorForType
();
FieldDescriptor
typeUrlField
=
descriptor
.
findFieldByName
(
"type_url"
);
FieldDescriptor
valueField
=
descriptor
.
findFieldByName
(
"value"
);
// Validates type of the message. Note that we can't just cast the message
// to com.google.protobuf.Any because it might be a DynamicMessage.
if
(
typeUrlField
==
null
||
valueField
==
null
||
typeUrlField
.
getType
()
!=
FieldDescriptor
.
Type
.
STRING
||
valueField
.
getType
()
!=
FieldDescriptor
.
Type
.
BYTES
)
{
throw
new
InvalidProtocolBufferException
(
"Invalid Any type."
);
}
if
(!(
json
instanceof
JsonObject
))
{
throw
new
InvalidProtocolBufferException
(
"Expect message object but got: "
+
json
);
}
JsonObject
object
=
(
JsonObject
)
json
;
JsonElement
typeUrlElement
=
object
.
get
(
"@type"
);
if
(
typeUrlElement
==
null
)
{
throw
new
InvalidProtocolBufferException
(
"Missing type url when parsing: "
+
json
);
}
String
typeUrl
=
typeUrlElement
.
getAsString
();
Descriptor
contentType
=
registry
.
find
(
getTypeName
(
typeUrl
));
if
(
contentType
==
null
)
{
throw
new
InvalidProtocolBufferException
(
"Cannot resolve type: "
+
typeUrl
);
}
builder
.
setField
(
typeUrlField
,
typeUrl
);
Message
.
Builder
contentBuilder
=
DynamicMessage
.
getDefaultInstance
(
contentType
).
newBuilderForType
();
WellKnownTypeParser
specialParser
=
wellKnownTypeParsers
.
get
(
contentType
.
getFullName
());
if
(
specialParser
!=
null
)
{
JsonElement
value
=
object
.
get
(
"value"
);
if
(
value
!=
null
)
{
specialParser
.
merge
(
this
,
value
,
contentBuilder
);
}
}
else
{
mergeMessage
(
json
,
contentBuilder
,
true
);
}
builder
.
setField
(
valueField
,
contentBuilder
.
build
().
toByteString
());
}
private
void
mergeFieldMask
(
JsonElement
json
,
Message
.
Builder
builder
)
throws
InvalidProtocolBufferException
{
FieldMask
value
=
FieldMaskUtil
.
fromString
(
json
.
getAsString
());
builder
.
mergeFrom
(
value
.
toByteString
());
}
private
void
mergeTimestamp
(
JsonElement
json
,
Message
.
Builder
builder
)
throws
InvalidProtocolBufferException
{
try
{
Timestamp
value
=
TimeUtil
.
parseTimestamp
(
json
.
getAsString
());
builder
.
mergeFrom
(
value
.
toByteString
());
}
catch
(
ParseException
e
)
{
throw
new
InvalidProtocolBufferException
(
"Failed to parse timestamp: "
+
json
);
}
}
private
void
mergeDuration
(
JsonElement
json
,
Message
.
Builder
builder
)
throws
InvalidProtocolBufferException
{
try
{
Duration
value
=
TimeUtil
.
parseDuration
(
json
.
getAsString
());
builder
.
mergeFrom
(
value
.
toByteString
());
}
catch
(
ParseException
e
)
{
throw
new
InvalidProtocolBufferException
(
"Failed to parse duration: "
+
json
);
}
}
private
void
mergeStruct
(
JsonElement
json
,
Message
.
Builder
builder
)
throws
InvalidProtocolBufferException
{
Descriptor
descriptor
=
builder
.
getDescriptorForType
();
FieldDescriptor
field
=
descriptor
.
findFieldByName
(
"fields"
);
if
(
field
==
null
)
{
throw
new
InvalidProtocolBufferException
(
"Invalid Struct type."
);
}
mergeMapField
(
field
,
json
,
builder
);
}
private
void
mergeValue
(
JsonElement
json
,
Message
.
Builder
builder
)
throws
InvalidProtocolBufferException
{
Descriptor
type
=
builder
.
getDescriptorForType
();
if
(
json
instanceof
JsonPrimitive
)
{
JsonPrimitive
primitive
=
(
JsonPrimitive
)
json
;
if
(
primitive
.
isBoolean
())
{
builder
.
setField
(
type
.
findFieldByName
(
"bool_value"
),
primitive
.
getAsBoolean
());
}
else
if
(
primitive
.
isNumber
())
{
builder
.
setField
(
type
.
findFieldByName
(
"number_value"
),
primitive
.
getAsDouble
());
}
else
{
builder
.
setField
(
type
.
findFieldByName
(
"string_value"
),
primitive
.
getAsString
());
}
}
else
if
(
json
instanceof
JsonObject
)
{
FieldDescriptor
field
=
type
.
findFieldByName
(
"struct_value"
);
Message
.
Builder
structBuilder
=
builder
.
newBuilderForField
(
field
);
merge
(
json
,
structBuilder
);
builder
.
setField
(
field
,
structBuilder
.
build
());
}
else
if
(
json
instanceof
JsonArray
)
{
FieldDescriptor
field
=
type
.
findFieldByName
(
"list_value"
);
Message
.
Builder
listBuilder
=
builder
.
newBuilderForField
(
field
);
FieldDescriptor
listField
=
listBuilder
.
getDescriptorForType
().
findFieldByName
(
"values"
);
mergeRepeatedField
(
listField
,
json
,
listBuilder
);
builder
.
setField
(
field
,
listBuilder
.
build
());
}
else
{
throw
new
IllegalStateException
(
"Unexpected json data: "
+
json
);
}
}
private
void
mergeWrapper
(
JsonElement
json
,
Message
.
Builder
builder
)
throws
InvalidProtocolBufferException
{
Descriptor
type
=
builder
.
getDescriptorForType
();
FieldDescriptor
field
=
type
.
findFieldByName
(
"value"
);
if
(
field
==
null
)
{
throw
new
InvalidProtocolBufferException
(
"Invalid wrapper type: "
+
type
.
getFullName
());
}
builder
.
setField
(
field
,
parseFieldValue
(
field
,
json
,
builder
));
}
private
void
mergeField
(
FieldDescriptor
field
,
JsonElement
json
,
Message
.
Builder
builder
)
throws
InvalidProtocolBufferException
{
if
(
json
instanceof
JsonNull
)
{
// We allow "null" as value for all field types and treat it as if the
// field is not present.
return
;
}
if
(
field
.
isMapField
())
{
mergeMapField
(
field
,
json
,
builder
);
}
else
if
(
field
.
isRepeated
())
{
mergeRepeatedField
(
field
,
json
,
builder
);
}
else
{
Object
value
=
parseFieldValue
(
field
,
json
,
builder
);
if
(
value
!=
null
)
{
builder
.
setField
(
field
,
value
);
}
}
}
private
void
mergeMapField
(
FieldDescriptor
field
,
JsonElement
json
,
Message
.
Builder
builder
)
throws
InvalidProtocolBufferException
{
if
(!(
json
instanceof
JsonObject
))
{
throw
new
InvalidProtocolBufferException
(
"Expect a map object but found: "
+
json
);
}
Descriptor
type
=
field
.
getMessageType
();
FieldDescriptor
keyField
=
type
.
findFieldByName
(
"key"
);
FieldDescriptor
valueField
=
type
.
findFieldByName
(
"value"
);
if
(
keyField
==
null
||
valueField
==
null
)
{
throw
new
InvalidProtocolBufferException
(
"Invalid map field: "
+
field
.
getFullName
());
}
JsonObject
object
=
(
JsonObject
)
json
;
for
(
Map
.
Entry
<
String
,
JsonElement
>
entry
:
object
.
entrySet
())
{
Message
.
Builder
entryBuilder
=
builder
.
newBuilderForField
(
field
);
Object
key
=
parseFieldValue
(
keyField
,
new
JsonPrimitive
(
entry
.
getKey
()),
entryBuilder
);
Object
value
=
parseFieldValue
(
valueField
,
entry
.
getValue
(),
entryBuilder
);
if
(
value
==
null
)
{
value
=
getDefaultValue
(
valueField
,
entryBuilder
);
}
entryBuilder
.
setField
(
keyField
,
key
);
entryBuilder
.
setField
(
valueField
,
value
);
builder
.
addRepeatedField
(
field
,
entryBuilder
.
build
());
}
}
/**
* Gets the default value for a field type. Note that we use proto3
* language defaults and ignore any default values set through the
* proto "default" option.
*/
private
Object
getDefaultValue
(
FieldDescriptor
field
,
Message
.
Builder
builder
)
{
switch
(
field
.
getType
())
{
case
INT32:
case
SINT32:
case
SFIXED32:
case
UINT32:
case
FIXED32:
return
0
;
case
INT64:
case
SINT64:
case
SFIXED64:
case
UINT64:
case
FIXED64:
return
0L
;
case
FLOAT:
return
0.0f
;
case
DOUBLE:
return
0.0
;
case
BOOL:
return
false
;
case
STRING:
return
""
;
case
BYTES:
return
ByteString
.
EMPTY
;
case
ENUM:
return
field
.
getEnumType
().
getValues
().
get
(
0
);
case
MESSAGE:
case
GROUP:
return
builder
.
newBuilderForField
(
field
).
getDefaultInstanceForType
();
default
:
throw
new
IllegalStateException
(
"Invalid field type: "
+
field
.
getType
());
}
}
private
void
mergeRepeatedField
(
FieldDescriptor
field
,
JsonElement
json
,
Message
.
Builder
builder
)
throws
InvalidProtocolBufferException
{
if
(!(
json
instanceof
JsonArray
))
{
throw
new
InvalidProtocolBufferException
(
"Expect an array but found: "
+
json
);
}
JsonArray
array
=
(
JsonArray
)
json
;
for
(
int
i
=
0
;
i
<
array
.
size
();
++
i
)
{
Object
value
=
parseFieldValue
(
field
,
array
.
get
(
i
),
builder
);
if
(
value
==
null
)
{
value
=
getDefaultValue
(
field
,
builder
);
}
builder
.
addRepeatedField
(
field
,
value
);
}
}
private
int
parseInt32
(
JsonElement
json
)
throws
InvalidProtocolBufferException
{
try
{
return
Integer
.
parseInt
(
json
.
getAsString
());
}
catch
(
Exception
e
)
{
throw
new
InvalidProtocolBufferException
(
"Not an int32 value: "
+
json
);
}
}
private
long
parseInt64
(
JsonElement
json
)
throws
InvalidProtocolBufferException
{
try
{
return
Long
.
parseLong
(
json
.
getAsString
());
}
catch
(
Exception
e
)
{
throw
new
InvalidProtocolBufferException
(
"Not an int64 value: "
+
json
);
}
}
private
int
parseUint32
(
JsonElement
json
)
throws
InvalidProtocolBufferException
{
try
{
long
result
=
Long
.
parseLong
(
json
.
getAsString
());
if
(
result
<
0
||
result
>
0xFFFFFFFF
L
)
{
throw
new
InvalidProtocolBufferException
(
"Out of range uint32 value: "
+
json
);
}
return
(
int
)
result
;
}
catch
(
InvalidProtocolBufferException
e
)
{
throw
e
;
}
catch
(
Exception
e
)
{
throw
new
InvalidProtocolBufferException
(
"Not an uint32 value: "
+
json
);
}
}
private
static
final
BigInteger
MAX_UINT64
=
new
BigInteger
(
"FFFFFFFFFFFFFFFF"
,
16
);
private
long
parseUint64
(
JsonElement
json
)
throws
InvalidProtocolBufferException
{
try
{
BigInteger
value
=
new
BigInteger
(
json
.
getAsString
());
if
(
value
.
compareTo
(
BigInteger
.
ZERO
)
<
0
||
value
.
compareTo
(
MAX_UINT64
)
>
0
)
{
throw
new
InvalidProtocolBufferException
(
"Out of range uint64 value: "
+
json
);
}
return
value
.
longValue
();
}
catch
(
InvalidProtocolBufferException
e
)
{
throw
e
;
}
catch
(
Exception
e
)
{
throw
new
InvalidProtocolBufferException
(
"Not an uint64 value: "
+
json
);
}
}
private
boolean
parseBool
(
JsonElement
json
)
throws
InvalidProtocolBufferException
{
if
(
json
.
getAsString
().
equals
(
"true"
))
{
return
true
;
}
if
(
json
.
getAsString
().
equals
(
"false"
))
{
return
false
;
}
throw
new
InvalidProtocolBufferException
(
"Invalid bool value: "
+
json
);
}
private
static
final
double
EPSILON
=
1
e
-
6
;
private
float
parseFloat
(
JsonElement
json
)
throws
InvalidProtocolBufferException
{
if
(
json
.
getAsString
().
equals
(
"NaN"
))
{
return
Float
.
NaN
;
}
else
if
(
json
.
getAsString
().
equals
(
"Infinity"
))
{
return
Float
.
POSITIVE_INFINITY
;
}
else
if
(
json
.
getAsString
().
equals
(
"-Infinity"
))
{
return
Float
.
NEGATIVE_INFINITY
;
}
try
{
// We don't use Float.parseFloat() here because that function simply
// accepts all double values. Here we parse the value into a Double
// and do explicit range check on it.
double
value
=
Double
.
parseDouble
(
json
.
getAsString
());
// When a float value is printed, the printed value might be a little
// larger or smaller due to precision loss. Here we need to add a bit
// of tolerance when checking whether the float value is in range.
if
(
value
>
Float
.
MAX_VALUE
*
(
1.0
+
EPSILON
)
||
value
<
-
Float
.
MAX_VALUE
*
(
1.0
+
EPSILON
))
{
throw
new
InvalidProtocolBufferException
(
"Out of range float value: "
+
json
);
}
return
(
float
)
value
;
}
catch
(
InvalidProtocolBufferException
e
)
{
throw
e
;
}
catch
(
Exception
e
)
{
throw
new
InvalidProtocolBufferException
(
"Not a float value: "
+
json
);
}
}
private
static
final
BigDecimal
MORE_THAN_ONE
=
new
BigDecimal
(
String
.
valueOf
(
1.0
+
EPSILON
));
// When a float value is printed, the printed value might be a little
// larger or smaller due to precision loss. Here we need to add a bit
// of tolerance when checking whether the float value is in range.
private
static
final
BigDecimal
MAX_DOUBLE
=
new
BigDecimal
(
String
.
valueOf
(
Double
.
MAX_VALUE
)).
multiply
(
MORE_THAN_ONE
);
private
static
final
BigDecimal
MIN_DOUBLE
=
new
BigDecimal
(
String
.
valueOf
(-
Double
.
MAX_VALUE
)).
multiply
(
MORE_THAN_ONE
);
private
double
parseDouble
(
JsonElement
json
)
throws
InvalidProtocolBufferException
{
if
(
json
.
getAsString
().
equals
(
"NaN"
))
{
return
Double
.
NaN
;
}
else
if
(
json
.
getAsString
().
equals
(
"Infinity"
))
{
return
Double
.
POSITIVE_INFINITY
;
}
else
if
(
json
.
getAsString
().
equals
(
"-Infinity"
))
{
return
Double
.
NEGATIVE_INFINITY
;
}
try
{
// We don't use Double.parseDouble() here because that function simply
// accepts all values. Here we parse the value into a BigDecimal and do
// explicit range check on it.
BigDecimal
value
=
new
BigDecimal
(
json
.
getAsString
());
if
(
value
.
compareTo
(
MAX_DOUBLE
)
>
0
||
value
.
compareTo
(
MIN_DOUBLE
)
<
0
)
{
throw
new
InvalidProtocolBufferException
(
"Out of range double value: "
+
json
);
}
return
value
.
doubleValue
();
}
catch
(
InvalidProtocolBufferException
e
)
{
throw
e
;
}
catch
(
Exception
e
)
{
throw
new
InvalidProtocolBufferException
(
"Not an double value: "
+
json
);
}
}
private
String
parseString
(
JsonElement
json
)
{
return
json
.
getAsString
();
}
private
ByteString
parseBytes
(
JsonElement
json
)
{
return
ByteString
.
copyFrom
(
BaseEncoding
.
base64
().
decode
(
json
.
getAsString
()));
}
private
EnumValueDescriptor
parseEnum
(
EnumDescriptor
enumDescriptor
,
JsonElement
json
)
throws
InvalidProtocolBufferException
{
String
value
=
json
.
getAsString
();
EnumValueDescriptor
result
=
enumDescriptor
.
findValueByName
(
value
);
if
(
result
==
null
)
{
throw
new
InvalidProtocolBufferException
(
"Invalid enum value: "
+
value
+
" for enum type: "
+
enumDescriptor
.
getFullName
());
}
return
result
;
}
private
Object
parseFieldValue
(
FieldDescriptor
field
,
JsonElement
json
,
Message
.
Builder
builder
)
throws
InvalidProtocolBufferException
{
if
(
json
instanceof
JsonNull
)
{
if
(
field
.
getJavaType
()
==
FieldDescriptor
.
JavaType
.
MESSAGE
&&
field
.
getMessageType
().
getFullName
().
equals
(
Value
.
getDescriptor
().
getFullName
()))
{
// For every other type, "null" means absence, but for the special
// Value message, it means the "null_value" field has been set.
Value
value
=
Value
.
newBuilder
().
setNullValueValue
(
0
).
build
();
return
builder
.
newBuilderForField
(
field
).
mergeFrom
(
value
.
toByteString
()).
build
();
}
return
null
;
}
switch
(
field
.
getType
())
{
case
INT32:
case
SINT32:
case
SFIXED32:
return
parseInt32
(
json
);
case
INT64:
case
SINT64:
case
SFIXED64:
return
parseInt64
(
json
);
case
BOOL:
return
parseBool
(
json
);
case
FLOAT:
return
parseFloat
(
json
);
case
DOUBLE:
return
parseDouble
(
json
);
case
UINT32:
case
FIXED32:
return
parseUint32
(
json
);
case
UINT64:
case
FIXED64:
return
parseUint64
(
json
);
case
STRING:
return
parseString
(
json
);
case
BYTES:
return
parseBytes
(
json
);
case
ENUM:
return
parseEnum
(
field
.
getEnumType
(),
json
);
case
MESSAGE:
case
GROUP:
Message
.
Builder
subBuilder
=
builder
.
newBuilderForField
(
field
);
merge
(
json
,
subBuilder
);
return
subBuilder
.
build
();
default
:
throw
new
InvalidProtocolBufferException
(
"Invalid field type: "
+
field
.
getType
());
}
}
}
}
java/util/src/main/java/com/google/protobuf/util/TimeUtil.java
0 → 100644
View file @
fbb3ef28
// 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
.
util
;
import
com.google.protobuf.Duration
;
import
com.google.protobuf.Timestamp
;
import
java.math.BigInteger
;
import
java.text.ParseException
;
import
java.text.SimpleDateFormat
;
import
java.util.Date
;
import
java.util.GregorianCalendar
;
import
java.util.TimeZone
;
/**
* Utilities to help create/manipulate Timestamp/Duration
*/
public
class
TimeUtil
{
// Timestamp for "0001-01-01T00:00:00Z"
public
static
final
long
TIMESTAMP_SECONDS_MIN
=
-
62135596800L
;
// Timestamp for "9999-12-31T23:59:59Z"
public
static
final
long
TIMESTAMP_SECONDS_MAX
=
253402300799L
;
public
static
final
long
DURATION_SECONDS_MIN
=
-
315576000000L
;
public
static
final
long
DURATION_SECONDS_MAX
=
315576000000L
;
private
static
final
long
NANOS_PER_SECOND
=
1000000000
;
private
static
final
long
NANOS_PER_MILLISECOND
=
1000000
;
private
static
final
long
NANOS_PER_MICROSECOND
=
1000
;
private
static
final
long
MILLIS_PER_SECOND
=
1000
;
private
static
final
long
MICROS_PER_SECOND
=
1000000
;
private
static
final
SimpleDateFormat
timestampFormat
=
createTimestampFormat
();
private
static
SimpleDateFormat
createTimestampFormat
()
{
SimpleDateFormat
sdf
=
new
SimpleDateFormat
(
"yyyy-MM-dd'T'HH:mm:ss"
);
GregorianCalendar
calendar
=
new
GregorianCalendar
(
TimeZone
.
getTimeZone
(
"UTC"
));
// We use Proleptic Gregorian Calendar (i.e., Gregorian calendar extends
// backwards to year one) for timestamp formating.
calendar
.
setGregorianChange
(
new
Date
(
Long
.
MIN_VALUE
));
sdf
.
setCalendar
(
calendar
);
return
sdf
;
}
private
TimeUtil
()
{}
/**
* Convert Timestamp to RFC 3339 date string format. The output will always
* be Z-normalized and uses 3, 6 or 9 fractional digits as required to
* represent the exact value. Note that Timestamp can only represent time
* from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. See
* https://www.ietf.org/rfc/rfc3339.txt
*
* <p>Example of generated format: "1972-01-01T10:00:20.021Z"
*
* @return The string representation of the given timestamp.
* @throws IllegalArgumentException if the given timestamp is not in the
* valid range.
*/
public
static
String
toString
(
Timestamp
timestamp
)
throws
IllegalArgumentException
{
StringBuilder
result
=
new
StringBuilder
();
// Format the seconds part.
if
(
timestamp
.
getSeconds
()
<
TIMESTAMP_SECONDS_MIN
||
timestamp
.
getSeconds
()
>
TIMESTAMP_SECONDS_MAX
)
{
throw
new
IllegalArgumentException
(
"Timestamp is out of range."
);
}
Date
date
=
new
Date
(
timestamp
.
getSeconds
()
*
MILLIS_PER_SECOND
);
result
.
append
(
timestampFormat
.
format
(
date
));
// Format the nanos part.
if
(
timestamp
.
getNanos
()
<
0
||
timestamp
.
getNanos
()
>=
NANOS_PER_SECOND
)
{
throw
new
IllegalArgumentException
(
"Timestamp has invalid nanos value."
);
}
if
(
timestamp
.
getNanos
()
!=
0
)
{
result
.
append
(
"."
);
result
.
append
(
formatNanos
(
timestamp
.
getNanos
()));
}
result
.
append
(
"Z"
);
return
result
.
toString
();
}
/**
* Parse from RFC 3339 date string to Timestamp. This method accepts all
* outputs of {@link #toString(Timestamp)} and it also accepts any fractional
* digits (or none) and any offset as long as they fit into nano-seconds
* precision.
*
* <p>Example of accepted format: "1972-01-01T10:00:20.021-05:00"
*
* @return A Timestamp parsed from the string.
* @throws ParseException if parsing fails.
*/
public
static
Timestamp
parseTimestamp
(
String
value
)
throws
ParseException
{
int
dayOffset
=
value
.
indexOf
(
'T'
);
if
(
dayOffset
==
-
1
)
{
throw
new
ParseException
(
"Failed to parse timestamp: invalid timestamp \""
+
value
+
"\""
,
0
);
}
int
timezoneOffsetPosition
=
value
.
indexOf
(
'Z'
,
dayOffset
);
if
(
timezoneOffsetPosition
==
-
1
)
{
timezoneOffsetPosition
=
value
.
indexOf
(
'+'
,
dayOffset
);
}
if
(
timezoneOffsetPosition
==
-
1
)
{
timezoneOffsetPosition
=
value
.
indexOf
(
'-'
,
dayOffset
);
}
if
(
timezoneOffsetPosition
==
-
1
)
{
throw
new
ParseException
(
"Failed to parse timestamp: missing valid timezone offset."
,
0
);
}
// Parse seconds and nanos.
String
timeValue
=
value
.
substring
(
0
,
timezoneOffsetPosition
);
String
secondValue
=
timeValue
;
String
nanoValue
=
""
;
int
pointPosition
=
timeValue
.
indexOf
(
'.'
);
if
(
pointPosition
!=
-
1
)
{
secondValue
=
timeValue
.
substring
(
0
,
pointPosition
);
nanoValue
=
timeValue
.
substring
(
pointPosition
+
1
);
}
Date
date
=
timestampFormat
.
parse
(
secondValue
);
long
seconds
=
date
.
getTime
()
/
MILLIS_PER_SECOND
;
int
nanos
=
nanoValue
.
isEmpty
()
?
0
:
parseNanos
(
nanoValue
);
// Parse timezone offsets.
if
(
value
.
charAt
(
timezoneOffsetPosition
)
==
'Z'
)
{
if
(
value
.
length
()
!=
timezoneOffsetPosition
+
1
)
{
throw
new
ParseException
(
"Failed to parse timestamp: invalid trailing data \""
+
value
.
substring
(
timezoneOffsetPosition
)
+
"\""
,
0
);
}
}
else
{
String
offsetValue
=
value
.
substring
(
timezoneOffsetPosition
+
1
);
long
offset
=
parseTimezoneOffset
(
offsetValue
);
if
(
value
.
charAt
(
timezoneOffsetPosition
)
==
'+'
)
{
seconds
-=
offset
;
}
else
{
seconds
+=
offset
;
}
}
try
{
return
normalizedTimestamp
(
seconds
,
nanos
);
}
catch
(
IllegalArgumentException
e
)
{
throw
new
ParseException
(
"Failed to parse timestmap: timestamp is out of range."
,
0
);
}
}
/**
* Convert Duration to string format. The string format will contains 3, 6,
* or 9 fractional digits depending on the precision required to represent
* the exact Duration value. For example: "1s", "1.010s", "1.000000100s",
* "-3.100s" The range that can be represented by Duration is from
* -315,576,000,000 to +315,576,000,000 inclusive (in seconds).
*
* @return The string representation of the given duration.
* @throws IllegalArgumentException if the given duration is not in the valid
* range.
*/
public
static
String
toString
(
Duration
duration
)
throws
IllegalArgumentException
{
if
(
duration
.
getSeconds
()
<
DURATION_SECONDS_MIN
||
duration
.
getSeconds
()
>
DURATION_SECONDS_MAX
)
{
throw
new
IllegalArgumentException
(
"Duration is out of valid range."
);
}
StringBuilder
result
=
new
StringBuilder
();
long
seconds
=
duration
.
getSeconds
();
int
nanos
=
duration
.
getNanos
();
if
(
seconds
<
0
||
nanos
<
0
)
{
if
(
seconds
>
0
||
nanos
>
0
)
{
throw
new
IllegalArgumentException
(
"Invalid duration: seconds value and nanos value must have the same"
+
"sign."
);
}
result
.
append
(
"-"
);
seconds
=
-
seconds
;
nanos
=
-
nanos
;
}
result
.
append
(
seconds
);
if
(
nanos
!=
0
)
{
result
.
append
(
"."
);
result
.
append
(
formatNanos
(
nanos
));
}
result
.
append
(
"s"
);
return
result
.
toString
();
}
/**
* Parse from a string to produce a duration.
*
* @return A Duration parsed from the string.
* @throws ParseException if parsing fails.
*/
public
static
Duration
parseDuration
(
String
value
)
throws
ParseException
{
// Must ended with "s".
if
(
value
.
isEmpty
()
||
value
.
charAt
(
value
.
length
()
-
1
)
!=
's'
)
{
throw
new
ParseException
(
"Invalid duration string: "
+
value
,
0
);
}
boolean
negative
=
false
;
if
(
value
.
charAt
(
0
)
==
'-'
)
{
negative
=
true
;
value
=
value
.
substring
(
1
);
}
String
secondValue
=
value
.
substring
(
0
,
value
.
length
()
-
1
);
String
nanoValue
=
""
;
int
pointPosition
=
secondValue
.
indexOf
(
'.'
);
if
(
pointPosition
!=
-
1
)
{
nanoValue
=
secondValue
.
substring
(
pointPosition
+
1
);
secondValue
=
secondValue
.
substring
(
0
,
pointPosition
);
}
long
seconds
=
Long
.
parseLong
(
secondValue
);
int
nanos
=
nanoValue
.
isEmpty
()
?
0
:
parseNanos
(
nanoValue
);
if
(
seconds
<
0
)
{
throw
new
ParseException
(
"Invalid duration string: "
+
value
,
0
);
}
if
(
negative
)
{
seconds
=
-
seconds
;
nanos
=
-
nanos
;
}
try
{
return
normalizedDuration
(
seconds
,
nanos
);
}
catch
(
IllegalArgumentException
e
)
{
throw
new
ParseException
(
"Duration value is out of range."
,
0
);
}
}
/**
* Create a Timestamp from the number of milliseconds elapsed from the epoch.
*/
public
static
Timestamp
createTimestampFromMillis
(
long
milliseconds
)
{
return
normalizedTimestamp
(
milliseconds
/
MILLIS_PER_SECOND
,
(
int
)
(
milliseconds
%
MILLIS_PER_SECOND
*
NANOS_PER_MILLISECOND
));
}
/**
* Create a Duration from the number of milliseconds.
*/
public
static
Duration
createDurationFromMillis
(
long
milliseconds
)
{
return
normalizedDuration
(
milliseconds
/
MILLIS_PER_SECOND
,
(
int
)
(
milliseconds
%
MILLIS_PER_SECOND
*
NANOS_PER_MILLISECOND
));
}
/**
* Convert a Timestamp to the number of milliseconds elapsed from the epoch.
*
* <p>The result will be rounded down to the nearest millisecond. E.g., if the
* timestamp represents "1969-12-31T23:59:59.999999999Z", it will be rounded
* to -1 millisecond.
*/
public
static
long
toMillis
(
Timestamp
timestamp
)
{
return
timestamp
.
getSeconds
()
*
MILLIS_PER_SECOND
+
timestamp
.
getNanos
()
/
NANOS_PER_MILLISECOND
;
}
/**
* Convert a Duration to the number of milliseconds.The result will be
* rounded towards 0 to the nearest millisecond. E.g., if the duration
* represents -1 nanosecond, it will be rounded to 0.
*/
public
static
long
toMillis
(
Duration
duration
)
{
return
duration
.
getSeconds
()
*
MILLIS_PER_SECOND
+
duration
.
getNanos
()
/
NANOS_PER_MILLISECOND
;
}
/**
* Create a Timestamp from the number of microseconds elapsed from the epoch.
*/
public
static
Timestamp
createTimestampFromMicros
(
long
microseconds
)
{
return
normalizedTimestamp
(
microseconds
/
MICROS_PER_SECOND
,
(
int
)
(
microseconds
%
MICROS_PER_SECOND
*
NANOS_PER_MICROSECOND
));
}
/**
* Create a Duration from the number of microseconds.
*/
public
static
Duration
createDurationFromMicros
(
long
microseconds
)
{
return
normalizedDuration
(
microseconds
/
MICROS_PER_SECOND
,
(
int
)
(
microseconds
%
MICROS_PER_SECOND
*
NANOS_PER_MICROSECOND
));
}
/**
* Convert a Timestamp to the number of microseconds elapsed from the epoch.
*
* <p>The result will be rounded down to the nearest microsecond. E.g., if the
* timestamp represents "1969-12-31T23:59:59.999999999Z", it will be rounded
* to -1 millisecond.
*/
public
static
long
toMicros
(
Timestamp
timestamp
)
{
return
timestamp
.
getSeconds
()
*
MICROS_PER_SECOND
+
timestamp
.
getNanos
()
/
NANOS_PER_MICROSECOND
;
}
/**
* Convert a Duration to the number of microseconds.The result will be
* rounded towards 0 to the nearest microseconds. E.g., if the duration
* represents -1 nanosecond, it will be rounded to 0.
*/
public
static
long
toMicros
(
Duration
duration
)
{
return
duration
.
getSeconds
()
*
MICROS_PER_SECOND
+
duration
.
getNanos
()
/
NANOS_PER_MICROSECOND
;
}
/**
* Create a Timestamp from the number of nanoseconds elapsed from the epoch.
*/
public
static
Timestamp
createTimestampFromNanos
(
long
nanoseconds
)
{
return
normalizedTimestamp
(
nanoseconds
/
NANOS_PER_SECOND
,
(
int
)
(
nanoseconds
%
NANOS_PER_SECOND
));
}
/**
* Create a Duration from the number of nanoseconds.
*/
public
static
Duration
createDurationFromNanos
(
long
nanoseconds
)
{
return
normalizedDuration
(
nanoseconds
/
NANOS_PER_SECOND
,
(
int
)
(
nanoseconds
%
NANOS_PER_SECOND
));
}
/**
* Convert a Timestamp to the number of nanoseconds elapsed from the epoch.
*/
public
static
long
toNanos
(
Timestamp
timestamp
)
{
return
timestamp
.
getSeconds
()
*
NANOS_PER_SECOND
+
timestamp
.
getNanos
();
}
/**
* Convert a Duration to the number of nanoseconds.
*/
public
static
long
toNanos
(
Duration
duration
)
{
return
duration
.
getSeconds
()
*
NANOS_PER_SECOND
+
duration
.
getNanos
();
}
/**
* Get the current time.
*/
public
static
Timestamp
getCurrentTime
()
{
return
createTimestampFromMillis
(
System
.
currentTimeMillis
());
}
/**
* Get the epoch.
*/
public
static
Timestamp
getEpoch
()
{
return
Timestamp
.
getDefaultInstance
();
}
/**
* Calculate the difference between two timestamps.
*/
public
static
Duration
distance
(
Timestamp
from
,
Timestamp
to
)
{
return
normalizedDuration
(
to
.
getSeconds
()
-
from
.
getSeconds
(),
to
.
getNanos
()
-
from
.
getNanos
());
}
/**
* Add a duration to a timestamp.
*/
public
static
Timestamp
add
(
Timestamp
start
,
Duration
length
)
{
return
normalizedTimestamp
(
start
.
getSeconds
()
+
length
.
getSeconds
(),
start
.
getNanos
()
+
length
.
getNanos
());
}
/**
* Subtract a duration from a timestamp.
*/
public
static
Timestamp
subtract
(
Timestamp
start
,
Duration
length
)
{
return
normalizedTimestamp
(
start
.
getSeconds
()
-
length
.
getSeconds
(),
start
.
getNanos
()
-
length
.
getNanos
());
}
/**
* Add two durations.
*/
public
static
Duration
add
(
Duration
d1
,
Duration
d2
)
{
return
normalizedDuration
(
d1
.
getSeconds
()
+
d2
.
getSeconds
(),
d1
.
getNanos
()
+
d2
.
getNanos
());
}
/**
* Subtract a duration from another.
*/
public
static
Duration
subtract
(
Duration
d1
,
Duration
d2
)
{
return
normalizedDuration
(
d1
.
getSeconds
()
-
d2
.
getSeconds
(),
d1
.
getNanos
()
-
d2
.
getNanos
());
}
// Multiplications and divisions.
public
static
Duration
multiply
(
Duration
duration
,
double
times
)
{
double
result
=
duration
.
getSeconds
()
*
times
+
duration
.
getNanos
()
*
times
/
1000000000.0
;
if
(
result
<
Long
.
MIN_VALUE
||
result
>
Long
.
MAX_VALUE
)
{
throw
new
IllegalArgumentException
(
"Result is out of valid range."
);
}
long
seconds
=
(
long
)
result
;
int
nanos
=
(
int
)
((
result
-
seconds
)
*
1000000000
);
return
normalizedDuration
(
seconds
,
nanos
);
}
public
static
Duration
divide
(
Duration
duration
,
double
value
)
{
return
multiply
(
duration
,
1.0
/
value
);
}
public
static
Duration
multiply
(
Duration
duration
,
long
times
)
{
return
createDurationFromBigInteger
(
toBigInteger
(
duration
).
multiply
(
toBigInteger
(
times
)));
}
public
static
Duration
divide
(
Duration
duration
,
long
times
)
{
return
createDurationFromBigInteger
(
toBigInteger
(
duration
).
divide
(
toBigInteger
(
times
)));
}
public
static
long
divide
(
Duration
d1
,
Duration
d2
)
{
return
toBigInteger
(
d1
).
divide
(
toBigInteger
(
d2
)).
longValue
();
}
public
static
Duration
remainder
(
Duration
d1
,
Duration
d2
)
{
return
createDurationFromBigInteger
(
toBigInteger
(
d1
).
remainder
(
toBigInteger
(
d2
)));
}
private
static
final
BigInteger
NANOS_PER_SECOND_BIG_INTEGER
=
new
BigInteger
(
String
.
valueOf
(
NANOS_PER_SECOND
));
private
static
BigInteger
toBigInteger
(
Duration
duration
)
{
return
toBigInteger
(
duration
.
getSeconds
())
.
multiply
(
NANOS_PER_SECOND_BIG_INTEGER
)
.
add
(
toBigInteger
(
duration
.
getNanos
()));
}
private
static
BigInteger
toBigInteger
(
long
value
)
{
return
new
BigInteger
(
String
.
valueOf
(
value
));
}
private
static
Duration
createDurationFromBigInteger
(
BigInteger
value
)
{
long
seconds
=
value
.
divide
(
new
BigInteger
(
String
.
valueOf
(
NANOS_PER_SECOND
))).
longValue
();
int
nanos
=
value
.
remainder
(
new
BigInteger
(
String
.
valueOf
(
NANOS_PER_SECOND
))).
intValue
();
return
normalizedDuration
(
seconds
,
nanos
);
}
private
static
Duration
normalizedDuration
(
long
seconds
,
int
nanos
)
{
if
(
nanos
<=
-
NANOS_PER_SECOND
||
nanos
>=
NANOS_PER_SECOND
)
{
seconds
+=
nanos
/
NANOS_PER_SECOND
;
nanos
%=
NANOS_PER_SECOND
;
}
if
(
seconds
>
0
&&
nanos
<
0
)
{
nanos
+=
NANOS_PER_SECOND
;
seconds
-=
1
;
}
if
(
seconds
<
0
&&
nanos
>
0
)
{
nanos
-=
NANOS_PER_SECOND
;
seconds
+=
1
;
}
if
(
seconds
<
DURATION_SECONDS_MIN
||
seconds
>
DURATION_SECONDS_MAX
)
{
throw
new
IllegalArgumentException
(
"Duration is out of valid range."
);
}
return
Duration
.
newBuilder
().
setSeconds
(
seconds
).
setNanos
(
nanos
).
build
();
}
private
static
Timestamp
normalizedTimestamp
(
long
seconds
,
int
nanos
)
{
if
(
nanos
<=
-
NANOS_PER_SECOND
||
nanos
>=
NANOS_PER_SECOND
)
{
seconds
+=
nanos
/
NANOS_PER_SECOND
;
nanos
%=
NANOS_PER_SECOND
;
}
if
(
nanos
<
0
)
{
nanos
+=
NANOS_PER_SECOND
;
seconds
-=
1
;
}
if
(
seconds
<
TIMESTAMP_SECONDS_MIN
||
seconds
>
TIMESTAMP_SECONDS_MAX
)
{
throw
new
IllegalArgumentException
(
"Timestamp is out of valid range."
);
}
return
Timestamp
.
newBuilder
().
setSeconds
(
seconds
).
setNanos
(
nanos
).
build
();
}
/**
* Format the nano part of a timestamp or a duration.
*/
private
static
String
formatNanos
(
int
nanos
)
{
assert
nanos
>=
1
&&
nanos
<=
999999999
;
// Determine whether to use 3, 6, or 9 digits for the nano part.
if
(
nanos
%
NANOS_PER_MILLISECOND
==
0
)
{
return
String
.
format
(
"%1$03d"
,
nanos
/
NANOS_PER_MILLISECOND
);
}
else
if
(
nanos
%
NANOS_PER_MICROSECOND
==
0
)
{
return
String
.
format
(
"%1$06d"
,
nanos
/
NANOS_PER_MICROSECOND
);
}
else
{
return
String
.
format
(
"%1$09d"
,
nanos
);
}
}
private
static
int
parseNanos
(
String
value
)
throws
ParseException
{
int
result
=
0
;
for
(
int
i
=
0
;
i
<
9
;
++
i
)
{
result
=
result
*
10
;
if
(
i
<
value
.
length
())
{
if
(
value
.
charAt
(
i
)
<
'0'
||
value
.
charAt
(
i
)
>
'9'
)
{
throw
new
ParseException
(
"Invalid nanosecnds."
,
0
);
}
result
+=
value
.
charAt
(
i
)
-
'0'
;
}
}
return
result
;
}
private
static
long
parseTimezoneOffset
(
String
value
)
throws
ParseException
{
int
pos
=
value
.
indexOf
(
':'
);
if
(
pos
==
-
1
)
{
throw
new
ParseException
(
"Invalid offset value: "
+
value
,
0
);
}
String
hours
=
value
.
substring
(
0
,
pos
);
String
minutes
=
value
.
substring
(
pos
+
1
);
return
(
Long
.
parseLong
(
hours
)
*
60
+
Long
.
parseLong
(
minutes
))
*
60
;
}
}
java/util/src/test/java/com/google/protobuf/util/FieldMaskTreeTest.java
0 → 100644
View file @
fbb3ef28
// 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
.
util
;
import
protobuf_unittest.UnittestProto.NestedTestAllTypes
;
import
protobuf_unittest.UnittestProto.TestAllTypes
;
import
protobuf_unittest.UnittestProto.TestAllTypes.NestedMessage
;
import
junit.framework.TestCase
;
public
class
FieldMaskTreeTest
extends
TestCase
{
public
void
testAddFieldPath
()
throws
Exception
{
FieldMaskTree
tree
=
new
FieldMaskTree
();
assertEquals
(
""
,
tree
.
toString
());
tree
.
addFieldPath
(
""
);
assertEquals
(
""
,
tree
.
toString
());
// New branch.
tree
.
addFieldPath
(
"foo"
);
assertEquals
(
"foo"
,
tree
.
toString
());
// Redundant path.
tree
.
addFieldPath
(
"foo"
);
assertEquals
(
"foo"
,
tree
.
toString
());
// New branch.
tree
.
addFieldPath
(
"bar.baz"
);
assertEquals
(
"bar.baz,foo"
,
tree
.
toString
());
// Redundant sub-path.
tree
.
addFieldPath
(
"foo.bar"
);
assertEquals
(
"bar.baz,foo"
,
tree
.
toString
());
// New branch from a non-root node.
tree
.
addFieldPath
(
"bar.quz"
);
assertEquals
(
"bar.baz,bar.quz,foo"
,
tree
.
toString
());
// A path that matches several existing sub-paths.
tree
.
addFieldPath
(
"bar"
);
assertEquals
(
"bar,foo"
,
tree
.
toString
());
}
public
void
testMergeFromFieldMask
()
throws
Exception
{
FieldMaskTree
tree
=
new
FieldMaskTree
(
FieldMaskUtil
.
fromString
(
"foo,bar.baz,bar.quz"
));
assertEquals
(
"bar.baz,bar.quz,foo"
,
tree
.
toString
());
tree
.
mergeFromFieldMask
(
FieldMaskUtil
.
fromString
(
"foo.bar,bar"
));
assertEquals
(
"bar,foo"
,
tree
.
toString
());
}
public
void
testIntersectFieldPath
()
throws
Exception
{
FieldMaskTree
tree
=
new
FieldMaskTree
(
FieldMaskUtil
.
fromString
(
"foo,bar.baz,bar.quz"
));
FieldMaskTree
result
=
new
FieldMaskTree
();
// Empty path.
tree
.
intersectFieldPath
(
""
,
result
);
assertEquals
(
""
,
result
.
toString
());
// Non-exist path.
tree
.
intersectFieldPath
(
"quz"
,
result
);
assertEquals
(
""
,
result
.
toString
());
// Sub-path of an existing leaf.
tree
.
intersectFieldPath
(
"foo.bar"
,
result
);
assertEquals
(
"foo.bar"
,
result
.
toString
());
// Match an existing leaf node.
tree
.
intersectFieldPath
(
"foo"
,
result
);
assertEquals
(
"foo"
,
result
.
toString
());
// Non-exist path.
tree
.
intersectFieldPath
(
"bar.foo"
,
result
);
assertEquals
(
"foo"
,
result
.
toString
());
// Match a non-leaf node.
tree
.
intersectFieldPath
(
"bar"
,
result
);
assertEquals
(
"bar.baz,bar.quz,foo"
,
result
.
toString
());
}
public
void
testMerge
()
throws
Exception
{
TestAllTypes
value
=
TestAllTypes
.
newBuilder
()
.
setOptionalInt32
(
1234
)
.
setOptionalNestedMessage
(
NestedMessage
.
newBuilder
().
setBb
(
5678
))
.
addRepeatedInt32
(
4321
)
.
addRepeatedNestedMessage
(
NestedMessage
.
newBuilder
().
setBb
(
8765
))
.
build
();
NestedTestAllTypes
source
=
NestedTestAllTypes
.
newBuilder
()
.
setPayload
(
value
)
.
setChild
(
NestedTestAllTypes
.
newBuilder
().
setPayload
(
value
))
.
build
();
// Now we have a message source with the following structure:
// [root] -+- payload -+- optional_int32
// | +- optional_nested_message
// | +- repeated_int32
// | +- repeated_nested_message
// |
// +- child --- payload -+- optional_int32
// +- optional_nested_message
// +- repeated_int32
// +- repeated_nested_message
FieldMaskUtil
.
MergeOptions
options
=
new
FieldMaskUtil
.
MergeOptions
();
// Test merging each individual field.
NestedTestAllTypes
.
Builder
builder
=
NestedTestAllTypes
.
newBuilder
();
new
FieldMaskTree
().
addFieldPath
(
"payload.optional_int32"
)
.
merge
(
source
,
builder
,
options
);
NestedTestAllTypes
.
Builder
expected
=
NestedTestAllTypes
.
newBuilder
();
expected
.
getPayloadBuilder
().
setOptionalInt32
(
1234
);
assertEquals
(
expected
.
build
(),
builder
.
build
());
builder
=
NestedTestAllTypes
.
newBuilder
();
new
FieldMaskTree
().
addFieldPath
(
"payload.optional_nested_message"
)
.
merge
(
source
,
builder
,
options
);
expected
=
NestedTestAllTypes
.
newBuilder
();
expected
.
getPayloadBuilder
().
setOptionalNestedMessage
(
NestedMessage
.
newBuilder
().
setBb
(
5678
));
assertEquals
(
expected
.
build
(),
builder
.
build
());
builder
=
NestedTestAllTypes
.
newBuilder
();
new
FieldMaskTree
().
addFieldPath
(
"payload.repeated_int32"
)
.
merge
(
source
,
builder
,
options
);
expected
=
NestedTestAllTypes
.
newBuilder
();
expected
.
getPayloadBuilder
().
addRepeatedInt32
(
4321
);
assertEquals
(
expected
.
build
(),
builder
.
build
());
builder
=
NestedTestAllTypes
.
newBuilder
();
new
FieldMaskTree
().
addFieldPath
(
"payload.repeated_nested_message"
)
.
merge
(
source
,
builder
,
options
);
expected
=
NestedTestAllTypes
.
newBuilder
();
expected
.
getPayloadBuilder
().
addRepeatedNestedMessage
(
NestedMessage
.
newBuilder
().
setBb
(
8765
));
assertEquals
(
expected
.
build
(),
builder
.
build
());
builder
=
NestedTestAllTypes
.
newBuilder
();
new
FieldMaskTree
().
addFieldPath
(
"child.payload.optional_int32"
)
.
merge
(
source
,
builder
,
options
);
expected
=
NestedTestAllTypes
.
newBuilder
();
expected
.
getChildBuilder
().
getPayloadBuilder
().
setOptionalInt32
(
1234
);
assertEquals
(
expected
.
build
(),
builder
.
build
());
builder
=
NestedTestAllTypes
.
newBuilder
();
new
FieldMaskTree
().
addFieldPath
(
"child.payload.optional_nested_message"
)
.
merge
(
source
,
builder
,
options
);
expected
=
NestedTestAllTypes
.
newBuilder
();
expected
.
getChildBuilder
().
getPayloadBuilder
().
setOptionalNestedMessage
(
NestedMessage
.
newBuilder
().
setBb
(
5678
));
assertEquals
(
expected
.
build
(),
builder
.
build
());
builder
=
NestedTestAllTypes
.
newBuilder
();
new
FieldMaskTree
().
addFieldPath
(
"child.payload.repeated_int32"
)
.
merge
(
source
,
builder
,
options
);
expected
=
NestedTestAllTypes
.
newBuilder
();
expected
.
getChildBuilder
().
getPayloadBuilder
().
addRepeatedInt32
(
4321
);
assertEquals
(
expected
.
build
(),
builder
.
build
());
builder
=
NestedTestAllTypes
.
newBuilder
();
new
FieldMaskTree
().
addFieldPath
(
"child.payload.repeated_nested_message"
)
.
merge
(
source
,
builder
,
options
);
expected
=
NestedTestAllTypes
.
newBuilder
();
expected
.
getChildBuilder
().
getPayloadBuilder
().
addRepeatedNestedMessage
(
NestedMessage
.
newBuilder
().
setBb
(
8765
));
assertEquals
(
expected
.
build
(),
builder
.
build
());
// Test merging all fields.
builder
=
NestedTestAllTypes
.
newBuilder
();
new
FieldMaskTree
().
addFieldPath
(
"child"
).
addFieldPath
(
"payload"
)
.
merge
(
source
,
builder
,
options
);
assertEquals
(
source
,
builder
.
build
());
// Test repeated options.
builder
=
NestedTestAllTypes
.
newBuilder
();
builder
.
getPayloadBuilder
().
addRepeatedInt32
(
1000
);
new
FieldMaskTree
().
addFieldPath
(
"payload.repeated_int32"
)
.
merge
(
source
,
builder
,
options
);
// Default behavior is to append repeated fields.
assertEquals
(
2
,
builder
.
getPayload
().
getRepeatedInt32Count
());
assertEquals
(
1000
,
builder
.
getPayload
().
getRepeatedInt32
(
0
));
assertEquals
(
4321
,
builder
.
getPayload
().
getRepeatedInt32
(
1
));
// Change to replace repeated fields.
options
.
setReplaceRepeatedFields
(
true
);
new
FieldMaskTree
().
addFieldPath
(
"payload.repeated_int32"
)
.
merge
(
source
,
builder
,
options
);
assertEquals
(
1
,
builder
.
getPayload
().
getRepeatedInt32Count
());
assertEquals
(
4321
,
builder
.
getPayload
().
getRepeatedInt32
(
0
));
// Test message options.
builder
=
NestedTestAllTypes
.
newBuilder
();
builder
.
getPayloadBuilder
().
setOptionalInt32
(
1000
);
builder
.
getPayloadBuilder
().
setOptionalUint32
(
2000
);
new
FieldMaskTree
().
addFieldPath
(
"payload"
)
.
merge
(
source
,
builder
,
options
);
// Default behavior is to merge message fields.
assertEquals
(
1234
,
builder
.
getPayload
().
getOptionalInt32
());
assertEquals
(
2000
,
builder
.
getPayload
().
getOptionalUint32
());
// Change to replace message fields.
options
.
setReplaceMessageFields
(
true
);
builder
=
NestedTestAllTypes
.
newBuilder
();
builder
.
getPayloadBuilder
().
setOptionalInt32
(
1000
);
builder
.
getPayloadBuilder
().
setOptionalUint32
(
2000
);
new
FieldMaskTree
().
addFieldPath
(
"payload"
)
.
merge
(
source
,
builder
,
options
);
assertEquals
(
1234
,
builder
.
getPayload
().
getOptionalInt32
());
assertEquals
(
0
,
builder
.
getPayload
().
getOptionalUint32
());
}
}
java/util/src/test/java/com/google/protobuf/util/FieldMaskUtilTest.java
0 → 100644
View file @
fbb3ef28
// 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
.
util
;
import
com.google.protobuf.FieldMask
;
import
protobuf_unittest.UnittestProto.NestedTestAllTypes
;
import
protobuf_unittest.UnittestProto.TestAllTypes
;
import
junit.framework.TestCase
;
/** Unit tests for {@link FieldMaskUtil}. */
public
class
FieldMaskUtilTest
extends
TestCase
{
public
void
testIsValid
()
throws
Exception
{
assertTrue
(
FieldMaskUtil
.
isValid
(
NestedTestAllTypes
.
class
,
"payload"
));
assertFalse
(
FieldMaskUtil
.
isValid
(
NestedTestAllTypes
.
class
,
"nonexist"
));
assertTrue
(
FieldMaskUtil
.
isValid
(
NestedTestAllTypes
.
class
,
"payload.optional_int32"
));
assertTrue
(
FieldMaskUtil
.
isValid
(
NestedTestAllTypes
.
class
,
"payload.repeated_int32"
));
assertTrue
(
FieldMaskUtil
.
isValid
(
NestedTestAllTypes
.
class
,
"payload.optional_nested_message"
));
assertTrue
(
FieldMaskUtil
.
isValid
(
NestedTestAllTypes
.
class
,
"payload.repeated_nested_message"
));
assertFalse
(
FieldMaskUtil
.
isValid
(
NestedTestAllTypes
.
class
,
"payload.nonexist"
));
assertTrue
(
FieldMaskUtil
.
isValid
(
NestedTestAllTypes
.
class
,
"payload.optional_nested_message.bb"
));
// Repeated fields cannot have sub-paths.
assertFalse
(
FieldMaskUtil
.
isValid
(
NestedTestAllTypes
.
class
,
"payload.repeated_nested_message.bb"
));
// Non-message fields cannot have sub-paths.
assertFalse
(
FieldMaskUtil
.
isValid
(
NestedTestAllTypes
.
class
,
"payload.optional_int32.bb"
));
}
public
void
testToString
()
throws
Exception
{
assertEquals
(
""
,
FieldMaskUtil
.
toString
(
FieldMask
.
getDefaultInstance
()));
FieldMask
mask
=
FieldMask
.
newBuilder
().
addPaths
(
"foo"
).
build
();
assertEquals
(
"foo"
,
FieldMaskUtil
.
toString
(
mask
));
mask
=
FieldMask
.
newBuilder
().
addPaths
(
"foo"
).
addPaths
(
"bar"
).
build
();
assertEquals
(
"foo,bar"
,
FieldMaskUtil
.
toString
(
mask
));
// Empty field paths are ignored.
mask
=
FieldMask
.
newBuilder
().
addPaths
(
""
).
addPaths
(
"foo"
).
addPaths
(
""
).
addPaths
(
"bar"
).
addPaths
(
""
).
build
();
assertEquals
(
"foo,bar"
,
FieldMaskUtil
.
toString
(
mask
));
}
public
void
testFromString
()
throws
Exception
{
FieldMask
mask
=
FieldMaskUtil
.
fromString
(
""
);
assertEquals
(
0
,
mask
.
getPathsCount
());
mask
=
FieldMaskUtil
.
fromString
(
"foo"
);
assertEquals
(
1
,
mask
.
getPathsCount
());
assertEquals
(
"foo"
,
mask
.
getPaths
(
0
));
mask
=
FieldMaskUtil
.
fromString
(
"foo,bar.baz"
);
assertEquals
(
2
,
mask
.
getPathsCount
());
assertEquals
(
"foo"
,
mask
.
getPaths
(
0
));
assertEquals
(
"bar.baz"
,
mask
.
getPaths
(
1
));
// Empty field paths are ignore.
mask
=
FieldMaskUtil
.
fromString
(
",foo,,bar,"
);
assertEquals
(
2
,
mask
.
getPathsCount
());
assertEquals
(
"foo"
,
mask
.
getPaths
(
0
));
assertEquals
(
"bar"
,
mask
.
getPaths
(
1
));
// Check whether the field paths are valid if a class parameter is provided.
mask
=
FieldMaskUtil
.
fromString
(
NestedTestAllTypes
.
class
,
",payload"
);
try
{
mask
=
FieldMaskUtil
.
fromString
(
NestedTestAllTypes
.
class
,
"payload,nonexist"
);
fail
(
"Exception is expected."
);
}
catch
(
IllegalArgumentException
e
)
{
// Expected.
}
}
public
void
testUnion
()
throws
Exception
{
// Only test a simple case here and expect
// {@link FieldMaskTreeTest#testAddFieldPath} to cover all scenarios.
FieldMask
mask1
=
FieldMaskUtil
.
fromString
(
"foo,bar.baz,bar.quz"
);
FieldMask
mask2
=
FieldMaskUtil
.
fromString
(
"foo.bar,bar"
);
FieldMask
result
=
FieldMaskUtil
.
union
(
mask1
,
mask2
);
assertEquals
(
"bar,foo"
,
FieldMaskUtil
.
toString
(
result
));
}
public
void
testIntersection
()
throws
Exception
{
// Only test a simple case here and expect
// {@link FieldMaskTreeTest#testIntersectFieldPath} to cover all scenarios.
FieldMask
mask1
=
FieldMaskUtil
.
fromString
(
"foo,bar.baz,bar.quz"
);
FieldMask
mask2
=
FieldMaskUtil
.
fromString
(
"foo.bar,bar"
);
FieldMask
result
=
FieldMaskUtil
.
intersection
(
mask1
,
mask2
);
assertEquals
(
"bar.baz,bar.quz,foo.bar"
,
FieldMaskUtil
.
toString
(
result
));
}
public
void
testMerge
()
throws
Exception
{
// Only test a simple case here and expect
// {@link FieldMaskTreeTest#testMerge} to cover all scenarios.
NestedTestAllTypes
source
=
NestedTestAllTypes
.
newBuilder
()
.
setPayload
(
TestAllTypes
.
newBuilder
().
setOptionalInt32
(
1234
))
.
build
();
NestedTestAllTypes
.
Builder
builder
=
NestedTestAllTypes
.
newBuilder
();
FieldMaskUtil
.
merge
(
FieldMaskUtil
.
fromString
(
"payload"
),
source
,
builder
);
assertEquals
(
1234
,
builder
.
getPayload
().
getOptionalInt32
());
}
}
java/util/src/test/java/com/google/protobuf/util/JsonFormatTest.java
0 → 100644
View file @
fbb3ef28
// 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
.
util
;
import
com.google.protobuf.Any
;
import
com.google.protobuf.BoolValue
;
import
com.google.protobuf.ByteString
;
import
com.google.protobuf.BytesValue
;
import
com.google.protobuf.DoubleValue
;
import
com.google.protobuf.FloatValue
;
import
com.google.protobuf.Int32Value
;
import
com.google.protobuf.Int64Value
;
import
com.google.protobuf.InvalidProtocolBufferException
;
import
com.google.protobuf.ListValue
;
import
com.google.protobuf.Message
;
import
com.google.protobuf.StringValue
;
import
com.google.protobuf.Struct
;
import
com.google.protobuf.UInt32Value
;
import
com.google.protobuf.UInt64Value
;
import
com.google.protobuf.Value
;
import
com.google.protobuf.util.JsonFormat.TypeRegistry
;
import
com.google.protobuf.util.JsonTestProto.TestAllTypes
;
import
com.google.protobuf.util.JsonTestProto.TestAllTypes.NestedEnum
;
import
com.google.protobuf.util.JsonTestProto.TestAllTypes.NestedMessage
;
import
com.google.protobuf.util.JsonTestProto.TestAny
;
import
com.google.protobuf.util.JsonTestProto.TestDuration
;
import
com.google.protobuf.util.JsonTestProto.TestFieldMask
;
import
com.google.protobuf.util.JsonTestProto.TestMap
;
import
com.google.protobuf.util.JsonTestProto.TestStruct
;
import
com.google.protobuf.util.JsonTestProto.TestTimestamp
;
import
com.google.protobuf.util.JsonTestProto.TestWrappers
;
import
junit.framework.TestCase
;
import
java.io.IOException
;
import
java.math.BigDecimal
;
import
java.math.BigInteger
;
public
class
JsonFormatTest
extends
TestCase
{
private
void
setAllFields
(
TestAllTypes
.
Builder
builder
)
{
builder
.
setOptionalInt32
(
1234
);
builder
.
setOptionalInt64
(
1234567890123456789L
);
builder
.
setOptionalUint32
(
5678
);
builder
.
setOptionalUint64
(
2345678901234567890L
);
builder
.
setOptionalSint32
(
9012
);
builder
.
setOptionalSint64
(
3456789012345678901L
);
builder
.
setOptionalFixed32
(
3456
);
builder
.
setOptionalFixed64
(
4567890123456789012L
);
builder
.
setOptionalSfixed32
(
7890
);
builder
.
setOptionalSfixed64
(
5678901234567890123L
);
builder
.
setOptionalFloat
(
1.5f
);
builder
.
setOptionalDouble
(
1.25
);
builder
.
setOptionalBool
(
true
);
builder
.
setOptionalString
(
"Hello world!"
);
builder
.
setOptionalBytes
(
ByteString
.
copyFrom
(
new
byte
[]{
0
,
1
,
2
}));
builder
.
setOptionalNestedEnum
(
NestedEnum
.
BAR
);
builder
.
getOptionalNestedMessageBuilder
().
setValue
(
100
);
builder
.
addRepeatedInt32
(
1234
);
builder
.
addRepeatedInt64
(
1234567890123456789L
);
builder
.
addRepeatedUint32
(
5678
);
builder
.
addRepeatedUint64
(
2345678901234567890L
);
builder
.
addRepeatedSint32
(
9012
);
builder
.
addRepeatedSint64
(
3456789012345678901L
);
builder
.
addRepeatedFixed32
(
3456
);
builder
.
addRepeatedFixed64
(
4567890123456789012L
);
builder
.
addRepeatedSfixed32
(
7890
);
builder
.
addRepeatedSfixed64
(
5678901234567890123L
);
builder
.
addRepeatedFloat
(
1.5f
);
builder
.
addRepeatedDouble
(
1.25
);
builder
.
addRepeatedBool
(
true
);
builder
.
addRepeatedString
(
"Hello world!"
);
builder
.
addRepeatedBytes
(
ByteString
.
copyFrom
(
new
byte
[]{
0
,
1
,
2
}));
builder
.
addRepeatedNestedEnum
(
NestedEnum
.
BAR
);
builder
.
addRepeatedNestedMessageBuilder
().
setValue
(
100
);
builder
.
addRepeatedInt32
(
234
);
builder
.
addRepeatedInt64
(
234567890123456789L
);
builder
.
addRepeatedUint32
(
678
);
builder
.
addRepeatedUint64
(
345678901234567890L
);
builder
.
addRepeatedSint32
(
012
);
builder
.
addRepeatedSint64
(
456789012345678901L
);
builder
.
addRepeatedFixed32
(
456
);
builder
.
addRepeatedFixed64
(
567890123456789012L
);
builder
.
addRepeatedSfixed32
(
890
);
builder
.
addRepeatedSfixed64
(
678901234567890123L
);
builder
.
addRepeatedFloat
(
11.5f
);
builder
.
addRepeatedDouble
(
11.25
);
builder
.
addRepeatedBool
(
true
);
builder
.
addRepeatedString
(
"ello world!"
);
builder
.
addRepeatedBytes
(
ByteString
.
copyFrom
(
new
byte
[]{
1
,
2
}));
builder
.
addRepeatedNestedEnum
(
NestedEnum
.
BAZ
);
builder
.
addRepeatedNestedMessageBuilder
().
setValue
(
200
);
}
private
void
assertRoundTripEquals
(
Message
message
)
throws
Exception
{
assertRoundTripEquals
(
message
,
TypeRegistry
.
getEmptyTypeRegistry
());
}
private
void
assertRoundTripEquals
(
Message
message
,
TypeRegistry
registry
)
throws
Exception
{
JsonFormat
.
Printer
printer
=
JsonFormat
.
printer
().
usingTypeRegistry
(
registry
);
JsonFormat
.
Parser
parser
=
JsonFormat
.
parser
().
usingTypeRegistry
(
registry
);
Message
.
Builder
builder
=
message
.
newBuilderForType
();
parser
.
merge
(
printer
.
print
(
message
),
builder
);
Message
parsedMessage
=
builder
.
build
();
assertEquals
(
message
.
toString
(),
parsedMessage
.
toString
());
}
private
String
toJsonString
(
Message
message
)
throws
IOException
{
return
JsonFormat
.
printer
().
print
(
message
);
}
private
void
mergeFromJson
(
String
json
,
Message
.
Builder
builder
)
throws
IOException
{
JsonFormat
.
parser
().
merge
(
json
,
builder
);
}
public
void
testAllFields
()
throws
Exception
{
TestAllTypes
.
Builder
builder
=
TestAllTypes
.
newBuilder
();
setAllFields
(
builder
);
TestAllTypes
message
=
builder
.
build
();
assertEquals
(
"{\n"
+
" \"optionalInt32\": 1234,\n"
+
" \"optionalInt64\": \"1234567890123456789\",\n"
+
" \"optionalUint32\": 5678,\n"
+
" \"optionalUint64\": \"2345678901234567890\",\n"
+
" \"optionalSint32\": 9012,\n"
+
" \"optionalSint64\": \"3456789012345678901\",\n"
+
" \"optionalFixed32\": 3456,\n"
+
" \"optionalFixed64\": \"4567890123456789012\",\n"
+
" \"optionalSfixed32\": 7890,\n"
+
" \"optionalSfixed64\": \"5678901234567890123\",\n"
+
" \"optionalFloat\": 1.5,\n"
+
" \"optionalDouble\": 1.25,\n"
+
" \"optionalBool\": true,\n"
+
" \"optionalString\": \"Hello world!\",\n"
+
" \"optionalBytes\": \"AAEC\",\n"
+
" \"optionalNestedMessage\": {\n"
+
" \"value\": 100\n"
+
" },\n"
+
" \"optionalNestedEnum\": \"BAR\",\n"
+
" \"repeatedInt32\": [1234, 234],\n"
+
" \"repeatedInt64\": [\"1234567890123456789\", \"234567890123456789\"],\n"
+
" \"repeatedUint32\": [5678, 678],\n"
+
" \"repeatedUint64\": [\"2345678901234567890\", \"345678901234567890\"],\n"
+
" \"repeatedSint32\": [9012, 10],\n"
+
" \"repeatedSint64\": [\"3456789012345678901\", \"456789012345678901\"],\n"
+
" \"repeatedFixed32\": [3456, 456],\n"
+
" \"repeatedFixed64\": [\"4567890123456789012\", \"567890123456789012\"],\n"
+
" \"repeatedSfixed32\": [7890, 890],\n"
+
" \"repeatedSfixed64\": [\"5678901234567890123\", \"678901234567890123\"],\n"
+
" \"repeatedFloat\": [1.5, 11.5],\n"
+
" \"repeatedDouble\": [1.25, 11.25],\n"
+
" \"repeatedBool\": [true, true],\n"
+
" \"repeatedString\": [\"Hello world!\", \"ello world!\"],\n"
+
" \"repeatedBytes\": [\"AAEC\", \"AQI=\"],\n"
+
" \"repeatedNestedMessage\": [{\n"
+
" \"value\": 100\n"
+
" }, {\n"
+
" \"value\": 200\n"
+
" }],\n"
+
" \"repeatedNestedEnum\": [\"BAR\", \"BAZ\"]\n"
+
"}"
,
toJsonString
(
message
));
assertRoundTripEquals
(
message
);
}
public
void
testUnknownEnumValues
()
throws
Exception
{
// Unknown enum values will be dropped.
// TODO(xiaofeng): We may want to revisit this (whether we should omit
// unknown enum values).
TestAllTypes
message
=
TestAllTypes
.
newBuilder
()
.
setOptionalNestedEnumValue
(
12345
)
.
addRepeatedNestedEnumValue
(
12345
)
.
addRepeatedNestedEnumValue
(
0
)
.
build
();
assertEquals
(
"{\n"
+
" \"repeatedNestedEnum\": [\"FOO\"]\n"
+
"}"
,
toJsonString
(
message
));
TestMap
.
Builder
mapBuilder
=
TestMap
.
newBuilder
();
mapBuilder
.
getMutableInt32ToEnumMapValue
().
put
(
1
,
0
);
mapBuilder
.
getMutableInt32ToEnumMapValue
().
put
(
2
,
12345
);
TestMap
mapMessage
=
mapBuilder
.
build
();
assertEquals
(
"{\n"
+
" \"int32ToEnumMap\": {\n"
+
" \"1\": \"FOO\"\n"
+
" }\n"
+
"}"
,
toJsonString
(
mapMessage
));
}
public
void
testSpecialFloatValues
()
throws
Exception
{
TestAllTypes
message
=
TestAllTypes
.
newBuilder
()
.
addRepeatedFloat
(
Float
.
NaN
)
.
addRepeatedFloat
(
Float
.
POSITIVE_INFINITY
)
.
addRepeatedFloat
(
Float
.
NEGATIVE_INFINITY
)
.
addRepeatedDouble
(
Double
.
NaN
)
.
addRepeatedDouble
(
Double
.
POSITIVE_INFINITY
)
.
addRepeatedDouble
(
Double
.
NEGATIVE_INFINITY
)
.
build
();
assertEquals
(
"{\n"
+
" \"repeatedFloat\": [\"NaN\", \"Infinity\", \"-Infinity\"],\n"
+
" \"repeatedDouble\": [\"NaN\", \"Infinity\", \"-Infinity\"]\n"
+
"}"
,
toJsonString
(
message
));
assertRoundTripEquals
(
message
);
}
public
void
testParserAcceptStringForNumbericField
()
throws
Exception
{
TestAllTypes
.
Builder
builder
=
TestAllTypes
.
newBuilder
();
mergeFromJson
(
"{\n"
+
" \"optionalInt32\": \"1234\",\n"
+
" \"optionalUint32\": \"5678\",\n"
+
" \"optionalSint32\": \"9012\",\n"
+
" \"optionalFixed32\": \"3456\",\n"
+
" \"optionalSfixed32\": \"7890\",\n"
+
" \"optionalFloat\": \"1.5\",\n"
+
" \"optionalDouble\": \"1.25\",\n"
+
" \"optionalBool\": \"true\"\n"
+
"}"
,
builder
);
TestAllTypes
message
=
builder
.
build
();
assertEquals
(
1234
,
message
.
getOptionalInt32
());
assertEquals
(
5678
,
message
.
getOptionalUint32
());
assertEquals
(
9012
,
message
.
getOptionalSint32
());
assertEquals
(
3456
,
message
.
getOptionalFixed32
());
assertEquals
(
7890
,
message
.
getOptionalSfixed32
());
assertEquals
(
1.5f
,
message
.
getOptionalFloat
());
assertEquals
(
1.25
,
message
.
getOptionalDouble
());
assertEquals
(
true
,
message
.
getOptionalBool
());
}
private
void
assertRejects
(
String
name
,
String
value
)
{
TestAllTypes
.
Builder
builder
=
TestAllTypes
.
newBuilder
();
try
{
// Numeric form is rejected.
mergeFromJson
(
"{\""
+
name
+
"\":"
+
value
+
"}"
,
builder
);
fail
(
"Exception is expected."
);
}
catch
(
IOException
e
)
{
// Expected.
}
try
{
// String form is also rejected.
mergeFromJson
(
"{\""
+
name
+
"\":\""
+
value
+
"\"}"
,
builder
);
fail
(
"Exception is expected."
);
}
catch
(
IOException
e
)
{
// Expected.
}
}
private
void
assertAccepts
(
String
name
,
String
value
)
throws
IOException
{
TestAllTypes
.
Builder
builder
=
TestAllTypes
.
newBuilder
();
// Both numeric form and string form are accepted.
mergeFromJson
(
"{\""
+
name
+
"\":"
+
value
+
"}"
,
builder
);
mergeFromJson
(
"{\""
+
name
+
"\":\""
+
value
+
"\"}"
,
builder
);
}
public
void
testParserRejectOutOfRangeNumericValues
()
throws
Exception
{
assertAccepts
(
"optionalInt32"
,
String
.
valueOf
(
Integer
.
MAX_VALUE
));
assertAccepts
(
"optionalInt32"
,
String
.
valueOf
(
Integer
.
MIN_VALUE
));
assertRejects
(
"optionalInt32"
,
String
.
valueOf
(
Integer
.
MAX_VALUE
+
1L
));
assertRejects
(
"optionalInt32"
,
String
.
valueOf
(
Integer
.
MIN_VALUE
-
1L
));
assertAccepts
(
"optionalUint32"
,
String
.
valueOf
(
Integer
.
MAX_VALUE
+
1L
));
assertRejects
(
"optionalUint32"
,
"123456789012345"
);
assertRejects
(
"optionalUint32"
,
"-1"
);
BigInteger
one
=
new
BigInteger
(
"1"
);
BigInteger
maxLong
=
new
BigInteger
(
String
.
valueOf
(
Long
.
MAX_VALUE
));
BigInteger
minLong
=
new
BigInteger
(
String
.
valueOf
(
Long
.
MIN_VALUE
));
assertAccepts
(
"optionalInt64"
,
maxLong
.
toString
());
assertAccepts
(
"optionalInt64"
,
minLong
.
toString
());
assertRejects
(
"optionalInt64"
,
maxLong
.
add
(
one
).
toString
());
assertRejects
(
"optionalInt64"
,
minLong
.
subtract
(
one
).
toString
());
assertAccepts
(
"optionalUint64"
,
maxLong
.
add
(
one
).
toString
());
assertRejects
(
"optionalUint64"
,
"1234567890123456789012345"
);
assertRejects
(
"optionalUint64"
,
"-1"
);
assertAccepts
(
"optionalBool"
,
"true"
);
assertRejects
(
"optionalBool"
,
"1"
);
assertRejects
(
"optionalBool"
,
"0"
);
assertAccepts
(
"optionalFloat"
,
String
.
valueOf
(
Float
.
MAX_VALUE
));
assertAccepts
(
"optionalFloat"
,
String
.
valueOf
(-
Float
.
MAX_VALUE
));
assertRejects
(
"optionalFloat"
,
String
.
valueOf
(
Double
.
MAX_VALUE
));
assertRejects
(
"optionalFloat"
,
String
.
valueOf
(-
Double
.
MAX_VALUE
));
BigDecimal
moreThanOne
=
new
BigDecimal
(
"1.000001"
);
BigDecimal
maxDouble
=
new
BigDecimal
(
Double
.
MAX_VALUE
);
BigDecimal
minDouble
=
new
BigDecimal
(-
Double
.
MAX_VALUE
);
assertAccepts
(
"optionalDouble"
,
maxDouble
.
toString
());
assertAccepts
(
"optionalDouble"
,
minDouble
.
toString
());
assertRejects
(
"optionalDouble"
,
maxDouble
.
multiply
(
moreThanOne
).
toString
());
assertRejects
(
"optionalDouble"
,
minDouble
.
multiply
(
moreThanOne
).
toString
());
}
public
void
testParserAcceptNull
()
throws
Exception
{
TestAllTypes
.
Builder
builder
=
TestAllTypes
.
newBuilder
();
mergeFromJson
(
"{\n"
+
" \"optionalInt32\": null,\n"
+
" \"optionalInt64\": null,\n"
+
" \"optionalUint32\": null,\n"
+
" \"optionalUint64\": null,\n"
+
" \"optionalSint32\": null,\n"
+
" \"optionalSint64\": null,\n"
+
" \"optionalFixed32\": null,\n"
+
" \"optionalFixed64\": null,\n"
+
" \"optionalSfixed32\": null,\n"
+
" \"optionalSfixed64\": null,\n"
+
" \"optionalFloat\": null,\n"
+
" \"optionalDouble\": null,\n"
+
" \"optionalBool\": null,\n"
+
" \"optionalString\": null,\n"
+
" \"optionalBytes\": null,\n"
+
" \"optionalNestedMessage\": null,\n"
+
" \"optionalNestedEnum\": null,\n"
+
" \"repeatedInt32\": null,\n"
+
" \"repeatedInt64\": null,\n"
+
" \"repeatedUint32\": null,\n"
+
" \"repeatedUint64\": null,\n"
+
" \"repeatedSint32\": null,\n"
+
" \"repeatedSint64\": null,\n"
+
" \"repeatedFixed32\": null,\n"
+
" \"repeatedFixed64\": null,\n"
+
" \"repeatedSfixed32\": null,\n"
+
" \"repeatedSfixed64\": null,\n"
+
" \"repeatedFloat\": null,\n"
+
" \"repeatedDouble\": null,\n"
+
" \"repeatedBool\": null,\n"
+
" \"repeatedString\": null,\n"
+
" \"repeatedBytes\": null,\n"
+
" \"repeatedNestedMessage\": null,\n"
+
" \"repeatedNestedEnum\": null\n"
+
"}"
,
builder
);
TestAllTypes
message
=
builder
.
build
();
assertEquals
(
TestAllTypes
.
getDefaultInstance
(),
message
);
// Repeated field elements can also be null.
builder
=
TestAllTypes
.
newBuilder
();
mergeFromJson
(
"{\n"
+
" \"repeatedInt32\": [null, null],\n"
+
" \"repeatedInt64\": [null, null],\n"
+
" \"repeatedUint32\": [null, null],\n"
+
" \"repeatedUint64\": [null, null],\n"
+
" \"repeatedSint32\": [null, null],\n"
+
" \"repeatedSint64\": [null, null],\n"
+
" \"repeatedFixed32\": [null, null],\n"
+
" \"repeatedFixed64\": [null, null],\n"
+
" \"repeatedSfixed32\": [null, null],\n"
+
" \"repeatedSfixed64\": [null, null],\n"
+
" \"repeatedFloat\": [null, null],\n"
+
" \"repeatedDouble\": [null, null],\n"
+
" \"repeatedBool\": [null, null],\n"
+
" \"repeatedString\": [null, null],\n"
+
" \"repeatedBytes\": [null, null],\n"
+
" \"repeatedNestedMessage\": [null, null],\n"
+
" \"repeatedNestedEnum\": [null, null]\n"
+
"}"
,
builder
);
message
=
builder
.
build
();
// "null" elements will be parsed to default values.
assertEquals
(
2
,
message
.
getRepeatedInt32Count
());
assertEquals
(
0
,
message
.
getRepeatedInt32
(
0
));
assertEquals
(
0
,
message
.
getRepeatedInt32
(
1
));
assertEquals
(
2
,
message
.
getRepeatedInt32Count
());
assertEquals
(
0
,
message
.
getRepeatedInt32
(
0
));
assertEquals
(
0
,
message
.
getRepeatedInt32
(
1
));
assertEquals
(
2
,
message
.
getRepeatedInt64Count
());
assertEquals
(
0
,
message
.
getRepeatedInt64
(
0
));
assertEquals
(
0
,
message
.
getRepeatedInt64
(
1
));
assertEquals
(
2
,
message
.
getRepeatedUint32Count
());
assertEquals
(
0
,
message
.
getRepeatedUint32
(
0
));
assertEquals
(
0
,
message
.
getRepeatedUint32
(
1
));
assertEquals
(
2
,
message
.
getRepeatedUint64Count
());
assertEquals
(
0
,
message
.
getRepeatedUint64
(
0
));
assertEquals
(
0
,
message
.
getRepeatedUint64
(
1
));
assertEquals
(
2
,
message
.
getRepeatedSint32Count
());
assertEquals
(
0
,
message
.
getRepeatedSint32
(
0
));
assertEquals
(
0
,
message
.
getRepeatedSint32
(
1
));
assertEquals
(
2
,
message
.
getRepeatedSint64Count
());
assertEquals
(
0
,
message
.
getRepeatedSint64
(
0
));
assertEquals
(
0
,
message
.
getRepeatedSint64
(
1
));
assertEquals
(
2
,
message
.
getRepeatedFixed32Count
());
assertEquals
(
0
,
message
.
getRepeatedFixed32
(
0
));
assertEquals
(
0
,
message
.
getRepeatedFixed32
(
1
));
assertEquals
(
2
,
message
.
getRepeatedFixed64Count
());
assertEquals
(
0
,
message
.
getRepeatedFixed64
(
0
));
assertEquals
(
0
,
message
.
getRepeatedFixed64
(
1
));
assertEquals
(
2
,
message
.
getRepeatedSfixed32Count
());
assertEquals
(
0
,
message
.
getRepeatedSfixed32
(
0
));
assertEquals
(
0
,
message
.
getRepeatedSfixed32
(
1
));
assertEquals
(
2
,
message
.
getRepeatedSfixed64Count
());
assertEquals
(
0
,
message
.
getRepeatedSfixed64
(
0
));
assertEquals
(
0
,
message
.
getRepeatedSfixed64
(
1
));
assertEquals
(
2
,
message
.
getRepeatedFloatCount
());
assertEquals
(
0
f
,
message
.
getRepeatedFloat
(
0
));
assertEquals
(
0
f
,
message
.
getRepeatedFloat
(
1
));
assertEquals
(
2
,
message
.
getRepeatedDoubleCount
());
assertEquals
(
0.0
,
message
.
getRepeatedDouble
(
0
));
assertEquals
(
0.0
,
message
.
getRepeatedDouble
(
1
));
assertEquals
(
2
,
message
.
getRepeatedBoolCount
());
assertFalse
(
message
.
getRepeatedBool
(
0
));
assertFalse
(
message
.
getRepeatedBool
(
1
));
assertEquals
(
2
,
message
.
getRepeatedStringCount
());
assertTrue
(
message
.
getRepeatedString
(
0
).
isEmpty
());
assertTrue
(
message
.
getRepeatedString
(
1
).
isEmpty
());
assertEquals
(
2
,
message
.
getRepeatedBytesCount
());
assertTrue
(
message
.
getRepeatedBytes
(
0
).
isEmpty
());
assertTrue
(
message
.
getRepeatedBytes
(
1
).
isEmpty
());
assertEquals
(
2
,
message
.
getRepeatedNestedMessageCount
());
assertEquals
(
NestedMessage
.
getDefaultInstance
(),
message
.
getRepeatedNestedMessage
(
0
));
assertEquals
(
NestedMessage
.
getDefaultInstance
(),
message
.
getRepeatedNestedMessage
(
1
));
assertEquals
(
2
,
message
.
getRepeatedNestedEnumCount
());
assertEquals
(
0
,
message
.
getRepeatedNestedEnumValue
(
0
));
assertEquals
(
0
,
message
.
getRepeatedNestedEnumValue
(
1
));
}
public
void
testMapFields
()
throws
Exception
{
TestMap
.
Builder
builder
=
TestMap
.
newBuilder
();
builder
.
getMutableInt32ToInt32Map
().
put
(
1
,
10
);
builder
.
getMutableInt64ToInt32Map
().
put
(
1234567890123456789L
,
10
);
builder
.
getMutableUint32ToInt32Map
().
put
(
2
,
20
);
builder
.
getMutableUint64ToInt32Map
().
put
(
2234567890123456789L
,
20
);
builder
.
getMutableSint32ToInt32Map
().
put
(
3
,
30
);
builder
.
getMutableSint64ToInt32Map
().
put
(
3234567890123456789L
,
30
);
builder
.
getMutableFixed32ToInt32Map
().
put
(
4
,
40
);
builder
.
getMutableFixed64ToInt32Map
().
put
(
4234567890123456789L
,
40
);
builder
.
getMutableSfixed32ToInt32Map
().
put
(
5
,
50
);
builder
.
getMutableSfixed64ToInt32Map
().
put
(
5234567890123456789L
,
50
);
builder
.
getMutableBoolToInt32Map
().
put
(
false
,
6
);
builder
.
getMutableStringToInt32Map
().
put
(
"Hello"
,
10
);
builder
.
getMutableInt32ToInt64Map
().
put
(
1
,
1234567890123456789L
);
builder
.
getMutableInt32ToUint32Map
().
put
(
2
,
20
);
builder
.
getMutableInt32ToUint64Map
().
put
(
2
,
2234567890123456789L
);
builder
.
getMutableInt32ToSint32Map
().
put
(
3
,
30
);
builder
.
getMutableInt32ToSint64Map
().
put
(
3
,
3234567890123456789L
);
builder
.
getMutableInt32ToFixed32Map
().
put
(
4
,
40
);
builder
.
getMutableInt32ToFixed64Map
().
put
(
4
,
4234567890123456789L
);
builder
.
getMutableInt32ToSfixed32Map
().
put
(
5
,
50
);
builder
.
getMutableInt32ToSfixed64Map
().
put
(
5
,
5234567890123456789L
);
builder
.
getMutableInt32ToFloatMap
().
put
(
6
,
1.5f
);
builder
.
getMutableInt32ToDoubleMap
().
put
(
6
,
1.25
);
builder
.
getMutableInt32ToBoolMap
().
put
(
7
,
false
);
builder
.
getMutableInt32ToStringMap
().
put
(
7
,
"World"
);
builder
.
getMutableInt32ToBytesMap
().
put
(
8
,
ByteString
.
copyFrom
(
new
byte
[]{
1
,
2
,
3
}));
builder
.
getMutableInt32ToMessageMap
().
put
(
8
,
NestedMessage
.
newBuilder
().
setValue
(
1234
).
build
());
builder
.
getMutableInt32ToEnumMap
().
put
(
9
,
NestedEnum
.
BAR
);
TestMap
message
=
builder
.
build
();
assertEquals
(
"{\n"
+
" \"int32ToInt32Map\": {\n"
+
" \"1\": 10\n"
+
" },\n"
+
" \"int64ToInt32Map\": {\n"
+
" \"1234567890123456789\": 10\n"
+
" },\n"
+
" \"uint32ToInt32Map\": {\n"
+
" \"2\": 20\n"
+
" },\n"
+
" \"uint64ToInt32Map\": {\n"
+
" \"2234567890123456789\": 20\n"
+
" },\n"
+
" \"sint32ToInt32Map\": {\n"
+
" \"3\": 30\n"
+
" },\n"
+
" \"sint64ToInt32Map\": {\n"
+
" \"3234567890123456789\": 30\n"
+
" },\n"
+
" \"fixed32ToInt32Map\": {\n"
+
" \"4\": 40\n"
+
" },\n"
+
" \"fixed64ToInt32Map\": {\n"
+
" \"4234567890123456789\": 40\n"
+
" },\n"
+
" \"sfixed32ToInt32Map\": {\n"
+
" \"5\": 50\n"
+
" },\n"
+
" \"sfixed64ToInt32Map\": {\n"
+
" \"5234567890123456789\": 50\n"
+
" },\n"
+
" \"boolToInt32Map\": {\n"
+
" \"false\": 6\n"
+
" },\n"
+
" \"stringToInt32Map\": {\n"
+
" \"Hello\": 10\n"
+
" },\n"
+
" \"int32ToInt64Map\": {\n"
+
" \"1\": \"1234567890123456789\"\n"
+
" },\n"
+
" \"int32ToUint32Map\": {\n"
+
" \"2\": 20\n"
+
" },\n"
+
" \"int32ToUint64Map\": {\n"
+
" \"2\": \"2234567890123456789\"\n"
+
" },\n"
+
" \"int32ToSint32Map\": {\n"
+
" \"3\": 30\n"
+
" },\n"
+
" \"int32ToSint64Map\": {\n"
+
" \"3\": \"3234567890123456789\"\n"
+
" },\n"
+
" \"int32ToFixed32Map\": {\n"
+
" \"4\": 40\n"
+
" },\n"
+
" \"int32ToFixed64Map\": {\n"
+
" \"4\": \"4234567890123456789\"\n"
+
" },\n"
+
" \"int32ToSfixed32Map\": {\n"
+
" \"5\": 50\n"
+
" },\n"
+
" \"int32ToSfixed64Map\": {\n"
+
" \"5\": \"5234567890123456789\"\n"
+
" },\n"
+
" \"int32ToFloatMap\": {\n"
+
" \"6\": 1.5\n"
+
" },\n"
+
" \"int32ToDoubleMap\": {\n"
+
" \"6\": 1.25\n"
+
" },\n"
+
" \"int32ToBoolMap\": {\n"
+
" \"7\": false\n"
+
" },\n"
+
" \"int32ToStringMap\": {\n"
+
" \"7\": \"World\"\n"
+
" },\n"
+
" \"int32ToBytesMap\": {\n"
+
" \"8\": \"AQID\"\n"
+
" },\n"
+
" \"int32ToMessageMap\": {\n"
+
" \"8\": {\n"
+
" \"value\": 1234\n"
+
" }\n"
+
" },\n"
+
" \"int32ToEnumMap\": {\n"
+
" \"9\": \"BAR\"\n"
+
" }\n"
+
"}"
,
toJsonString
(
message
));
assertRoundTripEquals
(
message
);
// Test multiple entries.
builder
=
TestMap
.
newBuilder
();
builder
.
getMutableInt32ToInt32Map
().
put
(
1
,
2
);
builder
.
getMutableInt32ToInt32Map
().
put
(
3
,
4
);
message
=
builder
.
build
();
assertEquals
(
"{\n"
+
" \"int32ToInt32Map\": {\n"
+
" \"1\": 2,\n"
+
" \"3\": 4\n"
+
" }\n"
+
"}"
,
toJsonString
(
message
));
assertRoundTripEquals
(
message
);
}
public
void
testMapNullValueIsDefault
()
throws
Exception
{
TestMap
.
Builder
builder
=
TestMap
.
newBuilder
();
mergeFromJson
(
"{\n"
+
" \"int32ToInt32Map\": {\"1\": null},\n"
+
" \"int32ToMessageMap\": {\"2\": null}\n"
+
"}"
,
builder
);
TestMap
message
=
builder
.
build
();
assertTrue
(
message
.
getInt32ToInt32Map
().
containsKey
(
1
));
assertEquals
(
0
,
message
.
getInt32ToInt32Map
().
get
(
1
).
intValue
());
assertTrue
(
message
.
getInt32ToMessageMap
().
containsKey
(
2
));
assertEquals
(
0
,
message
.
getInt32ToMessageMap
().
get
(
2
).
getValue
());
}
public
void
testParserAcceptNonQuotedObjectKey
()
throws
Exception
{
TestMap
.
Builder
builder
=
TestMap
.
newBuilder
();
mergeFromJson
(
"{\n"
+
" int32ToInt32Map: {1: 2},\n"
+
" stringToInt32Map: {hello: 3}\n"
+
"}"
,
builder
);
TestMap
message
=
builder
.
build
();
assertEquals
(
2
,
message
.
getInt32ToInt32Map
().
get
(
1
).
intValue
());
assertEquals
(
3
,
message
.
getStringToInt32Map
().
get
(
"hello"
).
intValue
());
}
public
void
testWrappers
()
throws
Exception
{
TestWrappers
.
Builder
builder
=
TestWrappers
.
newBuilder
();
builder
.
getBoolValueBuilder
().
setValue
(
false
);
builder
.
getInt32ValueBuilder
().
setValue
(
0
);
builder
.
getInt64ValueBuilder
().
setValue
(
0
);
builder
.
getUint32ValueBuilder
().
setValue
(
0
);
builder
.
getUint64ValueBuilder
().
setValue
(
0
);
builder
.
getFloatValueBuilder
().
setValue
(
0.0f
);
builder
.
getDoubleValueBuilder
().
setValue
(
0.0
);
builder
.
getStringValueBuilder
().
setValue
(
""
);
builder
.
getBytesValueBuilder
().
setValue
(
ByteString
.
EMPTY
);
TestWrappers
message
=
builder
.
build
();
assertEquals
(
"{\n"
+
" \"int32Value\": 0,\n"
+
" \"uint32Value\": 0,\n"
+
" \"int64Value\": \"0\",\n"
+
" \"uint64Value\": \"0\",\n"
+
" \"floatValue\": 0.0,\n"
+
" \"doubleValue\": 0.0,\n"
+
" \"boolValue\": false,\n"
+
" \"stringValue\": \"\",\n"
+
" \"bytesValue\": \"\"\n"
+
"}"
,
toJsonString
(
message
));
assertRoundTripEquals
(
message
);
builder
=
TestWrappers
.
newBuilder
();
builder
.
getBoolValueBuilder
().
setValue
(
true
);
builder
.
getInt32ValueBuilder
().
setValue
(
1
);
builder
.
getInt64ValueBuilder
().
setValue
(
2
);
builder
.
getUint32ValueBuilder
().
setValue
(
3
);
builder
.
getUint64ValueBuilder
().
setValue
(
4
);
builder
.
getFloatValueBuilder
().
setValue
(
5.0f
);
builder
.
getDoubleValueBuilder
().
setValue
(
6.0
);
builder
.
getStringValueBuilder
().
setValue
(
"7"
);
builder
.
getBytesValueBuilder
().
setValue
(
ByteString
.
copyFrom
(
new
byte
[]{
8
}));
message
=
builder
.
build
();
assertEquals
(
"{\n"
+
" \"int32Value\": 1,\n"
+
" \"uint32Value\": 3,\n"
+
" \"int64Value\": \"2\",\n"
+
" \"uint64Value\": \"4\",\n"
+
" \"floatValue\": 5.0,\n"
+
" \"doubleValue\": 6.0,\n"
+
" \"boolValue\": true,\n"
+
" \"stringValue\": \"7\",\n"
+
" \"bytesValue\": \"CA==\"\n"
+
"}"
,
toJsonString
(
message
));
assertRoundTripEquals
(
message
);
}
public
void
testTimestamp
()
throws
Exception
{
TestTimestamp
message
=
TestTimestamp
.
newBuilder
()
.
setTimestampValue
(
TimeUtil
.
parseTimestamp
(
"1970-01-01T00:00:00Z"
))
.
build
();
assertEquals
(
"{\n"
+
" \"timestampValue\": \"1970-01-01T00:00:00Z\"\n"
+
"}"
,
toJsonString
(
message
));
assertRoundTripEquals
(
message
);
}
public
void
testDuration
()
throws
Exception
{
TestDuration
message
=
TestDuration
.
newBuilder
()
.
setDurationValue
(
TimeUtil
.
parseDuration
(
"12345s"
))
.
build
();
assertEquals
(
"{\n"
+
" \"durationValue\": \"12345s\"\n"
+
"}"
,
toJsonString
(
message
));
assertRoundTripEquals
(
message
);
}
public
void
testFieldMask
()
throws
Exception
{
TestFieldMask
message
=
TestFieldMask
.
newBuilder
()
.
setFieldMaskValue
(
FieldMaskUtil
.
fromString
(
"foo.bar,baz"
))
.
build
();
assertEquals
(
"{\n"
+
" \"fieldMaskValue\": \"foo.bar,baz\"\n"
+
"}"
,
toJsonString
(
message
));
assertRoundTripEquals
(
message
);
}
public
void
testStruct
()
throws
Exception
{
// Build a struct with all possible values.
TestStruct
.
Builder
builder
=
TestStruct
.
newBuilder
();
Struct
.
Builder
structBuilder
=
builder
.
getStructValueBuilder
();
structBuilder
.
getMutableFields
().
put
(
"null_value"
,
Value
.
newBuilder
().
setNullValueValue
(
0
).
build
());
structBuilder
.
getMutableFields
().
put
(
"number_value"
,
Value
.
newBuilder
().
setNumberValue
(
1.25
).
build
());
structBuilder
.
getMutableFields
().
put
(
"string_value"
,
Value
.
newBuilder
().
setStringValue
(
"hello"
).
build
());
Struct
.
Builder
subStructBuilder
=
Struct
.
newBuilder
();
subStructBuilder
.
getMutableFields
().
put
(
"number_value"
,
Value
.
newBuilder
().
setNumberValue
(
1234
).
build
());
structBuilder
.
getMutableFields
().
put
(
"struct_value"
,
Value
.
newBuilder
().
setStructValue
(
subStructBuilder
.
build
()).
build
());
ListValue
.
Builder
listBuilder
=
ListValue
.
newBuilder
();
listBuilder
.
addValues
(
Value
.
newBuilder
().
setNumberValue
(
1.125
).
build
());
listBuilder
.
addValues
(
Value
.
newBuilder
().
setNullValueValue
(
0
).
build
());
structBuilder
.
getMutableFields
().
put
(
"list_value"
,
Value
.
newBuilder
().
setListValue
(
listBuilder
.
build
()).
build
());
TestStruct
message
=
builder
.
build
();
assertEquals
(
"{\n"
+
" \"structValue\": {\n"
+
" \"null_value\": null,\n"
+
" \"number_value\": 1.25,\n"
+
" \"string_value\": \"hello\",\n"
+
" \"struct_value\": {\n"
+
" \"number_value\": 1234.0\n"
+
" },\n"
+
" \"list_value\": [1.125, null]\n"
+
" }\n"
+
"}"
,
toJsonString
(
message
));
assertRoundTripEquals
(
message
);
}
public
void
testAnyFields
()
throws
Exception
{
TestAllTypes
content
=
TestAllTypes
.
newBuilder
().
setOptionalInt32
(
1234
).
build
();
TestAny
message
=
TestAny
.
newBuilder
().
setAnyValue
(
Any
.
pack
(
content
)).
build
();
// A TypeRegistry must be provided in order to convert Any types.
try
{
toJsonString
(
message
);
fail
(
"Exception is expected."
);
}
catch
(
IOException
e
)
{
// Expected.
}
JsonFormat
.
TypeRegistry
registry
=
JsonFormat
.
TypeRegistry
.
newBuilder
()
.
add
(
TestAllTypes
.
getDescriptor
()).
build
();
JsonFormat
.
Printer
printer
=
JsonFormat
.
printer
().
usingTypeRegistry
(
registry
);
assertEquals
(
"{\n"
+
" \"anyValue\": {\n"
+
" \"@type\": \"type.googleapis.com/json_test.TestAllTypes\",\n"
+
" \"optionalInt32\": 1234\n"
+
" }\n"
+
"}"
,
printer
.
print
(
message
));
assertRoundTripEquals
(
message
,
registry
);
// Well-known types have a special formatting when embedded in Any.
//
// 1. Any in Any.
Any
anyMessage
=
Any
.
pack
(
Any
.
pack
(
content
));
assertEquals
(
"{\n"
+
" \"@type\": \"type.googleapis.com/google.protobuf.Any\",\n"
+
" \"value\": {\n"
+
" \"@type\": \"type.googleapis.com/json_test.TestAllTypes\",\n"
+
" \"optionalInt32\": 1234\n"
+
" }\n"
+
"}"
,
printer
.
print
(
anyMessage
));
assertRoundTripEquals
(
anyMessage
,
registry
);
// 2. Wrappers in Any.
anyMessage
=
Any
.
pack
(
Int32Value
.
newBuilder
().
setValue
(
12345
).
build
());
assertEquals
(
"{\n"
+
" \"@type\": \"type.googleapis.com/google.protobuf.Int32Value\",\n"
+
" \"value\": 12345\n"
+
"}"
,
printer
.
print
(
anyMessage
));
assertRoundTripEquals
(
anyMessage
,
registry
);
anyMessage
=
Any
.
pack
(
UInt32Value
.
newBuilder
().
setValue
(
12345
).
build
());
assertEquals
(
"{\n"
+
" \"@type\": \"type.googleapis.com/google.protobuf.UInt32Value\",\n"
+
" \"value\": 12345\n"
+
"}"
,
printer
.
print
(
anyMessage
));
assertRoundTripEquals
(
anyMessage
,
registry
);
anyMessage
=
Any
.
pack
(
Int64Value
.
newBuilder
().
setValue
(
12345
).
build
());
assertEquals
(
"{\n"
+
" \"@type\": \"type.googleapis.com/google.protobuf.Int64Value\",\n"
+
" \"value\": \"12345\"\n"
+
"}"
,
printer
.
print
(
anyMessage
));
assertRoundTripEquals
(
anyMessage
,
registry
);
anyMessage
=
Any
.
pack
(
UInt64Value
.
newBuilder
().
setValue
(
12345
).
build
());
assertEquals
(
"{\n"
+
" \"@type\": \"type.googleapis.com/google.protobuf.UInt64Value\",\n"
+
" \"value\": \"12345\"\n"
+
"}"
,
printer
.
print
(
anyMessage
));
assertRoundTripEquals
(
anyMessage
,
registry
);
anyMessage
=
Any
.
pack
(
FloatValue
.
newBuilder
().
setValue
(
12345
).
build
());
assertEquals
(
"{\n"
+
" \"@type\": \"type.googleapis.com/google.protobuf.FloatValue\",\n"
+
" \"value\": 12345.0\n"
+
"}"
,
printer
.
print
(
anyMessage
));
assertRoundTripEquals
(
anyMessage
,
registry
);
anyMessage
=
Any
.
pack
(
DoubleValue
.
newBuilder
().
setValue
(
12345
).
build
());
assertEquals
(
"{\n"
+
" \"@type\": \"type.googleapis.com/google.protobuf.DoubleValue\",\n"
+
" \"value\": 12345.0\n"
+
"}"
,
printer
.
print
(
anyMessage
));
assertRoundTripEquals
(
anyMessage
,
registry
);
anyMessage
=
Any
.
pack
(
BoolValue
.
newBuilder
().
setValue
(
true
).
build
());
assertEquals
(
"{\n"
+
" \"@type\": \"type.googleapis.com/google.protobuf.BoolValue\",\n"
+
" \"value\": true\n"
+
"}"
,
printer
.
print
(
anyMessage
));
assertRoundTripEquals
(
anyMessage
,
registry
);
anyMessage
=
Any
.
pack
(
StringValue
.
newBuilder
().
setValue
(
"Hello"
).
build
());
assertEquals
(
"{\n"
+
" \"@type\": \"type.googleapis.com/google.protobuf.StringValue\",\n"
+
" \"value\": \"Hello\"\n"
+
"}"
,
printer
.
print
(
anyMessage
));
assertRoundTripEquals
(
anyMessage
,
registry
);
anyMessage
=
Any
.
pack
(
BytesValue
.
newBuilder
().
setValue
(
ByteString
.
copyFrom
(
new
byte
[]{
1
,
2
})).
build
());
assertEquals
(
"{\n"
+
" \"@type\": \"type.googleapis.com/google.protobuf.BytesValue\",\n"
+
" \"value\": \"AQI=\"\n"
+
"}"
,
printer
.
print
(
anyMessage
));
assertRoundTripEquals
(
anyMessage
,
registry
);
// 3. Timestamp in Any.
anyMessage
=
Any
.
pack
(
TimeUtil
.
parseTimestamp
(
"1969-12-31T23:59:59Z"
));
assertEquals
(
"{\n"
+
" \"@type\": \"type.googleapis.com/google.protobuf.Timestamp\",\n"
+
" \"value\": \"1969-12-31T23:59:59Z\"\n"
+
"}"
,
printer
.
print
(
anyMessage
));
assertRoundTripEquals
(
anyMessage
,
registry
);
// 4. Duration in Any
anyMessage
=
Any
.
pack
(
TimeUtil
.
parseDuration
(
"12345.10s"
));
assertEquals
(
"{\n"
+
" \"@type\": \"type.googleapis.com/google.protobuf.Duration\",\n"
+
" \"value\": \"12345.100s\"\n"
+
"}"
,
printer
.
print
(
anyMessage
));
assertRoundTripEquals
(
anyMessage
,
registry
);
// 5. FieldMask in Any
anyMessage
=
Any
.
pack
(
FieldMaskUtil
.
fromString
(
"foo.bar,baz"
));
assertEquals
(
"{\n"
+
" \"@type\": \"type.googleapis.com/google.protobuf.FieldMask\",\n"
+
" \"value\": \"foo.bar,baz\"\n"
+
"}"
,
printer
.
print
(
anyMessage
));
assertRoundTripEquals
(
anyMessage
,
registry
);
// 6. Struct in Any
Struct
.
Builder
structBuilder
=
Struct
.
newBuilder
();
structBuilder
.
getMutableFields
().
put
(
"number"
,
Value
.
newBuilder
().
setNumberValue
(
1.125
).
build
());
anyMessage
=
Any
.
pack
(
structBuilder
.
build
());
assertEquals
(
"{\n"
+
" \"@type\": \"type.googleapis.com/google.protobuf.Struct\",\n"
+
" \"value\": {\n"
+
" \"number\": 1.125\n"
+
" }\n"
+
"}"
,
printer
.
print
(
anyMessage
));
assertRoundTripEquals
(
anyMessage
,
registry
);
}
public
void
testParserMissingTypeUrl
()
throws
Exception
{
try
{
Any
.
Builder
builder
=
Any
.
newBuilder
();
mergeFromJson
(
"{\n"
+
" \"optionalInt32\": 1234\n"
+
"}"
,
builder
);
fail
(
"Exception is expected."
);
}
catch
(
IOException
e
)
{
// Expected.
}
}
public
void
testParserUnexpectedTypeUrl
()
throws
Exception
{
try
{
TestAllTypes
.
Builder
builder
=
TestAllTypes
.
newBuilder
();
mergeFromJson
(
"{\n"
+
" \"@type\": \"type.googleapis.com/json_test.TestAllTypes\",\n"
+
" \"optionalInt32\": 12345\n"
+
"}"
,
builder
);
fail
(
"Exception is expected."
);
}
catch
(
IOException
e
)
{
// Expected.
}
}
public
void
testParserRejectTrailingComma
()
throws
Exception
{
try
{
TestAllTypes
.
Builder
builder
=
TestAllTypes
.
newBuilder
();
mergeFromJson
(
"{\n"
+
" \"optionalInt32\": 12345,\n"
+
"}"
,
builder
);
fail
(
"Exception is expected."
);
}
catch
(
IOException
e
)
{
// Expected.
}
// TODO(xiaofeng): GSON allows trailing comma in arrays even after I set
// the JsonReader to non-lenient mode. If we want to enforce strict JSON
// compliance, we might want to switch to a different JSON parser or
// implement one by ourselves.
// try {
// TestAllTypes.Builder builder = TestAllTypes.newBuilder();
// JsonFormat.merge(
// "{\n"
// + " \"repeatedInt32\": [12345,]\n"
// + "}", builder);
// fail("Exception is expected.");
// } catch (IOException e) {
// // Expected.
// }
}
public
void
testParserRejectInvalidBase64
()
throws
Exception
{
try
{
TestAllTypes
.
Builder
builder
=
TestAllTypes
.
newBuilder
();
mergeFromJson
(
"{\n"
+
" \"optionalBytes\": \"!@#$\"\n"
+
"}"
,
builder
);
fail
(
"Exception is expected."
);
}
catch
(
InvalidProtocolBufferException
e
)
{
// Expected.
}
}
public
void
testParserRejectInvalidEnumValue
()
throws
Exception
{
try
{
TestAllTypes
.
Builder
builder
=
TestAllTypes
.
newBuilder
();
mergeFromJson
(
"{\n"
+
" \"optionalNestedEnum\": \"XXX\"\n"
+
"}"
,
builder
);
fail
(
"Exception is expected."
);
}
catch
(
InvalidProtocolBufferException
e
)
{
// Expected.
}
}
}
java/util/src/test/java/com/google/protobuf/util/TimeUtilTest.java
0 → 100644
View file @
fbb3ef28
// 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
.
util
;
import
com.google.protobuf.Duration
;
import
com.google.protobuf.Timestamp
;
import
junit.framework.TestCase
;
import
org.junit.Assert
;
import
java.text.ParseException
;
/** Unit tests for {@link TimeUtil}. */
public
class
TimeUtilTest
extends
TestCase
{
public
void
testTimestampStringFormat
()
throws
Exception
{
Timestamp
start
=
TimeUtil
.
parseTimestamp
(
"0001-01-01T00:00:00Z"
);
Timestamp
end
=
TimeUtil
.
parseTimestamp
(
"9999-12-31T23:59:59.999999999Z"
);
assertEquals
(
TimeUtil
.
TIMESTAMP_SECONDS_MIN
,
start
.
getSeconds
());
assertEquals
(
0
,
start
.
getNanos
());
assertEquals
(
TimeUtil
.
TIMESTAMP_SECONDS_MAX
,
end
.
getSeconds
());
assertEquals
(
999999999
,
end
.
getNanos
());
assertEquals
(
"0001-01-01T00:00:00Z"
,
TimeUtil
.
toString
(
start
));
assertEquals
(
"9999-12-31T23:59:59.999999999Z"
,
TimeUtil
.
toString
(
end
));
Timestamp
value
=
TimeUtil
.
parseTimestamp
(
"1970-01-01T00:00:00Z"
);
assertEquals
(
0
,
value
.
getSeconds
());
assertEquals
(
0
,
value
.
getNanos
());
// Test negative timestamps.
value
=
TimeUtil
.
parseTimestamp
(
"1969-12-31T23:59:59.999Z"
);
assertEquals
(-
1
,
value
.
getSeconds
());
// Nano part is in the range of [0, 999999999] for Timestamp.
assertEquals
(
999000000
,
value
.
getNanos
());
// Test that 3, 6, or 9 digits are used for the fractional part.
value
=
Timestamp
.
newBuilder
().
setNanos
(
10
).
build
();
assertEquals
(
"1970-01-01T00:00:00.000000010Z"
,
TimeUtil
.
toString
(
value
));
value
=
Timestamp
.
newBuilder
().
setNanos
(
10000
).
build
();
assertEquals
(
"1970-01-01T00:00:00.000010Z"
,
TimeUtil
.
toString
(
value
));
value
=
Timestamp
.
newBuilder
().
setNanos
(
10000000
).
build
();
assertEquals
(
"1970-01-01T00:00:00.010Z"
,
TimeUtil
.
toString
(
value
));
// Test that parsing accepts timezone offsets.
value
=
TimeUtil
.
parseTimestamp
(
"1970-01-01T00:00:00.010+08:00"
);
assertEquals
(
"1969-12-31T16:00:00.010Z"
,
TimeUtil
.
toString
(
value
));
value
=
TimeUtil
.
parseTimestamp
(
"1970-01-01T00:00:00.010-08:00"
);
assertEquals
(
"1970-01-01T08:00:00.010Z"
,
TimeUtil
.
toString
(
value
));
}
public
void
testTimetampInvalidFormat
()
throws
Exception
{
try
{
// Value too small.
Timestamp
value
=
Timestamp
.
newBuilder
()
.
setSeconds
(
TimeUtil
.
TIMESTAMP_SECONDS_MIN
-
1
).
build
();
TimeUtil
.
toString
(
value
);
Assert
.
fail
(
"Exception is expected."
);
}
catch
(
IllegalArgumentException
e
)
{
// Expected.
}
try
{
// Value too large.
Timestamp
value
=
Timestamp
.
newBuilder
()
.
setSeconds
(
TimeUtil
.
TIMESTAMP_SECONDS_MAX
+
1
).
build
();
TimeUtil
.
toString
(
value
);
Assert
.
fail
(
"Exception is expected."
);
}
catch
(
IllegalArgumentException
e
)
{
// Expected.
}
try
{
// Invalid nanos value.
Timestamp
value
=
Timestamp
.
newBuilder
().
setNanos
(-
1
).
build
();
TimeUtil
.
toString
(
value
);
Assert
.
fail
(
"Exception is expected."
);
}
catch
(
IllegalArgumentException
e
)
{
// Expected.
}
try
{
// Invalid nanos value.
Timestamp
value
=
Timestamp
.
newBuilder
().
setNanos
(
1000000000
).
build
();
TimeUtil
.
toString
(
value
);
Assert
.
fail
(
"Exception is expected."
);
}
catch
(
IllegalArgumentException
e
)
{
// Expected.
}
try
{
// Value to small.
TimeUtil
.
parseTimestamp
(
"0000-01-01T00:00:00Z"
);
Assert
.
fail
(
"Exception is expected."
);
}
catch
(
ParseException
e
)
{
// Expected.
}
try
{
// Value to large.
TimeUtil
.
parseTimestamp
(
"10000-01-01T00:00:00Z"
);
Assert
.
fail
(
"Exception is expected."
);
}
catch
(
ParseException
e
)
{
// Expected.
}
try
{
// Missing 'T'.
TimeUtil
.
parseTimestamp
(
"1970-01-01 00:00:00Z"
);
Assert
.
fail
(
"Exception is expected."
);
}
catch
(
ParseException
e
)
{
// Expected.
}
try
{
// Missing 'Z'.
TimeUtil
.
parseTimestamp
(
"1970-01-01T00:00:00"
);
Assert
.
fail
(
"Exception is expected."
);
}
catch
(
ParseException
e
)
{
// Expected.
}
try
{
// Invalid offset.
TimeUtil
.
parseTimestamp
(
"1970-01-01T00:00:00+0000"
);
Assert
.
fail
(
"Exception is expected."
);
}
catch
(
ParseException
e
)
{
// Expected.
}
try
{
// Trailing text.
TimeUtil
.
parseTimestamp
(
"1970-01-01T00:00:00Z0"
);
Assert
.
fail
(
"Exception is expected."
);
}
catch
(
ParseException
e
)
{
// Expected.
}
try
{
// Invalid nanosecond value.
TimeUtil
.
parseTimestamp
(
"1970-01-01T00:00:00.ABCZ"
);
Assert
.
fail
(
"Exception is expected."
);
}
catch
(
ParseException
e
)
{
// Expected.
}
}
public
void
testDurationStringFormat
()
throws
Exception
{
Timestamp
start
=
TimeUtil
.
parseTimestamp
(
"0001-01-01T00:00:00Z"
);
Timestamp
end
=
TimeUtil
.
parseTimestamp
(
"9999-12-31T23:59:59.999999999Z"
);
Duration
duration
=
TimeUtil
.
distance
(
start
,
end
);
assertEquals
(
"315537897599.999999999s"
,
TimeUtil
.
toString
(
duration
));
duration
=
TimeUtil
.
distance
(
end
,
start
);
assertEquals
(
"-315537897599.999999999s"
,
TimeUtil
.
toString
(
duration
));
// Generated output should contain 3, 6, or 9 fractional digits.
duration
=
Duration
.
newBuilder
().
setSeconds
(
1
).
build
();
assertEquals
(
"1s"
,
TimeUtil
.
toString
(
duration
));
duration
=
Duration
.
newBuilder
().
setNanos
(
10000000
).
build
();
assertEquals
(
"0.010s"
,
TimeUtil
.
toString
(
duration
));
duration
=
Duration
.
newBuilder
().
setNanos
(
10000
).
build
();
assertEquals
(
"0.000010s"
,
TimeUtil
.
toString
(
duration
));
duration
=
Duration
.
newBuilder
().
setNanos
(
10
).
build
();
assertEquals
(
"0.000000010s"
,
TimeUtil
.
toString
(
duration
));
// Parsing accepts an fractional digits as long as they fit into nano
// precision.
duration
=
TimeUtil
.
parseDuration
(
"0.1s"
);
assertEquals
(
100000000
,
duration
.
getNanos
());
duration
=
TimeUtil
.
parseDuration
(
"0.0001s"
);
assertEquals
(
100000
,
duration
.
getNanos
());
duration
=
TimeUtil
.
parseDuration
(
"0.0000001s"
);
assertEquals
(
100
,
duration
.
getNanos
());
// Duration must support range from -315,576,000,000s to +315576000000s
// which includes negative values.
duration
=
TimeUtil
.
parseDuration
(
"315576000000.999999999s"
);
assertEquals
(
315576000000L
,
duration
.
getSeconds
());
assertEquals
(
999999999
,
duration
.
getNanos
());
duration
=
TimeUtil
.
parseDuration
(
"-315576000000.999999999s"
);
assertEquals
(-
315576000000L
,
duration
.
getSeconds
());
assertEquals
(-
999999999
,
duration
.
getNanos
());
}
public
void
testDurationInvalidFormat
()
throws
Exception
{
try
{
// Value too small.
Duration
value
=
Duration
.
newBuilder
()
.
setSeconds
(
TimeUtil
.
DURATION_SECONDS_MIN
-
1
).
build
();
TimeUtil
.
toString
(
value
);
Assert
.
fail
(
"Exception is expected."
);
}
catch
(
IllegalArgumentException
e
)
{
// Expected.
}
try
{
// Value too large.
Duration
value
=
Duration
.
newBuilder
()
.
setSeconds
(
TimeUtil
.
DURATION_SECONDS_MAX
+
1
).
build
();
TimeUtil
.
toString
(
value
);
Assert
.
fail
(
"Exception is expected."
);
}
catch
(
IllegalArgumentException
e
)
{
// Expected.
}
try
{
// Invalid nanos value.
Duration
value
=
Duration
.
newBuilder
().
setSeconds
(
1
).
setNanos
(-
1
)
.
build
();
TimeUtil
.
toString
(
value
);
Assert
.
fail
(
"Exception is expected."
);
}
catch
(
IllegalArgumentException
e
)
{
// Expected.
}
try
{
// Invalid nanos value.
Duration
value
=
Duration
.
newBuilder
().
setSeconds
(-
1
).
setNanos
(
1
)
.
build
();
TimeUtil
.
toString
(
value
);
Assert
.
fail
(
"Exception is expected."
);
}
catch
(
IllegalArgumentException
e
)
{
// Expected.
}
try
{
// Value too small.
TimeUtil
.
parseDuration
(
"-315576000001s"
);
Assert
.
fail
(
"Exception is expected."
);
}
catch
(
ParseException
e
)
{
// Expected.
}
try
{
// Value too large.
TimeUtil
.
parseDuration
(
"315576000001s"
);
Assert
.
fail
(
"Exception is expected."
);
}
catch
(
ParseException
e
)
{
// Expected.
}
try
{
// Empty.
TimeUtil
.
parseDuration
(
""
);
Assert
.
fail
(
"Exception is expected."
);
}
catch
(
ParseException
e
)
{
// Expected.
}
try
{
// Missing "s".
TimeUtil
.
parseDuration
(
"0"
);
Assert
.
fail
(
"Exception is expected."
);
}
catch
(
ParseException
e
)
{
// Expected.
}
try
{
// Invalid trailing data.
TimeUtil
.
parseDuration
(
"0s0"
);
Assert
.
fail
(
"Exception is expected."
);
}
catch
(
ParseException
e
)
{
// Expected.
}
try
{
// Invalid prefix.
TimeUtil
.
parseDuration
(
"--1s"
);
Assert
.
fail
(
"Exception is expected."
);
}
catch
(
ParseException
e
)
{
// Expected.
}
}
public
void
testTimestampConversion
()
throws
Exception
{
Timestamp
timestamp
=
TimeUtil
.
parseTimestamp
(
"1970-01-01T00:00:01.111111111Z"
);
assertEquals
(
1111111111
,
TimeUtil
.
toNanos
(
timestamp
));
assertEquals
(
1111111
,
TimeUtil
.
toMicros
(
timestamp
));
assertEquals
(
1111
,
TimeUtil
.
toMillis
(
timestamp
));
timestamp
=
TimeUtil
.
createTimestampFromNanos
(
1111111111
);
assertEquals
(
"1970-01-01T00:00:01.111111111Z"
,
TimeUtil
.
toString
(
timestamp
));
timestamp
=
TimeUtil
.
createTimestampFromMicros
(
1111111
);
assertEquals
(
"1970-01-01T00:00:01.111111Z"
,
TimeUtil
.
toString
(
timestamp
));
timestamp
=
TimeUtil
.
createTimestampFromMillis
(
1111
);
assertEquals
(
"1970-01-01T00:00:01.111Z"
,
TimeUtil
.
toString
(
timestamp
));
timestamp
=
TimeUtil
.
parseTimestamp
(
"1969-12-31T23:59:59.111111111Z"
);
assertEquals
(-
888888889
,
TimeUtil
.
toNanos
(
timestamp
));
assertEquals
(-
888889
,
TimeUtil
.
toMicros
(
timestamp
));
assertEquals
(-
889
,
TimeUtil
.
toMillis
(
timestamp
));
timestamp
=
TimeUtil
.
createTimestampFromNanos
(-
888888889
);
assertEquals
(
"1969-12-31T23:59:59.111111111Z"
,
TimeUtil
.
toString
(
timestamp
));
timestamp
=
TimeUtil
.
createTimestampFromMicros
(-
888889
);
assertEquals
(
"1969-12-31T23:59:59.111111Z"
,
TimeUtil
.
toString
(
timestamp
));
timestamp
=
TimeUtil
.
createTimestampFromMillis
(-
889
);
assertEquals
(
"1969-12-31T23:59:59.111Z"
,
TimeUtil
.
toString
(
timestamp
));
}
public
void
testDurationConversion
()
throws
Exception
{
Duration
duration
=
TimeUtil
.
parseDuration
(
"1.111111111s"
);
assertEquals
(
1111111111
,
TimeUtil
.
toNanos
(
duration
));
assertEquals
(
1111111
,
TimeUtil
.
toMicros
(
duration
));
assertEquals
(
1111
,
TimeUtil
.
toMillis
(
duration
));
duration
=
TimeUtil
.
createDurationFromNanos
(
1111111111
);
assertEquals
(
"1.111111111s"
,
TimeUtil
.
toString
(
duration
));
duration
=
TimeUtil
.
createDurationFromMicros
(
1111111
);
assertEquals
(
"1.111111s"
,
TimeUtil
.
toString
(
duration
));
duration
=
TimeUtil
.
createDurationFromMillis
(
1111
);
assertEquals
(
"1.111s"
,
TimeUtil
.
toString
(
duration
));
duration
=
TimeUtil
.
parseDuration
(
"-1.111111111s"
);
assertEquals
(-
1111111111
,
TimeUtil
.
toNanos
(
duration
));
assertEquals
(-
1111111
,
TimeUtil
.
toMicros
(
duration
));
assertEquals
(-
1111
,
TimeUtil
.
toMillis
(
duration
));
duration
=
TimeUtil
.
createDurationFromNanos
(-
1111111111
);
assertEquals
(
"-1.111111111s"
,
TimeUtil
.
toString
(
duration
));
duration
=
TimeUtil
.
createDurationFromMicros
(-
1111111
);
assertEquals
(
"-1.111111s"
,
TimeUtil
.
toString
(
duration
));
duration
=
TimeUtil
.
createDurationFromMillis
(-
1111
);
assertEquals
(
"-1.111s"
,
TimeUtil
.
toString
(
duration
));
}
public
void
testTimeOperations
()
throws
Exception
{
Timestamp
start
=
TimeUtil
.
parseTimestamp
(
"0001-01-01T00:00:00Z"
);
Timestamp
end
=
TimeUtil
.
parseTimestamp
(
"9999-12-31T23:59:59.999999999Z"
);
Duration
duration
=
TimeUtil
.
distance
(
start
,
end
);
assertEquals
(
"315537897599.999999999s"
,
TimeUtil
.
toString
(
duration
));
Timestamp
value
=
TimeUtil
.
add
(
start
,
duration
);
assertEquals
(
end
,
value
);
value
=
TimeUtil
.
subtract
(
end
,
duration
);
assertEquals
(
start
,
value
);
duration
=
TimeUtil
.
distance
(
end
,
start
);
assertEquals
(
"-315537897599.999999999s"
,
TimeUtil
.
toString
(
duration
));
value
=
TimeUtil
.
add
(
end
,
duration
);
assertEquals
(
start
,
value
);
value
=
TimeUtil
.
subtract
(
start
,
duration
);
assertEquals
(
end
,
value
);
// Result is larger than Long.MAX_VALUE.
try
{
duration
=
TimeUtil
.
parseDuration
(
"315537897599.999999999s"
);
duration
=
TimeUtil
.
multiply
(
duration
,
315537897599.999999999
);
Assert
.
fail
(
"Exception is expected."
);
}
catch
(
IllegalArgumentException
e
)
{
// Expected.
}
// Result is lesser than Long.MIN_VALUE.
try
{
duration
=
TimeUtil
.
parseDuration
(
"315537897599.999999999s"
);
duration
=
TimeUtil
.
multiply
(
duration
,
-
315537897599.999999999
);
Assert
.
fail
(
"Exception is expected."
);
}
catch
(
IllegalArgumentException
e
)
{
// Expected.
}
duration
=
TimeUtil
.
parseDuration
(
"-1.125s"
);
duration
=
TimeUtil
.
divide
(
duration
,
2.0
);
assertEquals
(
"-0.562500s"
,
TimeUtil
.
toString
(
duration
));
duration
=
TimeUtil
.
multiply
(
duration
,
2.0
);
assertEquals
(
"-1.125s"
,
TimeUtil
.
toString
(
duration
));
duration
=
TimeUtil
.
add
(
duration
,
duration
);
assertEquals
(
"-2.250s"
,
TimeUtil
.
toString
(
duration
));
duration
=
TimeUtil
.
subtract
(
duration
,
TimeUtil
.
parseDuration
(
"-1s"
));
assertEquals
(
"-1.250s"
,
TimeUtil
.
toString
(
duration
));
// Multiplications (with results larger than Long.MAX_VALUE in nanoseconds).
duration
=
TimeUtil
.
parseDuration
(
"0.999999999s"
);
assertEquals
(
"315575999684.424s"
,
TimeUtil
.
toString
(
TimeUtil
.
multiply
(
duration
,
315576000000L
)));
duration
=
TimeUtil
.
parseDuration
(
"-0.999999999s"
);
assertEquals
(
"-315575999684.424s"
,
TimeUtil
.
toString
(
TimeUtil
.
multiply
(
duration
,
315576000000L
)));
assertEquals
(
"315575999684.424s"
,
TimeUtil
.
toString
(
TimeUtil
.
multiply
(
duration
,
-
315576000000L
)));
// Divisions (with values larger than Long.MAX_VALUE in nanoseconds).
Duration
d1
=
TimeUtil
.
parseDuration
(
"315576000000s"
);
Duration
d2
=
TimeUtil
.
subtract
(
d1
,
TimeUtil
.
createDurationFromNanos
(
1
));
assertEquals
(
1
,
TimeUtil
.
divide
(
d1
,
d2
));
assertEquals
(
0
,
TimeUtil
.
divide
(
d2
,
d1
));
assertEquals
(
"0.000000001s"
,
TimeUtil
.
toString
(
TimeUtil
.
remainder
(
d1
,
d2
)));
assertEquals
(
"315575999999.999999999s"
,
TimeUtil
.
toString
(
TimeUtil
.
remainder
(
d2
,
d1
)));
// Divisions involving negative values.
//
// (-5) / 2 = -2, remainder = -1
d1
=
TimeUtil
.
parseDuration
(
"-5s"
);
d2
=
TimeUtil
.
parseDuration
(
"2s"
);
assertEquals
(-
2
,
TimeUtil
.
divide
(
d1
,
d2
));
assertEquals
(-
2
,
TimeUtil
.
divide
(
d1
,
2
).
getSeconds
());
assertEquals
(-
1
,
TimeUtil
.
remainder
(
d1
,
d2
).
getSeconds
());
// (-5) / (-2) = 2, remainder = -1
d1
=
TimeUtil
.
parseDuration
(
"-5s"
);
d2
=
TimeUtil
.
parseDuration
(
"-2s"
);
assertEquals
(
2
,
TimeUtil
.
divide
(
d1
,
d2
));
assertEquals
(
2
,
TimeUtil
.
divide
(
d1
,
-
2
).
getSeconds
());
assertEquals
(-
1
,
TimeUtil
.
remainder
(
d1
,
d2
).
getSeconds
());
// 5 / (-2) = -2, remainder = 1
d1
=
TimeUtil
.
parseDuration
(
"5s"
);
d2
=
TimeUtil
.
parseDuration
(
"-2s"
);
assertEquals
(-
2
,
TimeUtil
.
divide
(
d1
,
d2
));
assertEquals
(-
2
,
TimeUtil
.
divide
(
d1
,
-
2
).
getSeconds
());
assertEquals
(
1
,
TimeUtil
.
remainder
(
d1
,
d2
).
getSeconds
());
}
}
java/util/src/test/java/com/google/protobuf/util/json_test.proto
0 → 100644
View file @
fbb3ef28
// 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
json_test
;
option
java_package
=
"com.google.protobuf.util"
;
option
java_outer_classname
=
"JsonTestProto"
;
import
"google/protobuf/any.proto"
;
import
"google/protobuf/wrappers.proto"
;
import
"google/protobuf/timestamp.proto"
;
import
"google/protobuf/duration.proto"
;
import
"google/protobuf/field_mask.proto"
;
import
"google/protobuf/struct.proto"
;
message
TestAllTypes
{
enum
NestedEnum
{
FOO
=
0
;
BAR
=
1
;
BAZ
=
2
;
}
message
NestedMessage
{
int32
value
=
1
;
}
int32
optional_int32
=
1
;
int64
optional_int64
=
2
;
uint32
optional_uint32
=
3
;
uint64
optional_uint64
=
4
;
sint32
optional_sint32
=
5
;
sint64
optional_sint64
=
6
;
fixed32
optional_fixed32
=
7
;
fixed64
optional_fixed64
=
8
;
sfixed32
optional_sfixed32
=
9
;
sfixed64
optional_sfixed64
=
10
;
float
optional_float
=
11
;
double
optional_double
=
12
;
bool
optional_bool
=
13
;
string
optional_string
=
14
;
bytes
optional_bytes
=
15
;
NestedMessage
optional_nested_message
=
18
;
NestedEnum
optional_nested_enum
=
21
;
// Repeated
repeated
int32
repeated_int32
=
31
;
repeated
int64
repeated_int64
=
32
;
repeated
uint32
repeated_uint32
=
33
;
repeated
uint64
repeated_uint64
=
34
;
repeated
sint32
repeated_sint32
=
35
;
repeated
sint64
repeated_sint64
=
36
;
repeated
fixed32
repeated_fixed32
=
37
;
repeated
fixed64
repeated_fixed64
=
38
;
repeated
sfixed32
repeated_sfixed32
=
39
;
repeated
sfixed64
repeated_sfixed64
=
40
;
repeated
float
repeated_float
=
41
;
repeated
double
repeated_double
=
42
;
repeated
bool
repeated_bool
=
43
;
repeated
string
repeated_string
=
44
;
repeated
bytes
repeated_bytes
=
45
;
repeated
NestedMessage
repeated_nested_message
=
48
;
repeated
NestedEnum
repeated_nested_enum
=
51
;
}
message
TestMap
{
// Instead of testing all combinations (too many), we only make sure all
// valid types have been used at least in one field as key and in one
// field as value.
map
<
int32
,
int32
>
int32_to_int32_map
=
1
;
map
<
int64
,
int32
>
int64_to_int32_map
=
2
;
map
<
uint32
,
int32
>
uint32_to_int32_map
=
3
;
map
<
uint64
,
int32
>
uint64_to_int32_map
=
4
;
map
<
sint32
,
int32
>
sint32_to_int32_map
=
5
;
map
<
sint64
,
int32
>
sint64_to_int32_map
=
6
;
map
<
fixed32
,
int32
>
fixed32_to_int32_map
=
7
;
map
<
fixed64
,
int32
>
fixed64_to_int32_map
=
8
;
map
<
sfixed32
,
int32
>
sfixed32_to_int32_map
=
9
;
map
<
sfixed64
,
int32
>
sfixed64_to_int32_map
=
10
;
map
<
bool
,
int32
>
bool_to_int32_map
=
11
;
map
<
string
,
int32
>
string_to_int32_map
=
12
;
map
<
int32
,
int64
>
int32_to_int64_map
=
101
;
map
<
int32
,
uint32
>
int32_to_uint32_map
=
102
;
map
<
int32
,
uint64
>
int32_to_uint64_map
=
103
;
map
<
int32
,
sint32
>
int32_to_sint32_map
=
104
;
map
<
int32
,
sint64
>
int32_to_sint64_map
=
105
;
map
<
int32
,
fixed32
>
int32_to_fixed32_map
=
106
;
map
<
int32
,
fixed64
>
int32_to_fixed64_map
=
107
;
map
<
int32
,
sfixed32
>
int32_to_sfixed32_map
=
108
;
map
<
int32
,
sfixed64
>
int32_to_sfixed64_map
=
109
;
map
<
int32
,
float
>
int32_to_float_map
=
110
;
map
<
int32
,
double
>
int32_to_double_map
=
111
;
map
<
int32
,
bool
>
int32_to_bool_map
=
112
;
map
<
int32
,
string
>
int32_to_string_map
=
113
;
map
<
int32
,
bytes
>
int32_to_bytes_map
=
114
;
map
<
int32
,
TestAllTypes.NestedMessage
>
int32_to_message_map
=
115
;
map
<
int32
,
TestAllTypes.NestedEnum
>
int32_to_enum_map
=
116
;
}
message
TestWrappers
{
google.protobuf.Int32Value
int32_value
=
1
;
google.protobuf.UInt32Value
uint32_value
=
2
;
google.protobuf.Int64Value
int64_value
=
3
;
google.protobuf.UInt64Value
uint64_value
=
4
;
google.protobuf.FloatValue
float_value
=
5
;
google.protobuf.DoubleValue
double_value
=
6
;
google.protobuf.BoolValue
bool_value
=
7
;
google.protobuf.StringValue
string_value
=
8
;
google.protobuf.BytesValue
bytes_value
=
9
;
}
message
TestTimestamp
{
google.protobuf.Timestamp
timestamp_value
=
1
;
}
message
TestDuration
{
google.protobuf.Duration
duration_value
=
1
;
}
message
TestFieldMask
{
google.protobuf.FieldMask
field_mask_value
=
1
;
}
message
TestStruct
{
google.protobuf.Struct
struct_value
=
1
;
}
message
TestAny
{
google.protobuf.Any
any_value
=
1
;
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment