1   /*
2    * SPDX-FileCopyrightText: none
3    * SPDX-License-Identifier: CC0-1.0
4    */
5   
6   package dev.metaschema.databind.model.impl;
7   
8   import java.lang.reflect.Field;
9   import java.util.Arrays;
10  import java.util.LinkedHashMap;
11  import java.util.Map;
12  import java.util.Optional;
13  import java.util.Set;
14  import java.util.stream.Collectors;
15  
16  import dev.metaschema.core.datatype.IDataTypeAdapter;
17  import dev.metaschema.core.datatype.markup.MarkupLine;
18  import dev.metaschema.core.datatype.markup.MarkupMultiline;
19  import dev.metaschema.core.model.AbstractInlineFlagDefinition;
20  import dev.metaschema.core.model.IAttributable;
21  import dev.metaschema.core.model.IBoundObject;
22  import dev.metaschema.core.model.IModule;
23  import dev.metaschema.core.model.ISource;
24  import dev.metaschema.core.model.constraint.IValueConstrained;
25  import dev.metaschema.core.model.constraint.ValueConstraintSet;
26  import dev.metaschema.core.util.CollectionUtil;
27  import dev.metaschema.core.util.ObjectUtils;
28  import dev.metaschema.databind.model.IBoundDefinitionFlag;
29  import dev.metaschema.databind.model.IBoundDefinitionModel;
30  import dev.metaschema.databind.model.IBoundInstanceFlag;
31  import dev.metaschema.databind.model.IBoundModule;
32  import dev.metaschema.databind.model.annotations.BoundFlag;
33  import dev.metaschema.databind.model.annotations.JsonFieldValueKeyFlag;
34  import dev.metaschema.databind.model.annotations.JsonKey;
35  import dev.metaschema.databind.model.annotations.ModelUtil;
36  import dev.metaschema.databind.model.annotations.ValueConstraints;
37  import edu.umd.cs.findbugs.annotations.NonNull;
38  import edu.umd.cs.findbugs.annotations.Nullable;
39  import nl.talsmasoftware.lazy4j.Lazy;
40  
41  /**
42   * Implements a Metaschema module inline flag instance/definition bound to a
43   * Java field.
44   */
45  public class InstanceFlagInline
46      extends AbstractInlineFlagDefinition<IBoundDefinitionModel<IBoundObject>, IBoundDefinitionFlag, IBoundInstanceFlag>
47      implements IBoundInstanceFlag {
48    @NonNull
49    private final Field javaField;
50    @NonNull
51    private final BoundFlag annotation;
52    @NonNull
53    private final IDataTypeAdapter<?> javaTypeAdapter;
54    @Nullable
55    private final Object defaultValue;
56    @NonNull
57    private final Lazy<IValueConstrained> constraints;
58    @NonNull
59    private final Lazy<Map<IAttributable.Key, Set<String>>> properties;
60  
61    /**
62     * Construct a new inline field instance/definition.
63     *
64     * @param javaField
65     *          the Java field bound to this instance
66     * @param parent
67     *          the definition containing this instance
68     */
69    public InstanceFlagInline(
70        @NonNull Field javaField,
71        @NonNull IBoundDefinitionModel<IBoundObject> parent) {
72      super(parent);
73      FieldSupport.bindField(javaField);
74      this.javaField = javaField;
75      this.annotation = ModelUtil.getAnnotation(javaField, BoundFlag.class);
76      Class<? extends IDataTypeAdapter<?>> adapterClass = ObjectUtils.notNull(getAnnotation().typeAdapter());
77      this.javaTypeAdapter = ModelUtil.getDataTypeAdapter(
78          adapterClass,
79          parent.getBindingContext());
80      this.defaultValue = ModelUtil.resolveDefaultValue(getAnnotation().defaultValue(), this.javaTypeAdapter);
81  
82      IModule module = parent.getContainingModule();
83      ISource source = module.getSource();
84  
85      this.constraints = ObjectUtils.notNull(Lazy.of(() -> {
86        IValueConstrained retval = new ValueConstraintSet(source);
87        ValueConstraints valueAnnotation = getAnnotation().valueConstraints();
88        ConstraintSupport.parse(valueAnnotation, source, retval);
89        return retval;
90      }));
91      this.properties = ObjectUtils.notNull(
92          Lazy.of(() -> CollectionUtil.unmodifiableMap(ObjectUtils.notNull(
93              Arrays.stream(annotation.properties())
94                  .map(ModelUtil::toPropertyEntry)
95                  .collect(
96                      Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (v1, v2) -> v2, LinkedHashMap::new))))));
97    }
98  
99    /**
100    * Get the bound Java field.
101    *
102    * @return the bound Java field
103    */
104   @Override
105   @NonNull
106   public Field getField() {
107     return javaField;
108   }
109 
110   /**
111    * Get the binding Java annotation.
112    *
113    * @return the binding Java annotation
114    */
115   @NonNull
116   private BoundFlag getAnnotation() {
117     return annotation;
118   }
119 
120   @Override
121   public IBoundModule getContainingModule() {
122     return getContainingDefinition().getContainingModule();
123   }
124 
125   // ------------------------------------------
126   // - Start annotation driven code - CPD-OFF -
127   // ------------------------------------------
128 
129   @SuppressWarnings("null")
130   @Override
131   @NonNull
132   public IValueConstrained getConstraintSupport() {
133     return constraints.get();
134   }
135 
136   @Override
137   public boolean isRequired() {
138     return getAnnotation().required();
139   }
140 
141   @Override
142   public Object getDefaultValue() {
143     return defaultValue;
144   }
145 
146   @Override
147   public boolean isJsonKey() {
148     return getField().isAnnotationPresent(JsonKey.class);
149   }
150 
151   @Override
152   public boolean isJsonValueKey() {
153     return getField().isAnnotationPresent(JsonFieldValueKeyFlag.class);
154   }
155 
156   @Override
157   public IDataTypeAdapter<?> getJavaTypeAdapter() {
158     return javaTypeAdapter;
159   }
160 
161   @Override
162   @Nullable
163   public String getFormalName() {
164     return ModelUtil.resolveNoneOrValue(getAnnotation().formalName());
165   }
166 
167   @Override
168   @Nullable
169   public MarkupLine getDescription() {
170     return ModelUtil.resolveToMarkupLine(getAnnotation().description());
171   }
172 
173   @Override
174   public String getName() {
175     return ObjectUtils.notNull(
176         Optional.ofNullable(ModelUtil.resolveNoneOrValue(getAnnotation().name())).orElse(getField().getName()));
177   }
178 
179   @Override
180   public Integer getUseIndex() {
181     return getAnnotation().useIndex();
182   }
183 
184   @Override
185   public Map<Key, Set<String>> getProperties() {
186     return ObjectUtils.notNull(properties.get());
187   }
188 
189   @Override
190   @Nullable
191   public MarkupMultiline getRemarks() {
192     return ModelUtil.resolveToMarkupMultiline(getAnnotation().remarks());
193   }
194 
195   // ----------------------------------------
196   // - End annotation driven code - CPD-OFF -
197   // ----------------------------------------
198 }