CUnion.java

  1. /*
  2.  * Copyright 2016 Providence Authors
  3.  *
  4.  * Licensed to the Apache Software Foundation (ASF) under one
  5.  * or more contributor license agreements. See the NOTICE file
  6.  * distributed with this work for additional information
  7.  * regarding copyright ownership. The ASF licenses this file
  8.  * to you under the Apache License, Version 2.0 (the
  9.  * "License"); you may not use this file except in compliance
  10.  * with the License. You may obtain a copy of the License at
  11.  *
  12.  *   http://www.apache.org/licenses/LICENSE-2.0
  13.  *
  14.  * Unless required by applicable law or agreed to in writing,
  15.  * software distributed under the License is distributed on an
  16.  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  17.  * KIND, either express or implied. See the License for the
  18.  * specific language governing permissions and limitations
  19.  * under the License.
  20.  */
  21. package net.morimekta.providence.reflect.contained;

  22. import net.morimekta.providence.PMessage;
  23. import net.morimekta.providence.PMessageBuilder;
  24. import net.morimekta.providence.PType;
  25. import net.morimekta.providence.PUnion;
  26. import net.morimekta.providence.descriptor.PAnnotation;
  27. import net.morimekta.providence.descriptor.PContainer;
  28. import net.morimekta.providence.descriptor.PMessageDescriptor;
  29. import net.morimekta.providence.descriptor.PUnionDescriptor;
  30. import net.morimekta.util.collect.UnmodifiableList;
  31. import net.morimekta.util.collect.UnmodifiableMap;
  32. import net.morimekta.util.collect.UnmodifiableSet;
  33. import net.morimekta.util.collect.UnmodifiableSortedMap;
  34. import net.morimekta.util.collect.UnmodifiableSortedSet;

  35. import javax.annotation.Nonnull;
  36. import java.util.ArrayList;
  37. import java.util.Collection;
  38. import java.util.LinkedHashMap;
  39. import java.util.LinkedHashSet;
  40. import java.util.List;
  41. import java.util.Map;
  42. import java.util.Objects;
  43. import java.util.Set;

  44. /**
  45.  * @author Stein Eldar Johnsen
  46.  * @since 07.09.15
  47.  */
  48. public class CUnion implements PUnion<CUnion> {
  49.     private final CUnionDescriptor descriptor;
  50.     private final CField<CUnion>   unionField;
  51.     private final Object           unionValue;

  52.     @SuppressWarnings("unchecked")
  53.     private CUnion(Builder builder) {
  54.         this.unionField = builder.unionField;
  55.         this.descriptor = builder.descriptor;

  56.         if (builder.currentValue instanceof PMessageBuilder) {
  57.             this.unionValue = ((PMessageBuilder<?>) builder.currentValue).build();
  58.         } else if (builder.currentValue instanceof List) {
  59.             this.unionValue = UnmodifiableList.copyOf((List<?>) builder.currentValue);
  60.         } else if (builder.currentValue instanceof Map) {
  61.             if (PContainer.typeForName(unionField.getAnnotationValue(PAnnotation.CONTAINER)) ==
  62.                 PContainer.Type.SORTED) {
  63.                 this.unionValue = UnmodifiableSortedMap.copyOf((Map<?,?>) builder.currentValue);
  64.             } else {
  65.                 this.unionValue = UnmodifiableMap.copyOf((Map<?,?>) builder.currentValue);
  66.             }
  67.         } else if (builder.currentValue instanceof Set) {
  68.             if (PContainer.typeForName(unionField.getAnnotationValue(PAnnotation.CONTAINER)) ==
  69.                 PContainer.Type.SORTED) {
  70.                 this.unionValue = UnmodifiableSortedSet.copyOf((Set<?>) builder.currentValue);
  71.             } else {
  72.                 this.unionValue = UnmodifiableSet.copyOf((Set<?>) builder.currentValue);
  73.             }
  74.         } else {
  75.             this.unionValue = builder.currentValue;
  76.         }
  77.     }

  78.     @Override
  79.     public boolean has(int key) {
  80.         return unionField != null && unionField.getId() == key && unionValue != null;
  81.     }

  82.     @Override
  83.     @SuppressWarnings("unchecked")
  84.     public <T> T get(int key) {
  85.         return has(key) ? (T) unionValue : null;
  86.     }

  87.     @Nonnull
  88.     @Override
  89.     public PMessageBuilder<CUnion> mutate() {
  90.         return new Builder(this);
  91.     }

  92.     @Nonnull
  93.     @Override
  94.     public String asString() {
  95.         return CStruct.asString(this);
  96.     }

  97.     @Override
  98.     public String toString() {
  99.         return descriptor.getQualifiedName() + asString();
  100.     }

  101.     @Nonnull
  102.     @Override
  103.     public CUnionDescriptor descriptor() {
  104.         return descriptor;
  105.     }

  106.     @Override
  107.     public boolean unionFieldIsSet() {
  108.         return unionField != null;
  109.     }

  110.     @Nonnull
  111.     @Override
  112.     public CField<CUnion> unionField() {
  113.         if (unionField == null) throw new IllegalStateException("No union field set in " + descriptor.getQualifiedName());
  114.         return unionField;
  115.     }

  116.     @Override
  117.     public boolean equals(Object o) {
  118.         if (this == o) return true;
  119.         if (!(o instanceof CUnion)) {
  120.             return false;
  121.         }

  122.         CUnion other = (CUnion) o;
  123.         return Objects.equals(descriptor, other.descriptor) &&
  124.                Objects.equals(unionField, other.unionField) &&
  125.                Objects.equals(unionValue, other.unionValue);
  126.     }

  127.     @Override
  128.     public int hashCode() {
  129.         return Objects.hash(descriptor().getQualifiedName(), unionField, unionValue);
  130.     }

  131.     @Override
  132.     public int compareTo(@Nonnull CUnion other) {
  133.         return CStruct.compareMessages(this, other);
  134.     }

  135.     public static class Builder extends PMessageBuilder<CUnion> {
  136.         private final CUnionDescriptor descriptor;
  137.         private       CField<CUnion>   originalField;
  138.         private       boolean          updated;

  139.         private CField<CUnion> unionField;
  140.         private Object currentValue;

  141.         public Builder(CUnionDescriptor descriptor) {
  142.             this.descriptor = descriptor;
  143.             this.originalField = null;
  144.             this.updated = false;
  145.         }

  146.         public Builder(CUnion union) {
  147.             this.descriptor = union.descriptor;
  148.             if (union.unionField != null) {
  149.                 this.set(union.unionField, union.unionValue);
  150.             }
  151.             this.originalField = union.unionField;
  152.             this.updated = false;
  153.         }

  154.         @Nonnull
  155.         @Override
  156.         public PMessageBuilder<?> mutator(int key) {
  157.             CField<CUnion> field = descriptor.findFieldById(key);
  158.             if (field == null) {
  159.                 throw new IllegalArgumentException("No such field ID " + key);
  160.             } else if (field.getType() != PType.MESSAGE) {
  161.                 throw new IllegalArgumentException("Not a message field ID " + key + ": " + field.getName());
  162.             }
  163.             if (unionField != field) {
  164.                 unionField = field;
  165.                 currentValue = null;
  166.             }

  167.             updated = true;
  168.             if (currentValue == null) {
  169.                 currentValue = ((PMessageDescriptor<?>) field.getDescriptor()).builder();
  170.             } else if (currentValue instanceof PMessage) {
  171.                 currentValue = ((PMessage<?>) currentValue).mutate();
  172.             } else if (!(currentValue instanceof PMessageBuilder)) {
  173.                 // This should in theory not be possible. This is just a safe-guard.
  174.                 throw new IllegalArgumentException("Invalid currentValue in map on message type: " + currentValue.getClass().getSimpleName());
  175.             }

  176.             return (PMessageBuilder<?>) currentValue;
  177.         }

  178.         @Nonnull
  179.         @Override
  180.         @SuppressWarnings("unchecked,rawtypes")
  181.         public Builder merge(@Nonnull CUnion from) {
  182.             if (unionField == null || unionField != from.unionField) {
  183.                 if (from.unionField != null) {
  184.                     set(from.unionField.getId(), from.unionValue);
  185.                 }
  186.             } else {
  187.                 this.updated = true;
  188.                 int key = unionField.getId();
  189.                 switch (unionField.getType()) {
  190.                     case MESSAGE: {
  191.                         PMessageBuilder src;
  192.                         if (currentValue instanceof PMessageBuilder) {
  193.                             src = (PMessageBuilder<?>) currentValue;
  194.                         } else {
  195.                             src = ((PMessage<?>) currentValue).mutate();
  196.                         }
  197.                         PMessage toMerge = from.get(key);

  198.                         currentValue = src.merge(toMerge);
  199.                         break;
  200.                     }
  201.                     case SET:
  202.                         ((Set<Object>) currentValue).addAll(from.get(key));
  203.                         break;
  204.                     case MAP:
  205.                         ((Map<Object, Object>) currentValue).putAll(from.get(key));
  206.                         break;
  207.                     default:
  208.                         // Lists replace, not add.
  209.                         set(key, from.get(key));
  210.                         break;
  211.                 }
  212.             }

  213.             return this;
  214.         }

  215.         @Override
  216.         public boolean has(int key) {
  217.             CField<?> field = descriptor.findFieldById(key);
  218.             return field != null && unionField == field;
  219.         }

  220.         @Override
  221.         @SuppressWarnings("unchecked")
  222.         public <T> T get(int key) {
  223.             CField<?> field = descriptor.findFieldById(key);
  224.             if (unionField == field) {
  225.                 return (T) currentValue;
  226.             }
  227.             return null;
  228.         }

  229.         @Nonnull
  230.         @Override
  231.         public PUnionDescriptor<CUnion> descriptor() {
  232.             return descriptor;
  233.         }

  234.         @Nonnull
  235.         @Override
  236.         public CUnion build() {
  237.             return new CUnion(this);
  238.         }

  239.         @Override
  240.         public boolean valid() {
  241.             return unionField != null && currentValue != null;
  242.         }

  243.         @Override
  244.         public Builder validate() {
  245.             if (!valid()) {
  246.                 throw new IllegalStateException("No union field set in " +
  247.                                                 descriptor().getQualifiedName());
  248.             }
  249.             return this;
  250.         }

  251.         @Nonnull
  252.         @Override
  253.         public Builder set(int key, Object value) {
  254.             CField<CUnion> field = descriptor.findFieldById(key);
  255.             if (field == null) {
  256.                 return this; // soft ignoring unsupported fields.
  257.             }
  258.             if (value == null) {
  259.                 return clear(key);
  260.             }
  261.             this.updated = true;
  262.             this.unionField = field;
  263.             switch (field.getType()) {
  264.                 case SET:
  265.                     this.currentValue = new LinkedHashSet<>((Collection<?>) value);
  266.                     break;
  267.                 case LIST:
  268.                     this.currentValue = new ArrayList<>((Collection<?>) value);
  269.                     break;
  270.                 case MAP:
  271.                     this.currentValue = new LinkedHashMap<>((Map<?,?>) value);
  272.                     break;
  273.                 default:
  274.                     this.currentValue = value;
  275.                     break;
  276.             }

  277.             return this;
  278.         }

  279.         @Override
  280.         public boolean isSet(int key) {
  281.             return unionField != null && unionField.getId() == key;
  282.         }

  283.         @Override
  284.         public boolean isModified(int key) {
  285.             if (updated) {
  286.                 if (unionField != null && unionField.getId() == key) return true;
  287.                 return originalField != null && originalField.getId() == key;
  288.             }
  289.             return false;
  290.         }

  291.         @Nonnull
  292.         @Override
  293.         @SuppressWarnings("unchecked")
  294.         public Builder addTo(int key, Object value) {
  295.             CField<CUnion> field = descriptor.findFieldById(key);
  296.             if (field == null) {
  297.                 return this; // soft ignoring unsupported fields.
  298.             }
  299.             if (field.getType() != PType.LIST &&
  300.                 field.getType() != PType.SET) {
  301.                 throw new IllegalArgumentException("Unable to accept addTo on non-list field " + field.getName());
  302.             }
  303.             if (value == null) {
  304.                 throw new IllegalArgumentException("Adding null item to collection " + field.getName());
  305.             }
  306.             this.updated = true;
  307.             if (this.unionField != field || this.currentValue == null) {
  308.                 this.unionField = field;
  309.                 switch (field.getType()) {
  310.                     case LIST: {
  311.                         this.currentValue = new ArrayList<>();
  312.                         break;
  313.                     }
  314.                     case SET: {
  315.                         this.currentValue = new LinkedHashSet<>();
  316.                         break;
  317.                     }
  318.                     default:
  319.                         break;
  320.                 }
  321.             }
  322.             ((Collection<Object>) this.currentValue).add(value);
  323.             return this;
  324.         }

  325.         @Nonnull
  326.         @Override
  327.         public Builder clear(int key) {
  328.             if (isSet(key)) {
  329.                 this.updated = true;
  330.                 if (originalField == null) {
  331.                     originalField = unionField;
  332.                 }
  333.                 this.unionField = null;
  334.                 this.currentValue = null;
  335.             }
  336.             return this;
  337.         }
  338.     }
  339. }