1   /*
2    * SPDX-FileCopyrightText: none
3    * SPDX-License-Identifier: CC0-1.0
4    */
5   
6   package gov.nist.secauto.metaschema.databind.model.annotations;
7   
8   import gov.nist.secauto.metaschema.core.datatype.IDataTypeAdapter;
9   import gov.nist.secauto.metaschema.core.datatype.adapter.MetaschemaDataTypeProvider;
10  import gov.nist.secauto.metaschema.core.datatype.markup.MarkupLine;
11  import gov.nist.secauto.metaschema.core.datatype.markup.MarkupMultiline;
12  import gov.nist.secauto.metaschema.core.model.IBoundObject;
13  import gov.nist.secauto.metaschema.core.model.IMetaschemaData;
14  import gov.nist.secauto.metaschema.core.model.IModule;
15  import gov.nist.secauto.metaschema.core.util.ObjectUtils;
16  import gov.nist.secauto.metaschema.databind.IBindingContext;
17  import gov.nist.secauto.metaschema.databind.model.IGroupAs;
18  import gov.nist.secauto.metaschema.databind.model.impl.DefaultGroupAs;
19  
20  import java.lang.annotation.Annotation;
21  import java.lang.reflect.Field;
22  import java.net.URI;
23  
24  import edu.umd.cs.findbugs.annotations.NonNull;
25  import edu.umd.cs.findbugs.annotations.Nullable;
26  
27  public final class ModelUtil {
28    // TODO: replace NO_STRING_VALUE with NULL_VALUE where possible. URIs will not
29    // allow NULL_VALUE.
30    public static final String NO_STRING_VALUE = "##none";
31    public static final String DEFAULT_STRING_VALUE = "##default";
32    /**
33     * A placeholder for a {@code null} value for use in annotations, which cannot
34     * be null by default.
35     * <p>
36     * Use of {@code "\u0000"} simple substitute for {@code null} to allow
37     * implementations to recognize the "no default value" state.
38     */
39    public static final String NULL_VALUE = "\u0000";
40  
41    private ModelUtil() {
42      // disable construction
43    }
44  
45    /**
46     * Get the requested annotation from the provided Java class.
47     *
48     * @param <A>
49     *          the annotation Java type
50     * @param clazz
51     *          the Java class to get the annotation from
52     * @param annotationClass
53     *          the annotation class instance
54     * @return the annotation
55     * @throws IllegalArgumentException
56     *           if the annotation was not present on the class
57     */
58    @NonNull
59    public static <A extends Annotation> A getAnnotation(
60        @NonNull Class<?> clazz,
61        Class<A> annotationClass) {
62      A annotation = clazz.getAnnotation(annotationClass);
63      if (annotation == null) {
64        throw new IllegalArgumentException(
65            String.format("Class '%s' is missing the '%s' annotation.",
66                clazz.getName(),
67                annotationClass.getName()));
68      }
69      return annotation;
70    }
71  
72    /**
73     * Get the requested annotation from the provided Java field.
74     *
75     * @param <A>
76     *          the annotation Java type
77     * @param javaField
78     *          the Java field to get the annotation from
79     * @param annotationClass
80     *          the annotation class instance
81     * @return the annotation
82     * @throws IllegalArgumentException
83     *           if the annotation was not present on the field
84     */
85    @NonNull
86    public static <A extends Annotation> A getAnnotation(
87        @NonNull Field javaField,
88        Class<A> annotationClass) {
89      A annotation = javaField.getAnnotation(annotationClass);
90      if (annotation == null) {
91        throw new IllegalArgumentException(
92            String.format("Field '%s' is missing the '%s' annotation.",
93                javaField.toGenericString(),
94                annotationClass.getName()));
95      }
96      return annotation;
97    }
98  
99    /**
100    * Resolves a string value. If the value is {@code null} or "##default", then
101    * the provided default value will be used instead. If the value is "##none",
102    * then the value will be {@code null}. Otherwise, the value is returned.
103    *
104    * @param value
105    *          the requested value
106    * @param defaultValue
107    *          the default value
108    * @return the resolved value or {@code null}
109    */
110   @Nullable
111   public static String resolveNoneOrDefault(@Nullable String value, @Nullable String defaultValue) {
112     String retval;
113     if (value == null || DEFAULT_STRING_VALUE.equals(value)) {
114       retval = defaultValue;
115     } else if (NO_STRING_VALUE.equals(value)) {
116       retval = null; // NOPMD - intentional
117     } else {
118       retval = value;
119     }
120     return retval;
121   }
122 
123   /**
124    * Get the processed value of a string. If the value is "##none", then the value
125    * will be {@code null}. Otherwise the value is returned.
126    *
127    * @param value
128    *          text or {@code "##none"} if no text is provided
129    * @return the resolved value or {@code null}
130    */
131   @Nullable
132   public static String resolveNoneOrValue(@NonNull String value) {
133     return NO_STRING_VALUE.equals(value) ? null : value;
134   }
135 
136   /**
137    * Get the markup value of a markdown string.
138    *
139    * @param value
140    *          markdown text or {@code "##none"} if no text is provided
141    * @return the markup line content or {@code null} if no markup content was
142    *         provided
143    */
144   @Nullable
145   public static MarkupLine resolveToMarkupLine(@NonNull String value) {
146     return resolveNoneOrValue(value) == null ? null : MarkupLine.fromMarkdown(value);
147   }
148 
149   /**
150    * Get the markup value of a markdown string.
151    *
152    * @param value
153    *          markdown text or {@code "##none"} if no text is provided
154    * @return the markup line content or {@code null} if no markup content was
155    *         provided
156    */
157   @Nullable
158   public static MarkupMultiline resolveToMarkupMultiline(@NonNull String value) {
159     return resolveNoneOrValue(value) == null ? null : MarkupMultiline.fromMarkdown(value);
160   }
161 
162   /**
163    * Get the data type adapter instance of the provided adapter class.
164    * <p>
165    * If the provided adapter Java class is the {@link NullJavaTypeAdapter} class,
166    * then the default data type adapter will be returned.
167    *
168    * @param adapterClass
169    *          the data type adapter class to get the data type adapter instance
170    *          for
171    * @param bindingContext
172    *          the Metaschema binding context used to lookup the data type adapter
173    * @return the data type adapter
174    */
175   @NonNull
176   public static IDataTypeAdapter<?> getDataTypeAdapter(
177       @NonNull Class<? extends IDataTypeAdapter<?>> adapterClass,
178       @NonNull IBindingContext bindingContext) {
179     IDataTypeAdapter<?> retval;
180     if (NullJavaTypeAdapter.class.equals(adapterClass)) {
181       retval = MetaschemaDataTypeProvider.DEFAULT_DATA_TYPE;
182     } else {
183       retval = ObjectUtils.requireNonNull(bindingContext.getJavaTypeAdapterInstance(adapterClass));
184     }
185     return retval;
186   }
187 
188   /**
189    * Given a provided default value string, get the data type specific default
190    * value using the provided data type adapter.
191    * <p>
192    * If the provided default value is {@link ModelUtil#NULL_VALUE}, then this
193    * method will return a {@code null} value.
194    *
195    * @param defaultValue
196    *          the string representation of the default value
197    * @param adapter
198    *          the data type adapter instance used to cast the default string value
199    *          to a data type specific object
200    * @return the data type specific object or {@code null} if the provided default
201    *         value was {@link ModelUtil#NULL_VALUE}
202    */
203   @Nullable
204   public static Object resolveDefaultValue(@NonNull String defaultValue, IDataTypeAdapter<?> adapter) {
205     Object retval = null;
206     if (!NULL_VALUE.equals(defaultValue)) {
207       retval = adapter.parse(defaultValue);
208     }
209     return retval;
210   }
211 
212   /**
213    * Resolves an integer value by determining if an actual value is provided or
214    * -2^31, which indicates that no actual value was provided.
215    * <p>
216    * The integer value -2^31 cannot be used, since this indicates no value.
217    *
218    * @param value
219    *          the integer value to resolve
220    * @return the integer value or {@code null} if the provided value was -2^31
221    */
222   public static Integer resolveDefaultInteger(int value) {
223     return value == Integer.MIN_VALUE ? null : value;
224   }
225 
226   /**
227    * Resolves a {@link GroupAs} annotation determining if an actual value is
228    * provided or if the value is the default, which indicates that no actual
229    * GroupAs was provided.
230    *
231    * @param groupAs
232    *          the GroupAs value to resolve
233    * @param module
234    *          the containing module instance
235    * @return a new {@link IGroupAs} instance or a singleton group as if the
236    *         provided value was the default value
237    */
238   @NonNull
239   public static IGroupAs resolveDefaultGroupAs(
240       @NonNull GroupAs groupAs,
241       @NonNull IModule module) {
242     return NULL_VALUE.equals(groupAs.name())
243         ? IGroupAs.SINGLETON_GROUP_AS
244         : new DefaultGroupAs(groupAs, module);
245   }
246 
247   public static String toLocation(@NonNull IBoundObject obj) {
248     IMetaschemaData data = obj.getMetaschemaData();
249 
250     String retval = "";
251     if (data != null) {
252       int line = data.getLine();
253       if (line > -1) {
254         retval = line + ":" + data.getColumn();
255       }
256     }
257     return retval;
258   }
259 
260   public static String toLocation(@NonNull IBoundObject obj, @Nullable URI uri) {
261     String retval = uri == null ? "" : uri.toASCIIString();
262 
263     String location = toLocation(obj);
264     if (!location.isEmpty()) {
265       retval = retval.isEmpty() ? location : retval + "@" + location;
266     }
267     return retval;
268   }
269 }