CUnion.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.PType;
- import net.morimekta.providence.PUnion;
- import net.morimekta.providence.descriptor.PAnnotation;
- import net.morimekta.providence.descriptor.PContainer;
- import net.morimekta.providence.descriptor.PMessageDescriptor;
- import net.morimekta.providence.descriptor.PUnionDescriptor;
- import net.morimekta.util.collect.UnmodifiableList;
- import net.morimekta.util.collect.UnmodifiableMap;
- import net.morimekta.util.collect.UnmodifiableSet;
- import net.morimekta.util.collect.UnmodifiableSortedMap;
- import net.morimekta.util.collect.UnmodifiableSortedSet;
- import javax.annotation.Nonnull;
- import java.util.ArrayList;
- import java.util.Collection;
- import java.util.LinkedHashMap;
- import java.util.LinkedHashSet;
- import java.util.List;
- import java.util.Map;
- import java.util.Objects;
- import java.util.Set;
- /**
- * @author Stein Eldar Johnsen
- * @since 07.09.15
- */
- public class CUnion implements PUnion<CUnion> {
- private final CUnionDescriptor descriptor;
- private final CField<CUnion> unionField;
- private final Object unionValue;
- @SuppressWarnings("unchecked")
- private CUnion(Builder builder) {
- this.unionField = builder.unionField;
- this.descriptor = builder.descriptor;
- if (builder.currentValue instanceof PMessageBuilder) {
- this.unionValue = ((PMessageBuilder<?>) builder.currentValue).build();
- } else if (builder.currentValue instanceof List) {
- this.unionValue = UnmodifiableList.copyOf((List<?>) builder.currentValue);
- } else if (builder.currentValue instanceof Map) {
- if (PContainer.typeForName(unionField.getAnnotationValue(PAnnotation.CONTAINER)) ==
- PContainer.Type.SORTED) {
- this.unionValue = UnmodifiableSortedMap.copyOf((Map<?,?>) builder.currentValue);
- } else {
- this.unionValue = UnmodifiableMap.copyOf((Map<?,?>) builder.currentValue);
- }
- } else if (builder.currentValue instanceof Set) {
- if (PContainer.typeForName(unionField.getAnnotationValue(PAnnotation.CONTAINER)) ==
- PContainer.Type.SORTED) {
- this.unionValue = UnmodifiableSortedSet.copyOf((Set<?>) builder.currentValue);
- } else {
- this.unionValue = UnmodifiableSet.copyOf((Set<?>) builder.currentValue);
- }
- } else {
- this.unionValue = builder.currentValue;
- }
- }
- @Override
- public boolean has(int key) {
- return unionField != null && unionField.getId() == key && unionValue != null;
- }
- @Override
- @SuppressWarnings("unchecked")
- public <T> T get(int key) {
- return has(key) ? (T) unionValue : null;
- }
- @Nonnull
- @Override
- public PMessageBuilder<CUnion> mutate() {
- return new Builder(this);
- }
- @Nonnull
- @Override
- public String asString() {
- return CStruct.asString(this);
- }
- @Override
- public String toString() {
- return descriptor.getQualifiedName() + asString();
- }
- @Nonnull
- @Override
- public CUnionDescriptor descriptor() {
- return descriptor;
- }
- @Override
- public boolean unionFieldIsSet() {
- return unionField != null;
- }
- @Nonnull
- @Override
- public CField<CUnion> unionField() {
- if (unionField == null) throw new IllegalStateException("No union field set in " + descriptor.getQualifiedName());
- return unionField;
- }
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (!(o instanceof CUnion)) {
- return false;
- }
- CUnion other = (CUnion) o;
- return Objects.equals(descriptor, other.descriptor) &&
- Objects.equals(unionField, other.unionField) &&
- Objects.equals(unionValue, other.unionValue);
- }
- @Override
- public int hashCode() {
- return Objects.hash(descriptor().getQualifiedName(), unionField, unionValue);
- }
- @Override
- public int compareTo(@Nonnull CUnion other) {
- return CStruct.compareMessages(this, other);
- }
- public static class Builder extends PMessageBuilder<CUnion> {
- private final CUnionDescriptor descriptor;
- private CField<CUnion> originalField;
- private boolean updated;
- private CField<CUnion> unionField;
- private Object currentValue;
- public Builder(CUnionDescriptor descriptor) {
- this.descriptor = descriptor;
- this.originalField = null;
- this.updated = false;
- }
- public Builder(CUnion union) {
- this.descriptor = union.descriptor;
- if (union.unionField != null) {
- this.set(union.unionField, union.unionValue);
- }
- this.originalField = union.unionField;
- this.updated = false;
- }
- @Nonnull
- @Override
- public PMessageBuilder<?> mutator(int key) {
- CField<CUnion> field = descriptor.findFieldById(key);
- if (field == null) {
- throw new IllegalArgumentException("No such field ID " + key);
- } else if (field.getType() != PType.MESSAGE) {
- throw new IllegalArgumentException("Not a message field ID " + key + ": " + field.getName());
- }
- if (unionField != field) {
- unionField = field;
- currentValue = null;
- }
- updated = true;
- if (currentValue == null) {
- currentValue = ((PMessageDescriptor<?>) field.getDescriptor()).builder();
- } else if (currentValue instanceof PMessage) {
- currentValue = ((PMessage<?>) currentValue).mutate();
- } else if (!(currentValue instanceof PMessageBuilder)) {
- // This should in theory not be possible. This is just a safe-guard.
- throw new IllegalArgumentException("Invalid currentValue in map on message type: " + currentValue.getClass().getSimpleName());
- }
- return (PMessageBuilder<?>) currentValue;
- }
- @Nonnull
- @Override
- @SuppressWarnings("unchecked,rawtypes")
- public Builder merge(@Nonnull CUnion from) {
- if (unionField == null || unionField != from.unionField) {
- if (from.unionField != null) {
- set(from.unionField.getId(), from.unionValue);
- }
- } else {
- this.updated = true;
- int key = unionField.getId();
- switch (unionField.getType()) {
- case MESSAGE: {
- PMessageBuilder src;
- if (currentValue instanceof PMessageBuilder) {
- src = (PMessageBuilder<?>) currentValue;
- } else {
- src = ((PMessage<?>) currentValue).mutate();
- }
- PMessage toMerge = from.get(key);
- currentValue = src.merge(toMerge);
- break;
- }
- case SET:
- ((Set<Object>) currentValue).addAll(from.get(key));
- break;
- case MAP:
- ((Map<Object, Object>) currentValue).putAll(from.get(key));
- break;
- default:
- // Lists replace, not add.
- set(key, from.get(key));
- break;
- }
- }
- return this;
- }
- @Override
- public boolean has(int key) {
- CField<?> field = descriptor.findFieldById(key);
- return field != null && unionField == field;
- }
- @Override
- @SuppressWarnings("unchecked")
- public <T> T get(int key) {
- CField<?> field = descriptor.findFieldById(key);
- if (unionField == field) {
- return (T) currentValue;
- }
- return null;
- }
- @Nonnull
- @Override
- public PUnionDescriptor<CUnion> descriptor() {
- return descriptor;
- }
- @Nonnull
- @Override
- public CUnion build() {
- return new CUnion(this);
- }
- @Override
- public boolean valid() {
- return unionField != null && currentValue != null;
- }
- @Override
- public Builder validate() {
- if (!valid()) {
- throw new IllegalStateException("No union field set in " +
- descriptor().getQualifiedName());
- }
- return this;
- }
- @Nonnull
- @Override
- public Builder set(int key, Object value) {
- CField<CUnion> field = descriptor.findFieldById(key);
- if (field == null) {
- return this; // soft ignoring unsupported fields.
- }
- if (value == null) {
- return clear(key);
- }
- this.updated = true;
- this.unionField = field;
- switch (field.getType()) {
- case SET:
- this.currentValue = new LinkedHashSet<>((Collection<?>) value);
- break;
- case LIST:
- this.currentValue = new ArrayList<>((Collection<?>) value);
- break;
- case MAP:
- this.currentValue = new LinkedHashMap<>((Map<?,?>) value);
- break;
- default:
- this.currentValue = value;
- break;
- }
- return this;
- }
- @Override
- public boolean isSet(int key) {
- return unionField != null && unionField.getId() == key;
- }
- @Override
- public boolean isModified(int key) {
- if (updated) {
- if (unionField != null && unionField.getId() == key) return true;
- return originalField != null && originalField.getId() == key;
- }
- return false;
- }
- @Nonnull
- @Override
- @SuppressWarnings("unchecked")
- public Builder addTo(int key, Object value) {
- CField<CUnion> field = descriptor.findFieldById(key);
- if (field == null) {
- return this; // soft ignoring unsupported fields.
- }
- if (field.getType() != PType.LIST &&
- field.getType() != PType.SET) {
- throw new IllegalArgumentException("Unable to accept addTo on non-list field " + field.getName());
- }
- if (value == null) {
- throw new IllegalArgumentException("Adding null item to collection " + field.getName());
- }
- this.updated = true;
- if (this.unionField != field || this.currentValue == null) {
- this.unionField = field;
- switch (field.getType()) {
- case LIST: {
- this.currentValue = new ArrayList<>();
- break;
- }
- case SET: {
- this.currentValue = new LinkedHashSet<>();
- break;
- }
- default:
- break;
- }
- }
- ((Collection<Object>) this.currentValue).add(value);
- return this;
- }
- @Nonnull
- @Override
- public Builder clear(int key) {
- if (isSet(key)) {
- this.updated = true;
- if (originalField == null) {
- originalField = unionField;
- }
- this.unionField = null;
- this.currentValue = null;
- }
- return this;
- }
- }
- }