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.IBoundObject; 013import gov.nist.secauto.metaschema.core.model.IMetaschemaData; 014import gov.nist.secauto.metaschema.core.model.IModule; 015import gov.nist.secauto.metaschema.core.util.ObjectUtils; 016import gov.nist.secauto.metaschema.databind.IBindingContext; 017import gov.nist.secauto.metaschema.databind.model.IGroupAs; 018import gov.nist.secauto.metaschema.databind.model.impl.DefaultGroupAs; 019 020import java.lang.annotation.Annotation; 021import java.lang.reflect.Field; 022import java.net.URI; 023 024import edu.umd.cs.findbugs.annotations.NonNull; 025import edu.umd.cs.findbugs.annotations.Nullable; 026 027public final class ModelUtil { 028 // TODO: replace NO_STRING_VALUE with NULL_VALUE where possible. URIs will not 029 // allow NULL_VALUE. 030 public static final String NO_STRING_VALUE = "##none"; 031 public static final String DEFAULT_STRING_VALUE = "##default"; 032 /** 033 * A placeholder for a {@code null} value for use in annotations, which cannot 034 * be null by default. 035 * <p> 036 * Use of {@code "\u0000"} simple substitute for {@code null} to allow 037 * implementations to recognize the "no default value" state. 038 */ 039 public static final String NULL_VALUE = "\u0000"; 040 041 private ModelUtil() { 042 // disable construction 043 } 044 045 /** 046 * Get the requested annotation from the provided Java class. 047 * 048 * @param <A> 049 * the annotation Java type 050 * @param clazz 051 * the Java class to get the annotation from 052 * @param annotationClass 053 * the annotation class instance 054 * @return the annotation 055 * @throws IllegalArgumentException 056 * if the annotation was not present on the class 057 */ 058 @NonNull 059 public static <A extends Annotation> A getAnnotation( 060 @NonNull Class<?> clazz, 061 Class<A> annotationClass) { 062 A annotation = clazz.getAnnotation(annotationClass); 063 if (annotation == null) { 064 throw new IllegalArgumentException( 065 String.format("Class '%s' is missing the '%s' annotation.", 066 clazz.getName(), 067 annotationClass.getName())); 068 } 069 return annotation; 070 } 071 072 /** 073 * Get the requested annotation from the provided Java field. 074 * 075 * @param <A> 076 * the annotation Java type 077 * @param javaField 078 * the Java field to get the annotation from 079 * @param annotationClass 080 * the annotation class instance 081 * @return the annotation 082 * @throws IllegalArgumentException 083 * if the annotation was not present on the field 084 */ 085 @NonNull 086 public static <A extends Annotation> A getAnnotation( 087 @NonNull Field javaField, 088 Class<A> annotationClass) { 089 A annotation = javaField.getAnnotation(annotationClass); 090 if (annotation == null) { 091 throw new IllegalArgumentException( 092 String.format("Field '%s' is missing the '%s' annotation.", 093 javaField.toGenericString(), 094 annotationClass.getName())); 095 } 096 return annotation; 097 } 098 099 /** 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}