1515 */
1616package com .datastax .oss .driver .internal .mapper .processor .entity ;
1717
18+ import com .datastax .oss .driver .api .mapper .annotations .ClusteringColumn ;
19+ import com .datastax .oss .driver .api .mapper .annotations .PartitionKey ;
1820import com .datastax .oss .driver .internal .mapper .processor .ProcessorContext ;
1921import com .datastax .oss .driver .internal .mapper .processor .util .generation .PropertyType ;
2022import com .datastax .oss .driver .shaded .guava .common .collect .ImmutableList ;
2123import com .squareup .javapoet .ClassName ;
2224import java .beans .Introspector ;
23- import java .util .HashMap ;
24- import java .util .Map ;
25+ import java .util .List ;
26+ import java .util .Set ;
27+ import java .util .SortedMap ;
28+ import java .util .TreeMap ;
2529import javax .lang .model .element .Element ;
2630import javax .lang .model .element .ElementKind ;
2731import javax .lang .model .element .ExecutableElement ;
32+ import javax .lang .model .element .Modifier ;
2833import javax .lang .model .element .TypeElement ;
2934import javax .lang .model .element .VariableElement ;
3035import javax .lang .model .type .TypeKind ;
@@ -45,78 +50,184 @@ public EntityDefinition getDefinition(TypeElement classElement) {
4550 // share the same name and operate on the same type.
4651 // This will get revisited in future tickets:
4752 // TODO support custom naming conventions
48- // TODO property annotations: PK, custom name, computed, ignored...
53+ // TODO property annotations: custom name, computed, ignored...
4954 // TODO inherit annotations and properties from superclass / parent interface
50- // TODO handle annotations on fields...
5155
52- Map <String , DefaultPropertyDefinition .Builder > propertyBuilders = new HashMap <>();
56+ SortedMap <Integer , PropertyDefinition > partitionKey = new TreeMap <>();
57+ SortedMap <Integer , PropertyDefinition > clusteringColumns = new TreeMap <>();
58+ ImmutableList .Builder <PropertyDefinition > regularColumns = ImmutableList .builder ();
5359 for (Element child : classElement .getEnclosedElements ()) {
54- if (child .getKind () == ElementKind .METHOD ) {
55- ExecutableElement method = (ExecutableElement ) child ;
56- String methodName = method .getSimpleName ().toString ();
57- if (methodName .startsWith ("get" ) && method .getParameters ().isEmpty ()) {
58- TypeMirror typeMirror = method .getReturnType ();
59- if (typeMirror .getKind () == TypeKind .VOID ) {
60- continue ;
61- }
62- String propertyName = Introspector .decapitalize (methodName .substring (3 ));
63- DefaultPropertyDefinition .Builder builder = propertyBuilders .get (propertyName );
64- if (builder == null ) {
65- builder =
66- new DefaultPropertyDefinition .Builder (
67- propertyName , typeMirror , PropertyType .parse (typeMirror , context ));
68- propertyBuilders .put (propertyName , builder );
69- } else if (!context .getTypeUtils ().isSameType (builder .getRawType (), typeMirror )) {
70- context
71- .getMessager ()
72- .warn (
73- method ,
74- "Ignoring method %s %s() because there is a setter "
75- + "with the same name but a different type: %s(%s)" ,
76- typeMirror ,
77- methodName ,
78- builder .getSetterName (),
79- builder .getRawType ());
80- continue ;
81- }
82- builder .withGetterName (methodName );
83- } else if (methodName .startsWith ("set" ) && method .getParameters ().size () == 1 ) {
84- String propertyName = Introspector .decapitalize (methodName .substring (3 ));
85- VariableElement parameter = method .getParameters ().get (0 );
86- TypeMirror typeMirror = parameter .asType ();
87- DefaultPropertyDefinition .Builder builder = propertyBuilders .get (propertyName );
88- if (builder == null ) {
89- builder =
90- new DefaultPropertyDefinition .Builder (
91- propertyName , typeMirror , PropertyType .parse (typeMirror , context ));
92- propertyBuilders .put (propertyName , builder );
93- } else if (!context .getTypeUtils ().isSameType (builder .getRawType (), typeMirror )) {
94- context
95- .getMessager ()
96- .warn (
97- method ,
98- "Ignoring method %s(%s) because there is a getter "
99- + "with the same name but a different type: %s %s()" ,
100- methodName ,
101- typeMirror ,
102- builder .getRawType (),
103- builder .getGetterName ());
104- continue ;
105- }
106- builder .withSetterName (methodName );
107- }
60+ Set <Modifier > modifiers = child .getModifiers ();
61+ if (child .getKind () != ElementKind .METHOD
62+ || modifiers .contains (Modifier .STATIC )
63+ || modifiers .contains (Modifier .PRIVATE )) {
64+ continue ;
10865 }
109- }
66+ ExecutableElement getMethod = (ExecutableElement ) child ;
67+ String getMethodName = getMethod .getSimpleName ().toString ();
68+ if (!getMethodName .startsWith ("get" ) || !getMethod .getParameters ().isEmpty ()) {
69+ continue ;
70+ }
71+ TypeMirror typeMirror = getMethod .getReturnType ();
72+ if (typeMirror .getKind () == TypeKind .VOID ) {
73+ continue ;
74+ }
75+ String propertyName = Introspector .decapitalize (getMethodName .substring (3 ));
76+ String setMethodName = getMethodName .replaceFirst ("get" , "set" );
77+ ExecutableElement setMethod = findSetMethod (classElement , setMethodName , typeMirror );
78+ if (setMethod == null ) {
79+ continue ; // must have both
80+ }
81+ VariableElement field = findField (classElement , propertyName , typeMirror );
82+
83+ PropertyType propertyType = PropertyType .parse (typeMirror , context );
84+ PropertyDefinition property =
85+ new DefaultPropertyDefinition (propertyName , getMethodName , setMethodName , propertyType );
11086
111- ImmutableList .Builder <PropertyDefinition > definitions = ImmutableList .builder ();
112- for (DefaultPropertyDefinition .Builder builder : propertyBuilders .values ()) {
113- if (builder .getGetterName () != null && builder .getSetterName () != null ) {
114- definitions .add (builder .build ());
87+ int partitionKeyIndex = getPartitionKeyIndex (getMethod , field );
88+ int clusteringColumnIndex = getClusteringColumnIndex (getMethod , field , partitionKeyIndex );
89+ if (partitionKeyIndex >= 0 ) {
90+ PropertyDefinition previous = partitionKey .putIfAbsent (partitionKeyIndex , property );
91+ if (previous != null ) {
92+ context
93+ .getMessager ()
94+ .error (
95+ getMethod ,
96+ "Duplicate partition key index: if multiple properties are annotated "
97+ + "with @%s, the annotation must be parameterized with an integer "
98+ + "indicating the position. Found duplicate index %d for %s and %s." ,
99+ PartitionKey .class .getSimpleName (),
100+ partitionKeyIndex ,
101+ previous .getGetterName (),
102+ property .getGetterName ());
103+ }
104+ } else if (clusteringColumnIndex >= 0 ) {
105+ PropertyDefinition previous =
106+ clusteringColumns .putIfAbsent (clusteringColumnIndex , property );
107+ if (previous != null ) {
108+ context
109+ .getMessager ()
110+ .error (
111+ getMethod ,
112+ "Duplicate clustering column index: if multiple properties are annotated "
113+ + "with @%s, the annotation must be parameterized with an integer "
114+ + "indicating the position. Found duplicate index %d for %s and %s." ,
115+ ClusteringColumn .class .getSimpleName (),
116+ clusteringColumnIndex ,
117+ previous .getGetterName (),
118+ property .getGetterName ());
119+ }
120+ } else {
121+ regularColumns .add (property );
115122 }
116123 }
117124
118125 String entityName = Introspector .decapitalize (classElement .getSimpleName ().toString ());
119126 return new DefaultEntityDefinition (
120- ClassName .get (classElement ), entityName , definitions .build ());
127+ ClassName .get (classElement ),
128+ entityName ,
129+ ImmutableList .copyOf (partitionKey .values ()),
130+ ImmutableList .copyOf (clusteringColumns .values ()),
131+ regularColumns .build ());
132+ }
133+
134+ private VariableElement findField (
135+ TypeElement classElement , String propertyName , TypeMirror fieldType ) {
136+ for (Element child : classElement .getEnclosedElements ()) {
137+ if (child .getKind () != ElementKind .FIELD ) {
138+ continue ;
139+ }
140+ VariableElement field = (VariableElement ) child ;
141+ if (field .getSimpleName ().toString ().equals (propertyName )
142+ && context .getTypeUtils ().isSameType (field .asType (), fieldType )) {
143+ return field ;
144+ }
145+ }
146+ return null ;
147+ }
148+
149+ private ExecutableElement findSetMethod (
150+ TypeElement classElement , String setMethodName , TypeMirror fieldType ) {
151+ for (Element child : classElement .getEnclosedElements ()) {
152+ Set <Modifier > modifiers = child .getModifiers ();
153+ if (child .getKind () != ElementKind .METHOD
154+ || modifiers .contains (Modifier .STATIC )
155+ || modifiers .contains (Modifier .PRIVATE )) {
156+ continue ;
157+ }
158+ ExecutableElement setMethod = (ExecutableElement ) child ;
159+ List <? extends VariableElement > parameters = setMethod .getParameters ();
160+
161+ if (setMethod .getSimpleName ().toString ().equals (setMethodName )
162+ && parameters .size () == 1
163+ && context .getTypeUtils ().isSameType (parameters .get (0 ).asType (), fieldType )) {
164+ return setMethod ;
165+ }
166+ }
167+ return null ;
168+ }
169+
170+ private int getPartitionKeyIndex (ExecutableElement getMethod , VariableElement field ) {
171+ PartitionKey annotation = getMethod .getAnnotation (PartitionKey .class );
172+ PartitionKey fieldAnnotation = (field == null ) ? null : field .getAnnotation (PartitionKey .class );
173+
174+ if (annotation != null ) {
175+ if (fieldAnnotation != null ) {
176+ context
177+ .getMessager ()
178+ .warn (
179+ field ,
180+ "@%s should be used either on the field or the getter, but not both. "
181+ + "The annotation on this field will be ignored." ,
182+ PartitionKey .class .getSimpleName ());
183+ }
184+ return annotation .value ();
185+ } else if (fieldAnnotation != null ) {
186+ return fieldAnnotation .value ();
187+ } else {
188+ return -1 ;
189+ }
190+ }
191+
192+ private int getClusteringColumnIndex (
193+ ExecutableElement getMethod , VariableElement field , int partitionKeyIndex ) {
194+ ClusteringColumn annotation = getMethod .getAnnotation (ClusteringColumn .class );
195+ ClusteringColumn fieldAnnotation =
196+ (field == null ) ? null : field .getAnnotation (ClusteringColumn .class );
197+
198+ if (annotation != null ) {
199+ if (partitionKeyIndex >= 0 ) {
200+ context
201+ .getMessager ()
202+ .error (
203+ getMethod ,
204+ "Properties can't be annotated with both @%s and @%s." ,
205+ PartitionKey .class .getSimpleName (),
206+ ClusteringColumn .class .getSimpleName ());
207+ return -1 ;
208+ } else if (fieldAnnotation != null ) {
209+ context
210+ .getMessager ()
211+ .warn (
212+ field ,
213+ "@%s should be used either on the field or the getter, but not both. "
214+ + "The annotation on this field will be ignored." ,
215+ ClusteringColumn .class .getSimpleName ());
216+ }
217+ return annotation .value ();
218+ } else if (fieldAnnotation != null ) {
219+ if (partitionKeyIndex >= 0 ) {
220+ context
221+ .getMessager ()
222+ .error (
223+ field ,
224+ "Properties can't be annotated with both @%s and @%s." ,
225+ PartitionKey .class .getSimpleName (),
226+ ClusteringColumn .class .getSimpleName ());
227+ }
228+ return fieldAnnotation .value ();
229+ } else {
230+ return -1 ;
231+ }
121232 }
122233}
0 commit comments