1   /*
2    * SPDX-FileCopyrightText: none
3    * SPDX-License-Identifier: CC0-1.0
4    */
5   
6   package gov.nist.secauto.metaschema.databind.model.impl;
7   
8   import gov.nist.secauto.metaschema.core.datatype.markup.MarkupLine;
9   import gov.nist.secauto.metaschema.core.datatype.markup.MarkupMultiline;
10  import gov.nist.secauto.metaschema.core.model.AbstractAssemblyInstance;
11  import gov.nist.secauto.metaschema.core.model.IBoundObject;
12  import gov.nist.secauto.metaschema.core.util.ObjectUtils;
13  import gov.nist.secauto.metaschema.databind.model.IBoundDefinitionModelAssembly;
14  import gov.nist.secauto.metaschema.databind.model.IBoundInstanceFlag;
15  import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelAssembly;
16  import gov.nist.secauto.metaschema.databind.model.IBoundModule;
17  import gov.nist.secauto.metaschema.databind.model.IBoundProperty;
18  import gov.nist.secauto.metaschema.databind.model.IGroupAs;
19  import gov.nist.secauto.metaschema.databind.model.annotations.BoundAssembly;
20  import gov.nist.secauto.metaschema.databind.model.annotations.GroupAs;
21  import gov.nist.secauto.metaschema.databind.model.annotations.ModelUtil;
22  import gov.nist.secauto.metaschema.databind.model.info.IModelInstanceCollectionInfo;
23  
24  import java.lang.reflect.Field;
25  import java.util.Map;
26  import java.util.function.Predicate;
27  
28  import edu.umd.cs.findbugs.annotations.NonNull;
29  import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
30  import nl.talsmasoftware.lazy4j.Lazy;
31  
32  /**
33   * Implements a Metaschema module assembly instance bound to a Java field,
34   * supported by a bound definition class.
35   */
36  // TODO: implement getProperties()
37  public final class InstanceModelAssemblyComplex
38      extends AbstractAssemblyInstance<
39          IBoundDefinitionModelAssembly,
40          IBoundDefinitionModelAssembly,
41          IBoundInstanceModelAssembly,
42          IBoundDefinitionModelAssembly>
43      implements IBoundInstanceModelAssembly, IFeatureInstanceModelGroupAs<IBoundObject> {
44    @NonNull
45    private final Field javaField;
46    @NonNull
47    private final BoundAssembly annotation;
48    @NonNull
49    private final Lazy<IModelInstanceCollectionInfo<IBoundObject>> collectionInfo;
50    @NonNull
51    private final IBoundDefinitionModelAssembly definition;
52    @NonNull
53    private final IGroupAs groupAs;
54    @NonNull
55    private final Lazy<Map<String, IBoundProperty<?>>> jsonProperties;
56  
57    /**
58     * Construct a new field instance bound to a Java field, supported by a bound
59     * definition class.
60     *
61     * @param javaField
62     *          the Java field bound to this instance
63     * @param definition
64     *          the assembly definition this instance is bound to
65     * @param containingDefinition
66     *          the definition containing this instance
67     * @return the instance
68     */
69    @NonNull
70    public static InstanceModelAssemblyComplex newInstance(
71        @NonNull Field javaField,
72        @NonNull IBoundDefinitionModelAssembly definition,
73        @NonNull IBoundDefinitionModelAssembly containingDefinition) {
74      BoundAssembly annotation = ModelUtil.getAnnotation(javaField, BoundAssembly.class);
75      IGroupAs groupAs = ModelUtil.resolveDefaultGroupAs(
76          annotation.groupAs(),
77          containingDefinition.getContainingModule());
78      if (annotation.maxOccurs() == -1 || annotation.maxOccurs() > 1) {
79        if (IGroupAs.SINGLETON_GROUP_AS.equals(groupAs)) {
80          throw new IllegalStateException(String.format("Field '%s' on class '%s' is missing the '%s' annotation.",
81              javaField.getName(),
82              containingDefinition.getBoundClass().getName(),
83              GroupAs.class.getName()));
84        }
85      } else if (!IGroupAs.SINGLETON_GROUP_AS.equals(groupAs)) {
86        // max is 1 and a groupAs is set
87        throw new IllegalStateException(
88            String.format(
89                "Field '%s' on class '%s' has the '%s' annotation, but maxOccurs=1. A groupAs must not be specfied.",
90                javaField.getName(),
91                containingDefinition.getBoundClass().getName(),
92                GroupAs.class.getName()));
93      }
94      return new InstanceModelAssemblyComplex(javaField, annotation, groupAs, definition, containingDefinition);
95    }
96  
97    @SuppressFBWarnings(value = "CT_CONSTRUCTOR_THROW", justification = "Use of final fields")
98    private InstanceModelAssemblyComplex(
99        @NonNull Field javaField,
100       @NonNull BoundAssembly annotation,
101       @NonNull IGroupAs groupAs,
102       @NonNull IBoundDefinitionModelAssembly definition,
103       @NonNull IBoundDefinitionModelAssembly containingDefinition) {
104     super(containingDefinition);
105     this.javaField = javaField;
106     this.annotation = annotation;
107     this.groupAs = groupAs;
108     this.collectionInfo = ObjectUtils.notNull(Lazy.lazy(() -> IModelInstanceCollectionInfo.of(this)));
109     this.definition = definition;
110     this.jsonProperties = ObjectUtils.notNull(Lazy.lazy(() -> {
111       IBoundInstanceFlag jsonKey = getEffectiveJsonKey();
112       Predicate<IBoundInstanceFlag> flagFilter = jsonKey == null ? null : flag -> !jsonKey.equals(flag);
113       return getDefinition().getJsonProperties(flagFilter);
114     }));
115   }
116 
117   // ------------------------------------------
118   // - Start annotation driven code - CPD-OFF -
119   // ------------------------------------------
120 
121   @Override
122   public Field getField() {
123     return javaField;
124   }
125 
126   /**
127    * Get the binding Java annotation.
128    *
129    * @return the binding Java annotation
130    */
131   @NonNull
132   public BoundAssembly getAnnotation() {
133     return annotation;
134   }
135 
136   @SuppressWarnings("null")
137   @Override
138   public IModelInstanceCollectionInfo<IBoundObject> getCollectionInfo() {
139     return collectionInfo.get();
140   }
141 
142   @Override
143   public Map<String, IBoundProperty<?>> getJsonProperties() {
144     return ObjectUtils.notNull(jsonProperties.get());
145   }
146 
147   @Override
148   public IBoundDefinitionModelAssembly getDefinition() {
149     return definition;
150   }
151 
152   @Override
153   public IBoundModule getContainingModule() {
154     return getContainingDefinition().getContainingModule();
155   }
156 
157   @Override
158   public IGroupAs getGroupAs() {
159     return groupAs;
160   }
161 
162   @Override
163   public String getFormalName() {
164     return ModelUtil.resolveNoneOrValue(getAnnotation().formalName());
165   }
166 
167   @Override
168   public MarkupLine getDescription() {
169     return ModelUtil.resolveToMarkupLine(getAnnotation().description());
170   }
171 
172   @Override
173   public String getUseName() {
174     return ModelUtil.resolveNoneOrValue(getAnnotation().useName());
175   }
176 
177   @Override
178   public Integer getUseIndex() {
179     int value = getAnnotation().useIndex();
180     return value == Integer.MIN_VALUE ? null : value;
181   }
182 
183   @Override
184   public int getMinOccurs() {
185     return getAnnotation().minOccurs();
186   }
187 
188   @Override
189   public int getMaxOccurs() {
190     return getAnnotation().maxOccurs();
191   }
192 
193   @Override
194   public MarkupMultiline getRemarks() {
195     return ModelUtil.resolveToMarkupMultiline(getAnnotation().remarks());
196   }
197 }