@@ -40,6 +40,7 @@ class ClassMetaData(env: ProcessingEnvironment, typeMirrors: TypeMirrors, privat
4040 private val backlinks = LinkedHashSet <Backlink >()
4141 private val nullableFields = LinkedHashSet <RealmFieldElement >() // Set of fields which can be nullable
4242 private val nullableValueListFields = LinkedHashSet <RealmFieldElement >() // Set of fields whose elements can be nullable
43+ private val nullableValueMapFields = LinkedHashSet <RealmFieldElement >() // Set of fields whose elements can be nullable
4344
4445 // package name for model class.
4546 private lateinit var packageName: String
@@ -59,7 +60,7 @@ class ClassMetaData(env: ProcessingEnvironment, typeMirrors: TypeMirrors, privat
5960 lateinit var internalClassName: String
6061 private set
6162
62- private val validPrimaryKeyTypes: List <TypeMirror > = Arrays .asList (
63+ private val validPrimaryKeyTypes: List <TypeMirror > = listOf (
6364 typeMirrors.STRING_MIRROR ,
6465 typeMirrors.PRIMITIVE_LONG_MIRROR ,
6566 typeMirrors.PRIMITIVE_INT_MIRROR ,
@@ -68,7 +69,7 @@ class ClassMetaData(env: ProcessingEnvironment, typeMirrors: TypeMirrors, privat
6869 typeMirrors.OBJECT_ID_MIRROR ,
6970 typeMirrors.UUID_MIRROR
7071 )
71- private val validListValueTypes: List <TypeMirror > = Arrays .asList (
72+ private val validListValueTypes: List <TypeMirror > = listOf (
7273 typeMirrors.STRING_MIRROR ,
7374 typeMirrors.BINARY_MIRROR ,
7475 typeMirrors.BOOLEAN_MIRROR ,
@@ -84,6 +85,23 @@ class ClassMetaData(env: ProcessingEnvironment, typeMirrors: TypeMirrors, privat
8485 typeMirrors.UUID_MIRROR ,
8586 typeMirrors.MIXED_MIRROR
8687 )
88+ private val validDictionaryTypes: List <TypeMirror > = listOf (
89+ typeMirrors.STRING_MIRROR ,
90+ typeMirrors.BINARY_MIRROR ,
91+ typeMirrors.BINARY_NON_PRIMITIVE_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 ,
100+ typeMirrors.DECIMAL128_MIRROR ,
101+ typeMirrors.OBJECT_ID_MIRROR ,
102+ typeMirrors.UUID_MIRROR ,
103+ typeMirrors.MIXED_MIRROR
104+ )
87105 private val stringType = typeMirrors.STRING_MIRROR
88106
89107 private val typeUtils: Types = env.typeUtils
@@ -205,6 +223,15 @@ class ClassMetaData(env: ProcessingEnvironment, typeMirrors: TypeMirrors, privat
205223 return nullableValueListFields.contains(realmListVariableElement)
206224 }
207225
226+ /* *
227+ * Checks if the value of a `RealmDictionary` entry designated by `realmDictionaryVariableElement` is nullable.
228+ *
229+ * @return `true` if the element is nullable type, `false` otherwise.
230+ */
231+ fun isDictionaryValueNullable (realmDictionaryVariableElement : VariableElement ): Boolean {
232+ return nullableValueMapFields.contains(realmDictionaryVariableElement)
233+ }
234+
208235 /* *
209236 * Checks if a VariableElement is indexed.
210237 *
@@ -303,6 +330,9 @@ class ClassMetaData(env: ProcessingEnvironment, typeMirrors: TypeMirrors, privat
303330 if (! checkCollectionTypes()) {
304331 return false
305332 }
333+ if (! checkDictionaryTypes()) {
334+ return false
335+ }
306336 if (! checkReferenceTypes()) {
307337 return false
308338 }
@@ -361,44 +391,88 @@ class ClassMetaData(env: ProcessingEnvironment, typeMirrors: TypeMirrors, privat
361391 return true
362392 }
363393
364- private fun checkRealmListType (field : VariableElement ): Boolean {
394+ private fun checkDictionaryTypes (): Boolean {
395+ for (field in fields) {
396+ if (Utils .isRealmDictionary(field)) {
397+ if (! checkDictionaryValuesType(field)) {
398+ return false
399+ }
400+ }
401+ }
402+
403+ return true
404+ }
405+
406+ private fun checkDictionaryValuesType (field : VariableElement ): Boolean {
365407 // Check for missing generic (default back to Object)
366408 if (Utils .getGenericTypeQualifiedName(field) == null ) {
367409 Utils .error(getFieldErrorSuffix(field) + " No generic type supplied for field" , field)
368410 return false
369411 }
370412
371- // Check that the referenced type is a concrete class and not an interface
372- val fieldType = field.asType()
373- val elementTypeMirror = (fieldType as DeclaredType ).typeArguments[0 ]
374- if (elementTypeMirror.kind == TypeKind .DECLARED /* class of interface*/ ) {
375- val elementTypeElement = (elementTypeMirror as DeclaredType ).asElement() as TypeElement
376- if (elementTypeElement.superclass.kind == TypeKind .NONE ) {
377- Utils .error(
378- getFieldErrorSuffix(field) + " Only concrete Realm classes are allowed in RealmLists. "
379- + " Neither interfaces nor abstract classes are allowed." ,
380- field)
381- return false
382- }
413+ val elementTypeMirror = checkReferenceIsNotInterface(field) ? : return false
414+ return checkAcceptableClass(
415+ field,
416+ elementTypeMirror,
417+ validDictionaryTypes,
418+ " Element type of RealmDictionary must be of type 'RealmAny' or any type that can be boxed inside 'RealmAny': "
419+ )
420+ }
421+
422+ private fun checkRealmListType (field : VariableElement ): Boolean {
423+ // Check for missing generic (default back to Object)
424+ if (Utils .getGenericTypeQualifiedName(field) == null ) {
425+ Utils .error(getFieldErrorSuffix(field) + " No generic type supplied for field" , field)
426+ return false
383427 }
384428
429+ val elementTypeMirror = checkReferenceIsNotInterface(field) ? : return false
430+ return checkAcceptableClass(
431+ field,
432+ elementTypeMirror,
433+ validListValueTypes,
434+ " Element type of RealmList must be a class implementing 'RealmModel' or one of "
435+ )
436+ }
437+
438+ private fun checkAcceptableClass (
439+ field : VariableElement ,
440+ elementTypeMirror : TypeMirror ,
441+ validTypes : List <TypeMirror >,
442+ specificFieldTypeMessage : String
443+ ): Boolean {
385444 // Check if the actual value class is acceptable
386- if (! containsType(validListValueTypes, elementTypeMirror) && ! Utils .isRealmModel(elementTypeMirror)) {
387- val messageBuilder = StringBuilder (
388- getFieldErrorSuffix(field) + " Element type of RealmList must be a class implementing 'RealmModel' or one of " )
445+ if (! containsType(validTypes, elementTypeMirror) && ! Utils .isRealmModel(elementTypeMirror)) {
446+ val messageBuilder = StringBuilder (getFieldErrorSuffix(field) + " Type was '$elementTypeMirror '. $specificFieldTypeMessage " )
389447 val separator = " , "
390- for (type in validListValueTypes ) {
448+ for (type in validTypes ) {
391449 messageBuilder.append(' \' ' ).append(type.toString()).append(' \' ' ).append(separator)
392450 }
393451 messageBuilder.setLength(messageBuilder.length - separator.length)
394452 messageBuilder.append(' .' )
395453 Utils .error(messageBuilder.toString(), field)
396454 return false
397455 }
398-
399456 return true
400457 }
401458
459+ private fun checkReferenceIsNotInterface (field : VariableElement ): TypeMirror ? {
460+ // Check that the referenced type is a concrete class and not an interface
461+ val fieldType = field.asType()
462+ val elementTypeMirror: TypeMirror = (fieldType as DeclaredType ).typeArguments[0 ]
463+ if (elementTypeMirror.kind == TypeKind .DECLARED /* class of interface*/ ) {
464+ val elementTypeElement = (elementTypeMirror as DeclaredType ).asElement() as TypeElement
465+ if (elementTypeElement.superclass.kind == TypeKind .NONE ) {
466+ Utils .error(
467+ getFieldErrorSuffix(field) + " Only concrete Realm classes are allowed in field '$field '. "
468+ + " Neither interfaces nor abstract classes are allowed." ,
469+ field)
470+ return null
471+ }
472+ }
473+ return elementTypeMirror
474+ }
475+
402476 private fun checkRealmResultsType (field : VariableElement ): Boolean {
403477 // Only classes implementing RealmModel are allowed since RealmResults field is used only for backlinks.
404478
@@ -467,11 +541,12 @@ class ClassMetaData(env: ProcessingEnvironment, typeMirrors: TypeMirrors, privat
467541 if (! field.modifiers.contains(Modifier .FINAL )) {
468542 continue
469543 }
470- if (Utils .isRealmList(field) || Utils .isMutableRealmInteger(field)) {
544+ if (Utils .isRealmList(field) || Utils .isMutableRealmInteger(field) ||
545+ Utils .isRealmDictionary(field)) {
471546 continue
472547 }
473548
474- Utils .error(String .format(Locale .US , " Class \" %s\" contains illegal final field \" %s\" ." , simpleJavaClassName,
549+ Utils .error(String .format(Locale .US , " Class \" %s\" contains illegal final/immutable field \" %s\" ." , simpleJavaClassName,
475550 field.simpleName.toString()))
476551
477552 return false
@@ -515,7 +590,7 @@ class ClassMetaData(env: ProcessingEnvironment, typeMirrors: TypeMirrors, privat
515590 }
516591 }
517592
518- // @Required annotation of RealmList field only affects its value type, not field itself.
593+ // @Required annotation of RealmList and RealmDictionary field only affects its value type, not field itself.
519594 if (Utils .isRealmList(field)) {
520595 val hasRequiredAnnotation = hasRequiredAnnotation(field)
521596 val listGenericType = (field.asType() as DeclaredType ).typeArguments
@@ -535,6 +610,26 @@ class ClassMetaData(env: ProcessingEnvironment, typeMirrors: TypeMirrors, privat
535610 nullableValueListFields.add(field)
536611 }
537612 }
613+ } else if (Utils .isRealmDictionary(field)) {
614+ // Same as RealmList
615+ val hasRequiredAnnotation = hasRequiredAnnotation(field)
616+ val listGenericType = (field.asType() as DeclaredType ).typeArguments
617+ val containsRealmModelClasses = (listGenericType.isNotEmpty() && Utils .isRealmModel(listGenericType[0 ]))
618+ val containsRealmAny = (listGenericType.isNotEmpty() && Utils .isRealmAny(listGenericType[0 ]))
619+
620+ // @Required not allowed if the dictionary contains Realm model classes
621+ if (hasRequiredAnnotation && (containsRealmModelClasses || containsRealmAny)) {
622+ Utils .error(" @Required not allowed on RealmDictionaries that contain other Realm model classes and RealmAny." )
623+ return false
624+ }
625+
626+ // @Required thus only makes sense for RealmDictionaries with primitive types
627+ // We only check @Required annotation. @org.jetbrains.annotations.NotNull annotation should not affect nullability of the list values.
628+ if (! hasRequiredAnnotation) {
629+ if (! containsRealmModelClasses) {
630+ nullableValueMapFields.add(field)
631+ }
632+ }
538633 } else if (isRequiredField(field)) {
539634 if (! checkBasicRequiredAnnotationUsage(field)) {
540635 return false
@@ -570,7 +665,12 @@ class ClassMetaData(env: ProcessingEnvironment, typeMirrors: TypeMirrors, privat
570665
571666 // Standard field that appears to be valid (more fine grained checks might fail later).
572667 fields.add(field)
573- if (Utils .isRealmModel(field) || Utils .isRealmModelList(field) || Utils .isRealmAnyList(field) || Utils .isRealmAny(field)) {
668+ if (Utils .isRealmModel(field) ||
669+ Utils .isRealmModelList(field) ||
670+ Utils .isRealmAnyList(field) ||
671+ Utils .isRealmAny(field) ||
672+ Utils .isRealmModelDictionary(field) ||
673+ Utils .isRealmAnyDictionary(field)) {
574674 _objectReferenceFields .add(field)
575675 } else {
576676 basicTypeFields.add(field)
0 commit comments