1
2
3
4
5
6 package dev.metaschema.databind.codegen.typeinfo;
7
8 import com.squareup.javapoet.AnnotationSpec;
9 import com.squareup.javapoet.FieldSpec;
10 import com.squareup.javapoet.MethodSpec;
11 import com.squareup.javapoet.ParameterSpec;
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.Set;
19
20 import javax.lang.model.element.Modifier;
21
22 import dev.metaschema.core.model.IAssemblyDefinition;
23 import dev.metaschema.core.model.IFlagInstance;
24 import dev.metaschema.core.model.IGroupable;
25 import dev.metaschema.core.model.IModelDefinition;
26 import dev.metaschema.core.model.INamedModelInstanceAbsolute;
27 import dev.metaschema.core.model.JsonGroupAsBehavior;
28 import dev.metaschema.core.util.CollectionUtil;
29 import dev.metaschema.core.util.ObjectUtils;
30 import dev.metaschema.databind.codegen.ClassUtils;
31 import dev.metaschema.databind.codegen.typeinfo.def.IAssemblyDefinitionTypeInfo;
32 import dev.metaschema.databind.codegen.typeinfo.def.IModelDefinitionTypeInfo;
33 import dev.metaschema.databind.model.annotations.BoundChoice;
34 import edu.umd.cs.findbugs.annotations.NonNull;
35 import edu.umd.cs.findbugs.annotations.Nullable;
36
37 abstract class AbstractNamedModelInstanceTypeInfo<INSTANCE extends INamedModelInstanceAbsolute>
38 extends AbstractModelInstanceTypeInfo<INSTANCE>
39 implements INamedModelInstanceTypeInfo {
40
41 @Nullable
42 private String choiceId;
43
44 public AbstractNamedModelInstanceTypeInfo(
45 @NonNull INSTANCE instance,
46 @NonNull IAssemblyDefinitionTypeInfo parentDefinition) {
47 super(instance, parentDefinition);
48 }
49
50
51
52
53
54
55 @Nullable
56 public String getChoiceId() {
57 return choiceId;
58 }
59
60
61
62
63
64
65
66
67
68
69 @Override
70 public void setChoiceId(@Nullable String choiceId) {
71 this.choiceId = choiceId;
72 }
73
74 @Override
75 public boolean isRequired() {
76
77
78 if (getChoiceId() != null) {
79 return false;
80 }
81
82 INSTANCE instance = getInstance();
83
84
85 return instance.getMinOccurs() >= 1 && instance.getMaxOccurs() == 1;
86 }
87
88 @Override
89 public boolean isCollectionType() {
90 INSTANCE instance = getInstance();
91
92 return instance.getMaxOccurs() == -1 || instance.getMaxOccurs() > 1;
93 }
94
95 @NonNull
96 @Override
97 public String getBaseName() {
98 INSTANCE modelInstance = getInstance();
99 String retval;
100 if (modelInstance.getMaxOccurs() == -1 || modelInstance.getMaxOccurs() > 1) {
101 retval = super.getBaseName();
102 } else {
103 retval = modelInstance.getEffectiveName();
104 }
105 return retval;
106 }
107
108 @Override
109 public String getItemBaseName() {
110 return getInstance().getEffectiveName();
111 }
112
113 @Override
114 public TypeName getJavaItemType() {
115 return getParentTypeInfo().getTypeResolver().getClassName(this);
116 }
117
118 @Override
119 public Set<IModelDefinition> buildField(
120 TypeSpec.Builder typeBuilder,
121 FieldSpec.Builder fieldBuilder) {
122 Set<IModelDefinition> retval = super.buildField(typeBuilder, fieldBuilder);
123
124
125 String choiceIdValue = getChoiceId();
126 if (choiceIdValue != null) {
127 AnnotationSpec.Builder choiceAnnotation = AnnotationSpec.builder(BoundChoice.class);
128 choiceAnnotation.addMember("choiceId", "$S", choiceIdValue);
129 fieldBuilder.addAnnotation(choiceAnnotation.build());
130 }
131
132 IModelDefinition definition = getInstance().getDefinition();
133 if (definition.isInline() && (definition.hasChildren() || definition instanceof IAssemblyDefinition)) {
134 retval = new LinkedHashSet<>(retval);
135
136
137 retval.add(definition);
138 }
139 return retval.isEmpty() ? CollectionUtil.emptySet() : CollectionUtil.unmodifiableSet(retval);
140 }
141
142 @Override
143 public Set<IModelDefinition> buildBindingAnnotation(
144 TypeSpec.Builder typeBuilder,
145 FieldSpec.Builder fieldBuilder,
146 AnnotationSpec.Builder annotation) {
147
148 buildBindingAnnotationCommon(annotation);
149
150 INamedModelInstanceAbsolute instance = getInstance();
151
152 int minOccurs = instance.getMinOccurs();
153 if (minOccurs != IGroupable.DEFAULT_GROUP_AS_MIN_OCCURS) {
154 annotation.addMember("minOccurs", "$L", minOccurs);
155 }
156
157 int maxOccurs = instance.getMaxOccurs();
158 if (maxOccurs != IGroupable.DEFAULT_GROUP_AS_MAX_OCCURS) {
159 annotation.addMember("maxOccurs", "$L", maxOccurs);
160 }
161 if (maxOccurs == -1 || maxOccurs > 1) {
162
163 annotation.addMember("groupAs", "$L", generateGroupAsAnnotation().build());
164 }
165
166 return CollectionUtil.emptySet();
167 }
168
169 @Override
170 protected void buildExtraMethods(TypeSpec.Builder builder, FieldSpec valueField) {
171 super.buildExtraMethods(builder, valueField);
172
173 INamedModelInstanceAbsolute instance = getInstance();
174 int maxOccurance = instance.getMaxOccurs();
175 if (maxOccurance == -1 || maxOccurance > 1) {
176 TypeName itemType = getJavaItemType();
177 ParameterSpec valueParam = ParameterSpec.builder(itemType, "item").build();
178
179 String itemPropertyName = ClassUtils.toPropertyName(getItemBaseName());
180
181 if (JsonGroupAsBehavior.KEYED.equals(instance.getJsonGroupAsBehavior())) {
182 IFlagInstance jsonKey = instance.getDefinition().getJsonKey();
183 if (jsonKey == null) {
184 throw new IllegalStateException(
185 String.format("JSON key not defined for property: %s", instance.toCoordinates()));
186 }
187
188
189 ITypeResolver typeResolver = getParentTypeInfo().getTypeResolver();
190 IModelDefinitionTypeInfo instanceTypeInfo = typeResolver.getTypeInfo(instance.getDefinition());
191 IFlagInstanceTypeInfo jsonKeyTypeInfo = instanceTypeInfo.getFlagInstanceTypeInfo(jsonKey);
192
193 if (jsonKeyTypeInfo == null) {
194 throw new IllegalStateException(
195 String.format("Unable to identify JSON key for property: %s", instance.toCoordinates()));
196 }
197
198 {
199
200 MethodSpec.Builder method = MethodSpec.methodBuilder("add" + itemPropertyName)
201 .addParameter(valueParam)
202 .returns(itemType)
203 .addModifiers(Modifier.PUBLIC)
204 .addJavadoc("Add a new {@link $T} item to the underlying collection.\n", itemType)
205 .addJavadoc("@param item the item to add\n")
206 .addJavadoc("@return the existing {@link $T} item in the collection or {@code null} if not item exists\n",
207 itemType)
208 .addStatement("$1T value = $2T.requireNonNull($3N,\"$3N value cannot be null\")",
209 itemType, ObjectUtils.class, valueParam)
210 .addStatement("$1T key = $2T.requireNonNull($3N.$4N(),\"$3N key cannot be null\")",
211 String.class, ObjectUtils.class, valueParam, "get" + jsonKeyTypeInfo.getPropertyName())
212 .beginControlFlow("if ($N == null)", valueField)
213 .addStatement("$N = new $T<>()", valueField, LinkedHashMap.class)
214 .endControlFlow()
215 .addStatement("return $N.put(key, value)", valueField);
216
217 builder.addMethod(method.build());
218 }
219 {
220
221 MethodSpec.Builder method = MethodSpec.methodBuilder("remove" + itemPropertyName)
222 .addParameter(valueParam)
223 .returns(TypeName.BOOLEAN)
224 .addModifiers(Modifier.PUBLIC)
225 .addJavadoc("Remove the {@link $T} item from the underlying collection.\n", itemType)
226 .addJavadoc("@param item the item to remove\n")
227 .addJavadoc("@return {@code true} if the item was removed or {@code false} otherwise\n")
228 .addStatement("$1T value = $2T.requireNonNull($3N,\"$3N value cannot be null\")",
229 itemType, ObjectUtils.class, valueParam)
230 .addStatement("$1T key = $2T.requireNonNull($3N.$4N(),\"$3N key cannot be null\")",
231 String.class, ObjectUtils.class, valueParam, "get" + jsonKeyTypeInfo.getPropertyName())
232 .addStatement("return $1N != null && $1N.remove(key, value)", valueField);
233 builder.addMethod(method.build());
234 }
235 } else {
236 {
237
238 MethodSpec.Builder method = MethodSpec.methodBuilder("add" + itemPropertyName)
239 .addParameter(valueParam)
240 .returns(TypeName.BOOLEAN)
241 .addModifiers(Modifier.PUBLIC)
242 .addJavadoc("Add a new {@link $T} item to the underlying collection.\n", itemType)
243 .addJavadoc("@param item the item to add\n")
244 .addJavadoc("@return {@code true}\n")
245 .addStatement("$T value = $T.requireNonNull($N,\"$N cannot be null\")",
246 itemType, ObjectUtils.class, valueParam, valueParam)
247 .beginControlFlow("if ($N == null)", valueField)
248 .addStatement("$N = new $T<>()", valueField, LinkedList.class)
249 .endControlFlow()
250 .addStatement("return $N.add(value)", valueField);
251
252 builder.addMethod(method.build());
253 }
254
255 {
256
257 MethodSpec.Builder method = MethodSpec.methodBuilder("remove" + itemPropertyName)
258 .addParameter(valueParam)
259 .returns(TypeName.BOOLEAN)
260 .addModifiers(Modifier.PUBLIC)
261 .addJavadoc("Remove the first matching {@link $T} item from the underlying collection.\n", itemType)
262 .addJavadoc("@param item the item to remove\n")
263 .addJavadoc("@return {@code true} if the item was removed or {@code false} otherwise\n")
264 .addStatement("$T value = $T.requireNonNull($N,\"$N cannot be null\")",
265 itemType, ObjectUtils.class, valueParam, valueParam)
266 .addStatement("return $1N != null && $1N.remove(value)", valueField);
267 builder.addMethod(method.build());
268 }
269 }
270 }
271 }
272
273 }