1+ package io .toolisticon .aptk .tools ;
2+
3+ import java .util .Arrays ;
4+ import java .util .Optional ;
5+ import java .util .regex .Matcher ;
6+ import java .util .regex .Pattern ;
7+ import java .util .stream .Collectors ;
8+
9+ import javax .lang .model .element .Modifier ;
10+
11+ import io .toolisticon .aptk .api .AnnotationToClassMapper ;
12+ import io .toolisticon .aptk .tools .corematcher .AptkCoreMatchers ;
13+ import io .toolisticon .aptk .tools .corematcher .ValidationMessage ;
14+ import io .toolisticon .aptk .tools .wrapper .AnnotationMirrorWrapper ;
15+ import io .toolisticon .aptk .tools .wrapper .AnnotationValueWrapper ;
16+ import io .toolisticon .aptk .tools .wrapper .ElementWrapper ;
17+ import io .toolisticon .aptk .tools .wrapper .ExecutableElementWrapper ;
18+ import io .toolisticon .aptk .tools .wrapper .TypeElementWrapper ;
19+
20+ public class AnnotationToClassMapperHelper {
21+
22+
23+ static class AnnotationToClassMapperWrapper {
24+
25+ AnnotationMirrorWrapper annotation ;
26+
27+ private AnnotationToClassMapperWrapper (AnnotationMirrorWrapper annotation ) {
28+ this .annotation = annotation ;
29+ }
30+
31+ TypeMirrorWrapper mappedClass () {
32+ return annotation .getAttribute ("mappedClass" ).get ().getClassValue ();
33+ }
34+
35+
36+ /**
37+ * the attribute names to map against method or constructor parameters.
38+ * @return
39+ */
40+ String [] mappedAttributeNames () {
41+ return annotation .getAttributeWithDefault ("mappedAttributeNames" ).getArrayValue ().stream ().map (e -> e .getStringValue ()).collect (Collectors .toList ()).toArray (new String [0 ]);
42+ }
43+
44+ static Optional <AnnotationToClassMapperWrapper > wrap (AnnotationMirrorWrapper annotation ) {
45+
46+ if (annotation != null && annotation .asElement ().hasAnnotation (AnnotationToClassMapper .class )) {
47+ return Optional .of (new AnnotationToClassMapperWrapper (annotation .asElement ().getAnnotationMirror (AnnotationToClassMapper .class ).get ()));
48+ } else {
49+ return Optional .empty ();
50+ }
51+
52+ }
53+
54+ static boolean hasAnnotation (AnnotationMirrorWrapper annotation ) {
55+ return wrap (annotation ).isPresent ();
56+ }
57+
58+
59+ }
60+
61+
62+ private final ElementWrapper <?> elementWrapper ;
63+ private final AnnotationMirrorWrapper annotation ;
64+
65+ private AnnotationToClassMapperHelper (ElementWrapper <?> elementWrapper , AnnotationMirrorWrapper annotation ) {
66+ this .elementWrapper = elementWrapper ;
67+ this .annotation = annotation ;
68+ }
69+
70+ /**
71+ * Gets an instance of the helper class.
72+ * @param elementWrapper the element wrapper of the annotated element
73+ * @param annotation the annotation mirror of the annotation annotated with AnnotationToClassMapper annotation
74+ * @return
75+ */
76+ public static AnnotationToClassMapperHelper getInstance (ElementWrapper <?> elementWrapper , AnnotationMirrorWrapper annotation ) {
77+ return new AnnotationToClassMapperHelper (elementWrapper , annotation );
78+ }
79+
80+ AnnotationToClassMapperWrapper getValidatorAnnotation () {
81+ return AnnotationToClassMapperWrapper .wrap (this .annotation ).get ();
82+ }
83+
84+ // visible for testing
85+ AnnotationMirrorWrapper getAnnotationMirrorWrapper () {
86+ return annotation ;
87+ }
88+
89+
90+ enum InternalValidationMessages implements ValidationMessage {
91+
92+ ERROR_BROKEN_VALIDATOR_ATTRIBUTE_NAME_MISMATCH ("INVALID_ATTRIBUTE_NAME" , "Passed attribute names for annotation '{}' aren't valid: {}" ),
93+ ERROR_BROKEN_VALIDATOR_CONSTRUCTOR_PARAMETER_MAPPING ("NO_MATCHING_CONSTRUCTOR" , "No matching constructor could be found for class : {}" ),
94+ ERROR_BROKEN_VALIDATOR_MISSING_NOARG_CONSTRUCTOR ("MISSING_NOARG_CONSTRUCTOR" , "Haven't found a noarg constructor for class: {}" ),
95+ ERROR_BROKEN_VALIDATOR_INCORRECT_METHOD_PARAMETER_MAPPING ("INCORRECT_METHOD_PARAMETER_MAPPING" , "Empty attributeNames can only be used if annotated element represents a method parameter" ),
96+ ;
97+
98+ private final String code ;
99+
100+ private final String message ;
101+
102+ InternalValidationMessages (String code , String message ) {
103+ this .code = code ;
104+ this .message = message ;
105+ }
106+
107+
108+ @ Override
109+ public String getCode () {
110+ return code ;
111+ }
112+
113+ @ Override
114+ public String getMessage () {
115+ return message ;
116+ }
117+
118+
119+
120+ }
121+
122+ private boolean isLocaleVariableName (String name ) {
123+ return name .matches ("[{].*[}]" );
124+ }
125+
126+ private String getLocalVariableName (String name ) {
127+ Pattern pattern = Pattern .compile ("[{](.*)[}]" );
128+ Matcher matcher = pattern .matcher (name );
129+ return matcher .matches ()? matcher .group (1 ): null ;
130+ }
131+
132+
133+ /**
134+ * Validates if the annotation has been properly configured and if constructor is available.
135+ * @return true if annotion configuration is correct and constructor is available, otherwise false
136+ */
137+ public boolean validate () {
138+ // must check if parameter types are assignable
139+ AnnotationToClassMapperWrapper mapperAnnotation = getValidatorAnnotation ();
140+ TypeMirrorWrapper mappedTypeMirror = mapperAnnotation .mappedClass ();
141+ String [] attributeNamesToConstructorParameterMapping = mapperAnnotation .mappedAttributeNames ();
142+
143+
144+
145+
146+ if (attributeNamesToConstructorParameterMapping .length > 0 ) {
147+
148+ // First check if annotation attribute Names are correct
149+ String [] invalidNames = Arrays .stream (attributeNamesToConstructorParameterMapping ).filter (e -> !e .isEmpty () && !isLocaleVariableName (e ) && !this .annotation .hasAttribute (e )).toArray (String []::new );
150+ if (invalidNames .length > 0 ) {
151+ this .elementWrapper .compilerMessage (this .annotation .unwrap ()).asError ().write (InternalValidationMessages .ERROR_BROKEN_VALIDATOR_ATTRIBUTE_NAME_MISMATCH , this .annotation .asElement ().getSimpleName (), invalidNames );
152+ return false ;
153+ }
154+
155+
156+
157+ // loop over constructors and find if one is matching
158+ outer :
159+ for (ExecutableElementWrapper constructor : mappedTypeMirror .getTypeElement ().get ().getConstructors (Modifier .PUBLIC )) {
160+
161+ if (constructor .getParameters ().size () != attributeNamesToConstructorParameterMapping .length ) {
162+ continue ;
163+ }
164+
165+ int i = 0 ;
166+ for (String attributeName : attributeNamesToConstructorParameterMapping ) {
167+
168+ if (!isLocaleVariableName (attributeName )) {
169+ TypeMirrorWrapper attribute ;
170+ if (attributeName .isEmpty ()) {
171+
172+ // This will only work if annotated element is a method parameter
173+ if (!this .elementWrapper .isMethodParameter ()) {
174+ this .elementWrapper .compilerMessage (annotation .unwrap ()).asError ().write (InternalValidationMessages .ERROR_BROKEN_VALIDATOR_INCORRECT_METHOD_PARAMETER_MAPPING );
175+ return false ;
176+ }
177+
178+ attribute = this .elementWrapper .asType ();
179+ } else {
180+ attribute = this .annotation .getAttributeTypeMirror (attributeName ).get ();
181+ }
182+
183+ if (!attribute .isAssignableTo (constructor .getParameters ().get (i ).asType ())) {
184+ continue outer ;
185+ }
186+ }
187+ // next
188+ i = i + 1 ;
189+ }
190+
191+ // if this is reached, the we have found a matching constructor
192+ return true ;
193+ }
194+
195+ this .elementWrapper .compilerMessage (annotation .unwrap ()).asError ().write (InternalValidationMessages .ERROR_BROKEN_VALIDATOR_CONSTRUCTOR_PARAMETER_MAPPING , mappedTypeMirror .getSimpleName ());
196+ return false ;
197+ } else {
198+ // must have a noarg constructor or just the default
199+ TypeElementWrapper validatorImplTypeElement = mappedTypeMirror .getTypeElement ().get ();
200+ boolean hasNoargConstructor = validatorImplTypeElement .filterEnclosedElements ().applyFilter (AptkCoreMatchers .IS_CONSTRUCTOR ).applyFilter (AptkCoreMatchers .HAS_NO_PARAMETERS ).getResult ().size () == 1 ;
201+ boolean hasJustDefaultConstructor = validatorImplTypeElement .filterEnclosedElements ().applyFilter (AptkCoreMatchers .IS_CONSTRUCTOR ).hasSize (0 );
202+
203+ if (!(hasNoargConstructor || hasJustDefaultConstructor )) {
204+ this .elementWrapper .compilerMessage (annotation .unwrap ()).asError ().write (InternalValidationMessages .ERROR_BROKEN_VALIDATOR_MISSING_NOARG_CONSTRUCTOR , validatorImplTypeElement .getSimpleName ());
205+ return false ;
206+ }
207+ }
208+
209+
210+
211+ return true ;
212+ }
213+
214+
215+ /**
216+ * Creates the command needed to initialize an instance based on annotation configuration.
217+ * @return
218+ */
219+ public String createInstanceInitializationCommand () {
220+ StringBuilder stringBuilder = new StringBuilder ();
221+
222+ String genericTypeString = "" ;
223+ // Need to handle generic validator separately
224+ if (getValidatorAnnotation ().mappedClass ().getTypeElement ().get ().hasTypeParameters ()) {
225+ TypeMirrorWrapper annotatedElementsTypeMirror = this .elementWrapper .asType ();
226+ if (annotatedElementsTypeMirror .isCollection () || annotatedElementsTypeMirror .isIterable () || annotatedElementsTypeMirror .isArray ()) {
227+ genericTypeString = "<" +annotatedElementsTypeMirror .getWrappedComponentType ().getTypeDeclaration () + ">" ;
228+ } else {
229+ genericTypeString = "<" + annotatedElementsTypeMirror .getTypeDeclaration () + ">" ;
230+ }
231+
232+ }
233+
234+ stringBuilder .append ("new " ).append (getValidatorAnnotation ().mappedClass ().getQualifiedName ()).append (genericTypeString ).append ("(" );
235+
236+ boolean isFirst = true ;
237+ for (String attributeName : getValidatorAnnotation ().mappedAttributeNames ()) {
238+
239+ // add separator
240+ if (!isFirst ) {
241+ stringBuilder .append (", " );
242+ } else {
243+ isFirst = false ;
244+ }
245+
246+ if (attributeName .isEmpty ()) {
247+ stringBuilder .append (this .elementWrapper .getSimpleName ());
248+ } else if (isLocaleVariableName (attributeName )) {
249+ stringBuilder .append (getLocalVariableName (attributeName ));
250+ } else {
251+ stringBuilder .append (getValidatorExpressionAttributeValueStringRepresentation (annotation .getAttributeWithDefault (attributeName ), annotation .getAttributeTypeMirror (attributeName ).get ()));
252+ }
253+
254+ }
255+
256+ stringBuilder .append (")" );
257+ return stringBuilder .toString ();
258+ }
259+
260+
261+ String getValidatorExpressionAttributeValueStringRepresentation (AnnotationValueWrapper annotationValueWrapper , TypeMirrorWrapper annotationAttributeTypeMirror ) {
262+
263+ if (annotationValueWrapper .isArray ()) {
264+ return annotationValueWrapper .getArrayValue ().stream ().map (e -> getValidatorExpressionAttributeValueStringRepresentation (e , annotationAttributeTypeMirror .getWrappedComponentType ())).collect (Collectors .joining (", " , "new " + annotationAttributeTypeMirror .getWrappedComponentType ().getQualifiedName () + "[]{" , "}" ));
265+ } else if (annotationValueWrapper .isString ()) {
266+ return "\" " + annotationValueWrapper .getStringValue () + "\" " ;
267+ } else if (annotationValueWrapper .isClass ()) {
268+ return annotationValueWrapper .getClassValue ().getQualifiedName () + ".class" ;
269+ } else if (annotationValueWrapper .isInteger ()) {
270+ return annotationValueWrapper .getIntegerValue ().toString ();
271+ } else if (annotationValueWrapper .isLong ()) {
272+ return annotationValueWrapper .getLongValue () + "L" ;
273+ } else if (annotationValueWrapper .isBoolean ()) {
274+ return annotationValueWrapper .getBooleanValue ().toString ();
275+ } else if (annotationValueWrapper .isFloat ()) {
276+ return annotationValueWrapper .getFloatValue () + "f" ;
277+ } else if (annotationValueWrapper .isDouble ()) {
278+ return annotationValueWrapper .getDoubleValue ().toString ();
279+ } else if (annotationValueWrapper .isEnum ()) {
280+ return TypeElementWrapper .toTypeElement (annotationValueWrapper .getEnumValue ().getEnclosingElement ().get ()).getQualifiedName () + "." + annotationValueWrapper .getEnumValue ().getSimpleName ();
281+ } else {
282+ throw new IllegalStateException ("Got unsupported annotation attribute type : USUALLY THIS CANNOT HAPPEN." );
283+ }
284+
285+ }
286+
287+ public String getStringRepresentationOfAnnotation () {
288+ return annotation .getStringRepresentation ();
289+ }
290+
291+ }
0 commit comments