@@ -57,6 +57,7 @@ public class ClassMetaData {
5757 private final List <VariableElement > indexedFields = new ArrayList <VariableElement >(); // list of all fields marked @Index.
5858 private final Set <Backlink > backlinks = new HashSet <Backlink >();
5959 private final Set <VariableElement > nullableFields = new HashSet <VariableElement >(); // Set of fields which can be nullable
60+ private final Set <VariableElement > nullableValueListFields = new HashSet <VariableElement >(); // Set of fields whose elements can be nullable
6061
6162 private String packageName ; // package name for model class.
6263 private boolean hasDefaultConstructor ; // True if model has a public no-arg constructor.
@@ -66,21 +67,36 @@ public class ClassMetaData {
6667 private boolean containsHashCode ;
6768
6869 private final List <TypeMirror > validPrimaryKeyTypes ;
70+ private final List <TypeMirror > validListValueTypes ;
6971 private final Types typeUtils ;
7072 private final Elements elements ;
7173
72- public ClassMetaData (ProcessingEnvironment env , TypeElement clazz ) {
74+ public ClassMetaData (ProcessingEnvironment env , TypeMirrors typeMirrors , TypeElement clazz ) {
7375 this .classType = clazz ;
7476 this .className = clazz .getSimpleName ().toString ();
7577 typeUtils = env .getTypeUtils ();
7678 elements = env .getElementUtils ();
77- TypeMirror stringType = env .getElementUtils ().getTypeElement ("java.lang.String" ).asType ();
79+
80+
7881 validPrimaryKeyTypes = Arrays .asList (
79- stringType ,
80- typeUtils .getPrimitiveType (TypeKind .SHORT ),
81- typeUtils .getPrimitiveType (TypeKind .INT ),
82- typeUtils .getPrimitiveType (TypeKind .LONG ),
83- typeUtils .getPrimitiveType (TypeKind .BYTE )
82+ typeMirrors .STRING_MIRROR ,
83+ typeMirrors .PRIMITIVE_LONG_MIRROR ,
84+ typeMirrors .PRIMITIVE_INT_MIRROR ,
85+ typeMirrors .PRIMITIVE_SHORT_MIRROR ,
86+ typeMirrors .PRIMITIVE_BYTE_MIRROR
87+ );
88+
89+ validListValueTypes = Arrays .asList (
90+ typeMirrors .STRING_MIRROR ,
91+ typeMirrors .BINARY_MIRROR ,
92+ typeMirrors .BOOLEAN_MIRROR ,
93+ typeMirrors .LONG_MIRROR ,
94+ typeMirrors .INTEGER_MIRROR ,
95+ typeMirrors .SHORT_MIRROR ,
96+ typeMirrors .BYTE_MIRROR ,
97+ typeMirrors .DOUBLE_MIRROR ,
98+ typeMirrors .FLOAT_MIRROR ,
99+ typeMirrors .DATE_MIRROR
84100 );
85101
86102 for (Element element : classType .getEnclosedElements ()) {
@@ -167,6 +183,15 @@ public boolean isNullable(VariableElement variableElement) {
167183 return nullableFields .contains (variableElement );
168184 }
169185
186+ /**
187+ * Checks if the element of {@code RealmList} designated by {@code realmListVariableElement} is nullable.
188+ *
189+ * @return {@code true} if the element is nullable type, {@code false} otherwise.
190+ */
191+ public boolean isElementNullable (VariableElement realmListVariableElement ) {
192+ return nullableValueListFields .contains (realmListVariableElement );
193+ }
194+
170195 /**
171196 * Checks if a VariableElement is indexed.
172197 *
@@ -240,7 +265,7 @@ public boolean generate() {
240265 packageName = packageElement .getQualifiedName ().toString ();
241266
242267 if (!categorizeClassElements ()) { return false ; }
243- if (!checkListTypes ()) { return false ; }
268+ if (!checkCollectionTypes ()) { return false ; }
244269 if (!checkReferenceTypes ()) { return false ; }
245270 if (!checkDefaultConstructor ()) { return false ; }
246271 if (!checkForFinalFields ()) { return false ; }
@@ -274,25 +299,14 @@ private boolean categorizeClassElements() {
274299 return true ;
275300 }
276301
277- private boolean checkListTypes () {
302+ private boolean checkCollectionTypes () {
278303 for (VariableElement field : fields ) {
279- if (Utils .isRealmList (field ) || Utils .isRealmResults (field )) {
280- // Check for missing generic (default back to Object)
281- if (Utils .getGenericTypeQualifiedName (field ) == null ) {
282- Utils .error ("No generic type supplied for field" , field );
304+ if (Utils .isRealmList (field )) {
305+ if (!checkRealmListType (field )) {
283306 return false ;
284307 }
285-
286- // Check that the referenced type is a concrete class and not an interface
287- TypeMirror fieldType = field .asType ();
288- List <? extends TypeMirror > typeArguments = ((DeclaredType ) fieldType ).getTypeArguments ();
289- String genericCanonicalType = typeArguments .get (0 ).toString ();
290- TypeElement typeElement = elements .getTypeElement (genericCanonicalType );
291- if (typeElement .getSuperclass ().getKind () == TypeKind .NONE ) {
292- Utils .error (
293- "Only concrete Realm classes are allowed in RealmLists. "
294- + "Neither interfaces nor abstract classes are allowed." ,
295- field );
308+ } else if (Utils .isRealmResults (field )) {
309+ if (!checkRealmResultsType (field )) {
296310 return false ;
297311 }
298312 }
@@ -301,6 +315,75 @@ private boolean checkListTypes() {
301315 return true ;
302316 }
303317
318+ private boolean checkRealmListType (VariableElement field ) {
319+ // Check for missing generic (default back to Object)
320+ if (Utils .getGenericTypeQualifiedName (field ) == null ) {
321+ Utils .error ("No generic type supplied for field" , field );
322+ return false ;
323+ }
324+
325+ // Check that the referenced type is a concrete class and not an interface
326+ TypeMirror fieldType = field .asType ();
327+ final TypeMirror elementTypeMirror = ((DeclaredType ) fieldType ).getTypeArguments ().get (0 );
328+ if (elementTypeMirror .getKind () == TypeKind .DECLARED /* class of interface*/ ) {
329+ TypeElement elementTypeElement = (TypeElement ) ((DeclaredType ) elementTypeMirror ).asElement ();
330+ if (elementTypeElement .getSuperclass ().getKind () == TypeKind .NONE ) {
331+ Utils .error (
332+ "Only concrete Realm classes are allowed in RealmLists. "
333+ + "Neither interfaces nor abstract classes are allowed." ,
334+ field );
335+ return false ;
336+ }
337+ }
338+
339+ // Check if the actual value class is acceptable
340+ if (!validListValueTypes .contains (elementTypeMirror ) && !Utils .isRealmModel (elementTypeMirror )) {
341+ final StringBuilder messageBuilder = new StringBuilder (
342+ "Element type of RealmList must be a class implementing 'RealmModel' or one of the " );
343+ final String separator = ", " ;
344+ for (TypeMirror type : validListValueTypes ) {
345+ messageBuilder .append ('\'' ).append (type .toString ()).append ('\'' ).append (separator );
346+ }
347+ messageBuilder .setLength (messageBuilder .length () - separator .length ());
348+ messageBuilder .append ('.' );
349+ Utils .error (messageBuilder .toString (), field );
350+ return false ;
351+ }
352+
353+ return true ;
354+ }
355+
356+ private boolean checkRealmResultsType (VariableElement field ) {
357+ // Only classes implementing RealmModel are allowed since RealmResults field is used only for backlinks.
358+
359+ // Check for missing generic (default back to Object)
360+ if (Utils .getGenericTypeQualifiedName (field ) == null ) {
361+ Utils .error ("No generic type supplied for field" , field );
362+ return false ;
363+ }
364+
365+ TypeMirror fieldType = field .asType ();
366+ final TypeMirror elementTypeMirror = ((DeclaredType ) fieldType ).getTypeArguments ().get (0 );
367+ if (elementTypeMirror .getKind () == TypeKind .DECLARED /* class or interface*/ ) {
368+ TypeElement elementTypeElement = (TypeElement ) ((DeclaredType ) elementTypeMirror ).asElement ();
369+ if (elementTypeElement .getSuperclass ().getKind () == TypeKind .NONE ) {
370+ Utils .error (
371+ "Only concrete Realm classes are allowed in RealmResults. "
372+ + "Neither interfaces nor abstract classes are allowed." ,
373+ field );
374+ return false ;
375+ }
376+ }
377+
378+ // Check if the actual value class is acceptable
379+ if (!Utils .isRealmModel (elementTypeMirror )) {
380+ Utils .error ("Element type of RealmResults must be a class implementing 'RealmModel'." , field );
381+ return false ;
382+ }
383+
384+ return true ;
385+ }
386+
304387 private boolean checkReferenceTypes () {
305388 for (VariableElement field : fields ) {
306389 if (Utils .isRealmModel (field )) {
@@ -376,14 +459,23 @@ private boolean categorizeField(Element element) {
376459 if (!categorizeIndexField (element , field )) { return false ; }
377460 }
378461
379- if (isRequiredField (field )) {
462+ // @Required annotation of RealmList field only affects its value type, not field itself.
463+ if (Utils .isRealmList (field )) {
464+ // We only check @Required annotation. @org.jetbrains.annotations.NotNull annotation should not affect nullability of the list values.
465+ if (!hasRequiredAnnotation (field )) {
466+ final List <? extends TypeMirror > fieldTypeArguments = ((DeclaredType ) field .asType ()).getTypeArguments ();
467+ if (fieldTypeArguments .isEmpty () || !Utils .isRealmModel (fieldTypeArguments .get (0 ))) {
468+ nullableValueListFields .add (field );
469+ }
470+ }
471+ } else if (isRequiredField (field )) {
380472 categorizeRequiredField (element , field );
381473 } else {
382- // The field doesn't have the @Required annotation.
474+ // The field doesn't have the @Required and @org.jetbrains.annotations.NotNull annotation.
383475 // Without @Required annotation, boxed types/RealmObject/Date/String/bytes should be added to
384476 // nullableFields.
385- // RealmList and Primitive types are NOT nullable always . @Required annotation is not supported.
386- if (!Utils .isPrimitiveType (field ) && !Utils .isRealmList (field )) {
477+ // RealmList of models, RealmResults(backlinks) and primitive types are NOT nullable. @Required annotation is not supported.
478+ if (!Utils .isPrimitiveType (field ) && !Utils .isRealmResults (field )) {
387479 nullableFields .add (field );
388480 }
389481 }
@@ -409,8 +501,26 @@ private boolean categorizeField(Element element) {
409501 return true ;
410502 }
411503
504+ /**
505+ * This method only checks if the field has {@code @Required} annotation.
506+ * In most cases, you should use {@link #isRequiredField(VariableElement)} to take into account
507+ * Kotlin's annotation as well.
508+ *
509+ * @param field target field.
510+ * @return {@code true} if the field has {@code @Required} annotation, {@code false} otherwise.
511+ * @see #isRequiredField(VariableElement)
512+ */
513+ private boolean hasRequiredAnnotation (VariableElement field ) {
514+ return field .getAnnotation (Required .class ) != null ;
515+ }
516+
517+ /**
518+ * Checks if the field is annotated as required.
519+ * @param field target field.
520+ * @return {@code true} if the field is annotated as required, {@code false} otherwise.
521+ */
412522 private boolean isRequiredField (VariableElement field ) {
413- if (field . getAnnotation ( Required . class ) != null ) {
523+ if (hasRequiredAnnotation ( field ) ) {
414524 return true ;
415525 }
416526
0 commit comments