001/* 002 * SPDX-FileCopyrightText: none 003 * SPDX-License-Identifier: CC0-1.0 004 */ 005 006package gov.nist.secauto.metaschema.databind.model.annotations; 007 008import gov.nist.secauto.metaschema.core.datatype.IDataTypeAdapter; 009import gov.nist.secauto.metaschema.core.datatype.adapter.MetaschemaDataTypeProvider; 010import gov.nist.secauto.metaschema.core.datatype.markup.MarkupLine; 011import gov.nist.secauto.metaschema.core.datatype.markup.MarkupMultiline; 012import gov.nist.secauto.metaschema.core.model.IAttributable; 013import gov.nist.secauto.metaschema.core.model.IBoundObject; 014import gov.nist.secauto.metaschema.core.model.IMetaschemaData; 015import gov.nist.secauto.metaschema.core.model.IModule; 016import gov.nist.secauto.metaschema.core.util.CollectionUtil; 017import gov.nist.secauto.metaschema.databind.IBindingContext; 018import gov.nist.secauto.metaschema.databind.model.IGroupAs; 019import gov.nist.secauto.metaschema.databind.model.impl.DefaultGroupAs; 020 021import java.lang.annotation.Annotation; 022import java.lang.reflect.Field; 023import java.net.URI; 024import java.util.Arrays; 025import java.util.LinkedHashSet; 026import java.util.List; 027import java.util.Map; 028import java.util.Set; 029 030import edu.umd.cs.findbugs.annotations.NonNull; 031import edu.umd.cs.findbugs.annotations.Nullable; 032 033public final class ModelUtil { 034 // TODO: replace NO_STRING_VALUE with NULL_VALUE where possible. URIs will not 035 // allow NULL_VALUE. 036 public static final String NO_STRING_VALUE = "##none"; 037 public static final String DEFAULT_STRING_VALUE = "##default"; 038 /** 039 * A placeholder for a {@code null} value for use in annotations, which cannot 040 * be null by default. 041 * <p> 042 * Use of {@code "\u0000"} simple substitute for {@code null} to allow 043 * implementations to recognize the "no default value" state. 044 */ 045 public static final String NULL_VALUE = "\u0000"; 046 047 private ModelUtil() { 048 // disable construction 049 } 050 051 /** 052 * Get the requested annotation from the provided Java class. 053 * 054 * @param <A> 055 * the annotation Java type 056 * @param clazz 057 * the Java class to get the annotation from 058 * @param annotationClass 059 * the annotation class instance 060 * @return the annotation 061 * @throws IllegalArgumentException 062 * if the annotation was not present on the class 063 */ 064 @NonNull 065 public static <A extends Annotation> A getAnnotation( 066 @NonNull Class<?> clazz, 067 Class<A> annotationClass) { 068 A annotation = clazz.getAnnotation(annotationClass); 069 if (annotation == null) { 070 throw new IllegalArgumentException( 071 String.format("Class '%s' is missing the '%s' annotation.", 072 clazz.getName(), 073 annotationClass.getName())); 074 } 075 return annotation; 076 } 077 078 /** 079 * Get the requested annotation from the provided Java field. 080 * 081 * @param <A> 082 * the annotation Java type 083 * @param javaField 084 * the Java field to get the annotation from 085 * @param annotationClass 086 * the annotation class instance 087 * @return the annotation 088 * @throws IllegalArgumentException 089 * if the annotation was not present on the field 090 */ 091 @NonNull 092 public static <A extends Annotation> A getAnnotation( 093 @NonNull Field javaField, 094 Class<A> annotationClass) { 095 A annotation = javaField.getAnnotation(annotationClass); 096 if (annotation == null) { 097 throw new IllegalArgumentException( 098 String.format("Field '%s' is missing the '%s' annotation.", 099 javaField.toGenericString(), 100 annotationClass.getName())); 101 } 102 return annotation; 103 } 104 105 /** 106 * Resolves a string value. If the value is {@code null} or "##default", then 107 * the provided default value will be used instead. If the value is "##none", 108 * then the value will be {@code null}. Otherwise, the value is returned. 109 * 110 * @param value 111 * the requested value 112 * @param defaultValue 113 * the default value 114 * @return the resolved value or {@code null} 115 */ 116 @Nullable 117 public static String resolveNoneOrDefault(@Nullable String value, @Nullable String defaultValue) { 118 String retval; 119 if (value == null || DEFAULT_STRING_VALUE.equals(value)) { 120 retval = defaultValue; 121 } else if (NO_STRING_VALUE.equals(value)) { 122 retval = null; // NOPMD - intentional 123 } else { 124 retval = value; 125 } 126 return retval; 127 } 128 129 /** 130 * Get the processed value of a string. If the value is "##none", then the value 131 * will be {@code null}. Otherwise the value is returned. 132 * 133 * @param value 134 * text or {@code "##none"} if no text is provided 135 * @return the resolved value or {@code null} 136 */ 137 @Nullable 138 public static String resolveNoneOrValue(@NonNull String value) { 139 return NO_STRING_VALUE.equals(value) ? null : value; 140 } 141 142 /** 143 * Get the markup value of a markdown string. 144 * 145 * @param value 146 * markdown text or {@code "##none"} if no text is provided 147 * @return the markup line content or {@code null} if no markup content was 148 * provided 149 */ 150 @Nullable 151 public static MarkupLine resolveToMarkupLine(@NonNull String value) { 152 return resolveNoneOrValue(value) == null ? null : MarkupLine.fromMarkdown(value); 153 } 154 155 /** 156 * Get the markup value of a markdown string. 157 * 158 * @param value 159 * markdown text or {@code "##none"} if no text is provided 160 * @return the markup line content or {@code null} if no markup content was 161 * provided 162 */ 163 @Nullable 164 public static MarkupMultiline resolveToMarkupMultiline(@NonNull String value) { 165 return resolveNoneOrValue(value) == null ? null : MarkupMultiline.fromMarkdown(value); 166 } 167 168 /** 169 * Get the data type adapter instance of the provided adapter class. 170 * <p> 171 * If the provided adapter Java class is the {@link NullJavaTypeAdapter} class, 172 * then the default data type adapter will be returned. 173 * 174 * @param adapterClass 175 * the data type adapter class to get the data type adapter instance 176 * for 177 * @param bindingContext 178 * the Metaschema binding context used to lookup the data type adapter 179 * @return the data type adapter 180 * @throws IllegalArgumentException 181 * if the provided adapter is not registered with the binding context 182 */ 183 @NonNull 184 public static IDataTypeAdapter<?> getDataTypeAdapter( 185 @NonNull Class<? extends IDataTypeAdapter<?>> adapterClass, 186 @NonNull IBindingContext bindingContext) { 187 IDataTypeAdapter<?> retval; 188 if (NullJavaTypeAdapter.class.equals(adapterClass)) { 189 retval = MetaschemaDataTypeProvider.DEFAULT_DATA_TYPE; 190 } else { 191 retval = bindingContext.getJavaTypeAdapterInstance(adapterClass); 192 if (retval == null) { 193 throw new IllegalArgumentException("Unable to get type adapter instance for class: " + adapterClass.getName()); 194 } 195 } 196 return retval; 197 } 198 199 /** 200 * Given a provided default value string, get the data type specific default 201 * value using the provided data type adapter. 202 * <p> 203 * If the provided default value is {@link ModelUtil#NULL_VALUE}, then this 204 * method will return a {@code null} value. 205 * 206 * @param defaultValue 207 * the string representation of the default value 208 * @param adapter 209 * the data type adapter instance used to cast the default string value 210 * to a data type specific object 211 * @return the data type specific object or {@code null} if the provided default 212 * value was {@link ModelUtil#NULL_VALUE} 213 */ 214 @Nullable 215 public static Object resolveDefaultValue(@NonNull String defaultValue, IDataTypeAdapter<?> adapter) { 216 Object retval = null; 217 if (!NULL_VALUE.equals(defaultValue)) { 218 retval = adapter.parse(defaultValue); 219 } 220 return retval; 221 } 222 223 /** 224 * Resolves an integer value by determining if an actual value is provided or 225 * -2^31, which indicates that no actual value was provided. 226 * <p> 227 * The integer value -2^31 cannot be used, since this indicates no value. 228 * 229 * @param value 230 * the integer value to resolve 231 * @return the integer value or {@code null} if the provided value was -2^31 232 */ 233 public static Integer resolveDefaultInteger(int value) { 234 return value == Integer.MIN_VALUE ? null : value; 235 } 236 237 /** 238 * Resolves a {@link GroupAs} annotation determining if an actual value is 239 * provided or if the value is the default, which indicates that no actual 240 * GroupAs was provided. 241 * 242 * @param groupAs 243 * the GroupAs value to resolve 244 * @param module 245 * the containing module instance 246 * @return a new {@link IGroupAs} instance or a singleton group as if the 247 * provided value was the default value 248 */ 249 @NonNull 250 public static IGroupAs resolveDefaultGroupAs( 251 @NonNull GroupAs groupAs, 252 @NonNull IModule module) { 253 return NULL_VALUE.equals(groupAs.name()) 254 ? IGroupAs.SINGLETON_GROUP_AS 255 : new DefaultGroupAs(groupAs, module); 256 } 257 258 public static String toLocation(@NonNull IBoundObject obj) { 259 IMetaschemaData data = obj.getMetaschemaData(); 260 261 String retval = ""; 262 if (data != null) { 263 int line = data.getLine(); 264 if (line > -1) { 265 retval = line + ":" + data.getColumn(); 266 } 267 } 268 return retval; 269 } 270 271 public static String toLocation(@NonNull IBoundObject obj, @Nullable URI uri) { 272 String retval = uri == null ? "" : uri.toASCIIString(); 273 274 String location = toLocation(obj); 275 if (!location.isEmpty()) { 276 retval = retval.isEmpty() ? location : retval + "@" + location; 277 } 278 return retval; 279 } 280 281 public static Map.Entry<IAttributable.Key, Set<String>> toPropertyEntry(@NonNull Property property) { 282 String name = property.name(); 283 String namespace = property.namespace(); 284 @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops") // ok 285 IAttributable.Key key = IAttributable.key(namespace, name); 286 287 String[] values = property.values(); 288 List<String> valueList = Arrays.asList(values); 289 @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops") // ok 290 Set<String> valueSet = new LinkedHashSet<>(valueList); 291 292 return Map.entry(key, CollectionUtil.unmodifiableSet(valueSet)); 293 } 294}