Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/concurrency tests #6

Merged
merged 19 commits into from
Jan 13, 2017
Merged

Feature/concurrency tests #6

merged 19 commits into from
Jan 13, 2017

Conversation

frankwis
Copy link
Member

  • Added custom types and conversion for UUID, Date, BigInteger and BigDecimal
  • Fixed initialization of custom type converters
  • Added types for custom values and operations, fixed conversion
  • CustomTypeSerializer and corresponding Builder
  • Added headers to Patch operation
  • Concurrency tests for serialization

private final List<Operation> operations;

public Patch() {
operations = new ArrayList<>();
headers = new HashMap<>();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps we should align on whether to use this.....

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

btw why don't we use

this(new ArrayList<>()):

?

* This interface holds the schema for all valid value types.
*/
public interface DefaultSchema {
String VALUE =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Eventually move to file and load with getClass().getResource(...)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would probably make it more readable?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would not compile since the value is used in an annotation (only constants allowed there). Would need an indirection for compilation. I am adding a card to the backlog

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would not compile per se since the value is used in an annotation (only constants allowed there). Would need an indirection for compilation. I am adding a card to the backlog

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In that case generating that file from a json is probably the easiest?

}

public static class Builder {
final List<Schema> types = new ArrayList<>();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be private

import java.math.BigInteger;

/**
* This class converts an {@link BigDecimal} value into am Avro {@link IndexedRecord} and back. It depends on
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/am/an/g

public void serializesDefaultValueTypes() throws IOException {
Date date = new Date();
UUID uuid = UUID.randomUUID();
Patch patch = new Patch(ImmutableList.of(new Add<>(Path.of("some", "value"), "John Doe"),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps insert line break before new Add...


@Override
public int hashCode() {
return Objects.hash(name, number, id, bommel);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

futures.stream().forEach(future -> {
try {
verify(PATCHES, future.get());
} catch (InterruptedException ex) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use Java 8 multicatch?


public Operation generateOperation(Type type) {
switch (type) {
case ADD:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possibly refactor to enum strategy pattern?

}

public Object generate(Type type) {
switch (type) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactor to enum strategy pattern.

.withSchemata(Schema.createArray(BigDecimalType.SCHEMA))
.withConverters(new BigIntegerConversion(), new BigDecimalConversion())
.reserializeAndAssert(ImmutableList.of(BigDecimal.valueOf(1234567890L),
new BigDecimal("12345678901234567.8901234567890123456789012345678901234567890"),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extract constants.

@frankwis frankwis requested a review from thgreiner December 14, 2016 13:58

interface PatchType {
String NAME = Patch.class.getSimpleName();
String DOC = "This record represents a JSON PATCH operation holding a sequence of operations to apply to a"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JSON PATCH - does this description fit?

}

public Patch(List<Operation> operations) {
this.operations = new ArrayList<>(operations);
this.headers = new HashMap<>();
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not call the two-argument constructor here? This would follow the same principle as the no-argument constructor...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct :-)

new DateConversion(),
new BigIntegerConversion(),
new BigDecimalConversion(),
new UUIDConversion());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd say according to K&R style we would need to insert a line break before the last closing brace...


return allOf(all);
return allOf(items.stream()
.map(item -> hasItem(item))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.map(this::hasItem)

}

return allOf(all);
return allOf(items.stream()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Insert line break before items.stream()

ImmutableMap.of("header", UUID.randomUUID()));

@Test
public void equality() throws IOException {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Split the two asserts into two tests. Each assertion should be in a separate test to avoid failing assertions being hidden by assertions already failing before.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Best rule would be one assertion per test method.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps you could elaborate a bit on the test methods by naming them so they explain what is tested exactly.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we're considering to get rid of these classes (see #7), I'll ignore it for now.


@Test
public void equality() throws IOException {
assertThat(copyMovePatch, is(equalTo(copyMovePatchSibling)));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW: this equalTo() is an own matcher that would have been unnecessary if equals had been overridden in the first place. Then you would have been able to just use is as that is equivalent to is(equalTo(...) using the default hamcrest matcher

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am going to add a card to the backlog in order to remove the boilerplate test code

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I created issue #7 for future discussions and reference on this topic

MOVE(() -> new Move(generatePath(), generatePath())),
REMOVE(() -> new Remove(generatePath())),
REPLACE(() -> new Replace<>(generatePath(), generateValue())),
@SuppressWarnings("unchecked")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add comment why this was necessary?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dug a little bit deeper and figured out it's not necessary 👍

this.operationSupplier = operationSupplier;
}

public static Operation generate() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename method to reflect its random nature.

}

public static Operation generate() {
OperationGenerator operationType = values()[random.nextInt(values().length)];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inline variable as it's only used once.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMHO a variable should only be declared if it's used more than once.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. It just became a one liner during the last refactorization :-)

}

private static List<?> generateList(ValueType type) {
List<Object> data = new ArrayList<>();
Copy link
Member

@dhiller dhiller Dec 15, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe replace with

return IntStream
    .range(0, MAX_LIST_SIZE)
    .mapToObject(i -> type.generate())
    .collect(toList());

}

private static BigDecimal randomBigDecimal() {
BigInteger unscaledValue= new BigInteger(random.nextInt(256), random);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Space missing before =

}

public Builder withCustomTypes() {
types.addAll(Arrays.asList(BigDecimalType.SCHEMA, BigIntegerType.SCHEMA, DateType.SCHEMA, UuidType.SCHEMA));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm just wondering .. we have been using a special formatter with the ISO8601 format for dates. Would we need such a format as well for the DateTypes here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No. Date is just converted to a long value and is defined accurate as a timestamp representing the number of milliseconds since January 1, 1970, 00:00:00 GMT. (See io.datanerds.avropatch.value.conversion.DateConversion)

import org.apache.avro.generic.GenericFixed;

import java.nio.ByteBuffer;
import java.util.Date;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm .. just a general question .. could we built a DateConversion for the yodatime DateTime?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, but do we need it? ;-)

public static Operation generate() {
OperationGenerator operationType = values()[random.nextInt(values().length)];
return operationType.generateOperation();
public static Operation generateOperation() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

random? :)

Copy link

@dguenms dguenms left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please fix the typos.

Otherwise only formatting / minor refactoring suggestions.

this(new ArrayList<>(operations), new HashMap<>());
}

public Patch(List<Operation> operations, Map<String, ?> headers) {
this.operations = new ArrayList<>(operations);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Following the pattern above, this(operations);


interface BigDecimalType {
String NAME = "big-decimal";
String DOC = "BigDecimal value represented via it's scale and unscaled value.";
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"its" (typo)


interface UuidType {
String NAME = LogicalTypes.uuid().getName();
String DOC = "UUID serialized via two long values: It's most significant and least significant 64 bits.";
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Its" (see above)

static Schema create(Schema valueSchema) {
Field path = new Field("path", Path.SCHEMA, "Path pointing out where the JSON value should be replaced",
NO_DEFAULT);
Field value = new Field("value", valueSchema, "Actual value to replaced in patched object", NO_DEFAULT);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"to be replaced"

static Schema create(Schema valueSchema) {
Field path = new Field("path", Path.SCHEMA, "Path pointing out which JSON value should be tested against",
NO_DEFAULT);
Field value = new Field("value", valueSchema, "Actual value to test< against", NO_DEFAULT);
Copy link

@dguenms dguenms Dec 16, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove "<"


List<Future<List<Patch>>> futures = new ArrayList<>();
for (int i = 0; i < NUMBER_OF_THREADS; i++) {
futures.add(executorService.submit(() -> {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might improve readability to give this function a name so we can only do something like executorService.submit(this::serialize) or whatever name is appropriate.

Copy link

@abhishekjain88 abhishekjain88 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have only reviewed a few files. Plan to continue reviewing. Please take your time to address existing comments till then! :)

}

public Patch(List<Operation> operations) {
this(new ArrayList<>(operations), new HashMap<>());

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would potentially result in copying the list twice. Perhaps, makes more sense to just pass "operations" as is to the other constructor where this list would be copied again into a new list.

this.operations = new ArrayList<>(operations);
this.headers = new HashMap<>(headers);
}

public boolean addOperation(Operation operation) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thread safety will become an issue here. You are using a thread unsafe list for "operations". If there are 2 competing threads one of which does a getOperations and tries to iterate on it where as the other tries to add an operation at the same time, it will result in a ConcurrentModificationException. Would be better to use either a CopyOnWriteArrayList if operations are not expected to be added too frequently or synchronize getOperations and create a copy of the list before returning. AFAIK Collections.unmodifiableList is only a shallow copy and not a deep copy, so that would not handle it.

Copy link
Member

@dhiller dhiller Dec 19, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or explicitly point out the Thread unsafety in the class documentation. This might well be desired in favor of performance as a tradeoff. I think Effective Concurrency In Java notes mentioning something like

<b>This method is not thread-safe</b>

in the java docs of a class.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See here "Document Thread-Safety"

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Originally the class wasn't designed to be thread-safe, because there would only be one thread processing a PATCH call, but since removing this convenient method results in thread-safety I will do that.

@@ -35,16 +45,29 @@ public boolean addOperation(Operation operation) {
return Collections.unmodifiableList(operations);
}

public Map<String, ?> getHeaders() {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a concurrency issue with the map as long as the map is not open to modification. It might not be the case right now but leaves the code open to errors in case there is a write interface exposed to the map erroneously in the future. Would suggest initializing the map as immutable during initialization in the constructor.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the map should not be open to modifications

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not:

    public Map<String, ?> getHeaders() {
        return Collections.unmodifiableMap(headers);
    }

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed that it is not open to modifications to external modifiers but from a code extensibility perspective, if there is a method in this class which tries to modify this map, extra care would need to be taken to ensure that the map is made immutable then. By making it immutable now, we can future proof it so that we don't run the risk of somebody not making it immutable/thread safe while adding code modifying it in future.

private final List<Operation> operations;

public Patch() {
operations = new ArrayList<>();
this(new ArrayList<>(), new HashMap<>());
}

public Patch(List<Operation> operations) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Always a good idea to declare method arguments as final (especially in case of collections) which makes it explicit to the calling function that the list would not be modified in the called method.

Copy link
Member

@dhiller dhiller Dec 19, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Disagree. The final only states that the reference can't be reassigned within the block. The list is still mutable.

Agree though on parameter declarations being implicitly final would be a good thing :) Though I think that the boilerplate doesn't satisfy the improvement, would support a source code check rather for a variable being reassigned, as i.e. Findbugs supports AFAIR.

return outputStream.toByteArray();
}

public static Patch of(byte[] bytes) throws IOException {
DatumReader<Patch> reader = new ReflectDatumReader<>(SCHEMA);
return reader.read(null, DecoderFactory.get().binaryDecoder(bytes, null));
return SERIALIZER.reader.read(null, DecoderFactory.get().binaryDecoder(bytes, null));

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume we are not reusing the read "Patch" object here because it is being returned to an external method as it is, correct?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes and we'd like to have thread-safety

CONVERTERS.forEach(this::addLogicalTypeConversion);
}

public static AvroData get() {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getInstance() sounds like a more intuitive method name to me. Just a good to have. We can agree or disagree. :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is a matter of taste IMHO. There are variations of this like instance() or getSingleton() or whatever :) As far as we agree on what it means this should be OK. Although I generally would favor a more descriptive method name to support static import of the method...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It hides ReflectData::get. Since it's static I cannot annotate with @Override. Will add javadoc instead.

interface PatchType {
String NAME = Patch.class.getSimpleName();
String DOC = "This record represents a PATCH holding a sequence of operations to apply to a given object.";
static Schema create(Schema valueSchema) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Defining an interface method. This is new to me. Would love to understand more.

String NAME = Patch.class.getSimpleName();
String DOC = "This record represents a PATCH holding a sequence of operations to apply to a given object.";
static Schema create(Schema valueSchema) {
Schema.Field operations = new Schema.Field("operations",

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better to move the literal ("operations") to a constant before using it here and other places.

String NAME = "big-decimal";
String DOC = "BigDecimal value represented via it's scale and unscaled value.";
LogicalType LOGICAL_TYPE = new LogicalType(NAME);
Schema RECORD = SchemaBuilder.record("decimal").doc(DOC).fields()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better to move the literal ("decimal") to a constant before using it here and other places.

String DOC = "BigDecimal value represented via it's scale and unscaled value.";
LogicalType LOGICAL_TYPE = new LogicalType(NAME);
Schema RECORD = SchemaBuilder.record("decimal").doc(DOC).fields()
.name("unscaledValue").type(BigIntegerType.SCHEMA).noDefault()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same comment for all literals. I guess these are the same literals used in the DefaultSchema class. It would be better for code coupling to put them in a common class of constants and use the constants at both places.

@larsp
Copy link
Member

larsp commented Dec 19, 2016

AvroPatchException could become abstract

@@ -16,8 +16,9 @@ project.ext {
dependencies {

compile([
"org.apache.avro:avro:1.8.0",
"org.slf4j:slf4j-api:1.7.18"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

slf4j version should become a variable since it is also used in testCompile

* This interface holds the schema for all valid value types.
*/
public interface DefaultSchema {
String VALUE =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would probably make it more readable?

public class Patch {

public static final Schema SCHEMA = ReflectData.get().getSchema(Patch.class);
private static final DatumWriter<Patch> writer = new ReflectDatumWriter<>(SCHEMA);
private static final PatchSerializer SERIALIZER = new PatchSerializer();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is domain and (de)-serialization combined?

private static final PatchSerializer SERIALIZER = new PatchSerializer();

@AvroSchema(DefaultSchema.HEADERS)
private final Map<String, ?> headers;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't headers be a multimap?

*
* @see <a href="https://tools.ietf.org/html/rfc6902">https://tools.ietf.org/html/rfc6902</a>
*/
@ThreadSafe
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think helper methods for getFirstOperation and getSingle/FirstHeader(somekey) would be nice

ByteBuffer buffer = ByteBuffer.wrap(value.bytes());
long leastSignificantBits = buffer.getLong();
long mostSignificantBits = buffer.getLong();
return new UUID(mostSignificantBits, leastSignificantBits);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice, just the two longs! 👍

}

@Test
public void serializesCopy() throws IOException {
Patch patch = new Patch();
patch.addOperation(new Copy(Path.parse("/person/firstName"), Path.parse("/person/lastName")));
Patch patch = patchOf(new Copy(Path.parse("/person/firstName"), Path.parse("/person/lastName")));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe copy,move etc need a method where you can pass a string which will be parsed?

}

public boolean addOperation(Operation operation) {
return operations.add(operation);
public Patch(List<Operation> operations, Map<String, ?> headers) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we do a sanity check that the value instances are of a supported type?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I created issue #10 for this feature

* This interface holds the schema for all valid value types.
*/
public interface DefaultSchema {
String VALUE =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In that case generating that file from a json is probably the easiest?

.endRecord();
}

Schema VALUE_TYPE_UNION = createUnion(BOOLEAN, DOUBLE, FLOAT, INTEGER, LONG, NULL, STRING,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would appreciate a comment what it does :D

Copy link

@utobi utobi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be great if we could do something about the withArray() endArray() stuff. Does not seem like the best solution for what we want to solve there.

return this;
}

public Builder.ArrayBuilder withArray() {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a particularly good choice regarding naming IMO. Not obvious that you are starting a subbuilder here that requires endArray to be called to close it. Same goes for endArray. It will be very hard looking at code using this that there is a nested structure in there.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about something like public Builder withArray(Builder.ArrayBuilder arrayBuilder) and then go from there? Having the ArrayBuilder explicitly as a method parameter would emphasise the nesting.

this.parentBuilder = parentBuilder;
}

public Builder endArray() {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See comment regarding withArray

}

interface DateType {
String NAME = "timestamp";
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Date or timestamp? What is it? :D

Date date = new Date();
UUID uuid = UUID.randomUUID();
Patch patch = new Patch(ImmutableList.of(
new Add<>(Path.of("some", "value"), "John Doe"),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

API wise this seems to be strange and very cumbersome. Path.of seems to be a looooot of boilerplate. Especially when it is not possible (maybe I'm missing something?) to create a base path and then just add a child to it.

IMO Path either needs a possibility to have a base path and then add children or otherwise people will likely start using it like this which does not seem to be right:

String base = "/some/namespace/here/";
[...]
new Add<>(Path.parse(base+"foobar"), "Knackwurst");

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could do new Add<>(Path.of(base, "foobar"), but it needs documentation. A method append(...) makes sense either way :-)

}

private static Matcher<Patch> operationsEqualTo(Patch expected) {
return new CustomTypeSafeMatcher<Patch>(Joiner.on(",").join(expected.getOperations())) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Java 8 has String.join() in case you haven't seen that one yet :)

.withCustomTypes()
.nullable()
.with(Bimmel.class)
.endArray()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is what I meant with withArray() and endArray(). Very hard to see whats going on here, especially with the next line being indented one more level :)

return this;
}

public Builder with(Schema schema) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

withSchema()?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Schema seems to be the most simple building block of the List this builder is creating, so having a simple method name for this case might be okay.

return this;
}

public Builder with(Type type) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

withType()?

}

public static class Builder {
protected final List<Schema> types;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Considering that both a method for adding a type and a schema exist, the combination of declaration and variable name is a little confusing.

Copy link
Member

@larsp larsp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh... one can set this one here. 😃

private final List<Operation> operations;

public Patch() {
operations = new ArrayList<>();
@SuppressWarnings("unused") // no arg constructor needed by Avro

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rename to: no-arg constructor, otherwise the meaning is that an arg constructor is not needed. Change everywhere else, too.

@@ -13,9 +13,9 @@
public final Path from;
public final Path path;

@SuppressWarnings("unused") // no arg constructor needed by Avro

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no-arg

@@ -12,20 +12,20 @@
@AvroIgnore
public static final String op = "replace";

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why/where is this string needed anyway? Is it not redundant information?

"header 2", new Date(),
"header 3", 1234L,
"header 4", new BigDecimal("3214123453.123512345")));
byte[] bytes = patch.toBytes();;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove ;

public Map<String, ?> getHeaders() {
return Collections.unmodifiableMap(headers);
}

public byte[] toBytes() throws IOException {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
Encoder binaryEncoder = EncoderFactory.get().directBinaryEncoder(outputStream, null);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this local variables binaryEncoder and outputStream can probably shortened for readability, as they are used only once and right after declaration. (see heuristic N5 in Clean Code).

String NAME = io.datanerds.avropatch.operation.Add.class.getSimpleName();
String DOC = "This record represents the add operation of RFC 6902 for JSON Patch'.";

static Schema create(Schema valueSchema) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMHO, the name valueSchema is not concise enough. what is it really, the allowed types of a field in the schema?

is the name valueSchema only used because value is used already below?

.nullable()
.with(Bimmel.class)
.endArray()
.withPrimitives()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is the indentation correct here?

.withSchemata(UuidType.SCHEMA)
.withConverters(new UUIDConversion())
.reserializeAndAssert(UUID.randomUUID())
.reserializeAndAssert(UUID.randomUUID());

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is testing one randomUUID not enough?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be ;-)

}

public byte[] toBytes(Patch value) throws IOException {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shorten local variable names (see comment somewhere above).

@frankwis frankwis merged commit 32f65d0 into develop Jan 13, 2017
@frankwis frankwis deleted the feature/Concurrency_tests branch January 13, 2017 04:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants