CStruct.java
/*
* Copyright 2016 Providence Authors
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package net.morimekta.providence.reflect.contained;
import net.morimekta.providence.PMessage;
import net.morimekta.providence.PMessageBuilder;
import net.morimekta.providence.descriptor.PField;
import net.morimekta.providence.descriptor.PMessageDescriptor;
import net.morimekta.providence.serializer.PrettySerializer;
import net.morimekta.providence.serializer.json.JsonCompactible;
import javax.annotation.Nonnull;
import java.io.ByteArrayOutputStream;
import java.util.Map;
import java.util.Objects;
import static java.nio.charset.StandardCharsets.UTF_8;
/**
* A contained message of variant struct.
*/
public class CStruct implements CMessage<CStruct>, JsonCompactible {
private static final PrettySerializer PRETTY_SERIALIZER = new PrettySerializer().compact();
private Map<Integer,Object> values;
private CStructDescriptor descriptor;
private CStruct(Builder builder) {
descriptor = builder.descriptor;
values = builder.getValueMap();
}
public Map<Integer,Object> values() {
return values;
}
@Override
public boolean jsonCompact() {
CStructDescriptor descriptor = descriptor();
if (!descriptor.isJsonCompactible()) {
return false;
}
boolean missing = false;
for (CField<?> field : descriptor.getFields()) {
if (has(field.getId())) {
if (missing) {
return false;
}
} else {
missing = true;
}
}
return true;
}
@Override
public boolean equals(Object o) {
return this == o || o instanceof CStruct && equals(this, (CStruct) o);
}
@Override
public int hashCode() {
return hashCode(this);
}
@Override
public String toString() {
return descriptor().getQualifiedName() + asString();
}
@Nonnull
@Override
public PMessageBuilder<CStruct> mutate() {
return new Builder(descriptor).merge(this);
}
@Nonnull
@Override
public CStructDescriptor descriptor() {
return descriptor;
}
public static class Builder extends CMessageBuilder<Builder,CStruct> {
private final CStructDescriptor descriptor;
public Builder(CStructDescriptor descriptor) {
this.descriptor = descriptor;
}
@Nonnull
@Override
public CStructDescriptor descriptor() {
return descriptor;
}
@Nonnull
@Override
public CStruct build() {
return new CStruct(this);
}
}
protected static <M extends PMessage<M>> boolean equals(M a, M b) {
PMessageDescriptor<?> type = b.descriptor();
if (!a.descriptor()
.getQualifiedName()
.equals(type.getQualifiedName()) ||
!a.descriptor()
.getVariant()
.equals(type.getVariant())) {
return false;
}
for (PField<?> field : a.descriptor().getFields()) {
int id = field.getId();
if (a.has(id) != b.has(id)) {
return false;
}
if (!Objects.equals(a.get(id), b.get(id))) {
return false;
}
}
return true;
}
protected static <M extends CMessage<M>> int hashCode(M self) {
int hash = self.descriptor().hashCode();
for (PField<M> field : self.descriptor().getFields()) {
hash *= 29251;
hash ^= Objects.hash(field, self.get(field));
}
return hash;
}
/**
* Prints a jsonCompact string representation of the message.
*
* @param message The message to stringify.
* @param <Message> The contained message type.
* @return The resulting string.
*/
protected static <Message extends PMessage<Message>>
String asString(Message message) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
PRETTY_SERIALIZER.serialize(baos, message);
return new String(baos.toByteArray(), UTF_8);
}
/**
* Compare two values to each other.
*
* @param o1 The first value.
* @param o2 The second value.
* @param <T> The object type.
* @return The compare value (-1, 0 or 1).
*/
@SuppressWarnings("unchecked,rawtypes")
private static <T extends Comparable<T>> int compare(T o1, T o2) {
if (o1 instanceof PMessage && o2 instanceof PMessage) {
return compareMessages((PMessage) o1, (PMessage) o2);
}
return o1.compareTo(o2);
}
static <T extends PMessage<T>> int compareMessages(T m1, T m2) {
int c = m1.descriptor()
.getQualifiedName()
.compareTo(m2.descriptor()
.getQualifiedName());
if (c != 0) {
return c;
}
for (PField<T> field : m1.descriptor().getFields()) {
c = Boolean.compare(m1.has(field.getId()), m2.has(field.getId()));
if (c != 0) {
return c;
} else if (m1.has(field.getId()) && m2.has(field.getId())) {
c = compare(m1.get(field.getId()), m2.get(field.getId()));
if (c != 0) {
return c;
}
}
}
return 0;
}
}