ProtoMessageOrBuilder.java
/*
* Copyright 2022 Proto Utils 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.proto;
import com.google.protobuf.Descriptors;
import com.google.protobuf.MessageOrBuilder;
import net.morimekta.proto.utils.ValueUtil;
import net.morimekta.strings.Stringable;
import java.util.Objects;
import java.util.Optional;
import static java.util.Objects.requireNonNull;
import static net.morimekta.proto.utils.FieldUtil.getDefaultFieldValue;
import static net.morimekta.proto.utils.ValueUtil.toJavaValue;
/**
* Base class for proto message or its builder.
*/
public abstract class ProtoMessageOrBuilder
implements Stringable {
private final MessageOrBuilder messageOrBuilder;
private final Descriptors.Descriptor descriptor;
/**
* @param messageOrBuilder The message or builder to wrap.
*/
public ProtoMessageOrBuilder(MessageOrBuilder messageOrBuilder) {
this.messageOrBuilder = requireNonNull(messageOrBuilder, "message == null");
this.descriptor = messageOrBuilder.getDescriptorForType();
}
/**
* @return The wrapped message or builder.
*/
public MessageOrBuilder getMessage() {
return messageOrBuilder;
}
/**
* @return The wrapped message
*/
public Descriptors.Descriptor getDescriptor() {
return descriptor;
}
/**
* @param field The field descriptor.
* @return If the message has the field set.
*/
public boolean has(Descriptors.FieldDescriptor field) {
requireNonNull(field, "field == null");
return hasInternal(field);
}
/**
* Get the field value as if calling the <code>getMyField()</code> method, or
* the <code>getMyFieldList()</code> method for repeated fields. It will return
* a non-null value if at all possible, only exception is for non-set proto2
* enums without a 0 value.
*
* @param field The field descriptor.
* @param <T> The field value type.
* @return The field value.
*/
public abstract <T> T get(Descriptors.FieldDescriptor field);
/**
* Get the value of a field if present, and an empty optional if it is not. This
* should be using the message's field presence. Note that the presence calculation
* may be different depending on the proto syntax version and various optionality
* options set on the field.
* <p>
* The optional can be used to see if a value should be serialized or not. If the
* optional is empty the {@link #has(Descriptors.FieldDescriptor)} method should
* also return false for the same field.
*
* @param field The field descriptor.
* @param <T> The field value type.
* @return Optional field value.
*/
public abstract <T> Optional<T> optional(Descriptors.FieldDescriptor field);
// --- Stringable ---
@Override
public String asString() {
return ValueUtil.asString(messageOrBuilder);
}
// --- Object ---
@Override
public String toString() {
return getDescriptor().getFullName() + asString();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ProtoMessageOrBuilder that = (ProtoMessageOrBuilder) o;
return Objects.equals(descriptor, that.descriptor) &&
Objects.equals(messageOrBuilder, that.messageOrBuilder);
}
@Override
public int hashCode() {
return Objects.hash(messageOrBuilder, descriptor);
}
// --- Protected ---
/**
* @param field The field descriptor.
* @return If the field is present on the message.
*/
protected boolean hasInternal(Descriptors.FieldDescriptor field) {
if (field.isRepeated()) {
return messageOrBuilder.getRepeatedFieldCount(field) > 0;
} else {
return messageOrBuilder.hasField(field);
}
}
/**
* @param field The field descriptor.
* @param <T> The field value type.
* @return The field value.
*/
@SuppressWarnings("unchecked")
protected <T> T getInternal(Descriptors.FieldDescriptor field) {
return (T) optionalInternal(field)
.orElseGet(() -> getDefaultFieldValue(field));
}
/**
* @param field The field descriptor.
* @param <T> The field value type.
* @return The optional value for the field.
*/
@SuppressWarnings("unchecked")
protected <T> Optional<T> optionalInternal(Descriptors.FieldDescriptor field) {
if (hasInternal(field)) {
return Optional.ofNullable((T) toJavaValue(field, messageOrBuilder.getField(field)));
}
return Optional.empty();
}
}