1   /*
2    * SPDX-FileCopyrightText: none
3    * SPDX-License-Identifier: CC0-1.0
4    */
5   
6   package dev.metaschema.databind.codegen.typeinfo;
7   
8   import com.squareup.javapoet.AnnotationSpec;
9   import com.squareup.javapoet.ClassName;
10  import com.squareup.javapoet.FieldSpec;
11  import com.squareup.javapoet.ParameterizedTypeName;
12  import com.squareup.javapoet.TypeName;
13  import com.squareup.javapoet.TypeSpec;
14  
15  import java.util.LinkedHashMap;
16  import java.util.LinkedHashSet;
17  import java.util.LinkedList;
18  import java.util.List;
19  import java.util.Map;
20  import java.util.Set;
21  
22  import dev.metaschema.core.model.IGroupable;
23  import dev.metaschema.core.model.IModelDefinition;
24  import dev.metaschema.core.model.IModelInstanceAbsolute;
25  import dev.metaschema.core.model.JsonGroupAsBehavior;
26  import dev.metaschema.core.model.XmlGroupAsBehavior;
27  import dev.metaschema.core.util.ObjectUtils;
28  import dev.metaschema.databind.codegen.typeinfo.def.IAssemblyDefinitionTypeInfo;
29  import dev.metaschema.databind.model.annotations.GroupAs;
30  import edu.umd.cs.findbugs.annotations.NonNull;
31  import edu.umd.cs.findbugs.annotations.Nullable;
32  
33  abstract class AbstractModelInstanceTypeInfo<INSTANCE extends IModelInstanceAbsolute>
34      extends AbstractInstanceTypeInfo<INSTANCE, IAssemblyDefinitionTypeInfo>
35      implements IModelInstanceTypeInfo {
36  
37    protected AbstractModelInstanceTypeInfo(
38        @NonNull INSTANCE instance,
39        @NonNull IAssemblyDefinitionTypeInfo parentDefinition) {
40      super(instance, parentDefinition);
41    }
42  
43    @Override
44    public String getBaseName() {
45      INSTANCE instance = getInstance();
46      String baseName = getInstance().getGroupAsName();
47      if (baseName == null) {
48        throw new IllegalStateException(String.format(
49            "Unable to derive the property name, due to missing group as name, for '%s' in the module '%s'.",
50            instance.toCoordinates(),
51            instance.getContainingModule().getLocation()));
52      }
53      return baseName;
54    }
55  
56    @NonNull
57    @Override
58    public TypeName getJavaFieldType() {
59      TypeName item = getJavaItemType();
60  
61      @NonNull
62      TypeName retval;
63      IModelInstanceAbsolute instance = getInstance();
64      int maxOccurance = instance.getMaxOccurs();
65      if (maxOccurance == -1 || maxOccurance > 1) {
66        if (JsonGroupAsBehavior.KEYED.equals(instance.getJsonGroupAsBehavior())) {
67          retval = ObjectUtils.notNull(
68              ParameterizedTypeName.get(ClassName.get(Map.class), ClassName.get(String.class), item));
69        } else {
70          retval = ObjectUtils.notNull(ParameterizedTypeName.get(ClassName.get(List.class), item));
71        }
72      } else {
73        retval = item;
74      }
75  
76      return retval;
77    }
78  
79    @Override
80    public boolean isCollectionType() {
81      IModelInstanceAbsolute instance = getInstance();
82      int maxOccurs = instance.getMaxOccurs();
83      return maxOccurs == -1 || maxOccurs > 1;
84    }
85  
86    @Nullable
87    @Override
88    public Class<?> getCollectionImplementationClass() {
89      IModelInstanceAbsolute instance = getInstance();
90      int maxOccurs = instance.getMaxOccurs();
91      if (maxOccurs == -1 || maxOccurs > 1) {
92        // This is a collection - return the appropriate implementation class
93        if (JsonGroupAsBehavior.KEYED.equals(instance.getJsonGroupAsBehavior())) {
94          return LinkedHashMap.class;
95        }
96        return LinkedList.class;
97      }
98      // Not a collection
99      return null;
100   }
101 
102   @NonNull
103   protected abstract AnnotationSpec.Builder newBindingAnnotation();
104 
105   @Override
106   public Set<IModelDefinition> buildField(
107       TypeSpec.Builder typeBuilder,
108       FieldSpec.Builder fieldBuilder) {
109     Set<IModelDefinition> retval = new LinkedHashSet<>(super.buildField(typeBuilder, fieldBuilder));
110 
111     AnnotationSpec.Builder annotation = newBindingAnnotation();
112 
113     retval.addAll(buildBindingAnnotation(typeBuilder, fieldBuilder, annotation));
114 
115     fieldBuilder.addAnnotation(annotation.build());
116 
117     return retval;
118   }
119 
120   @NonNull
121   protected AnnotationSpec.Builder generateGroupAsAnnotation() {
122     AnnotationSpec.Builder groupAsAnnoation = AnnotationSpec.builder(GroupAs.class);
123 
124     IModelInstanceAbsolute modelInstance = getInstance();
125 
126     groupAsAnnoation.addMember("name", "$S",
127         ObjectUtils.requireNonNull(modelInstance.getGroupAsName(), "The grouping name must be non-null"));
128 
129     // FIXME: handle group-as namespace as a prefix
130 
131     JsonGroupAsBehavior jsonGroupAsBehavior = modelInstance.getJsonGroupAsBehavior();
132     assert jsonGroupAsBehavior != null;
133     if (!IGroupable.DEFAULT_JSON_GROUP_AS_BEHAVIOR.equals(jsonGroupAsBehavior)) {
134       groupAsAnnoation.addMember("inJson", "$T.$L",
135           JsonGroupAsBehavior.class, jsonGroupAsBehavior.toString());
136     }
137 
138     XmlGroupAsBehavior xmlGroupAsBehavior = modelInstance.getXmlGroupAsBehavior();
139     assert xmlGroupAsBehavior != null;
140     if (!IGroupable.DEFAULT_XML_GROUP_AS_BEHAVIOR.equals(xmlGroupAsBehavior)) {
141       groupAsAnnoation.addMember("inXml", "$T.$L",
142           XmlGroupAsBehavior.class, xmlGroupAsBehavior.toString());
143     }
144     return groupAsAnnoation;
145   }
146 }