Skip to content

Commit d9e0ae4

Browse files
zaki50cmelchior
authored andcommitted
Add Supports for Primitive Lists (#5031)
* Extended Annotation Processor to support it in model classes * Relaxed Generic Constraints * Support in insert/insertOrUpdate * Support in copyToRealm/copyToRealmOrUpdate * Support in copyFromRealm
1 parent e528777 commit d9e0ae4

70 files changed

Lines changed: 10076 additions & 821 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,14 @@
99
* Removed deprecated API `RealmObject.removeChangeListeners()`. Use `RealmObject.removeAllChangeListeners()` instead.
1010
* `SyncUser.Callback` to becomes generic.
1111
* Removed `SyncUser.getAccessToken` method from public API, and rename it to `getRefreshToken`.
12+
* Relaxed upper bound of type parameter of `RealmList`, `RealmQuery`, `RealmResults`, `RealmCollection`, `OrderedRealmCollection` and `OrderedRealmCollectionSnapshot`.
1213

1314
## Deprecated
1415

1516
## Enhancements
1617

18+
* Now users can use `String`, `byte[]`, `Boolean`, `Long`, `Integer`, `Short`, `Byte`, `Double`, `Float` and `Date` as a type parameter of `RealmList`.
19+
1720
## Bug Fixes
1821

1922
## Internal
@@ -29,6 +32,7 @@
2932
### Breaking Changes
3033

3134
* `RealmResults.distinct()`/`RealmResults.distinctAsync()` have been removed. Use `RealmQuery.distinct()`/`RealmQuery.distinctAsync()` instead.
35+
* `RealmQuery.createQuery(Realm, Class)`, `RealmQuery.createDynamicQuery(DynamicRealm, String)`, `RealmQuery.createQueryFromResult(RealmResults)` and `RealmQuery.createQueryFromList(RealmList)` have been removed. Use `Realm.where(Class)`, `DynamicRealm.where(String)`, RealmResults.where()` and `RealmList.where()` instead.
3236

3337
### Enhancements
3438

examples/kotlinExample/src/main/kotlin/io/realm/examples/kotlin/KotlinExampleActivity.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ class KotlinExampleActivity : Activity() {
174174

175175
// Sorting
176176
val sortedPersons = realm.where(Person::class.java).findAllSorted("age", Sort.DESCENDING)
177-
status += "\nSorting ${sortedPersons.last().name} == ${realm.where(Person::class.java).findAll().first().name}"
177+
status += "\nSorting ${sortedPersons.last()?.name} == ${realm.where(Person::class.java).findAll().first()?.name}"
178178

179179
} finally {
180180
realm.close()

realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.java

Lines changed: 139 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -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

realm/realm-annotations-processor/src/main/java/io/realm/processor/Constants.java

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,16 @@ public enum RealmFieldType {
5454
REALM_INTEGER("INTEGER", "Long"),
5555
OBJECT("OBJECT", "Object"),
5656
LIST("LIST", "List"),
57-
BACKLINK("BACKLINK", null);
57+
58+
BACKLINK("LINKING_OBJECTS", null),
59+
60+
INTEGER_LIST("INTEGER_LIST", "List"),
61+
BOOLEAN_LIST("BOOLEAN_LIST", "List"),
62+
STRING_LIST("STRING_LIST", "List"),
63+
BINARY_LIST("BINARY_LIST", "List"),
64+
DATE_LIST("DATE_LIST", "List"),
65+
FLOAT_LIST("FLOAT_LIST", "List"),
66+
DOUBLE_LIST("DOUBLE_LIST", "List");
5867

5968
private final String realmType;
6069
private final String javaType;
@@ -110,4 +119,22 @@ public String getJavaType() {
110119
// TODO: add support for char and Char
111120
JAVA_TO_REALM_TYPES = Collections.unmodifiableMap(m);
112121
}
122+
123+
124+
static final Map<String, RealmFieldType> LIST_ELEMENT_TYPE_TO_REALM_TYPES;
125+
126+
static {
127+
Map<String, RealmFieldType> m = new HashMap<String, RealmFieldType>();
128+
m.put("java.lang.Byte", RealmFieldType.INTEGER_LIST);
129+
m.put("java.lang.Short", RealmFieldType.INTEGER_LIST);
130+
m.put("java.lang.Integer", RealmFieldType.INTEGER_LIST);
131+
m.put("java.lang.Long", RealmFieldType.INTEGER_LIST);
132+
m.put("java.lang.Float", RealmFieldType.FLOAT_LIST);
133+
m.put("java.lang.Double", RealmFieldType.DOUBLE_LIST);
134+
m.put("java.lang.Boolean", RealmFieldType.BOOLEAN_LIST);
135+
m.put("java.lang.String", RealmFieldType.STRING_LIST);
136+
m.put("java.util.Date", RealmFieldType.DATE_LIST);
137+
m.put("byte[]", RealmFieldType.BINARY_LIST);
138+
LIST_ELEMENT_TYPE_TO_REALM_TYPES = Collections.unmodifiableMap(m);
139+
}
113140
}

realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProcessor.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,8 @@ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment
172172

173173
// Create all proxy classes
174174
private boolean processAnnotations(RoundEnvironment roundEnv) {
175+
final TypeMirrors typeMirrors = new TypeMirrors(processingEnv);
176+
175177
for (Element classElement : roundEnv.getElementsAnnotatedWith(RealmClass.class)) {
176178

177179
// The class must either extend RealmObject or implement RealmModel
@@ -186,7 +188,7 @@ private boolean processAnnotations(RoundEnvironment roundEnv) {
186188
return false;
187189
}
188190

189-
ClassMetaData metadata = new ClassMetaData(processingEnv, (TypeElement) classElement);
191+
ClassMetaData metadata = new ClassMetaData(processingEnv, typeMirrors, (TypeElement) classElement);
190192
if (!metadata.isModelClass()) { continue; }
191193

192194
Utils.note("Processing class " + metadata.getSimpleClassName());
@@ -202,7 +204,7 @@ private boolean processAnnotations(RoundEnvironment roundEnv) {
202204
Utils.error(e.getMessage(), classElement);
203205
}
204206

205-
RealmProxyClassGenerator sourceCodeGenerator = new RealmProxyClassGenerator(processingEnv, metadata);
207+
RealmProxyClassGenerator sourceCodeGenerator = new RealmProxyClassGenerator(processingEnv, typeMirrors, metadata);
206208
try {
207209
sourceCodeGenerator.generate();
208210
} catch (IOException e) {

0 commit comments

Comments
 (0)