Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
9e4879e
Introduce composite SpineProtoGenerator
dmdashenkov May 10, 2019
557904a
Merge branch 'master' into builder-vbuild
dmdashenkov May 10, 2019
b5401b2
Introduce generator for `vBuilder` method
dmdashenkov May 10, 2019
de3c48a
Fix `base` tests
dmdashenkov May 13, 2019
0b34058
Make ValidatingBuilder interface implement Message.Builder
dmdashenkov May 13, 2019
06b656a
Introduce diff
dmdashenkov May 13, 2019
05df0cf
Add methods for testing message state transitions into `Validate`
dmdashenkov May 13, 2019
f775ec3
Fix base builder generation and drop base VBuilder generation
dmdashenkov May 13, 2019
5d2e130
Clean up
dmdashenkov May 13, 2019
2d0d491
Add a test
dmdashenkov May 13, 2019
5ee7107
Mark `validate.ValidatingBuilder` as deprecated
dmdashenkov May 13, 2019
debefe3
Make `UseValidatingBuilders` check NOP
dmdashenkov May 13, 2019
78eed2c
Remove utilities for `UseValidatingBuilders`
dmdashenkov May 13, 2019
3e155cf
Remove more utilities for `UseValidatingBuilders`
dmdashenkov May 13, 2019
bee38f8
Create `UseVBuild` error-prone check
dmdashenkov May 13, 2019
4646983
Clean up
dmdashenkov May 14, 2019
b7640f8
Fix Javadoc links
dmdashenkov May 14, 2019
0b20292
Clean up matchers and add one more suggested fix
dmdashenkov May 14, 2019
f6f7506
Clean up
dmdashenkov May 14, 2019
0669a53
Enable and fix ErrorProne check tests
dmdashenkov May 14, 2019
4d8976c
Mark generated validating builders as deprecated
dmdashenkov May 14, 2019
3f8626a
Remove an unused class
dmdashenkov May 14, 2019
019eeae
Un-deprecate validating builders
dmdashenkov May 14, 2019
aec7d6f
Remove a TODO and clean up
dmdashenkov May 14, 2019
e64f824
Document new API
dmdashenkov May 14, 2019
98f4c35
Fix test method naming
dmdashenkov May 14, 2019
c64b7a0
Add test for the new API of Validate
dmdashenkov May 14, 2019
9c4e47e
Improve doc of Diff
dmdashenkov May 14, 2019
ee19e3a
Remove empty lines
dmdashenkov May 14, 2019
9dccc97
Document `UseVBuilder`.
dmdashenkov May 14, 2019
ab8b016
Properly document ErrorProne check
dmdashenkov May 14, 2019
9c6d097
Remove unnecessary test logging
dmdashenkov May 15, 2019
b3f3775
Clean up ErrorProne check
dmdashenkov May 15, 2019
0ed7243
Fix doc
dmdashenkov May 15, 2019
7d8f1d1
Document protobuf.ValidatingBuilder properly
dmdashenkov May 15, 2019
c66719f
Fix doc
dmdashenkov May 15, 2019
a9984ce
Match method references in the ErrorProne check
dmdashenkov May 15, 2019
bee3bd6
Fix test Java package
dmdashenkov May 15, 2019
973c32f
Add more test cases
dmdashenkov May 15, 2019
88f7f21
Clean up
dmdashenkov May 15, 2019
09c13c8
Publish base locally before ErrorProne check tests
dmdashenkov May 15, 2019
84c224c
Clean up doc
dmdashenkov May 15, 2019
8a2a017
Disable ErrorProne check tests
dmdashenkov May 15, 2019
5cffd00
Merge branch 'master' into builder-vbuild
dmdashenkov May 15, 2019
31ad049
Delete an unused method
dmdashenkov May 15, 2019
f9eccd0
Clean up and add tests for FieldDeclaration
dmdashenkov May 15, 2019
2f1f2ab
Merge branch 'builder-vbuild' of github.com:SpineEventEngine/base int…
dmdashenkov May 15, 2019
cf72a9f
Add functional tests for `protobuf.ValidatingBuilder`
dmdashenkov May 15, 2019
f1ac15e
Rename temporary artifact dir in `base-validating-builders`
dmdashenkov May 16, 2019
9a05fcc
Document a package
dmdashenkov May 16, 2019
d958a23
Use kebab case for a directory name instead of camel case
dmdashenkov May 16, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 7 additions & 10 deletions base-validating-builders/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -92,35 +92,32 @@ dependencies {
}

modelCompiler {
mainProtoSrcDir = "$projectDir/../base/src/main/proto"
generateValidatingBuilders = false
}

sourceSets {
main.java.srcDirs "$projectDir/generated/main/spine"
main.proto.srcDirs = [modelCompiler.mainProtoSrcDir]
main.proto.srcDirs = ["$projectDir/../base/src/main/proto"]
}

protobuf {
generateProtoTasks {
all().each {
it.builtins {
remove java
}
it.plugins {
remove grpc
}
}
}
}

ext.buildersDir = "$projectDir/builders"
ext.compiledProtoDir = "$projectDir/compiled-proto"

task copyCompiledClasses(type: Copy) {
from sourceSets.main.java.outputDir
into buildersDir
into compiledProtoDir

include {
it.isDirectory() || it.name.endsWith('VBuilder.class')
it.isDirectory() || it.name.endsWith('.class')
}

dependsOn compileJava
Expand All @@ -142,11 +139,11 @@ build.doLast {
}

task cleanGenerated(type: Delete) {
delete files("$projectDir/generated", "$projectDir/build", "$projectDir/.spine", buildersDir)
delete files("$projectDir/generated", "$projectDir/build", "$projectDir/.spine", compiledProtoDir)
}

clean.dependsOn cleanGenerated

idea.module {
generatedSourceDirs += buildersDir
generatedSourceDirs += compiledProtoDir
}
2 changes: 1 addition & 1 deletion base/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ sourceSets {

jar {
// See `base-validating-builders/README.md`.
from "$rootDir/base-validating-builders/builders"
from "$rootDir/base-validating-builders/compiled-proto"
}

build.doLast {
Expand Down
62 changes: 53 additions & 9 deletions base/src/main/java/io/spine/code/proto/FieldDeclaration.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,12 @@
package io.spine.code.proto;

import com.google.common.base.Joiner;
import com.google.protobuf.DescriptorProtos.DescriptorProto;
import com.google.common.base.Objects;
import com.google.protobuf.DescriptorProtos.FieldDescriptorProto;
import com.google.protobuf.Descriptors.FieldDescriptor;
import com.google.protobuf.Descriptors.FieldDescriptor.JavaType;
import com.google.protobuf.Descriptors.FileDescriptor;
import com.google.protobuf.Message;
import io.spine.base.MessageFile;
import io.spine.code.java.ClassName;
import io.spine.logging.Logging;
Expand All @@ -37,11 +38,13 @@
import io.spine.type.TypeName;
import io.spine.type.TypeUrl;
import io.spine.type.UnknownTypeException;
import io.spine.validate.Validate;

import java.util.List;
import java.util.Optional;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.protobuf.DescriptorProtos.DescriptorProto.FIELD_FIELD_NUMBER;
import static com.google.protobuf.Descriptors.FieldDescriptor.Type.ENUM;
import static com.google.protobuf.Descriptors.FieldDescriptor.Type.MESSAGE;
import static com.google.protobuf.Descriptors.FieldDescriptor.Type.STRING;
Expand Down Expand Up @@ -97,6 +100,34 @@ public MessageType declaringType() {
return declaringMessage;
}

/**
* Checks if the given value is the default value for this field.
*
* @param fieldValue
* the value of the field
* @return {@code true} if the given value is default for this field, {@code false} otherwise
*/
public boolean isDefault(Object fieldValue) {
checkNotNull(fieldValue);
if (isMessage()) {
if (fieldValue instanceof Message) {
Message message = (Message) fieldValue;
return Validate.isDefault(message) && sameMessageType(message);
} else {
return false;
}
} else {
return fieldValue.equals(field.getDefaultValue());
}
}

private boolean sameMessageType(Message msg) {
String messageClassName = msg.getClass()
.getName();
String fieldClassName = messageClassName();
return fieldClassName.equals(messageClassName);
}

/**
* Obtains fully-qualified name of the Java class that corresponds to the declared type
* of the field.
Expand Down Expand Up @@ -247,11 +278,6 @@ public FieldDeclaration valueDeclaration() {
return new FieldDeclaration(valueDescriptor);
}

/** Returns the name of the type of this field. */
public String typeName(){
return field.getType().name();
}

private boolean isEntityField() {
EntityOption entityOption = field.getContainingType()
.getOptions()
Expand Down Expand Up @@ -304,17 +330,35 @@ public Optional<String> leadingComments() {
private LocationPath fieldPath() {
LocationPath locationPath = new LocationPath();
locationPath.addAll(declaringMessage.path());
locationPath.add(DescriptorProto.FIELD_FIELD_NUMBER);
locationPath.add(FIELD_FIELD_NUMBER);
int fieldIndex = fieldIndex();
locationPath.add(fieldIndex);
return locationPath;
}

private int fieldIndex() {
FieldDescriptorProto fproto = this.field.toProto();
FieldDescriptorProto proto = this.field.toProto();
return declaringMessage.descriptor()
.toProto()
.getFieldList()
.indexOf(fproto);
.indexOf(proto);
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof FieldDeclaration)) {
return false;
}
FieldDeclaration that = (FieldDeclaration) o;
return Objects.equal(declaringMessage, that.declaringMessage) &&
Objects.equal(field.getFullName(), that.field.getFullName());
}

@Override
public int hashCode() {
return Objects.hashCode(declaringMessage, field.getFullName());
}
}
136 changes: 136 additions & 0 deletions base/src/main/java/io/spine/protobuf/Diff.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/*
* Copyright 2019, TeamDev. All rights reserved.
*
* Redistribution and use in source and/or binary forms, with or without
* modification, must retain the above copyright notice and the following
* disclaimer.
*
* 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 io.spine.protobuf;

import com.google.common.base.Objects;
import com.google.common.collect.ImmutableSet;
import com.google.protobuf.Descriptors.FieldDescriptor;
import com.google.protobuf.Message;
import io.spine.annotation.Internal;
import io.spine.code.proto.FieldDeclaration;

import java.util.Map;
import java.util.Set;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.common.collect.Sets.symmetricDifference;
import static java.util.stream.Collectors.toSet;

/**
* Difference between two messages of the same type.
*
* <p>For two messages {@code A} and {@code B}, their diff includes all the fields which are present
* in {@code A} and not in {@code B}, all the fields which are present in {@code B} and not in
* {@code A}, and all the fields which are present in both messages but have different values.
*
* @see com.google.common.collect.Sets#symmetricDifference(Set, Set) Sets.symmetricDifference(..)
*/
@Internal
public final class Diff {

private final ImmutableSet<FieldDeclaration> fields;

private Diff(ImmutableSet<FieldDeclaration> fields) {
this.fields = fields;
}

/**
* Calculates the difference between the given two messages.
*
* @param a
* one message
* @param b
* the other message
* @param <M>
* the type of the messages
* @return difference between the messages
* @throws IllegalArgumentException
* if the types of the messages are not the same
*/
public static <M extends Message> Diff between(M a, M b) {
checkNotNull(a);
checkNotNull(b);
checkArgument(a.getClass().equals(b.getClass()));
ImmutableSet<FieldDeclaration> fields =
symmetricDifference(decompose(a), decompose(b))
.stream()
.map(tuple -> tuple.declaration)
.collect(toImmutableSet());
return new Diff(fields);
}

private static Set<FieldTuple> decompose(Message message) {
Map<FieldDescriptor, Object> fieldMap = message.getAllFields();
return fieldMap
.entrySet()
.stream()
.map(entry -> new FieldTuple(
new FieldDeclaration(entry.getKey()), entry.getValue()
))
.collect(toSet());
}

/**
* Checks if the given field is present in the diff or not.
*
* @param field
* the field declaration to find
* @return {@code true} if the field has different values in the two given messages,
* {@code false} otherwise
*/
public boolean contains(FieldDeclaration field) {
return fields.contains(field);
}

/**
* A field declaration and a value of that field.
*/
private static final class FieldTuple {

private final FieldDeclaration declaration;
private final Object value;

private FieldTuple(FieldDeclaration declaration, Object value) {
this.declaration = checkNotNull(declaration);
this.value = checkNotNull(value);
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof FieldTuple)) {
return false;
}
FieldTuple tuple = (FieldTuple) o;
return Objects.equal(declaration, tuple.declaration) &&
Objects.equal(value, tuple.value);
}

@Override
public int hashCode() {
return Objects.hashCode(declaration, value);
}
}
}
77 changes: 77 additions & 0 deletions base/src/main/java/io/spine/protobuf/ValidatingBuilder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* Copyright 2019, TeamDev. All rights reserved.
*
* Redistribution and use in source and/or binary forms, with or without
* modification, must retain the above copyright notice and the following
* disclaimer.
*
* 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 io.spine.protobuf;

import com.google.protobuf.Message;
import io.spine.annotation.GeneratedMixin;
import io.spine.validate.NotValidated;
import io.spine.validate.Validate;
import io.spine.validate.Validated;
import io.spine.validate.ValidationException;

/**
* Implementation base for generated message builders.
*
* <p>This interface defines a default method {@link #vBuild()} which validates the built message
* before returning it to the user. In most cases, the users should use {@code vBuild()} and not
* the {@code build()}. If a user specifically needs to skip validation, they should use
* {@link #buildPartial()} to make the intent explicit.
*
* @param <M>
* the type of the message to build
*/
@GeneratedMixin
public interface ValidatingBuilder<M extends Message> extends Message.Builder {

/**
* Constructs the message with the given fields.
*
* <p>Users should not call this method directly. Instead, call {@link #vBuild()} for
* a validated message or {@link #buildPartial()} to skip message validation.
*/
@Override
@NotValidated M build();

/**
* Constructs the message with the given fields without validation.
*
* <p>Users should prefer {@link #vBuild()} over this method. However, in cases, when validation
* is not required, call this method instead of {@link #build()}.
*
* @return the build message, potentially invalid
*/
@Override
@NotValidated M buildPartial();

/**
* Constructs the message and {@linkplain Validate validates} it according to the constraints
* declared in Protobuf.
*
* @return the built message
* @throws ValidationException
* if the message is invalid
*/
default @Validated M vBuild() throws ValidationException {
M message = build();
Validate.checkValid(message);
return message;
}
}
Loading