CField.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.descriptor.PDescriptor;
import net.morimekta.providence.descriptor.PDescriptorProvider;
import net.morimekta.providence.descriptor.PField;
import net.morimekta.providence.descriptor.PInterfaceDescriptor;
import net.morimekta.providence.descriptor.PMessageDescriptor;
import net.morimekta.providence.descriptor.PRequirement;
import net.morimekta.providence.descriptor.PStructDescriptor;
import net.morimekta.providence.descriptor.PStructDescriptorProvider;
import net.morimekta.providence.descriptor.PValueProvider;
import net.morimekta.util.Strings;
import net.morimekta.util.collect.UnmodifiableMap;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import static net.morimekta.providence.descriptor.PAnnotation.JSON_NAME;
/**
* Description of a single contained field. Part of the message descriptor.
*
* @param <M> The message type.
*/
public class CField<M extends PMessage<M>> implements PField<M>, CAnnotatedDescriptor {
public static final CField<?>[] EMPTY_ARRAY = new CField[0];
private final String comment;
private final int id;
private final PRequirement requirement;
private final PDescriptorProvider typeProvider;
private final String name;
private final String pojoName;
private final PValueProvider<?> defaultValue;
private final Map<String, String> annotations;
private final PStructDescriptorProvider<?> argumentsProvider;
private final PDescriptorProvider implementing;
private PMessageDescriptor<M> messageType;
public CField(@Nullable String docs,
int id,
@Nonnull PRequirement requirement,
@Nonnull String name,
@Nonnull PDescriptorProvider typeProvider,
@Nullable PStructDescriptorProvider<?> argumentsProvider,
@Nullable PValueProvider<?> defaultValue,
@Nullable Map<String, String> annotations,
@Nullable PDescriptorProvider implementing) {
this.comment = docs;
this.id = id;
this.requirement = requirement;
this.typeProvider = typeProvider;
this.argumentsProvider = argumentsProvider;
this.name = name;
this.defaultValue = defaultValue;
this.annotations = annotations == null ? UnmodifiableMap.mapOf() : UnmodifiableMap.copyOf(annotations);
this.implementing = implementing;
String tmp = Strings.camelCase(name);
tmp = tmp.substring(0, 1).toLowerCase(Locale.US) + tmp.substring(1);
if (this.annotations.containsKey(JSON_NAME.tag)) {
tmp = this.annotations.get(JSON_NAME.tag);
}
this.pojoName = tmp;
}
@Override
public String getDocumentation() {
return comment;
}
@Override
public int getId() {
return id;
}
@Nonnull
@Override
public PRequirement getRequirement() {
return requirement;
}
@Nonnull
@Override
public PDescriptor getDescriptor() {
return typeProvider.descriptor();
}
@Override
public PStructDescriptor<?> getArgumentsType() {
return argumentsProvider == null ? argumentsFromInterface() : argumentsProvider.descriptor();
}
@Nonnull
@Override
public String getName() {
return name;
}
@Nonnull
@Override
public String getPojoName() {
return pojoName;
}
@Override
public boolean hasDefaultValue() {
return defaultValue != null || hasDefaultFromInterface();
}
@Override
public Object getDefaultValue() {
try {
return defaultValue != null ? defaultValue.get() : defaultFromInterfaceOrType();
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new IllegalStateException("Unable to parse default value " + getName(), e);
}
}
@Nonnull
@Override
public Set<String> getAnnotations() {
if (implementing != null) {
Set<String> union = new HashSet<>(annotations.keySet());
@SuppressWarnings("unchecked")
CField<M> field = (CField<M>) getImplementing().findFieldByName(getName());
if (field != null) {
union.addAll(field.getAnnotations());
}
return union;
}
return annotations.keySet();
}
@Override
public boolean hasAnnotation(@Nonnull String name) {
if (annotations.containsKey(name)) return true;
if (implementing != null) {
CField<?> field = (CField<?>) getImplementing().findFieldByName(getName());
if (field != null) {
return field.hasAnnotation(name);
}
}
return false;
}
@Override
public String getAnnotationValue(@Nonnull String name) {
if (annotations.containsKey(name)) {
return annotations.get(name);
} else if (implementing != null) {
CField<?> field = (CField<?>) getImplementing().findFieldByName(getName());
if (field != null) {
return field.getAnnotationValue(name);
}
}
return null;
}
@Nonnull
@Override
public PMessageDescriptor<M> onMessageType() {
return Objects.requireNonNull(messageType, "Not set message type of CField");
}
void setMessageType(@Nonnull PMessageDescriptor<M> descriptor) {
this.messageType = descriptor;
}
@Override
public String toString() {
return PField.asString(this);
}
@Override
public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof CField)) {
return false;
}
CField<?> other = (CField<?>) o;
return id == other.id &&
requirement == other.requirement &&
// We cannot test that the types are deep-equals as it may have circular
// containment.
equalsQualifiedName(getDescriptor(), other.getDescriptor()) &&
name.equals(other.name) &&
Objects.equals(defaultValue, other.defaultValue);
}
@Override
public int hashCode() {
return Objects.hash(CField.class, id, requirement, name, getDefaultValue());
}
private PInterfaceDescriptor<?> getImplementing() {
return (PInterfaceDescriptor<?>) implementing.descriptor();
}
private boolean hasDefaultFromInterface() {
if (implementing == null) return false;
PField<?> ifField = getImplementing().findFieldByName(getName());
if (ifField == null) return false;
return ifField.hasDefaultValue();
}
private Object defaultFromInterfaceOrType() {
if (implementing == null) return getDescriptor().getDefaultValue();
PField ifField = getImplementing().findFieldByName(getName());
if (ifField == null) return getDescriptor().getDefaultValue();
return ifField.getDefaultValue();
}
private PStructDescriptor argumentsFromInterface() {
if (implementing == null) return null;
PField ifField = getImplementing().findFieldByName(getName());
if (ifField == null) return null;
return ifField.getArgumentsType();
}
/**
* Check if the two descriptors has the same qualified name, i..e
* symbolically represent the same type.
*
* @param a The first type.
* @param b The second type.
* @return If the two types are the same.
*/
private static boolean equalsQualifiedName(PDescriptor a, PDescriptor b) {
return (a != null) && (b != null) && (a.getQualifiedName()
.equals(b.getQualifiedName()));
}
}