diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f74bbe824..15707564a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,9 @@ * Destructive updates of a schema of a synced Realm will now consistently throw an `UnsupportedOperationException` instead of some methods throwing `IllegalArgumentException`. The affected methods are `RealmSchema.remove(String)`, `RealmSchema.rename(String, String)`, `RealmObjectSchema.setClassName(String)`, `RealmObjectSchema.removeField(String)`, `RealmObjectSchema.renameField(String, String)`, `RealmObjectSchema.removeIndex(String)`, `RealmObjectSchema.removePrimaryKey()`, `RealmObjectSchema.addPrimaryKey(String)` and `RealmObjectSchema.addField(String, Class, FieldAttribute)` ### Enhancements -* None. +* Added support for `org.bson.types.Decimal128` and `org.bson.types.ObjectId` as supported fields in model classes. +* Added support for `org.bson.types.ObjectId` as a primary key. +* Added support for "Embedded Objects". They are enabled using `@RealmClass(embedded = true)`. An embedded object must have exactly one parent object linking to it and it will be deleted when the the parent is. Embedded objects can also be the parent of other embedded classes. Read more [here](https://realm.io/docs/java/latest/#embedded-objects). (Issue [#6713](https://github.com/realm/realm-java/issues/6713)) ### Fixed * None. @@ -41,12 +43,12 @@ NOTE: This version bumps the Realm file format to version 10. Files created with * [ObjectServer] `IncompatibleSyncedFileException` is removed and no longer thrown. ### Enhancements -* Added support for `org.bson.types.Decimal128` and `org.bson.types.ObjectId` as supported fields in model classes. -* Add support for `org.bson.types.ObjectId` as a primary key. * Added `Realm.freeze()`, `RealmObject.freeze()`, `RealmResults.freeze()` and `RealmList.freeze()`. These methods will return a frozen version of the current Realm data. This data can be read from any thread without throwing an `IllegalStateException`, but will never change. All frozen Realms and data can be closed by calling `Realm.close()` on the frozen Realm, but fully closing all live Realms will also close the frozen ones. Frozen data can be queried as normal, but trying to mutate it in any way will throw an `IllegalStateException`. This includes all methods that attempt to refresh or add change listeners. (Issue [#6590](https://github.com/realm/realm-java/pull/6590)) * Added `Realm.isFrozen()`, `RealmObject.isFrozen()`, `RealmObject.isFrozen(RealmModel)`, `RealmResults.isFrozen()` and `RealmList.isFrozen()`, which returns whether or not the data is frozen. * Added `RealmConfiguration.Builder.maxNumberOfActiveVersions(long number)`. Setting this will cause Realm to throw an `IllegalStateException` if too many versions of the Realm data are live at the same time. Having too many versions can dramatically increase the filesize of the Realm. -* `RealmResults.asJSON()` is no longer `@Beta`. +* Storing large binary blobs in Realm files no longer forces the file to be at least 8x the size of the largest blob. +* Reduce the size of transaction logs stored inside the Realm file, reducing file size growth from large transactions. +* `RealmResults.asJSON()` is no longer `@Beta` * The default `toString()` for proxy objects now print the length of binary fields. (Issue [#6767](https://github.com/realm/realm-java/pull/6767)) ### Fixes diff --git a/realm-annotations/src/main/java/io/realm/annotations/PrimaryKey.java b/realm-annotations/src/main/java/io/realm/annotations/PrimaryKey.java index daac8c110f..b654e69451 100644 --- a/realm-annotations/src/main/java/io/realm/annotations/PrimaryKey.java +++ b/realm-annotations/src/main/java/io/realm/annotations/PrimaryKey.java @@ -33,6 +33,9 @@ * It is allowed to apply this annotation on the following primitive types: byte, short, int, and long. * String, Byte, Short, Integer, and Long are also allowed, and further permitted to have {@code null} * as a primary key value. + *

+ * This annotation is not allowed inside Realm classes marked as {@code \@RealmClass(embedded = true)}. + *

*/ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) diff --git a/realm-annotations/src/main/java/io/realm/annotations/RealmClass.java b/realm-annotations/src/main/java/io/realm/annotations/RealmClass.java index 6f284714e8..396c5f9f95 100644 --- a/realm-annotations/src/main/java/io/realm/annotations/RealmClass.java +++ b/realm-annotations/src/main/java/io/realm/annotations/RealmClass.java @@ -50,6 +50,35 @@ */ String name() default ""; + /** + * Define objects of this type as "Embedded". Embedded objects have a slightly different behavior than + * normal objects: + * + */ + boolean embedded() default false; + /** * The naming policy applied to all fields in this class. The default policy is {@link RealmNamingPolicy#NO_POLICY}. *

diff --git a/realm/kotlin-extensions/src/main/kotlin/io/realm/kotlin/RealmExtensions.kt b/realm/kotlin-extensions/src/main/kotlin/io/realm/kotlin/RealmExtensions.kt index 1307e48a38..15382339c0 100644 --- a/realm/kotlin-extensions/src/main/kotlin/io/realm/kotlin/RealmExtensions.kt +++ b/realm/kotlin-extensions/src/main/kotlin/io/realm/kotlin/RealmExtensions.kt @@ -74,6 +74,23 @@ inline fun Realm.createObject(primaryKeyValue: Any?): T return this.createObject(T::class.java, primaryKeyValue) } +/** + * Instantiates and adds a new embedded object to the Realm. + *

+ * This method should only be used to create objects of types marked as embedded. + * + * @param T the Class of the object to create. It must be marked with {@code \@RealmClass(embedded = true)}. + * @param parentObject The parent object which should hold a reference to the embedded object. If the parent property is a list + * the embedded object will be added to the end of that list. + * @param parentProperty the property in the parent class which holds the reference. + * @return the newly created embedded object. + * @throws IllegalArgumentException if {@code clazz} is not an embedded class or if the property + * in the parent class cannot hold objects of the appropriate type. + */ +inline fun Realm.createEmbeddedObject(parentObject: RealmModel, parentProperty: String): T { + return this.createEmbeddedObject(T::class.java, parentObject, parentProperty) +} + /** TODO: Figure out if we should include this is or not. Using this makes it possible to do @@ -99,4 +116,4 @@ Missing functions. Consider these for inclusion later: - createOrUpdateObjectFromJson(Class clazz, org.json.JSONObject json) - createOrUpdateObjectFromJson(Class clazz, String json) - createOrUpdateObjectFromJson(Class clazz, String json) -*/ \ No newline at end of file +*/ diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/Backlink.kt b/realm/realm-annotations-processor/src/main/java/io/realm/processor/Backlink.kt index 862db60de5..415c52355a 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/Backlink.kt +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/Backlink.kt @@ -36,11 +36,21 @@ import io.realm.annotations.Required * To expose backlinks for use, create a declaration as follows: * * ``` + * // For Normal top-level objects * class TargetClass { * // ... * @LinkingObjects("sourceField") * final RealmResults targetField = null; * } + * + * // If the class is an embedded object, we know there is always one parent, so + * // backlinks in this case can also be defined this way: + * class TargetClass { + * // ... + * @LinkingObjects("sourceField") + * final SourceClass targetField; + * } + * *``` * * The `targetField`, the field annotated with the @LinkingObjects annotation must be final. @@ -57,7 +67,7 @@ import io.realm.annotations.Required * An unmanaged Model object will have, as the value of its backlink field, the value with which * the field is initialized (typically null). */ -class Backlink(clazz: ClassMetaData, private val backlinkField: VariableElement) { +class Backlink(private val clazz: ClassMetaData, private val backlinkField: VariableElement) { /** * The fully-qualified name of the class containing the `targetField`, which is the field @@ -74,7 +84,7 @@ class Backlink(clazz: ClassMetaData, private val backlinkField: VariableElement) /** * The fully-qualified name of the class to which the backlinks, from `targetField`, point. */ - val sourceClass: QualifiedClassName? = Utils.getRealmResultsType(backlinkField) + val sourceClass: QualifiedClassName? = if (Utils.isRealmResults(backlinkField)) Utils.getRealmResultsType(backlinkField) else Utils.getModelClassQualifiedName(backlinkField) /** * The name of the field, in `SourceClass` that has a normal link to `targetClass`. @@ -83,8 +93,13 @@ class Backlink(clazz: ClassMetaData, private val backlinkField: VariableElement) */ val sourceField: String? = backlinkField.getAnnotation(LinkingObjects::class.java)?.value - val targetFieldType: String - get() = backlinkField.asType().toString() + /** + * {@code true} if the parent link should be modeled as a RealmResults instead of a single link. + * Single links are only supported in classes that are embedded. + */ + val exposeAsRealmResults: Boolean = Utils.isRealmResults(backlinkField) + + val targetFieldType: String = backlinkField.asType().toString() /** * Validate the source side of the backlink. @@ -92,17 +107,7 @@ class Backlink(clazz: ClassMetaData, private val backlinkField: VariableElement) * @return true if the backlink source looks good. */ fun validateSource(): Boolean { - // A @LinkingObjects cannot be @Required - if (backlinkField.getAnnotation(Required::class.java) != null) { - Utils.error(String.format( - Locale.US, - "The @LinkingObjects field \"%s.%s\" cannot be @Required.", - targetClass, - targetField)) - return false - } - - // The annotation must have an argument, identifying the linked field + // The annotation must have an argument, identifying the linked field. if (sourceField == null || sourceField == "") { Utils.error(String.format( Locale.US, @@ -122,8 +127,17 @@ class Backlink(clazz: ClassMetaData, private val backlinkField: VariableElement) return false } - // The annotated element must be a RealmResult - if (!Utils.isRealmResults(backlinkField)) { + if (Utils.isRealmResults(backlinkField)) { + return validateBacklinksAsRealmResults(backlinkField) + } else { + return validateBacklinkAsObjectReference(backlinkField) + } + } + + private fun validateBacklinkAsObjectReference(field: VariableElement): Boolean { + + // Using @LinkingObjects as a single parent reference is only allowed in embedded classes + if (!clazz.embedded && !Utils.isRealmResults(backlinkField)) { Utils.error(String.format( Locale.US, "The field \"%s.%s\" is a \"%s\". Fields annotated with @LinkingObjects must be RealmResults.", @@ -133,6 +147,44 @@ class Backlink(clazz: ClassMetaData, private val backlinkField: VariableElement) return false } + // A @LinkingObjects can only be required if for the class being embedded there is + // only one @LinkingField field defined. And even in that case, it requires runtime + // schema validation since we need to know if only one other type is pointing to it. + // If multiple types point to it, we cannot keep the contract of @Required. + if (field.getAnnotation(Required::class.java) != null && clazz.backlinkFields.isNotEmpty()) { + Utils.error(String.format( + Locale.US, + "@Required cannot be used on @LinkingObjects field if multiple @LinkingParents are defined: \"%s.%s\".", + targetClass, + targetField)) + return false + } + + // A @LinkingObjects field must be final. + if (!field.modifiers.contains(Modifier.FINAL)) { + Utils.error(String.format( + Locale.US, + "The @LinkingObjects field \"%s.%s\" must be final.", + targetClass, + targetField)) + return false + } + + return true + } + + private fun validateBacklinksAsRealmResults(field: VariableElement): Boolean { + // A @LinkingObjects on a RealmResults cannot be @Required as doesn't have any + // meaning. + if (field.getAnnotation(Required::class.java) != null) { + Utils.error(String.format( + Locale.US, + "The @LinkingObjects field \"%s.%s\" cannot be @Required.", + targetClass, + targetField)) + return false + } + if (sourceClass == null) { Utils.error(String.format( Locale.US, @@ -142,7 +194,7 @@ class Backlink(clazz: ClassMetaData, private val backlinkField: VariableElement) return false } - // A @LinkingObjects field must be final + // A @LinkingObjects field must be final. if (!backlinkField.modifiers.contains(Modifier.FINAL)) { Utils.error(String.format( Locale.US, diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.kt b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.kt index f8590ae51b..7c16b9177a 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.kt +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.kt @@ -128,12 +128,13 @@ class ClassMetaData(env: ProcessingEnvironment, typeMirrors: TypeMirrors, privat return type != "io.realm.DynamicRealmObject" && !type.endsWith(".RealmObject") && !type.endsWith("RealmProxy") } + var embedded: Boolean = false + private set + val classElement: Element get() = classType init { - - for (element in classType.enclosedElements) { if (element is ExecutableElement) { val name = element.getSimpleName() @@ -309,6 +310,8 @@ class ClassMetaData(env: ProcessingEnvironment, typeMirrors: TypeMirrors, privat defaultFieldNameFormatter = Utils.getNameFormatter(realmClassAnnotation.fieldNamingPolicy) } + embedded = realmClassAnnotation.embedded + // Categorize and check the rest of the file if (!categorizeClassElements()) { return false @@ -548,7 +551,7 @@ class ClassMetaData(env: ProcessingEnvironment, typeMirrors: TypeMirrors, privat } } } else if (isRequiredField(field)) { - if (!checkBasicRequiredAnnotationUsage(element, field)) { + if (!checkBasicRequiredAnnotationUsage(field)) { return false } } else { @@ -675,25 +678,30 @@ class ClassMetaData(env: ProcessingEnvironment, typeMirrors: TypeMirrors, privat // The field has the @Required annotation // Returns `true` if the field could be correctly validated, `false` if an error was reported. - private fun checkBasicRequiredAnnotationUsage(element: Element, variableElement: VariableElement): Boolean { - if (Utils.isPrimitiveType(variableElement)) { + private fun checkBasicRequiredAnnotationUsage(field: VariableElement): Boolean { + if (Utils.isPrimitiveType(field)) { Utils.error(String.format(Locale.US, - "@Required or @NotNull annotation is unnecessary for primitive field \"%s\".", element)) + "@Required or @NotNull annotation is unnecessary for primitive field \"%s\".", field)) return false } - if (Utils.isRealmModel(variableElement)) { - Utils.error(String.format(Locale.US, - "Field \"%s\" with type \"%s\" cannot be @Required or @NotNull.", element, element.asType())) - return false + if (Utils.isRealmModel(field)) { + /** + * Defer checking if @Required usage is valid when checking backlinks. See [categorizeBacklinkField] + */ + if (!embedded || field.getAnnotation(LinkingObjects::class.java) == null) { + Utils.error(String.format(Locale.US, + "Field \"%s\" with type \"%s\" cannot be @Required or @NotNull.", field, field.asType())) + return false + } } // Should never get here - user should remove @Required - if (nullableFields.contains(variableElement)) { + if (nullableFields.contains(field)) { Utils.error(String.format(Locale.US, "Field \"%s\" with type \"%s\" appears to be nullable. Consider removing @Required.", - element, - element.asType())) + field, + field.asType())) return false } @@ -706,6 +714,15 @@ class ClassMetaData(env: ProcessingEnvironment, typeMirrors: TypeMirrors, privat // From Core 6 String primary keys no longer needs to be indexed, and from Core 10 // none of the primary key types do. private fun categorizePrimaryKeyField(fieldElement: RealmFieldElement): Boolean { + // Embedded Objects do not support primary keys at all + if (embedded) { + Utils.error(String.format(Locale.US, + "A model class marked as embedded cannot contain a @PrimaryKey. One was defined for: %s", + fieldElement.simpleName.toString())) + return false + } + + // Only one primary key pr. class is allowed if (primaryKey != null) { Utils.error(String.format(Locale.US, "A class cannot have more than one @PrimaryKey. Both \"%s\" and \"%s\" are annotated as @PrimaryKey.", @@ -714,6 +731,7 @@ class ClassMetaData(env: ProcessingEnvironment, typeMirrors: TypeMirrors, privat return false } + // Check that the primary key is defined on a supported field val fieldType = fieldElement.asType() if (!isValidPrimaryKeyType(fieldType)) { Utils.error(String.format(Locale.US, diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProcessor.kt b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProcessor.kt index 47123ba632..2d68ebee44 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProcessor.kt +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProcessor.kt @@ -30,6 +30,7 @@ import javax.lang.model.element.TypeElement import io.realm.annotations.RealmClass import io.realm.annotations.RealmModule import javax.lang.model.element.Name +import javax.lang.model.type.TypeMirror /** @@ -115,6 +116,7 @@ import javax.lang.model.element.Name inline class QualifiedClassName(val name: String) { constructor(name: Name): this(name.toString()) + constructor(name: TypeMirror) : this(name.toString()) fun getSimpleName(): SimpleClassName { return SimpleClassName(Utils.stripPackage(name)) } diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt index 26b97deab7..be04a300e0 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt @@ -106,6 +106,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi emitInsertOrUpdateListMethod(writer) emitCreateDetachedCopyMethod(writer) emitUpdateMethod(writer) + emitUpdateEmbeddedObjectMethod(writer) emitToStringMethod(writer) emitRealmObjectProxyImplementation(writer) emitHashcodeMethod(writer) @@ -368,15 +369,26 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi // Getter - End // Setter - Start + val fieldType = QualifiedClassName(field.asType()) + val fieldTypeMetaData: ClassMetaData = classCollection.getClassFromQualifiedName(fieldType) + val linkedQualifiedClassName: QualifiedClassName = Utils.getFieldTypeQualifiedName(field) + val linkedProxyClass: SimpleClassName = Utils.getProxyClassSimpleName(field) emitAnnotation("Override") beginMethod("void", metadata.getInternalSetter(fieldName), EnumSet.of(Modifier.PUBLIC), fieldTypeCanonicalName, "value") + emitStatement("Realm realm = (Realm) proxyState.getRealm\$realm()") emitCodeForUnderConstruction(writer, metadata.isPrimaryKey(field)) { // check excludeFields beginControlFlow("if (proxyState.getExcludeFields\$realm().contains(\"%1\$s\"))", field.simpleName.toString()) emitStatement("return") endControlFlow() beginControlFlow("if (value != null && !RealmObject.isManaged(value))") - emitStatement("value = ((Realm) proxyState.getRealm\$realm()).copyToRealm(value)") + if (fieldTypeMetaData.embedded) { + emitStatement("%1\$s proxyObject = realm.createEmbeddedObject(%1\$s.class, this, \"%2\$s\")", linkedQualifiedClassName, fieldName) + emitStatement("%s.updateEmbeddedObject(realm, value, proxyObject, new HashMap(), Collections.EMPTY_SET)", linkedProxyClass) + emitStatement("value = proxyObject") + } else { + emitStatement("value = realm.copyToRealm(value)") + } endControlFlow() // set value as default value @@ -395,8 +407,17 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi emitStatement("proxyState.getRow\$realm().nullifyLink(%s)", fieldColKeyVariableReference(field)) emitStatement("return") endControlFlow() - emitStatement("proxyState.checkValidObject(value)") - emitStatement("proxyState.getRow\$realm().setLink(%s, ((RealmObjectProxy) value).realmGet\$proxyState().getRow\$realm().getObjectKey())", fieldColKeyVariableReference(field)) + + if (fieldTypeMetaData.embedded) { + beginControlFlow("if (RealmObject.isManaged(value))") + emitStatement("proxyState.checkValidObject(value)") + endControlFlow() + emitStatement("%1\$s proxyObject = realm.createEmbeddedObject(%1\$s.class, this, \"%2\$s\")", linkedQualifiedClassName, fieldName) + emitStatement("%s.updateEmbeddedObject(realm, value, proxyObject, new HashMap(), Collections.EMPTY_SET)", linkedProxyClass) + } else { + emitStatement("proxyState.checkValidObject(value)") + emitStatement("proxyState.getRow\$realm().setLink(%s, ((RealmObjectProxy) value).realmGet\$proxyState().getRow\$realm().getObjectKey())", fieldColKeyVariableReference(field)) + } endMethod() // Setter - End } @@ -598,19 +619,39 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi for (backlink in metadata.backlinkFields) { val cacheFieldName = backlink.targetField + BACKLINKS_FIELD_EXTENSION val realmResultsType = "RealmResults<" + backlink.sourceClass + ">" - // Getter, no setter - writer.apply { - emitAnnotation("Override") - beginMethod(realmResultsType, metadata.getInternalGetter(backlink.targetField), EnumSet.of(Modifier.PUBLIC)) - emitStatement("BaseRealm realm = proxyState.getRealm\$realm()") - emitStatement("realm.checkIfValid()") - emitStatement("proxyState.getRow\$realm().checkIfAttached()") - beginControlFlow("if ($cacheFieldName == null)") + when (backlink.exposeAsRealmResults) { + true -> { + // Getter, no setter + writer.apply { + emitAnnotation("Override") + beginMethod(realmResultsType, metadata.getInternalGetter(backlink.targetField), EnumSet.of(Modifier.PUBLIC)) + emitStatement("BaseRealm realm = proxyState.getRealm\$realm()") + emitStatement("realm.checkIfValid()") + emitStatement("proxyState.getRow\$realm().checkIfAttached()") + beginControlFlow("if ($cacheFieldName == null)") emitStatement("$cacheFieldName = RealmResults.createBacklinkResults(realm, proxyState.getRow\$realm(), %s.class, \"%s\")", backlink.sourceClass, backlink.sourceField) - endControlFlow() - emitStatement("return $cacheFieldName") - endMethod() - emitEmptyLine() + endControlFlow() + emitStatement("return $cacheFieldName") + endMethod() + emitEmptyLine() + } + } + false -> { + // Getter, no setter + writer.apply { + emitAnnotation("Override") + beginMethod(backlink.sourceClass.toString(), metadata.getInternalGetter(backlink.targetField), EnumSet.of(Modifier.PUBLIC)) + emitStatement("BaseRealm realm = proxyState.getRealm\$realm()") + emitStatement("realm.checkIfValid()") + emitStatement("proxyState.getRow\$realm().checkIfAttached()") + beginControlFlow("if ($cacheFieldName == null)") + emitStatement("$cacheFieldName = RealmResults.createBacklinkResults(realm, proxyState.getRow\$realm(), %s.class, \"%s\").first()", backlink.sourceClass, backlink.sourceField) + endControlFlow() + emitStatement("return $cacheFieldName") // TODO: Figure out the exact API for this + endMethod() + emitEmptyLine() + } + } } } } @@ -634,8 +675,9 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi // Used to prevent array resizing at runtime val persistedFields = metadata.fields.size val computedFields = metadata.backlinkFields.size + val embeddedClass = if (metadata.embedded) "true" else "false" - emitStatement("OsObjectSchemaInfo.Builder builder = new OsObjectSchemaInfo.Builder(\"%s\", %s, %s)", internalClassName, persistedFields, computedFields) + emitStatement("OsObjectSchemaInfo.Builder builder = new OsObjectSchemaInfo.Builder(\"%s\", %s, %s, %s)", internalClassName, embeddedClass, persistedFields, computedFields) // For each field generate corresponding table index constant for (field in metadata.fields) { @@ -742,7 +784,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi @Throws(IOException::class) private fun emitNewProxyInstance(writer: JavaWriter) { writer.apply { - beginMethod(generatedClassName, "newProxyInstance", EnumSet.of(Modifier.PRIVATE, Modifier.STATIC), "BaseRealm", "realm", "Row", "row") + beginMethod(generatedClassName, "newProxyInstance", EnumSet.of(Modifier.STATIC), "BaseRealm", "realm", "Row", "row") emitSingleLineComment("Ignore default values to avoid creating unexpected objects from RealmModel/RealmList fields") emitStatement("final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get()") emitStatement("objectContext.set(realm, row, realm.getSchema().getColumnInfo(%s.class), false, Collections.emptyList())", qualifiedJavaClassName) @@ -985,12 +1027,25 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi @Throws(IOException::class) private fun emitInsertMethod(writer: JavaWriter) { writer.apply { - beginMethod("long","insert", EnumSet.of(Modifier.PUBLIC, Modifier.STATIC), "Realm", "realm", qualifiedJavaClassName.toString(), "object", "Map", "cache") - - // If object is already in the Realm there is nothing to update - beginControlFlow("if (object instanceof RealmObjectProxy && !RealmObject.isFrozen(object) && ((RealmObjectProxy) object).realmGet\$proxyState().getRealm\$realm() != null && ((RealmObjectProxy) object).realmGet\$proxyState().getRealm\$realm().getPath().equals(realm.getPath()))") - emitStatement("return ((RealmObjectProxy) object).realmGet\$proxyState().getRow\$realm().getObjectKey()") - endControlFlow() + val topLevelArgs = arrayOf("Realm", "realm", + qualifiedJavaClassName.toString(), "object", + "Map", "cache") + val embeddedArgs = arrayOf("Realm", "realm", + "Table", "parentObjectTable", + "long", "parentColumnKey", + "long", "parentObjectKey", + qualifiedJavaClassName.toString(), "object", + "Map", "cache") + val args = if (metadata.embedded) embeddedArgs else topLevelArgs + beginMethod("long","insert", EnumSet.of(Modifier.PUBLIC, Modifier.STATIC), *args) + + // If object is already in the Realm there is nothing to update, unless it is an embedded + // object. In which case we always update the underlying object. + if (!metadata.embedded) { + beginControlFlow("if (object instanceof RealmObjectProxy && !RealmObject.isFrozen(object) && ((RealmObjectProxy) object).realmGet\$proxyState().getRealm\$realm() != null && ((RealmObjectProxy) object).realmGet\$proxyState().getRealm\$realm().getPath().equals(realm.getPath()))") + emitStatement("return ((RealmObjectProxy) object).realmGet\$proxyState().getRow\$realm().getObjectKey()") + endControlFlow() + } emitStatement("Table table = realm.getTable(%s.class)", qualifiedJavaClassName) emitStatement("long tableNativePtr = table.getNativePtr()") @@ -1003,33 +1058,55 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi for (field in metadata.fields) { val fieldName = field.simpleName.toString() - val fieldType = field.asType().toString() + val fieldType = QualifiedClassName(field.asType().toString()) val getter = metadata.getInternalGetter(fieldName) when { Utils.isRealmModel(field) -> { + // FIXME: How to support types from other compilation units? + val fieldTypeMetaData: ClassMetaData = classCollection.getClassFromQualifiedName(fieldType) + emitEmptyLine() emitStatement("%s %sObj = ((%s) object).%s()", fieldType, fieldName, interfaceName, getter) beginControlFlow("if (%sObj != null)", fieldName) emitStatement("Long cache%1\$s = cache.get(%1\$sObj)", fieldName) - beginControlFlow("if (cache%s == null)", fieldName) - emitStatement("cache%s = %s.insert(realm, %sObj, cache)", fieldName, Utils.getProxyClassSimpleName(field), fieldName) - endControlFlow() - emitStatement("Table.nativeSetLink(tableNativePtr, columnInfo.%1\$sColKey, objKey, cache%1\$s, false)", fieldName) + if (fieldTypeMetaData.embedded) { + beginControlFlow("if (cache%s != null)", fieldName) + emitStatement("throw new IllegalArgumentException(\"Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: \" + cache%s.toString())", fieldName) + nextControlFlow("else") + emitStatement("cache%1\$s = %2\$s.insert(realm, table, columnInfo.%3\$sColKey, objKey, %3\$sObj, cache)", fieldName, Utils.getProxyClassSimpleName(field), fieldName) + endControlFlow() + } else { + beginControlFlow("if (cache%s == null)", fieldName) + emitStatement("cache%s = %s.insert(realm, %sObj, cache)", fieldName, Utils.getProxyClassSimpleName(field), fieldName) + endControlFlow() + emitStatement("Table.nativeSetLink(tableNativePtr, columnInfo.%1\$sColKey, objKey, cache%1\$s, false)", fieldName) + } endControlFlow() } Utils.isRealmModelList(field) -> { - val genericType = Utils.getGenericTypeQualifiedName(field) + val genericType = Utils.getGenericTypeQualifiedName(field)!! + // FIXME: How to support types from other compilation units? + val fieldTypeMetaData: ClassMetaData = classCollection.getClassFromQualifiedName(genericType) + emitEmptyLine() emitStatement("RealmList<%s> %sList = ((%s) object).%s()", genericType, fieldName, interfaceName, getter) beginControlFlow("if (%sList != null)", fieldName) emitStatement("OsList %1\$sOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.%1\$sColKey)", fieldName) beginControlFlow("for (%1\$s %2\$sItem : %2\$sList)", genericType, fieldName) emitStatement("Long cacheItemIndex%1\$s = cache.get(%1\$sItem)", fieldName) - beginControlFlow("if (cacheItemIndex%s == null)", fieldName) - emitStatement("cacheItemIndex%1\$s = %2\$s.insert(realm, %1\$sItem, cache)", fieldName, Utils.getProxyClassSimpleName(field)) - endControlFlow() - emitStatement("%1\$sOsList.addRow(cacheItemIndex%1\$s)", fieldName) + if (fieldTypeMetaData.embedded) { + beginControlFlow("if (cacheItemIndex%s != null)", fieldName) + emitStatement("throw new IllegalArgumentException(\"Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: \" + cacheItemIndex%s.toString())", fieldName) + nextControlFlow("else") + emitStatement("cacheItemIndex%1\$s = %2\$s.insert(realm, table, columnInfo.%3\$sColKey, objKey, %3\$sItem, cache)", fieldName, Utils.getProxyClassName(genericType), fieldName) + endControlFlow() + } else { + beginControlFlow("if (cacheItemIndex%s == null)", fieldName) + emitStatement("cacheItemIndex%1\$s = %2\$s.insert(realm, %1\$sItem, cache)", fieldName, Utils.getProxyClassSimpleName(field)) + endControlFlow() + emitStatement("%1\$sOsList.addRow(cacheItemIndex%1\$s)", fieldName) + } endControlFlow() endControlFlow() } @@ -1051,7 +1128,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi } else -> { if (metadata.primaryKey !== field) { - setTableValues(writer, fieldType, fieldName, interfaceName, getter, false) + setTableValues(writer, fieldType.toString(), fieldName, interfaceName, getter, false) } } } @@ -1066,7 +1143,18 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi @Throws(IOException::class) private fun emitInsertListMethod(writer: JavaWriter) { writer.apply { - beginMethod("void", "insert", EnumSet.of(Modifier.PUBLIC, Modifier.STATIC), "Realm", "realm", "Iterator", "objects", "Map", "cache") + val topLevelArgs = arrayOf("Realm", "realm", + "Iterator", "objects", + "Map", "cache") + val embeddedArgs = arrayOf("Realm", "realm", + "Table", "parentObjectTable", + "long", "parentColumnKey", + "long", "parentObjectKey", + "Iterator", "objects", + "Map", "cache") + val args = if (metadata.embedded) embeddedArgs else topLevelArgs + + beginMethod("void", "insert", EnumSet.of(Modifier.PUBLIC, Modifier.STATIC), *args) emitStatement("Table table = realm.getTable(%s.class)", qualifiedJavaClassName) emitStatement("long tableNativePtr = table.getNativePtr()") emitStatement("%s columnInfo = (%s) realm.getSchema().getColumnInfo(%s.class)", columnInfoClassName(), columnInfoClassName(), qualifiedJavaClassName) @@ -1089,31 +1177,53 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi for (field in metadata.fields) { val fieldName = field.simpleName.toString() - val fieldType = field.asType().toString() + val fieldType = QualifiedClassName(field.asType().toString()) val getter = metadata.getInternalGetter(fieldName) if (Utils.isRealmModel(field)) { + // FIXME: How to support types from other compilation units? + val fieldTypeMetaData: ClassMetaData = classCollection.getClassFromQualifiedName(fieldType) + emitEmptyLine() emitStatement("%s %sObj = ((%s) object).%s()", fieldType, fieldName, interfaceName, getter) beginControlFlow("if (%sObj != null)", fieldName) emitStatement("Long cache%1\$s = cache.get(%1\$sObj)", fieldName) - beginControlFlow("if (cache%s == null)", fieldName) - emitStatement("cache%s = %s.insert(realm, %sObj, cache)", fieldName, Utils.getProxyClassSimpleName(field), fieldName) - endControlFlow() - emitStatement("table.setLink(columnInfo.%1\$sColKey, objKey, cache%1\$s, false)", fieldName) + if (fieldTypeMetaData.embedded) { + beginControlFlow("if (cache%s != null)", fieldName) + emitStatement("throw new IllegalArgumentException(\"Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: \" + cache%s.toString())", fieldName) + nextControlFlow("else") + emitStatement("cache%1\$s = %2\$s.insert(realm, table, columnInfo.%3\$sColKey, objKey, %3\$sObj, cache)", fieldName, Utils.getProxyClassSimpleName(field), fieldName) + endControlFlow() + } else { + beginControlFlow("if (cache%s == null)", fieldName) + emitStatement("cache%s = %s.insert(realm, %sObj, cache)", fieldName, Utils.getProxyClassSimpleName(field), fieldName) + endControlFlow() + emitStatement("table.setLink(columnInfo.%1\$sColKey, objKey, cache%1\$s, false)", fieldName) + } endControlFlow() } else if (Utils.isRealmModelList(field)) { - val genericType = Utils.getGenericTypeQualifiedName(field) + val genericType = Utils.getGenericTypeQualifiedName(field)!! + // FIXME: How to support types from other compilation units? + val fieldTypeMetaData: ClassMetaData = classCollection.getClassFromQualifiedName(genericType) + emitEmptyLine() emitStatement("RealmList<%s> %sList = ((%s) object).%s()", genericType, fieldName, interfaceName, getter) beginControlFlow("if (%sList != null)", fieldName) emitStatement("OsList %1\$sOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.%1\$sColKey)", fieldName) beginControlFlow("for (%1\$s %2\$sItem : %2\$sList)", genericType, fieldName) emitStatement("Long cacheItemIndex%1\$s = cache.get(%1\$sItem)", fieldName) - beginControlFlow("if (cacheItemIndex%s == null)", fieldName) - emitStatement("cacheItemIndex%1\$s = %2\$s.insert(realm, %1\$sItem, cache)", fieldName, Utils.getProxyClassSimpleName(field)) - endControlFlow() - emitStatement("%1\$sOsList.addRow(cacheItemIndex%1\$s)", fieldName) + if (fieldTypeMetaData.embedded) { + beginControlFlow("if (cacheItemIndex%s != null)", fieldName) + emitStatement("throw new IllegalArgumentException(\"Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: \" + cacheItemIndex%s.toString())", fieldName) + nextControlFlow("else") + emitStatement("cacheItemIndex%1\$s = %2\$s.insert(realm, table, columnInfo.%3\$sColKey, objKey, %3\$sItem, cache)", fieldName, Utils.getProxyClassName(genericType), fieldName) + endControlFlow() + } else { + beginControlFlow("if (cacheItemIndex%s == null)", fieldName) + emitStatement("cacheItemIndex%1\$s = %2\$s.insert(realm, %1\$sItem, cache)", fieldName, Utils.getProxyClassSimpleName(field)) + endControlFlow() + emitStatement("%1\$sOsList.addRow(cacheItemIndex%1\$s)", fieldName) + } endControlFlow() endControlFlow() } else if (Utils.isRealmValueList(field)) { @@ -1133,7 +1243,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi endControlFlow() } else { if (metadata.primaryKey !== field) { - setTableValues(writer, fieldType, fieldName, interfaceName, getter, false) + setTableValues(writer, fieldType.toString(), fieldName, interfaceName, getter, false) } } } @@ -1146,7 +1256,17 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi @Throws(IOException::class) private fun emitInsertOrUpdateMethod(writer: JavaWriter) { writer.apply { - beginMethod("long", "insertOrUpdate", EnumSet.of(Modifier.PUBLIC, Modifier.STATIC), "Realm", "realm", qualifiedJavaClassName.toString(), "object", "Map", "cache") + val topLevelArgs = arrayOf("Realm", "realm", + qualifiedJavaClassName.toString(), "object", + "Map", "cache") + val embeddedArgs = arrayOf("Realm", "realm", + "Table", "parentObjectTable", + "long", "parentColumnKey", + "long", "parentObjectKey", + qualifiedJavaClassName.toString(), "object", + "Map", "cache") + val args = if (metadata.embedded) embeddedArgs else topLevelArgs + beginMethod("long", "insertOrUpdate", EnumSet.of(Modifier.PUBLIC, Modifier.STATIC), *args) // If object is already in the Realm there is nothing to update beginControlFlow("if (object instanceof RealmObjectProxy && !RealmObject.isFrozen(object) && ((RealmObjectProxy) object).realmGet\$proxyState().getRealm\$realm() != null && ((RealmObjectProxy) object).realmGet\$proxyState().getRealm\$realm().getPath().equals(realm.getPath()))") @@ -1163,24 +1283,38 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi for (field in metadata.fields) { val fieldName = field.simpleName.toString() - val fieldType = field.asType().toString() + val fieldType = QualifiedClassName(field.asType().toString()) val getter = metadata.getInternalGetter(fieldName) if (Utils.isRealmModel(field)) { + // FIXME: How to support types from other compilation units? + val fieldTypeMetaData: ClassMetaData = classCollection.getClassFromQualifiedName(fieldType) + emitEmptyLine() emitStatement("%s %sObj = ((%s) object).%s()", fieldType, fieldName, interfaceName, getter) beginControlFlow("if (%sObj != null)", fieldName) emitStatement("Long cache%1\$s = cache.get(%1\$sObj)", fieldName) - beginControlFlow("if (cache%s == null)", fieldName) - emitStatement("cache%1\$s = %2\$s.insertOrUpdate(realm, %1\$sObj, cache)", fieldName, Utils.getProxyClassSimpleName(field)) - endControlFlow() - emitStatement("Table.nativeSetLink(tableNativePtr, columnInfo.%1\$sColKey, objKey, cache%1\$s, false)", fieldName) + if (fieldTypeMetaData.embedded) { + beginControlFlow("if (cache%s != null)", fieldName) + emitStatement("throw new IllegalArgumentException(\"Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: \" + cache%s.toString())", fieldName) + nextControlFlow("else") + emitStatement("cache%1\$s = %2\$s.insertOrUpdate(realm, table, columnInfo.%3\$sColKey, objKey, %3\$sObj, cache)", fieldName, Utils.getProxyClassSimpleName(field), fieldName) + endControlFlow() + } else { + beginControlFlow("if (cache%s == null)", fieldName) + emitStatement("cache%1\$s = %2\$s.insertOrUpdate(realm, %1\$sObj, cache)", fieldName, Utils.getProxyClassSimpleName(field)) + endControlFlow() + emitStatement("Table.nativeSetLink(tableNativePtr, columnInfo.%1\$sColKey, objKey, cache%1\$s, false)", fieldName) + } nextControlFlow("else") // No need to throw exception here if the field is not nullable. A exception will be thrown in setter. emitStatement("Table.nativeNullifyLink(tableNativePtr, columnInfo.%sColKey, objKey)", fieldName) endControlFlow() } else if (Utils.isRealmModelList(field)) { - val genericType = Utils.getGenericTypeQualifiedName(field) + val genericType = Utils.getGenericTypeQualifiedName(field)!! + // FIXME: How to support types from other compilation units? + val fieldTypeMetaData: ClassMetaData = classCollection.getClassFromQualifiedName(genericType) + emitEmptyLine() emitStatement("OsList %1\$sOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.%1\$sColKey)", fieldName) emitStatement("RealmList<%s> %sList = ((%s) object).%s()", genericType, fieldName, interfaceName, getter) @@ -1190,20 +1324,36 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi beginControlFlow("for (int i = 0; i < objects; i++)") emitStatement("%1\$s %2\$sItem = %2\$sList.get(i)", genericType, fieldName) emitStatement("Long cacheItemIndex%1\$s = cache.get(%1\$sItem)", fieldName) - beginControlFlow("if (cacheItemIndex%s == null)", fieldName) - emitStatement("cacheItemIndex%1\$s = %2\$s.insertOrUpdate(realm, %1\$sItem, cache)", fieldName, Utils.getProxyClassSimpleName(field)) - endControlFlow() - emitStatement("%1\$sOsList.setRow(i, cacheItemIndex%1\$s)", fieldName) + if (fieldTypeMetaData.embedded) { + beginControlFlow("if (cacheItemIndex%s != null)", fieldName) + emitStatement("throw new IllegalArgumentException(\"Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: \" + cacheItemIndex%s.toString())", fieldName) + nextControlFlow("else") + emitStatement("cacheItemIndex%1\$s = %2\$s.insertOrUpdate(realm, table, columnInfo.%3\$sColKey, objKey, %3\$sItem, cache)", fieldName, Utils.getProxyClassName(genericType), fieldName) + endControlFlow() + } else { + beginControlFlow("if (cacheItemIndex%s == null)", fieldName) + emitStatement("cacheItemIndex%1\$s = %2\$s.insertOrUpdate(realm, %1\$sItem, cache)", fieldName, Utils.getProxyClassSimpleName(field)) + endControlFlow() + emitStatement("%1\$sOsList.setRow(i, cacheItemIndex%1\$s)", fieldName) + } endControlFlow() nextControlFlow("else") emitStatement("%1\$sOsList.removeAll()", fieldName) beginControlFlow("if (%sList != null)", fieldName) beginControlFlow("for (%1\$s %2\$sItem : %2\$sList)", genericType, fieldName) emitStatement("Long cacheItemIndex%1\$s = cache.get(%1\$sItem)", fieldName) - beginControlFlow("if (cacheItemIndex%s == null)", fieldName) - emitStatement("cacheItemIndex%1\$s = %2\$s.insertOrUpdate(realm, %1\$sItem, cache)", fieldName, Utils.getProxyClassSimpleName(field)) - endControlFlow() - emitStatement("%1\$sOsList.addRow(cacheItemIndex%1\$s)", fieldName) + if (fieldTypeMetaData.embedded) { + beginControlFlow("if (cacheItemIndex%s != null)", fieldName) + emitStatement("throw new IllegalArgumentException(\"Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: \" + cacheItemIndex%s.toString())", fieldName) + nextControlFlow("else") + emitStatement("cacheItemIndex%1\$s = %2\$s.insertOrUpdate(realm, table, columnInfo.%3\$sColKey, objKey, %3\$sItem, cache)", fieldName, Utils.getProxyClassName(genericType), fieldName) + endControlFlow() + } else { + beginControlFlow("if (cacheItemIndex%s == null)", fieldName) + emitStatement("cacheItemIndex%1\$s = %2\$s.insertOrUpdate(realm, %1\$sItem, cache)", fieldName, Utils.getProxyClassSimpleName(field)) + endControlFlow() + emitStatement("%1\$sOsList.addRow(cacheItemIndex%1\$s)", fieldName) + } endControlFlow() endControlFlow() endControlFlow() @@ -1227,7 +1377,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi emitEmptyLine() } else { if (metadata.primaryKey !== field) { - setTableValues(writer, fieldType, fieldName, interfaceName, getter, true) + setTableValues(writer, fieldType.toString(), fieldName, interfaceName, getter, true) } } } @@ -1241,7 +1391,18 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi @Throws(IOException::class) private fun emitInsertOrUpdateListMethod(writer: JavaWriter) { writer.apply { - beginMethod("void", "insertOrUpdate", EnumSet.of(Modifier.PUBLIC, Modifier.STATIC), "Realm", "realm", "Iterator", "objects", "Map", "cache") + val topLevelArgs = arrayOf("Realm", "realm", + "Iterator", "objects", + "Map", "cache") + val embeddedArgs = arrayOf("Realm", "realm", + "Table", "parentObjectTable", + "long", "parentColumnKey", + "long", "parentObjectKey", + "Iterator", "objects", + "Map", "cache") + val args = if (metadata.embedded) embeddedArgs else topLevelArgs + + beginMethod("void", "insertOrUpdate", EnumSet.of(Modifier.PUBLIC, Modifier.STATIC), *args) emitStatement("Table table = realm.getTable(%s.class)", qualifiedJavaClassName) emitStatement("long tableNativePtr = table.getNativePtr()") emitStatement("%s columnInfo = (%s) realm.getSchema().getColumnInfo(%s.class)", columnInfoClassName(), columnInfoClassName(), qualifiedJavaClassName) @@ -1263,26 +1424,40 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi for (field in metadata.fields) { val fieldName = field.simpleName.toString() - val fieldType = field.asType().toString() + val fieldType = QualifiedClassName(field.asType().toString()) val getter = metadata.getInternalGetter(fieldName) when { Utils.isRealmModel(field) -> { + // FIXME: How to support types from other compilation units? + val fieldTypeMetaData: ClassMetaData = classCollection.getClassFromQualifiedName(fieldType) + emitEmptyLine() emitStatement("%s %sObj = ((%s) object).%s()", fieldType, fieldName, interfaceName, getter) beginControlFlow("if (%sObj != null)", fieldName) emitStatement("Long cache%1\$s = cache.get(%1\$sObj)", fieldName) - beginControlFlow("if (cache%s == null)", fieldName) - emitStatement("cache%1\$s = %2\$s.insertOrUpdate(realm, %1\$sObj, cache)", fieldName, Utils.getProxyClassSimpleName(field)) - endControlFlow() - emitStatement("Table.nativeSetLink(tableNativePtr, columnInfo.%1\$sColKey, objKey, cache%1\$s, false)", fieldName) + if (fieldTypeMetaData.embedded) { + beginControlFlow("if (cache%s != null)", fieldName) + emitStatement("throw new IllegalArgumentException(\"Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: \" + cache%s.toString())", fieldName) + nextControlFlow("else") + emitStatement("cache%1\$s = %2\$s.insertOrUpdate(realm, table, columnInfo.%3\$sColKey, objKey, %3\$sObj, cache)", fieldName, Utils.getProxyClassSimpleName(field), fieldName) + endControlFlow() + } else { + beginControlFlow("if (cache%s == null)", fieldName) + emitStatement("cache%1\$s = %2\$s.insertOrUpdate(realm, %1\$sObj, cache)", fieldName, Utils.getProxyClassSimpleName(field)) + endControlFlow() + emitStatement("Table.nativeSetLink(tableNativePtr, columnInfo.%1\$sColKey, objKey, cache%1\$s, false)", fieldName) + } nextControlFlow("else") // No need to throw exception here if the field is not nullable. A exception will be thrown in setter. emitStatement("Table.nativeNullifyLink(tableNativePtr, columnInfo.%sColKey, objKey)", fieldName) endControlFlow() } Utils.isRealmModelList(field) -> { - val genericType = Utils.getGenericTypeQualifiedName(field) + val genericType = Utils.getGenericTypeQualifiedName(field)!! + // FIXME: How to support types from other compilation units? + val fieldTypeMetaData: ClassMetaData = classCollection.getClassFromQualifiedName(genericType) + emitEmptyLine() emitStatement("OsList %1\$sOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.%1\$sColKey)", fieldName) emitStatement("RealmList<%s> %sList = ((%s) object).%s()", genericType, fieldName, interfaceName, getter) @@ -1292,20 +1467,36 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi beginControlFlow("for (int i = 0; i < objectCount; i++)") emitStatement("%1\$s %2\$sItem = %2\$sList.get(i)", genericType, fieldName) emitStatement("Long cacheItemIndex%1\$s = cache.get(%1\$sItem)", fieldName) - beginControlFlow("if (cacheItemIndex%s == null)", fieldName) - emitStatement("cacheItemIndex%1\$s = %2\$s.insertOrUpdate(realm, %1\$sItem, cache)", fieldName, Utils.getProxyClassSimpleName(field)) - endControlFlow() - emitStatement("%1\$sOsList.setRow(i, cacheItemIndex%1\$s)", fieldName) + if (fieldTypeMetaData.embedded) { + beginControlFlow("if (cacheItemIndex%s != null)", fieldName) + emitStatement("throw new IllegalArgumentException(\"Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: \" + cacheItemIndex%s.toString())", fieldName) + nextControlFlow("else") + emitStatement("cacheItemIndex%1\$s = %2\$s.insertOrUpdate(realm, table, columnInfo.%3\$sColKey, objKey, %3\$sItem, cache)", fieldName, Utils.getProxyClassName(genericType), fieldName) + endControlFlow() + } else { + beginControlFlow("if (cacheItemIndex%s == null)", fieldName) + emitStatement("cacheItemIndex%1\$s = %2\$s.insertOrUpdate(realm, %1\$sItem, cache)", fieldName, Utils.getProxyClassSimpleName(field)) + endControlFlow() + emitStatement("%1\$sOsList.setRow(i, cacheItemIndex%1\$s)", fieldName) + } endControlFlow() nextControlFlow("else") emitStatement("%1\$sOsList.removeAll()", fieldName) beginControlFlow("if (%sList != null)", fieldName) beginControlFlow("for (%1\$s %2\$sItem : %2\$sList)", genericType, fieldName) emitStatement("Long cacheItemIndex%1\$s = cache.get(%1\$sItem)", fieldName) - beginControlFlow("if (cacheItemIndex%s == null)", fieldName) - emitStatement("cacheItemIndex%1\$s = %2\$s.insertOrUpdate(realm, %1\$sItem, cache)", fieldName, Utils.getProxyClassSimpleName(field)) - endControlFlow() - emitStatement("%1\$sOsList.addRow(cacheItemIndex%1\$s)", fieldName) + if (fieldTypeMetaData.embedded) { + beginControlFlow("if (cacheItemIndex%s != null)", fieldName) + emitStatement("throw new IllegalArgumentException(\"Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: \" + cacheItemIndex%s.toString())", fieldName) + nextControlFlow("else") + emitStatement("cacheItemIndex%1\$s = %2\$s.insertOrUpdate(realm, table, columnInfo.%3\$sColKey, objKey, %3\$sItem, cache)", fieldName, Utils.getProxyClassName(genericType), fieldName) + endControlFlow() + } else { + beginControlFlow("if (cacheItemIndex%s == null)", fieldName) + emitStatement("cacheItemIndex%1\$s = %2\$s.insertOrUpdate(realm, %1\$sItem, cache)", fieldName, Utils.getProxyClassSimpleName(field)) + endControlFlow() + emitStatement("%1\$sOsList.addRow(cacheItemIndex%1\$s)", fieldName) + } endControlFlow() endControlFlow() endControlFlow() @@ -1331,7 +1522,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi } else -> { if (metadata.primaryKey !== field) { - setTableValues(writer, fieldType, fieldName, interfaceName, getter, true) + setTableValues(writer, fieldType.toString(), fieldName, interfaceName, getter, true) } } } @@ -1402,8 +1593,13 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi endControlFlow() emitStatement("cache.put(object, objKey)") } else { - emitStatement("long objKey = OsObject.createRow(table)") - emitStatement("cache.put(object, objKey)") + if (metadata.embedded) { + emitStatement("long objKey = OsObject.createEmbeddedObject(parentObjectTable, parentObjectKey, parentColumnKey)") + emitStatement("cache.put(object, objKey)") + } else { + emitStatement("long objKey = OsObject.createRow(table)") + emitStatement("cache.put(object, objKey)") + } } } } @@ -1424,7 +1620,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi emitStatement("return (%s) cachedRealmObject", qualifiedJavaClassName) endControlFlow() emitEmptyLine() - emitStatement("%1\$s realmObjectSource = (%1\$s) newObject", interfaceName) + emitStatement("%1\$s unmanagedSource = (%1\$s) newObject", interfaceName) emitEmptyLine() emitStatement("Table table = realm.getTable(%s.class)", qualifiedJavaClassName) emitStatement("OsObjectBuilder builder = new OsObjectBuilder(table, flags)") @@ -1436,7 +1632,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi val fieldColKey = fieldColKeyVariableReference(field) val fieldName = field.simpleName.toString() val getter = metadata.getInternalGetter(fieldName) - emitStatement("builder.%s(%s, realmObjectSource.%s())", OsObjectBuilderTypeHelper.getOsObjectBuilderName(field), fieldColKey, getter) + emitStatement("builder.%s(%s, unmanagedSource.%s())", OsObjectBuilderTypeHelper.getOsObjectBuilderName(field), fieldColKey, getter) } // Create the underlying object @@ -1444,8 +1640,8 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi emitSingleLineComment("Create the underlying object and cache it before setting any object/objectlist references") emitSingleLineComment("This will allow us to break any circular dependencies by using the object cache.") emitStatement("Row row = builder.createNewObject()") - emitStatement("%s realmObjectCopy = newProxyInstance(realm, row)", generatedClassName) - emitStatement("cache.put(newObject, realmObjectCopy)") + emitStatement("%s managedCopy = newProxyInstance(realm, row)", generatedClassName) + emitStatement("cache.put(newObject, managedCopy)") // Copy all object references or lists-of-objects emitEmptyLine() @@ -1453,42 +1649,82 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi emitSingleLineComment("Finally add all fields that reference other Realm Objects, either directly or through a list") } for (field in metadata.objectReferenceFields) { - val fieldType = field.asType().toString() - val fieldName = field.simpleName.toString() - val getter = metadata.getInternalGetter(fieldName) - val setter = metadata.getInternalSetter(fieldName) + val fieldType = QualifiedClassName(field.asType()) + val fieldName: String = field.simpleName.toString() + val getter: String = metadata.getInternalGetter(fieldName) + val setter: String = metadata.getInternalSetter(fieldName) when { Utils.isRealmModel(field) -> { - emitStatement("%s %sObj = realmObjectSource.%s()", fieldType, fieldName, getter) + // FIXME: How to support Embedded objects defined in another compilation unit? + val fieldTypeMetaData: ClassMetaData = classCollection.getClassFromQualifiedName(fieldType) + val fieldColKey: String = fieldColKeyVariableReference(field) + val linkedQualifiedClassName: QualifiedClassName = Utils.getFieldTypeQualifiedName(field) + val linkedProxyClass: SimpleClassName = Utils.getProxyClassSimpleName(field) + + emitStatement("%s %sObj = unmanagedSource.%s()", fieldType, fieldName, getter) beginControlFlow("if (%sObj == null)", fieldName) - emitStatement("realmObjectCopy.%s(null)", setter) + emitStatement("managedCopy.%s(null)", setter) nextControlFlow("else") emitStatement("%s cache%s = (%s) cache.get(%sObj)", fieldType, fieldName, fieldType, fieldName) - beginControlFlow("if (cache%s != null)", fieldName) - emitStatement("realmObjectCopy.%s(cache%s)", setter, fieldName) - nextControlFlow("else") - emitStatement("realmObjectCopy.%s(%s.copyOrUpdate(realm, (%s) realm.getSchema().getColumnInfo(%s.class), %sObj, update, cache, flags))", setter, Utils.getProxyClassSimpleName(field), columnInfoClassName(field), Utils.getFieldTypeQualifiedName(field), fieldName) - endControlFlow() + + if (fieldTypeMetaData.embedded) { + beginControlFlow("if (cache%s != null)", fieldName) + emitStatement("throw new IllegalArgumentException(\"Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: cache%s.toString()\")", fieldName) + nextControlFlow("else") + emitStatement("long objKey = ((RealmObjectProxy) managedCopy).realmGet\$proxyState().getRow\$realm().createEmbeddedObject(%s)", fieldColKey) + emitStatement("Row linkedObjectRow = realm.getTable(%s.class).getUncheckedRow(objKey)", linkedQualifiedClassName) + emitStatement("%s linkedObject = %s.newProxyInstance(realm, linkedObjectRow)", linkedQualifiedClassName, linkedProxyClass) + emitStatement("cache.put(%sObj, (RealmObjectProxy) linkedObject)", fieldName) + emitStatement("%s.updateEmbeddedObject(realm, %sObj, linkedObject, cache, flags)", linkedProxyClass, fieldName) + endControlFlow() + } else { + beginControlFlow("if (cache%s != null)", fieldName) + emitStatement("managedCopy.%s(cache%s)", setter, fieldName) + nextControlFlow("else") + emitStatement("managedCopy.%s(%s.copyOrUpdate(realm, (%s) realm.getSchema().getColumnInfo(%s.class), %sObj, update, cache, flags))", setter, linkedProxyClass, columnInfoClassName(field), linkedQualifiedClassName, fieldName) + endControlFlow() + } + // No need to throw exception here if the field is not nullable. A exception will be thrown in setter. endControlFlow() emitEmptyLine() } Utils.isRealmModelList(field) -> { - val genericType = Utils.getGenericTypeQualifiedName(field) - emitStatement("RealmList<%s> %sList = realmObjectSource.%s()", genericType, fieldName, getter) - beginControlFlow("if (%sList != null)", fieldName) - emitStatement("RealmList<%s> %sRealmList = realmObjectCopy.%s()", genericType, fieldName, getter) + // FIXME: How to support Embedded objects defined in another compilation unit? + val listElementType: QualifiedClassName = Utils.getRealmListType(field)!! + val fieldTypeMetaData: ClassMetaData = classCollection.getClassFromQualifiedName(listElementType) + val genericType: QualifiedClassName = Utils.getGenericTypeQualifiedName(field)!! + val linkedProxyClass: SimpleClassName = Utils.getProxyClassSimpleName(field) + + emitStatement("RealmList<%s> %sUnmanagedList = unmanagedSource.%s()", genericType, fieldName, getter) + beginControlFlow("if (%sUnmanagedList != null)", fieldName) + emitStatement("RealmList<%s> %sManagedList = managedCopy.%s()", genericType, fieldName, getter) // Clear is needed. See bug https://github.com/realm/realm-java/issues/4957 - emitStatement("%sRealmList.clear()", fieldName) - beginControlFlow("for (int i = 0; i < %sList.size(); i++)", fieldName) - emitStatement("%1\$s %2\$sItem = %2\$sList.get(i)", genericType, fieldName) - emitStatement("%1\$s cache%2\$s = (%1\$s) cache.get(%2\$sItem)", genericType, fieldName) - beginControlFlow("if (cache%s != null)", fieldName) - emitStatement("%1\$sRealmList.add(cache%1\$s)", fieldName) - nextControlFlow("else") - emitStatement("%1\$sRealmList.add(%2\$s.copyOrUpdate(realm, (%3\$s) realm.getSchema().getColumnInfo(%4\$s.class), %1\$sItem, update, cache, flags))", fieldName, Utils.getProxyClassSimpleName(field), columnInfoClassName(field), Utils.getGenericTypeQualifiedName(field)) - endControlFlow() + emitStatement("%sManagedList.clear()", fieldName) + beginControlFlow("for (int i = 0; i < %sUnmanagedList.size(); i++)", fieldName) + emitStatement("%1\$s %2\$sUnmanagedItem = %2\$sUnmanagedList.get(i)", genericType, fieldName) + emitStatement("%1\$s cache%2\$s = (%1\$s) cache.get(%2\$sUnmanagedItem)", genericType, fieldName) + + if (fieldTypeMetaData.embedded) { + beginControlFlow("if (cache%s != null)", fieldName) + emitStatement("throw new IllegalArgumentException(\"Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: cache%s.toString()\")", fieldName) + nextControlFlow("else") + emitStatement("long objKey = %sManagedList.getOsList().createAndAddEmbeddedObject()", fieldName) + emitStatement("Row linkedObjectRow = realm.getTable(%s.class).getUncheckedRow(objKey)", genericType) + emitStatement("%s linkedObject = %s.newProxyInstance(realm, linkedObjectRow)", genericType, linkedProxyClass) + emitStatement("cache.put(%sUnmanagedItem, (RealmObjectProxy) linkedObject)", fieldName) + emitStatement("%s.updateEmbeddedObject(realm, %sUnmanagedItem, linkedObject, new HashMap(), Collections.EMPTY_SET)", linkedProxyClass, fieldName) + endControlFlow() + + } else { + beginControlFlow("if (cache%s != null)", fieldName) + emitStatement("%1\$sManagedList.add(cache%1\$s)", fieldName) + nextControlFlow("else") + emitStatement("%1\$sManagedList.add(%2\$s.copyOrUpdate(realm, (%3\$s) realm.getSchema().getColumnInfo(%4\$s.class), %1\$sUnmanagedItem, update, cache, flags))", fieldName, Utils.getProxyClassSimpleName(field), columnInfoClassName(field), Utils.getGenericTypeQualifiedName(field)) + endControlFlow() + } + endControlFlow() endControlFlow() emitEmptyLine() @@ -1498,7 +1734,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi } } } - emitStatement("return realmObjectCopy") + emitStatement("return managedCopy") endMethod() emitEmptyLine() } @@ -1577,7 +1813,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi @Throws(IOException::class) private fun emitUpdateMethod(writer: JavaWriter) { - if (!metadata.hasPrimaryKey()) { + if (!metadata.hasPrimaryKey() && !metadata.embedded) { return } writer.apply { @@ -1594,43 +1830,91 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi emitStatement("Table table = realm.getTable(%s.class)", qualifiedJavaClassName) emitStatement("OsObjectBuilder builder = new OsObjectBuilder(table, flags)") for (field in metadata.fields) { - val fieldType = field.asType().toString() + val fieldType = QualifiedClassName(field.asType()) val fieldName = field.simpleName.toString() val getter = metadata.getInternalGetter(fieldName) val fieldColKey = fieldColKeyVariableReference(field) when { Utils.isRealmModel(field) -> { + // FIXME: How to support Embedded objects defined in another compilation unit? + val fieldTypeMetaData: ClassMetaData = classCollection.getClassFromQualifiedName(fieldType) + emitEmptyLine() emitStatement("%s %sObj = realmObjectSource.%s()", fieldType, fieldName, getter) beginControlFlow("if (%sObj == null)", fieldName) emitStatement("builder.addNull(%s)", fieldColKeyVariableReference(field)) nextControlFlow("else") + + if (fieldTypeMetaData.embedded) { + // Embedded objects are created in-place as we need to know the + // parent object + the property containing it. + // After this we know that changing values will always be considered + // an "update + emitSingleLineComment("Embedded objects are created directly instead of using the builder.") + emitStatement("%s cache%s = (%s) cache.get(%sObj)", fieldType, fieldName, fieldType, fieldName) + beginControlFlow("if (cache%s != null)", fieldName) + emitStatement("throw new IllegalArgumentException(\"Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: cache%s.toString()\")", fieldName) + endControlFlow() + emitEmptyLine() + emitStatement("long objKey = ((RealmObjectProxy) realmObject).realmGet\$proxyState().getRow\$realm().createEmbeddedObject(%s)", fieldColKey) + emitStatement("Row row = realm.getTable(%s.class).getUncheckedRow(objKey)", Utils.getFieldTypeQualifiedName(field)) + emitStatement("%s proxyObject = %s.newProxyInstance(realm, row)", fieldType, Utils.getProxyClassSimpleName(field)) + emitStatement("cache.put(%sObj, (RealmObjectProxy) proxyObject)", fieldName) + emitStatement("%s.updateEmbeddedObject(realm, %sObj, proxyObject, cache, flags)", Utils.getProxyClassSimpleName(field), fieldName) + } else { + // Non-embedded classes are updating using normal recursive bottom-up approach emitStatement("%s cache%s = (%s) cache.get(%sObj)", fieldType, fieldName, fieldType, fieldName) beginControlFlow("if (cache%s != null)", fieldName) emitStatement("builder.addObject(%s, cache%s)", fieldColKey, fieldName) nextControlFlow("else") emitStatement("builder.addObject(%s, %s.copyOrUpdate(realm, (%s) realm.getSchema().getColumnInfo(%s.class), %sObj, true, cache, flags))", fieldColKey, Utils.getProxyClassSimpleName(field), columnInfoClassName(field), Utils.getFieldTypeQualifiedName(field), fieldName) endControlFlow() + } + // No need to throw exception here if the field is not nullable. A exception will be thrown in setter. endControlFlow() } Utils.isRealmModelList(field) -> { - val genericType = Utils.getGenericTypeQualifiedName(field) + // FIXME: How to support Embedded objects defined in another compilation unit? + val genericType: QualifiedClassName = Utils.getRealmListType(field)!! + val fieldTypeMetaData: ClassMetaData = classCollection.getClassFromQualifiedName(genericType) + val proxyClass: SimpleClassName = Utils.getProxyClassSimpleName(field) + emitEmptyLine() - emitStatement("RealmList<%s> %sList = realmObjectSource.%s()", genericType, fieldName, getter) - beginControlFlow("if (%sList != null)", fieldName) + emitStatement("RealmList<%s> %sUnmanagedList = realmObjectSource.%s()", genericType, fieldName, getter) + beginControlFlow("if (%sUnmanagedList != null)", fieldName) emitStatement("RealmList<%s> %sManagedCopy = new RealmList<%s>()", genericType, fieldName, genericType) - beginControlFlow("for (int i = 0; i < %sList.size(); i++)", fieldName) - emitStatement("%1\$s %2\$sItem = %2\$sList.get(i)", genericType, fieldName) - emitStatement("%1\$s cache%2\$s = (%1\$s) cache.get(%2\$sItem)", genericType, fieldName) - beginControlFlow("if (cache%s != null)", fieldName) - emitStatement("%1\$sManagedCopy.add(cache%1\$s)", fieldName) - nextControlFlow("else") - emitStatement("%1\$sManagedCopy.add(%2\$s.copyOrUpdate(realm, (%3\$s) realm.getSchema().getColumnInfo(%4\$s.class), %1\$sItem, true, cache, flags))", fieldName, Utils.getProxyClassSimpleName(field), columnInfoClassName(field), Utils.getGenericTypeQualifiedName(field)) + + if (fieldTypeMetaData.embedded) { + beginControlFlow("for (int i = 0; i < %sUnmanagedList.size(); i++)", fieldName) + emitStatement("%1\$s %2\$sUnmanagedItem = %2\$sUnmanagedList.get(i)", genericType, fieldName) + emitStatement("%1\$s cache%2\$s = (%1\$s) cache.get(%2\$sUnmanagedItem)", genericType, fieldName) + beginControlFlow("if (cache%s != null)", fieldName) + emitStatement("throw new IllegalArgumentException(\"Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: cache%s.toString()\")", fieldName) + nextControlFlow("else") + emitStatement("long objKey = realmObjectTarget.%s().getOsList().createAndAddEmbeddedObject()", getter) + emitStatement("Row row = realm.getTable(%s.class).getUncheckedRow(objKey)", genericType) + emitStatement("%s proxyObject = %s.newProxyInstance(realm, row)", genericType, proxyClass) + emitStatement("cache.put(%sUnmanagedItem, (RealmObjectProxy) proxyObject)", fieldName) + emitStatement("%sManagedCopy.add(proxyObject)", fieldName) + emitStatement("%s.updateEmbeddedObject(realm, %sUnmanagedItem, proxyObject, new HashMap(), Collections.EMPTY_SET)", Utils.getProxyClassSimpleName(field), fieldName) + endControlFlow() endControlFlow() - endControlFlow() - emitStatement("builder.addObjectList(%s, %sManagedCopy)", fieldColKey, fieldName) + emitStatement("builder.addObjectList(%s, %sManagedCopy)", fieldColKey, fieldName) + } else { + beginControlFlow("for (int i = 0; i < %sUnmanagedList.size(); i++)", fieldName) + emitStatement("%1\$s %2\$sItem = %2\$sUnmanagedList.get(i)", genericType, fieldName) + emitStatement("%1\$s cache%2\$s = (%1\$s) cache.get(%2\$sItem)", genericType, fieldName) + beginControlFlow("if (cache%s != null)", fieldName) + emitStatement("%1\$sManagedCopy.add(cache%1\$s)", fieldName) + nextControlFlow("else") + emitStatement("%1\$sManagedCopy.add(%2\$s.copyOrUpdate(realm, (%3\$s) realm.getSchema().getColumnInfo(%4\$s.class), %1\$sItem, true, cache, flags))", fieldName, proxyClass, columnInfoClassName(field), genericType) + endControlFlow() + endControlFlow() + emitStatement("builder.addObjectList(%s, %sManagedCopy)", fieldColKey, fieldName) + } + nextControlFlow("else") emitStatement("builder.addObjectList(%s, new RealmList<%s>())", fieldColKey, genericType) endControlFlow() @@ -1641,13 +1925,37 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi } } emitEmptyLine() - emitStatement("builder.updateExistingObject()") + if (metadata.embedded) { + emitStatement("builder.updateExistingEmbeddedObject((RealmObjectProxy) realmObject)") + } else { + emitStatement("builder.updateExistingTopLevelObject()") + } emitStatement("return realmObject") endMethod() emitEmptyLine() } } + @Throws(IOException::class) + private fun emitUpdateEmbeddedObjectMethod(writer: JavaWriter) { + if (!metadata.embedded) { + return + } + + writer.apply { + beginMethod("void", "updateEmbeddedObject", EnumSet.of(Modifier.STATIC, Modifier.PUBLIC), + "Realm", "realm", // Argument type & argument name + qualifiedJavaClassName.toString(), "unmanagedObject", + qualifiedJavaClassName.toString(), "managedObject", + "Map", "cache", + "Set", "flags" + ) + emitStatement("update(realm, (%s) realm.getSchema().getColumnInfo(%s.class), managedObject, unmanagedObject, cache, flags)", Utils.getSimpleColumnInfoClassName(metadata.qualifiedClassName), metadata.qualifiedClassName) + endMethod() + emitEmptyLine() + } + } + @Throws(IOException::class) private fun emitToStringMethod(writer: JavaWriter) { if (metadata.containsToString()) { diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.kt b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.kt index 18fd5dea62..c6e3d17793 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.kt +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.kt @@ -39,6 +39,7 @@ class RealmProxyMediatorGenerator(private val processingEnvironment: ProcessingE private val qualifiedProxyClasses = ArrayList() private val simpleModelClassNames = ArrayList() private val internalClassNames = ArrayList() + private val embeddedClass = ArrayList() init { for (metadata in classesToValidate) { @@ -47,6 +48,7 @@ class RealmProxyMediatorGenerator(private val processingEnvironment: ProcessingE qualifiedProxyClasses.add(qualifiedProxyClassName) simpleModelClassNames.add(metadata.simpleJavaClassName) internalClassNames.add(metadata.internalClassName) + embeddedClass.add(metadata.embedded) } } @@ -101,6 +103,8 @@ class RealmProxyMediatorGenerator(private val processingEnvironment: ProcessingE emitCreteOrUpdateUsingJsonObject(this) emitCreateUsingJsonStream(this) emitCreateDetachedCopyMethod(this) + emitIsEmbeddedMethod(this) + emitUpdateEmbeddedObjectMethod(this) endType() close() } @@ -147,9 +151,9 @@ class RealmProxyMediatorGenerator(private val processingEnvironment: ProcessingE "Class", "clazz", // Argument type & argument name "OsSchemaInfo", "schemaInfo" ) - emitMediatorShortCircuitSwitch({ i: Int -> + emitMediatorShortCircuitSwitch(writer, emitStatement = { i: Int -> emitStatement("return %s.createColumnInfo(schemaInfo)", qualifiedProxyClasses[i]) - }, writer) + }) endMethod() emitEmptyLine() } @@ -165,9 +169,9 @@ class RealmProxyMediatorGenerator(private val processingEnvironment: ProcessingE EnumSet.of(Modifier.PUBLIC), "Class", "clazz" ) - emitMediatorShortCircuitSwitch({ i: Int -> - emitStatement("return \"%s\"", internalClassNames[i]) - }, writer) + emitMediatorShortCircuitSwitch(writer, emitStatement = { i: Int -> + emitStatement("return \"%s\"", internalClassNames[i]) + }) endMethod() emitEmptyLine() } @@ -191,9 +195,9 @@ class RealmProxyMediatorGenerator(private val processingEnvironment: ProcessingE emitStatement("final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get()") beginControlFlow("try") emitStatement("objectContext.set((BaseRealm) baseRealm, row, columnInfo, acceptDefaultValue, excludeFields)") - emitMediatorShortCircuitSwitch({ i: Int -> + emitMediatorShortCircuitSwitch(writer, emitStatement = { i: Int -> emitStatement("return clazz.cast(new %s())", qualifiedProxyClasses[i]) - }, writer) + }) nextControlFlow("finally") emitStatement("objectContext.clear()") endControlFlow() @@ -231,10 +235,10 @@ class RealmProxyMediatorGenerator(private val processingEnvironment: ProcessingE emitSingleLineComment("generated by RealmProxy or the original type extending directly from RealmObject") emitStatement("@SuppressWarnings(\"unchecked\") Class clazz = (Class) ((obj instanceof RealmObjectProxy) ? obj.getClass().getSuperclass() : obj.getClass())") emitEmptyLine() - emitMediatorShortCircuitSwitch({i: Int -> + emitMediatorShortCircuitSwitch(writer, false) { i: Int -> emitStatement("%1\$s columnInfo = (%1\$s) realm.getSchema().getColumnInfo(%2\$s.class)", Utils.getSimpleColumnInfoClassName(qualifiedModelClasses[i]), qualifiedModelClasses[i]) emitStatement("return clazz.cast(%s.copyOrUpdate(realm, columnInfo, (%s) obj, update, cache, flags))", qualifiedProxyClasses[i], qualifiedModelClasses[i]) - }, writer, false) + } endMethod() emitEmptyLine() } @@ -249,13 +253,23 @@ class RealmProxyMediatorGenerator(private val processingEnvironment: ProcessingE "insert", EnumSet.of(Modifier.PUBLIC), "Realm", "realm", "RealmModel", "object", "Map", "cache") - emitSingleLineComment("This cast is correct because obj is either") - emitSingleLineComment("generated by RealmProxy or the original type extending directly from RealmObject") - emitStatement("@SuppressWarnings(\"unchecked\") Class clazz = (Class) ((object instanceof RealmObjectProxy) ? object.getClass().getSuperclass() : object.getClass())") - emitEmptyLine() - emitMediatorSwitch({ i: Int -> - emitStatement("%s.insert(realm, (%s) object, cache)", qualifiedProxyClasses[i], qualifiedModelClasses[i]) - }, writer, false) + + if (embeddedClass.contains(false)) { + emitSingleLineComment("This cast is correct because obj is either") + emitSingleLineComment("generated by RealmProxy or the original type extending directly from RealmObject") + emitStatement("@SuppressWarnings(\"unchecked\") Class clazz = (Class) ((object instanceof RealmObjectProxy) ? object.getClass().getSuperclass() : object.getClass())") + emitEmptyLine() + emitMediatorSwitch(writer, false, { i: Int -> + if (embeddedClass[i]) { + emitEmbeddedObjectsCannotBeCopiedException(writer) + } else { + emitStatement("%s.insert(realm, (%s) object, cache)", qualifiedProxyClasses[i], qualifiedModelClasses[i]) + } + }) + } else { + emitEmbeddedObjectsCannotBeCopiedException(writer) + } + endMethod() emitEmptyLine() } @@ -270,13 +284,22 @@ class RealmProxyMediatorGenerator(private val processingEnvironment: ProcessingE "insertOrUpdate", EnumSet.of(Modifier.PUBLIC), "Realm", "realm", "RealmModel", "obj", "Map", "cache") + + if (embeddedClass.contains(false)) { emitSingleLineComment("This cast is correct because obj is either") emitSingleLineComment("generated by RealmProxy or the original type extending directly from RealmObject") emitStatement("@SuppressWarnings(\"unchecked\") Class clazz = (Class) ((obj instanceof RealmObjectProxy) ? obj.getClass().getSuperclass() : obj.getClass())") emitEmptyLine() - emitMediatorSwitch({ i: Int -> - emitStatement("%s.insertOrUpdate(realm, (%s) obj, cache)", qualifiedProxyClasses[i], qualifiedModelClasses[i]) - }, writer, false) + emitMediatorSwitch(writer, false, { i: Int -> + if (embeddedClass[i]) { + emitEmbeddedObjectsCannotBeCopiedException(writer) + } else { + emitStatement("%s.insertOrUpdate(realm, (%s) obj, cache)", qualifiedProxyClasses[i], qualifiedModelClasses[i]) + } + }) + } else { + emitEmbeddedObjectsCannotBeCopiedException(writer) + } endMethod() emitEmptyLine() } @@ -292,33 +315,52 @@ class RealmProxyMediatorGenerator(private val processingEnvironment: ProcessingE EnumSet.of(Modifier.PUBLIC), "Realm", "realm", "Collection", "objects") + if (embeddedClass.contains(false)) { emitStatement("Iterator iterator = objects.iterator()") emitStatement("RealmModel object = null") emitStatement("Map cache = new HashMap(objects.size())") beginControlFlow("if (iterator.hasNext())") - emitSingleLineComment(" access the first element to figure out the clazz for the routing below") - emitStatement("object = iterator.next()") - emitSingleLineComment("This cast is correct because obj is either") - emitSingleLineComment("generated by RealmProxy or the original type extending directly from RealmObject") - emitStatement("@SuppressWarnings(\"unchecked\") Class clazz = (Class) ((object instanceof RealmObjectProxy) ? object.getClass().getSuperclass() : object.getClass())") - emitEmptyLine() + emitSingleLineComment(" access the first element to figure out the clazz for the routing below") + emitStatement("object = iterator.next()") + emitSingleLineComment("This cast is correct because obj is either") + emitSingleLineComment("generated by RealmProxy or the original type extending directly from RealmObject") + emitStatement("@SuppressWarnings(\"unchecked\") Class clazz = (Class) ((object instanceof RealmObjectProxy) ? object.getClass().getSuperclass() : object.getClass())") + emitEmptyLine() - emitMediatorSwitch({ i: Int -> + emitMediatorSwitch(writer, false) { i: Int -> + if (embeddedClass[i]) { + emitEmbeddedObjectsCannotBeCopiedException(writer) + } else { emitStatement("%s.insertOrUpdate(realm, (%s) object, cache)", qualifiedProxyClasses[i], qualifiedModelClasses[i]) - }, writer, false) + } + } - beginControlFlow("if (iterator.hasNext())") - emitMediatorSwitch({ i: Int -> - emitStatement("%s.insertOrUpdate(realm, iterator, cache)", qualifiedProxyClasses[i]) - }, writer, false) - endControlFlow() + beginControlFlow("if (iterator.hasNext())") + emitMediatorSwitch(writer, false) { i: Int -> + if (embeddedClass[i]) { + emitEmbeddedObjectsCannotBeCopiedException(writer) + } else { + emitStatement("%s.insertOrUpdate(realm, iterator, cache)", qualifiedProxyClasses[i]) + } + } endControlFlow() + endControlFlow() + } else { + emitEmbeddedObjectsCannotBeCopiedException(writer) + } + endMethod() emitEmptyLine() } } + private fun emitEmbeddedObjectsCannotBeCopiedException(writer: JavaWriter) { + writer.apply { + emitStatement("throw new IllegalArgumentException(\"Embedded objects cannot be copied into Realm by themselves. They need to be attached to a parent object\")") + } + } + @Throws(IOException::class) private fun emitInsertListToRealmMethod(writer: JavaWriter) { writer.apply { @@ -329,29 +371,41 @@ class RealmProxyMediatorGenerator(private val processingEnvironment: ProcessingE EnumSet.of(Modifier.PUBLIC), "Realm", "realm", "Collection", "objects") - emitStatement("Iterator iterator = objects.iterator()") - emitStatement("RealmModel object = null") - emitStatement("Map cache = new HashMap(objects.size())") + if (embeddedClass.contains(false)) { + emitStatement("Iterator iterator = objects.iterator()") + emitStatement("RealmModel object = null") + emitStatement("Map cache = new HashMap(objects.size())") - beginControlFlow("if (iterator.hasNext())") - .emitSingleLineComment(" access the first element to figure out the clazz for the routing below") - .emitStatement("object = iterator.next()") - .emitSingleLineComment("This cast is correct because obj is either") - .emitSingleLineComment("generated by RealmProxy or the original type extending directly from RealmObject") - .emitStatement("@SuppressWarnings(\"unchecked\") Class clazz = (Class) ((object instanceof RealmObjectProxy) ? object.getClass().getSuperclass() : object.getClass())") - .emitEmptyLine() - - emitMediatorSwitch({ i: Int -> + beginControlFlow("if (iterator.hasNext())") + .emitSingleLineComment(" access the first element to figure out the clazz for the routing below") + .emitStatement("object = iterator.next()") + .emitSingleLineComment("This cast is correct because obj is either") + .emitSingleLineComment("generated by RealmProxy or the original type extending directly from RealmObject") + .emitStatement("@SuppressWarnings(\"unchecked\") Class clazz = (Class) ((object instanceof RealmObjectProxy) ? object.getClass().getSuperclass() : object.getClass())") + .emitEmptyLine() + + emitMediatorSwitch(writer, false, { i: Int -> + if (embeddedClass[i]) { + emitEmbeddedObjectsCannotBeCopiedException(writer) + } else { emitStatement("%s.insert(realm, (%s) object, cache)", qualifiedProxyClasses[i], qualifiedModelClasses[i]) - }, writer, false) + } + }) beginControlFlow("if (iterator.hasNext())") - emitMediatorSwitch({ i: Int -> + emitMediatorSwitch(writer, false, { i: Int -> + if (embeddedClass[i]) { + emitEmbeddedObjectsCannotBeCopiedException(writer) + } else { emitStatement("%s.insert(realm, iterator, cache)", qualifiedProxyClasses[i]) - }, writer, false) + } + }) + endControlFlow() endControlFlow() - endControlFlow() + } else { + emitEmbeddedObjectsCannotBeCopiedException(writer) + } endMethod() emitEmptyLine() } @@ -368,9 +422,9 @@ class RealmProxyMediatorGenerator(private val processingEnvironment: ProcessingE Arrays.asList("Class", "clazz", "Realm", "realm", "JSONObject", "json", "boolean", "update"), Arrays.asList("JSONException") ) - emitMediatorShortCircuitSwitch({ i: Int -> - emitStatement("return clazz.cast(%s.createOrUpdateUsingJsonObject(realm, json, update))", qualifiedProxyClasses[i]) - }, writer) + emitMediatorShortCircuitSwitch(writer, emitStatement = { i: Int -> + emitStatement("return clazz.cast(%s.createOrUpdateUsingJsonObject(realm, json, update))", qualifiedProxyClasses[i]) + }) endMethod() emitEmptyLine() } @@ -387,9 +441,9 @@ class RealmProxyMediatorGenerator(private val processingEnvironment: ProcessingE Arrays.asList("Class", "clazz", "Realm", "realm", "JsonReader", "reader"), Arrays.asList("java.io.IOException") ) - emitMediatorShortCircuitSwitch({ i: Int -> + emitMediatorShortCircuitSwitch(writer, emitStatement = { i: Int -> emitStatement("return clazz.cast(%s.createUsingJsonStream(realm, reader))", qualifiedProxyClasses[i]) - }, writer) + }) endMethod() emitEmptyLine() } @@ -409,20 +463,70 @@ class RealmProxyMediatorGenerator(private val processingEnvironment: ProcessingE emitSingleLineComment("generated by RealmProxy or the original type extending directly from RealmObject") emitStatement("@SuppressWarnings(\"unchecked\") Class clazz = (Class) realmObject.getClass().getSuperclass()") emitEmptyLine() - emitMediatorShortCircuitSwitch({ i: Int -> + emitMediatorShortCircuitSwitch(writer, false, { i: Int -> emitStatement("return clazz.cast(%s.createDetachedCopy((%s) realmObject, 0, maxDepth, cache))", qualifiedProxyClasses[i], qualifiedModelClasses[i]) - }, writer, false) + }) endMethod() emitEmptyLine() } } + @Throws(IOException::class) + private fun emitIsEmbeddedMethod(writer: JavaWriter) { + writer.apply { + emitAnnotation("Override") + beginMethod( + " boolean", + "isEmbedded", + EnumSet.of(Modifier.PUBLIC), + "Class", "clazz" + ) + emitMediatorShortCircuitSwitch(writer, false, { i: Int -> + emitStatement("return %s", if (embeddedClass[i]) "true" else "false") + }) + endMethod() + emitEmptyLine() + } + } + + @Throws(IOException::class) + private fun emitUpdateEmbeddedObjectMethod(writer: JavaWriter) { + writer.apply { + emitAnnotation("Override") + beginMethod( + " void", + "updateEmbeddedObject", + EnumSet.of(Modifier.PUBLIC), + "Realm", "realm", + "E", "unmanagedObject", + "E", "managedObject", + "Map", "cache", + "Set", "flags" + ) + + emitSingleLineComment("This cast is correct because obj is either") + emitSingleLineComment("generated by RealmProxy or the original type extending directly from RealmObject") + emitStatement("@SuppressWarnings(\"unchecked\") Class clazz = (Class) managedObject.getClass().getSuperclass()") + emitEmptyLine() + emitMediatorSwitch(writer, false) { i: Int -> + if (embeddedClass[i]) { + emitStatement("%1\$s.updateEmbeddedObject(realm, (%2\$s) unmanagedObject, (%2\$s) managedObject, cache, flags)", qualifiedProxyClasses[i], qualifiedModelClasses[i]) + } else { + emitStatement("throw getNotEmbeddedClassException(\"%s\")", qualifiedModelClasses[i]) + } + } + endMethod() + emitEmptyLine() + } + } + + // Emits the control flow for selecting the appropriate proxy class based on the model class // Currently it is just if..else, which is inefficient for large amounts amounts of model classes. // Consider switching to HashMap or similar. @Throws(IOException::class) - private fun emitMediatorSwitch(emitStatement: (index: Int) -> Unit, writer: JavaWriter, nullPointerCheck: Boolean) { + private fun emitMediatorSwitch(writer: JavaWriter, nullPointerCheck: Boolean, emitStatement: (index: Int) -> Unit) { writer.apply { if (nullPointerCheck) { emitStatement("checkClass(clazz)") @@ -445,7 +549,7 @@ class RealmProxyMediatorGenerator(private val processingEnvironment: ProcessingE } @Throws(IOException::class) - private fun emitMediatorShortCircuitSwitch(emitStatement: (index: Int) -> Unit, writer: JavaWriter, nullPointerCheck: Boolean = true) { + private fun emitMediatorShortCircuitSwitch(writer: JavaWriter, nullPointerCheck: Boolean = true, emitStatement: (index: Int) -> Unit) { writer.apply { if (nullPointerCheck) { emitStatement("checkClass(clazz)") diff --git a/realm/realm-annotations-processor/src/test/java/io/realm/processor/RealmEmbeddedObjectsTest.java b/realm/realm-annotations-processor/src/test/java/io/realm/processor/RealmEmbeddedObjectsTest.java new file mode 100644 index 0000000000..4aae15f466 --- /dev/null +++ b/realm/realm-annotations-processor/src/test/java/io/realm/processor/RealmEmbeddedObjectsTest.java @@ -0,0 +1,115 @@ +package io.realm.processor; + +import com.google.testing.compile.JavaFileObjects; + +import org.junit.Test; + +import java.util.Arrays; + +import static com.google.testing.compile.JavaSourceSubjectFactory.javaSource; +import static com.google.testing.compile.JavaSourcesSubjectFactory.javaSources; +import static org.truth0.Truth.ASSERT; + +public class RealmEmbeddedObjectsTest { + + @Test + public void compileEmbeddedObjectFile() { + ASSERT.about(javaSource()) + .that(JavaFileObjects.forResource("some/test/EmbeddedClass.java")) + .processedWith(new RealmProcessor()) + .compilesWithoutError(); + } + + @Test + public void compileParentToEmbeddedObjectFile() { + ASSERT.about(javaSources()) + .that(Arrays.asList( + JavaFileObjects.forResource("some/test/EmbeddedClassSimpleParent.java"), + JavaFileObjects.forResource("some/test/EmbeddedClass.java") + )) + .processedWith(new RealmProcessor()) + .compilesWithoutError() + .and() + .generatesSources(JavaFileObjects.forResource("io/realm/some_test_EmbeddedClassSimpleParentRealmProxy.java")); + } + + @Test + public void compileWithSingleRequiredParent() { + ASSERT.about(javaSources()) + .that(Arrays.asList( + JavaFileObjects.forResource("some/test/EmbeddedClassParent.java"), + JavaFileObjects.forResource("some/test/EmbeddedClass.java"), + JavaFileObjects.forResource("some/test/EmbeddedClassOptionalParents.java"), + JavaFileObjects.forResource("some/test/EmbeddedClassRequiredParent.java") + )) + .processedWith(new RealmProcessor()) + .compilesWithoutError(); + } + + + @Test + public void compileWithMultipleOptionalParents() { + ASSERT.about(javaSources()) + .that(Arrays.asList( + JavaFileObjects.forResource("some/test/EmbeddedClassParent.java"), + JavaFileObjects.forResource("some/test/EmbeddedClass.java"), + JavaFileObjects.forResource("some/test/EmbeddedClassRequiredParent.java"), + JavaFileObjects.forResource("some/test/EmbeddedClassOptionalParents.java") + )) + .processedWith(new RealmProcessor()) + .compilesWithoutError(); + } + + @Test + public void failToCompileIfSingleParentIsMissingFinal() { + ASSERT.about(javaSources()) + .that(Arrays.asList( + JavaFileObjects.forResource("some/test/EmbeddedClassParent.java"), + JavaFileObjects.forResource("some/test/EmbeddedClassMissingFinalOnLinkingObjects.java") + )) + .processedWith(new RealmProcessor()) + .failsToCompile() + .withErrorContaining("The @LinkingObjects field \"some.test.EmbeddedClassMissingFinalOnLinkingObjects.parent\" must be final."); + } + + // If a single parent type has multiple potential fields that can act as parent. Any + // @LinkingObject field in the child must designate the field name in the parent. + @Test + public void failToCompileIfMissingFieldDescriptor() { + ASSERT.about(javaSources()) + .that(Arrays.asList( + JavaFileObjects.forResource("some/test/EmbeddedClassParent.java"), + JavaFileObjects.forResource("some/test/EmbeddedClassMissingFieldDescription.java") + )) + .processedWith(new RealmProcessor()) + .failsToCompile() + .withErrorContaining("The @LinkingObjects annotation for the field \"some.test.EmbeddedClassMissingFieldDescription.parent1\" must have a parameter identifying the link target."); + } + + + // @PrimaryKey is not allowed inside embedded classes + @Test + public void failToCompileWithPrimaryKey() { + ASSERT.about(javaSources()) + .that(Arrays.asList( + JavaFileObjects.forResource("some/test/EmbeddedClassPrimaryKey.java") + )) + .processedWith(new RealmProcessor()) + .failsToCompile() + .withErrorContaining("A model class marked as embedded cannot contain a @PrimaryKey."); + } + + // If a child has multiple potential parents, none of them are allowed to be marked + // @Required. + @Test + public void failToCompileWithMultipleRequiredParents() { + ASSERT.about(javaSources()) + .that(Arrays.asList( + JavaFileObjects.forResource("some/test/EmbeddedClassParent.java"), + JavaFileObjects.forResource("some/test/EmbeddedClassMultipleRequiredParents.java") + )) + .processedWith(new RealmProcessor()) + .failsToCompile() + .withErrorContaining("@Required cannot be used on @LinkingObjects field if multiple @LinkingParents are defined"); + } +} diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/RealmDefaultModuleMediator.java b/realm/realm-annotations-processor/src/test/resources/io/realm/RealmDefaultModuleMediator.java index 9c5b47c9da..082303afba 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/RealmDefaultModuleMediator.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/RealmDefaultModuleMediator.java @@ -206,4 +206,25 @@ public E createDetachedCopy(E realmObject, int maxDepth, throw getMissingProxyClassException(clazz); } -} + @Override + public boolean isEmbedded(Class clazz) { + if (clazz.equals(some.test.AllTypes.class)) { + return false; + } + throw getMissingProxyClassException(clazz); + } + + @Override + public void updateEmbeddedObject(Realm realm, E unmanagedObject, E managedObject, Map cache, Set flags) { + // This cast is correct because obj is either + // generated by RealmProxy or the original type extending directly from RealmObject + @SuppressWarnings("unchecked") Class clazz = (Class) managedObject.getClass().getSuperclass(); + + if (clazz.equals(some.test.AllTypes.class)) { + throw getNotEmbeddedClassException("some.test.AllTypes"); + } else { + throw getMissingProxyClassException(clazz); + } + } + +} \ No newline at end of file diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_AllTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_AllTypesRealmProxy.java index 2a61aac1bf..918f1b8bb8 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_AllTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_AllTypesRealmProxy.java @@ -411,6 +411,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { @Override public void realmSet$columnObject(some.test.AllTypes value) { + Realm realm = (Realm) proxyState.getRealm$realm(); if (proxyState.isUnderConstruction()) { if (!proxyState.getAcceptDefaultValue$realm()) { return; @@ -419,7 +420,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { return; } if (value != null && !RealmObject.isManaged(value)) { - value = ((Realm) proxyState.getRealm$realm()).copyToRealm(value); + value = realm.copyToRealm(value); } final Row row = proxyState.getRow$realm(); if (value == null) { @@ -982,7 +983,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } private static OsObjectSchemaInfo createExpectedObjectSchemaInfo() { - OsObjectSchemaInfo.Builder builder = new OsObjectSchemaInfo.Builder("AllTypes", 24, 1); + OsObjectSchemaInfo.Builder builder = new OsObjectSchemaInfo.Builder("AllTypes", false, 24, 1); builder.addPersistedProperty("columnString", RealmFieldType.STRING, Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); builder.addPersistedProperty("columnLong", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); builder.addPersistedProperty("columnFloat", RealmFieldType.FLOAT, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); @@ -1368,7 +1369,7 @@ public static some.test.AllTypes createUsingJsonStream(Realm realm, JsonReader r return realm.copyToRealm(obj); } - private static some_test_AllTypesRealmProxy newProxyInstance(BaseRealm realm, Row row) { + static some_test_AllTypesRealmProxy newProxyInstance(BaseRealm realm, Row row) { // Ignore default values to avoid creating unexpected objects from RealmModel/RealmList fields final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get(); objectContext.set(realm, row, realm.getSchema().getColumnInfo(some.test.AllTypes.class), false, Collections.emptyList()); @@ -1427,70 +1428,70 @@ public static some.test.AllTypes copy(Realm realm, AllTypesColumnInfo columnInfo return (some.test.AllTypes) cachedRealmObject; } - some_test_AllTypesRealmProxyInterface realmObjectSource = (some_test_AllTypesRealmProxyInterface) newObject; + some_test_AllTypesRealmProxyInterface unmanagedSource = (some_test_AllTypesRealmProxyInterface) newObject; Table table = realm.getTable(some.test.AllTypes.class); OsObjectBuilder builder = new OsObjectBuilder(table, flags); // Add all non-"object reference" fields - builder.addString(columnInfo.columnStringColKey, realmObjectSource.realmGet$columnString()); - builder.addInteger(columnInfo.columnLongColKey, realmObjectSource.realmGet$columnLong()); - builder.addFloat(columnInfo.columnFloatColKey, realmObjectSource.realmGet$columnFloat()); - builder.addDouble(columnInfo.columnDoubleColKey, realmObjectSource.realmGet$columnDouble()); - builder.addBoolean(columnInfo.columnBooleanColKey, realmObjectSource.realmGet$columnBoolean()); - builder.addDecimal128(columnInfo.columnDecimal128ColKey, realmObjectSource.realmGet$columnDecimal128()); - builder.addObjectId(columnInfo.columnObjectIdColKey, realmObjectSource.realmGet$columnObjectId()); - builder.addDate(columnInfo.columnDateColKey, realmObjectSource.realmGet$columnDate()); - builder.addByteArray(columnInfo.columnBinaryColKey, realmObjectSource.realmGet$columnBinary()); - builder.addMutableRealmInteger(columnInfo.columnMutableRealmIntegerColKey, realmObjectSource.realmGet$columnMutableRealmInteger()); - builder.addStringList(columnInfo.columnStringListColKey, realmObjectSource.realmGet$columnStringList()); - builder.addByteArrayList(columnInfo.columnBinaryListColKey, realmObjectSource.realmGet$columnBinaryList()); - builder.addBooleanList(columnInfo.columnBooleanListColKey, realmObjectSource.realmGet$columnBooleanList()); - builder.addLongList(columnInfo.columnLongListColKey, realmObjectSource.realmGet$columnLongList()); - builder.addIntegerList(columnInfo.columnIntegerListColKey, realmObjectSource.realmGet$columnIntegerList()); - builder.addShortList(columnInfo.columnShortListColKey, realmObjectSource.realmGet$columnShortList()); - builder.addByteList(columnInfo.columnByteListColKey, realmObjectSource.realmGet$columnByteList()); - builder.addDoubleList(columnInfo.columnDoubleListColKey, realmObjectSource.realmGet$columnDoubleList()); - builder.addFloatList(columnInfo.columnFloatListColKey, realmObjectSource.realmGet$columnFloatList()); - builder.addDateList(columnInfo.columnDateListColKey, realmObjectSource.realmGet$columnDateList()); - builder.addDecimal128List(columnInfo.columnDecimal128ListColKey, realmObjectSource.realmGet$columnDecimal128List()); - builder.addObjectIdList(columnInfo.columnObjectIdListColKey, realmObjectSource.realmGet$columnObjectIdList()); + builder.addString(columnInfo.columnStringColKey, unmanagedSource.realmGet$columnString()); + builder.addInteger(columnInfo.columnLongColKey, unmanagedSource.realmGet$columnLong()); + builder.addFloat(columnInfo.columnFloatColKey, unmanagedSource.realmGet$columnFloat()); + builder.addDouble(columnInfo.columnDoubleColKey, unmanagedSource.realmGet$columnDouble()); + builder.addBoolean(columnInfo.columnBooleanColKey, unmanagedSource.realmGet$columnBoolean()); + builder.addDecimal128(columnInfo.columnDecimal128ColKey, unmanagedSource.realmGet$columnDecimal128()); + builder.addObjectId(columnInfo.columnObjectIdColKey, unmanagedSource.realmGet$columnObjectId()); + builder.addDate(columnInfo.columnDateColKey, unmanagedSource.realmGet$columnDate()); + builder.addByteArray(columnInfo.columnBinaryColKey, unmanagedSource.realmGet$columnBinary()); + builder.addMutableRealmInteger(columnInfo.columnMutableRealmIntegerColKey, unmanagedSource.realmGet$columnMutableRealmInteger()); + builder.addStringList(columnInfo.columnStringListColKey, unmanagedSource.realmGet$columnStringList()); + builder.addByteArrayList(columnInfo.columnBinaryListColKey, unmanagedSource.realmGet$columnBinaryList()); + builder.addBooleanList(columnInfo.columnBooleanListColKey, unmanagedSource.realmGet$columnBooleanList()); + builder.addLongList(columnInfo.columnLongListColKey, unmanagedSource.realmGet$columnLongList()); + builder.addIntegerList(columnInfo.columnIntegerListColKey, unmanagedSource.realmGet$columnIntegerList()); + builder.addShortList(columnInfo.columnShortListColKey, unmanagedSource.realmGet$columnShortList()); + builder.addByteList(columnInfo.columnByteListColKey, unmanagedSource.realmGet$columnByteList()); + builder.addDoubleList(columnInfo.columnDoubleListColKey, unmanagedSource.realmGet$columnDoubleList()); + builder.addFloatList(columnInfo.columnFloatListColKey, unmanagedSource.realmGet$columnFloatList()); + builder.addDateList(columnInfo.columnDateListColKey, unmanagedSource.realmGet$columnDateList()); + builder.addDecimal128List(columnInfo.columnDecimal128ListColKey, unmanagedSource.realmGet$columnDecimal128List()); + builder.addObjectIdList(columnInfo.columnObjectIdListColKey, unmanagedSource.realmGet$columnObjectIdList()); // Create the underlying object and cache it before setting any object/objectlist references // This will allow us to break any circular dependencies by using the object cache. Row row = builder.createNewObject(); - io.realm.some_test_AllTypesRealmProxy realmObjectCopy = newProxyInstance(realm, row); - cache.put(newObject, realmObjectCopy); + io.realm.some_test_AllTypesRealmProxy managedCopy = newProxyInstance(realm, row); + cache.put(newObject, managedCopy); // Finally add all fields that reference other Realm Objects, either directly or through a list - some.test.AllTypes columnObjectObj = realmObjectSource.realmGet$columnObject(); + some.test.AllTypes columnObjectObj = unmanagedSource.realmGet$columnObject(); if (columnObjectObj == null) { - realmObjectCopy.realmSet$columnObject(null); + managedCopy.realmSet$columnObject(null); } else { some.test.AllTypes cachecolumnObject = (some.test.AllTypes) cache.get(columnObjectObj); if (cachecolumnObject != null) { - realmObjectCopy.realmSet$columnObject(cachecolumnObject); + managedCopy.realmSet$columnObject(cachecolumnObject); } else { - realmObjectCopy.realmSet$columnObject(some_test_AllTypesRealmProxy.copyOrUpdate(realm, (some_test_AllTypesRealmProxy.AllTypesColumnInfo) realm.getSchema().getColumnInfo(some.test.AllTypes.class), columnObjectObj, update, cache, flags)); + managedCopy.realmSet$columnObject(some_test_AllTypesRealmProxy.copyOrUpdate(realm, (some_test_AllTypesRealmProxy.AllTypesColumnInfo) realm.getSchema().getColumnInfo(some.test.AllTypes.class), columnObjectObj, update, cache, flags)); } } - RealmList columnRealmListList = realmObjectSource.realmGet$columnRealmList(); - if (columnRealmListList != null) { - RealmList columnRealmListRealmList = realmObjectCopy.realmGet$columnRealmList(); - columnRealmListRealmList.clear(); - for (int i = 0; i < columnRealmListList.size(); i++) { - some.test.AllTypes columnRealmListItem = columnRealmListList.get(i); - some.test.AllTypes cachecolumnRealmList = (some.test.AllTypes) cache.get(columnRealmListItem); + RealmList columnRealmListUnmanagedList = unmanagedSource.realmGet$columnRealmList(); + if (columnRealmListUnmanagedList != null) { + RealmList columnRealmListManagedList = managedCopy.realmGet$columnRealmList(); + columnRealmListManagedList.clear(); + for (int i = 0; i < columnRealmListUnmanagedList.size(); i++) { + some.test.AllTypes columnRealmListUnmanagedItem = columnRealmListUnmanagedList.get(i); + some.test.AllTypes cachecolumnRealmList = (some.test.AllTypes) cache.get(columnRealmListUnmanagedItem); if (cachecolumnRealmList != null) { - columnRealmListRealmList.add(cachecolumnRealmList); + columnRealmListManagedList.add(cachecolumnRealmList); } else { - columnRealmListRealmList.add(some_test_AllTypesRealmProxy.copyOrUpdate(realm, (some_test_AllTypesRealmProxy.AllTypesColumnInfo) realm.getSchema().getColumnInfo(some.test.AllTypes.class), columnRealmListItem, update, cache, flags)); + columnRealmListManagedList.add(some_test_AllTypesRealmProxy.copyOrUpdate(realm, (some_test_AllTypesRealmProxy.AllTypesColumnInfo) realm.getSchema().getColumnInfo(some.test.AllTypes.class), columnRealmListUnmanagedItem, update, cache, flags)); } } } - return realmObjectCopy; + return managedCopy; } public static long insert(Realm realm, some.test.AllTypes object, Map cache) { @@ -2572,11 +2573,11 @@ static some.test.AllTypes update(Realm realm, AllTypesColumnInfo columnInfo, som } } - RealmList columnRealmListList = realmObjectSource.realmGet$columnRealmList(); - if (columnRealmListList != null) { + RealmList columnRealmListUnmanagedList = realmObjectSource.realmGet$columnRealmList(); + if (columnRealmListUnmanagedList != null) { RealmList columnRealmListManagedCopy = new RealmList(); - for (int i = 0; i < columnRealmListList.size(); i++) { - some.test.AllTypes columnRealmListItem = columnRealmListList.get(i); + for (int i = 0; i < columnRealmListUnmanagedList.size(); i++) { + some.test.AllTypes columnRealmListItem = columnRealmListUnmanagedList.get(i); some.test.AllTypes cachecolumnRealmList = (some.test.AllTypes) cache.get(columnRealmListItem); if (cachecolumnRealmList != null) { columnRealmListManagedCopy.add(cachecolumnRealmList); @@ -2601,7 +2602,7 @@ static some.test.AllTypes update(Realm realm, AllTypesColumnInfo columnInfo, som builder.addDecimal128List(columnInfo.columnDecimal128ListColKey, realmObjectSource.realmGet$columnDecimal128List()); builder.addObjectIdList(columnInfo.columnObjectIdListColKey, realmObjectSource.realmGet$columnObjectIdList()); - builder.updateExistingObject(); + builder.updateExistingTopLevelObject(); return realmObject; } diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_BooleansRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_BooleansRealmProxy.java index 6906c18754..719b35e008 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_BooleansRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_BooleansRealmProxy.java @@ -185,7 +185,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } private static OsObjectSchemaInfo createExpectedObjectSchemaInfo() { - OsObjectSchemaInfo.Builder builder = new OsObjectSchemaInfo.Builder("Booleans", 4, 0); + OsObjectSchemaInfo.Builder builder = new OsObjectSchemaInfo.Builder("Booleans", false, 4, 0); builder.addPersistedProperty("done", RealmFieldType.BOOLEAN, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); builder.addPersistedProperty("isReady", RealmFieldType.BOOLEAN, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); builder.addPersistedProperty("mCompleted", RealmFieldType.BOOLEAN, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); @@ -293,7 +293,7 @@ public static some.test.Booleans createUsingJsonStream(Realm realm, JsonReader r return realm.copyToRealm(obj); } - private static some_test_BooleansRealmProxy newProxyInstance(BaseRealm realm, Row row) { + static some_test_BooleansRealmProxy newProxyInstance(BaseRealm realm, Row row) { // Ignore default values to avoid creating unexpected objects from RealmModel/RealmList fields final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get(); objectContext.set(realm, row, realm.getSchema().getColumnInfo(some.test.Booleans.class), false, Collections.emptyList()); @@ -327,24 +327,24 @@ public static some.test.Booleans copy(Realm realm, BooleansColumnInfo columnInfo return (some.test.Booleans) cachedRealmObject; } - some_test_BooleansRealmProxyInterface realmObjectSource = (some_test_BooleansRealmProxyInterface) newObject; + some_test_BooleansRealmProxyInterface unmanagedSource = (some_test_BooleansRealmProxyInterface) newObject; Table table = realm.getTable(some.test.Booleans.class); OsObjectBuilder builder = new OsObjectBuilder(table, flags); // Add all non-"object reference" fields - builder.addBoolean(columnInfo.doneColKey, realmObjectSource.realmGet$done()); - builder.addBoolean(columnInfo.isReadyColKey, realmObjectSource.realmGet$isReady()); - builder.addBoolean(columnInfo.mCompletedColKey, realmObjectSource.realmGet$mCompleted()); - builder.addBoolean(columnInfo.anotherBooleanColKey, realmObjectSource.realmGet$anotherBoolean()); + builder.addBoolean(columnInfo.doneColKey, unmanagedSource.realmGet$done()); + builder.addBoolean(columnInfo.isReadyColKey, unmanagedSource.realmGet$isReady()); + builder.addBoolean(columnInfo.mCompletedColKey, unmanagedSource.realmGet$mCompleted()); + builder.addBoolean(columnInfo.anotherBooleanColKey, unmanagedSource.realmGet$anotherBoolean()); // Create the underlying object and cache it before setting any object/objectlist references // This will allow us to break any circular dependencies by using the object cache. Row row = builder.createNewObject(); - io.realm.some_test_BooleansRealmProxy realmObjectCopy = newProxyInstance(realm, row); - cache.put(newObject, realmObjectCopy); + io.realm.some_test_BooleansRealmProxy managedCopy = newProxyInstance(realm, row); + cache.put(newObject, managedCopy); - return realmObjectCopy; + return managedCopy; } public static long insert(Realm realm, some.test.Booleans object, Map cache) { diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_EmbeddedClassSimpleParentRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_EmbeddedClassSimpleParentRealmProxy.java new file mode 100644 index 0000000000..44e2dc1f99 --- /dev/null +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_EmbeddedClassSimpleParentRealmProxy.java @@ -0,0 +1,867 @@ +package io.realm; + + +import android.annotation.TargetApi; +import android.os.Build; +import android.util.JsonReader; +import android.util.JsonToken; +import io.realm.ImportFlag; +import io.realm.ProxyUtils; +import io.realm.exceptions.RealmMigrationNeededException; +import io.realm.internal.ColumnInfo; +import io.realm.internal.OsList; +import io.realm.internal.OsObject; +import io.realm.internal.OsObjectSchemaInfo; +import io.realm.internal.OsSchemaInfo; +import io.realm.internal.Property; +import io.realm.internal.RealmObjectProxy; +import io.realm.internal.Row; +import io.realm.internal.Table; +import io.realm.internal.android.JsonUtils; +import io.realm.internal.objectstore.OsObjectBuilder; +import io.realm.log.RealmLog; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +@SuppressWarnings("all") +public class some_test_EmbeddedClassSimpleParentRealmProxy extends some.test.EmbeddedClassSimpleParent + implements RealmObjectProxy, some_test_EmbeddedClassSimpleParentRealmProxyInterface { + + static final class EmbeddedClassSimpleParentColumnInfo extends ColumnInfo { + long idColKey; + long childColKey; + long childrenColKey; + + EmbeddedClassSimpleParentColumnInfo(OsSchemaInfo schemaInfo) { + super(3); + OsObjectSchemaInfo objectSchemaInfo = schemaInfo.getObjectSchemaInfo("EmbeddedClassSimpleParent"); + this.idColKey = addColumnDetails("id", "id", objectSchemaInfo); + this.childColKey = addColumnDetails("child", "child", objectSchemaInfo); + this.childrenColKey = addColumnDetails("children", "children", objectSchemaInfo); + } + + EmbeddedClassSimpleParentColumnInfo(ColumnInfo src, boolean mutable) { + super(src, mutable); + copy(src, this); + } + + @Override + protected final ColumnInfo copy(boolean mutable) { + return new EmbeddedClassSimpleParentColumnInfo(this, mutable); + } + + @Override + protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { + final EmbeddedClassSimpleParentColumnInfo src = (EmbeddedClassSimpleParentColumnInfo) rawSrc; + final EmbeddedClassSimpleParentColumnInfo dst = (EmbeddedClassSimpleParentColumnInfo) rawDst; + dst.idColKey = src.idColKey; + dst.childColKey = src.childColKey; + dst.childrenColKey = src.childrenColKey; + } + } + + private static final OsObjectSchemaInfo expectedObjectSchemaInfo = createExpectedObjectSchemaInfo(); + + private EmbeddedClassSimpleParentColumnInfo columnInfo; + private ProxyState proxyState; + private RealmList childrenRealmList; + + some_test_EmbeddedClassSimpleParentRealmProxy() { + proxyState.setConstructionFinished(); + } + + @Override + public void realm$injectObjectContext() { + if (this.proxyState != null) { + return; + } + final BaseRealm.RealmObjectContext context = BaseRealm.objectContext.get(); + this.columnInfo = (EmbeddedClassSimpleParentColumnInfo) context.getColumnInfo(); + this.proxyState = new ProxyState(this); + proxyState.setRealm$realm(context.getRealm()); + proxyState.setRow$realm(context.getRow()); + proxyState.setAcceptDefaultValue$realm(context.getAcceptDefaultValue()); + proxyState.setExcludeFields$realm(context.getExcludeFields()); + } + + @Override + @SuppressWarnings("cast") + public String realmGet$id() { + proxyState.getRealm$realm().checkIfValid(); + return (java.lang.String) proxyState.getRow$realm().getString(columnInfo.idColKey); + } + + @Override + public void realmSet$id(String value) { + if (proxyState.isUnderConstruction()) { + // default value of the primary key is always ignored. + return; + } + + proxyState.getRealm$realm().checkIfValid(); + throw new io.realm.exceptions.RealmException("Primary key field 'id' cannot be changed after object was created."); + } + + @Override + public some.test.EmbeddedClass realmGet$child() { + proxyState.getRealm$realm().checkIfValid(); + if (proxyState.getRow$realm().isNullLink(columnInfo.childColKey)) { + return null; + } + return proxyState.getRealm$realm().get(some.test.EmbeddedClass.class, proxyState.getRow$realm().getLink(columnInfo.childColKey), false, Collections.emptyList()); + } + + @Override + public void realmSet$child(some.test.EmbeddedClass value) { + Realm realm = (Realm) proxyState.getRealm$realm(); + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + if (proxyState.getExcludeFields$realm().contains("child")) { + return; + } + if (value != null && !RealmObject.isManaged(value)) { + some.test.EmbeddedClass proxyObject = realm.createEmbeddedObject(some.test.EmbeddedClass.class, this, "child"); + some_test_EmbeddedClassRealmProxy.updateEmbeddedObject(realm, value, proxyObject, new HashMap(), Collections.EMPTY_SET); + value = proxyObject; + } + final Row row = proxyState.getRow$realm(); + if (value == null) { + // Table#nullifyLink() does not support default value. Just using Row. + row.nullifyLink(columnInfo.childColKey); + return; + } + proxyState.checkValidObject(value); + row.getTable().setLink(columnInfo.childColKey, row.getObjectKey(), ((RealmObjectProxy) value).realmGet$proxyState().getRow$realm().getObjectKey(), true); + return; + } + + proxyState.getRealm$realm().checkIfValid(); + if (value == null) { + proxyState.getRow$realm().nullifyLink(columnInfo.childColKey); + return; + } + if (RealmObject.isManaged(value)) { + proxyState.checkValidObject(value); + } + some.test.EmbeddedClass proxyObject = realm.createEmbeddedObject(some.test.EmbeddedClass.class, this, "child"); + some_test_EmbeddedClassRealmProxy.updateEmbeddedObject(realm, value, proxyObject, new HashMap(), Collections.EMPTY_SET); + } + + @Override + public RealmList realmGet$children() { + proxyState.getRealm$realm().checkIfValid(); + // use the cached value if available + if (childrenRealmList != null) { + return childrenRealmList; + } else { + OsList osList = proxyState.getRow$realm().getModelList(columnInfo.childrenColKey); + childrenRealmList = new RealmList(some.test.EmbeddedClass.class, osList, proxyState.getRealm$realm()); + return childrenRealmList; + } + } + + @Override + public void realmSet$children(RealmList value) { + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + if (proxyState.getExcludeFields$realm().contains("children")) { + return; + } + // if the list contains unmanaged RealmObjects, convert them to managed. + if (value != null && !value.isManaged()) { + final Realm realm = (Realm) proxyState.getRealm$realm(); + final RealmList original = value; + value = new RealmList(); + for (some.test.EmbeddedClass item : original) { + if (item == null || RealmObject.isManaged(item)) { + value.add(item); + } else { + value.add(realm.copyToRealm(item)); + } + } + } + } + + proxyState.getRealm$realm().checkIfValid(); + OsList osList = proxyState.getRow$realm().getModelList(columnInfo.childrenColKey); + // For lists of equal lengths, we need to set each element directly as clearing the receiver list can be wrong if the input and target list are the same. + if (value != null && value.size() == osList.size()) { + int objects = value.size(); + for (int i = 0; i < objects; i++) { + some.test.EmbeddedClass linkedObject = value.get(i); + proxyState.checkValidObject(linkedObject); + osList.setRow(i, ((RealmObjectProxy) linkedObject).realmGet$proxyState().getRow$realm().getObjectKey()); + } + } else { + osList.removeAll(); + if (value == null) { + return; + } + int objects = value.size(); + for (int i = 0; i < objects; i++) { + some.test.EmbeddedClass linkedObject = value.get(i); + proxyState.checkValidObject(linkedObject); + osList.addRow(((RealmObjectProxy) linkedObject).realmGet$proxyState().getRow$realm().getObjectKey()); + } + } + } + + private static OsObjectSchemaInfo createExpectedObjectSchemaInfo() { + OsObjectSchemaInfo.Builder builder = new OsObjectSchemaInfo.Builder("EmbeddedClassSimpleParent", false, 3, 0); + builder.addPersistedProperty("id", RealmFieldType.STRING, Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); + builder.addPersistedLinkProperty("child", RealmFieldType.OBJECT, "EmbeddedClass"); + builder.addPersistedLinkProperty("children", RealmFieldType.LIST, "EmbeddedClass"); + return builder.build(); + } + + public static OsObjectSchemaInfo getExpectedObjectSchemaInfo() { + return expectedObjectSchemaInfo; + } + + public static EmbeddedClassSimpleParentColumnInfo createColumnInfo(OsSchemaInfo schemaInfo) { + return new EmbeddedClassSimpleParentColumnInfo(schemaInfo); + } + + public static String getSimpleClassName() { + return "EmbeddedClassSimpleParent"; + } + + public static final class ClassNameHelper { + public static final String INTERNAL_CLASS_NAME = "EmbeddedClassSimpleParent"; + } + + @SuppressWarnings("cast") + public static some.test.EmbeddedClassSimpleParent createOrUpdateUsingJsonObject(Realm realm, JSONObject json, boolean update) + throws JSONException { + final List excludeFields = new ArrayList(2); + some.test.EmbeddedClassSimpleParent obj = null; + if (update) { + Table table = realm.getTable(some.test.EmbeddedClassSimpleParent.class); + EmbeddedClassSimpleParentColumnInfo columnInfo = (EmbeddedClassSimpleParentColumnInfo) realm.getSchema().getColumnInfo(some.test.EmbeddedClassSimpleParent.class); + long pkColumnKey = columnInfo.idColKey; + long objKey = Table.NO_MATCH; + if (json.isNull("id")) { + objKey = table.findFirstNull(pkColumnKey); + } else { + objKey = table.findFirstString(pkColumnKey, json.getString("id")); + } + if (objKey != Table.NO_MATCH) { + final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get(); + try { + objectContext.set(realm, table.getUncheckedRow(objKey), realm.getSchema().getColumnInfo(some.test.EmbeddedClassSimpleParent.class), false, Collections. emptyList()); + obj = new io.realm.some_test_EmbeddedClassSimpleParentRealmProxy(); + } finally { + objectContext.clear(); + } + } + } + if (obj == null) { + if (json.has("child")) { + excludeFields.add("child"); + } + if (json.has("children")) { + excludeFields.add("children"); + } + if (json.has("id")) { + if (json.isNull("id")) { + obj = (io.realm.some_test_EmbeddedClassSimpleParentRealmProxy) realm.createObjectInternal(some.test.EmbeddedClassSimpleParent.class, null, true, excludeFields); + } else { + obj = (io.realm.some_test_EmbeddedClassSimpleParentRealmProxy) realm.createObjectInternal(some.test.EmbeddedClassSimpleParent.class, json.getString("id"), true, excludeFields); + } + } else { + throw new IllegalArgumentException("JSON object doesn't have the primary key field 'id'."); + } + } + + final some_test_EmbeddedClassSimpleParentRealmProxyInterface objProxy = (some_test_EmbeddedClassSimpleParentRealmProxyInterface) obj; + if (json.has("child")) { + if (json.isNull("child")) { + objProxy.realmSet$child(null); + } else { + some.test.EmbeddedClass childObj = some_test_EmbeddedClassRealmProxy.createOrUpdateUsingJsonObject(realm, json.getJSONObject("child"), update); + objProxy.realmSet$child(childObj); + } + } + if (json.has("children")) { + if (json.isNull("children")) { + objProxy.realmSet$children(null); + } else { + objProxy.realmGet$children().clear(); + JSONArray array = json.getJSONArray("children"); + for (int i = 0; i < array.length(); i++) { + some.test.EmbeddedClass item = some_test_EmbeddedClassRealmProxy.createOrUpdateUsingJsonObject(realm, array.getJSONObject(i), update); + objProxy.realmGet$children().add(item); + } + } + } + return obj; + } + + @SuppressWarnings("cast") + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + public static some.test.EmbeddedClassSimpleParent createUsingJsonStream(Realm realm, JsonReader reader) + throws IOException { + boolean jsonHasPrimaryKey = false; + final some.test.EmbeddedClassSimpleParent obj = new some.test.EmbeddedClassSimpleParent(); + final some_test_EmbeddedClassSimpleParentRealmProxyInterface objProxy = (some_test_EmbeddedClassSimpleParentRealmProxyInterface) obj; + reader.beginObject(); + while (reader.hasNext()) { + String name = reader.nextName(); + if (false) { + } else if (name.equals("id")) { + if (reader.peek() != JsonToken.NULL) { + objProxy.realmSet$id((String) reader.nextString()); + } else { + reader.skipValue(); + objProxy.realmSet$id(null); + } + jsonHasPrimaryKey = true; + } else if (name.equals("child")) { + if (reader.peek() == JsonToken.NULL) { + reader.skipValue(); + objProxy.realmSet$child(null); + } else { + some.test.EmbeddedClass childObj = some_test_EmbeddedClassRealmProxy.createUsingJsonStream(realm, reader); + objProxy.realmSet$child(childObj); + } + } else if (name.equals("children")) { + if (reader.peek() == JsonToken.NULL) { + reader.skipValue(); + objProxy.realmSet$children(null); + } else { + objProxy.realmSet$children(new RealmList()); + reader.beginArray(); + while (reader.hasNext()) { + some.test.EmbeddedClass item = some_test_EmbeddedClassRealmProxy.createUsingJsonStream(realm, reader); + objProxy.realmGet$children().add(item); + } + reader.endArray(); + } + } else { + reader.skipValue(); + } + } + reader.endObject(); + if (!jsonHasPrimaryKey) { + throw new IllegalArgumentException("JSON object doesn't have the primary key field 'id'."); + } + return realm.copyToRealm(obj); + } + + static some_test_EmbeddedClassSimpleParentRealmProxy newProxyInstance(BaseRealm realm, Row row) { + // Ignore default values to avoid creating unexpected objects from RealmModel/RealmList fields + final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get(); + objectContext.set(realm, row, realm.getSchema().getColumnInfo(some.test.EmbeddedClassSimpleParent.class), false, Collections.emptyList()); + io.realm.some_test_EmbeddedClassSimpleParentRealmProxy obj = new io.realm.some_test_EmbeddedClassSimpleParentRealmProxy(); + objectContext.clear(); + return obj; + } + + public static some.test.EmbeddedClassSimpleParent copyOrUpdate(Realm realm, EmbeddedClassSimpleParentColumnInfo columnInfo, some.test.EmbeddedClassSimpleParent object, boolean update, Map cache, Set flags) { + if (object instanceof RealmObjectProxy && !RealmObject.isFrozen(object) && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null) { + final BaseRealm otherRealm = ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm(); + if (otherRealm.threadId != realm.threadId) { + throw new IllegalArgumentException("Objects which belong to Realm instances in other threads cannot be copied into this Realm instance."); + } + if (otherRealm.getPath().equals(realm.getPath())) { + return object; + } + } + final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get(); + RealmObjectProxy cachedRealmObject = cache.get(object); + if (cachedRealmObject != null) { + return (some.test.EmbeddedClassSimpleParent) cachedRealmObject; + } + + some.test.EmbeddedClassSimpleParent realmObject = null; + boolean canUpdate = update; + if (canUpdate) { + Table table = realm.getTable(some.test.EmbeddedClassSimpleParent.class); + long pkColumnKey = columnInfo.idColKey; + String value = ((some_test_EmbeddedClassSimpleParentRealmProxyInterface) object).realmGet$id(); + long objKey = Table.NO_MATCH; + if (value == null) { + objKey = table.findFirstNull(pkColumnKey); + } else { + objKey = table.findFirstString(pkColumnKey, value); + } + if (objKey == Table.NO_MATCH) { + canUpdate = false; + } else { + try { + objectContext.set(realm, table.getUncheckedRow(objKey), columnInfo, false, Collections. emptyList()); + realmObject = new io.realm.some_test_EmbeddedClassSimpleParentRealmProxy(); + cache.put(object, (RealmObjectProxy) realmObject); + } finally { + objectContext.clear(); + } + } + } + + return (canUpdate) ? update(realm, columnInfo, realmObject, object, cache, flags) : copy(realm, columnInfo, object, update, cache, flags); + } + + public static some.test.EmbeddedClassSimpleParent copy(Realm realm, EmbeddedClassSimpleParentColumnInfo columnInfo, some.test.EmbeddedClassSimpleParent newObject, boolean update, Map cache, Set flags) { + RealmObjectProxy cachedRealmObject = cache.get(newObject); + if (cachedRealmObject != null) { + return (some.test.EmbeddedClassSimpleParent) cachedRealmObject; + } + + some_test_EmbeddedClassSimpleParentRealmProxyInterface unmanagedSource = (some_test_EmbeddedClassSimpleParentRealmProxyInterface) newObject; + + Table table = realm.getTable(some.test.EmbeddedClassSimpleParent.class); + OsObjectBuilder builder = new OsObjectBuilder(table, flags); + + // Add all non-"object reference" fields + builder.addString(columnInfo.idColKey, unmanagedSource.realmGet$id()); + + // Create the underlying object and cache it before setting any object/objectlist references + // This will allow us to break any circular dependencies by using the object cache. + Row row = builder.createNewObject(); + io.realm.some_test_EmbeddedClassSimpleParentRealmProxy managedCopy = newProxyInstance(realm, row); + cache.put(newObject, managedCopy); + + // Finally add all fields that reference other Realm Objects, either directly or through a list + some.test.EmbeddedClass childObj = unmanagedSource.realmGet$child(); + if (childObj == null) { + managedCopy.realmSet$child(null); + } else { + some.test.EmbeddedClass cachechild = (some.test.EmbeddedClass) cache.get(childObj); + if (cachechild != null) { + throw new IllegalArgumentException("Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: cachechild.toString()"); + } else { + long objKey = ((RealmObjectProxy) managedCopy).realmGet$proxyState().getRow$realm().createEmbeddedObject(columnInfo.childColKey); + Row linkedObjectRow = realm.getTable(some.test.EmbeddedClass.class).getUncheckedRow(objKey); + some.test.EmbeddedClass linkedObject = some_test_EmbeddedClassRealmProxy.newProxyInstance(realm, linkedObjectRow); + cache.put(childObj, (RealmObjectProxy) linkedObject); + some_test_EmbeddedClassRealmProxy.updateEmbeddedObject(realm, childObj, linkedObject, cache, flags); + } + } + + RealmList childrenUnmanagedList = unmanagedSource.realmGet$children(); + if (childrenUnmanagedList != null) { + RealmList childrenManagedList = managedCopy.realmGet$children(); + childrenManagedList.clear(); + for (int i = 0; i < childrenUnmanagedList.size(); i++) { + some.test.EmbeddedClass childrenUnmanagedItem = childrenUnmanagedList.get(i); + some.test.EmbeddedClass cachechildren = (some.test.EmbeddedClass) cache.get(childrenUnmanagedItem); + if (cachechildren != null) { + throw new IllegalArgumentException("Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: cachechildren.toString()"); + } else { + long objKey = childrenManagedList.getOsList().createAndAddEmbeddedObject(); + Row linkedObjectRow = realm.getTable(some.test.EmbeddedClass.class).getUncheckedRow(objKey); + some.test.EmbeddedClass linkedObject = some_test_EmbeddedClassRealmProxy.newProxyInstance(realm, linkedObjectRow); + cache.put(childrenUnmanagedItem, (RealmObjectProxy) linkedObject); + some_test_EmbeddedClassRealmProxy.updateEmbeddedObject(realm, childrenUnmanagedItem, linkedObject, new HashMap(), Collections.EMPTY_SET); + } + } + } + + return managedCopy; + } + + public static long insert(Realm realm, some.test.EmbeddedClassSimpleParent object, Map cache) { + if (object instanceof RealmObjectProxy && !RealmObject.isFrozen(object) && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { + return ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getObjectKey(); + } + Table table = realm.getTable(some.test.EmbeddedClassSimpleParent.class); + long tableNativePtr = table.getNativePtr(); + EmbeddedClassSimpleParentColumnInfo columnInfo = (EmbeddedClassSimpleParentColumnInfo) realm.getSchema().getColumnInfo(some.test.EmbeddedClassSimpleParent.class); + long pkColumnKey = columnInfo.idColKey; + String primaryKeyValue = ((some_test_EmbeddedClassSimpleParentRealmProxyInterface) object).realmGet$id(); + long objKey = Table.NO_MATCH; + if (primaryKeyValue == null) { + objKey = Table.nativeFindFirstNull(tableNativePtr, pkColumnKey); + } else { + objKey = Table.nativeFindFirstString(tableNativePtr, pkColumnKey, primaryKeyValue); + } + if (objKey == Table.NO_MATCH) { + objKey = OsObject.createRowWithPrimaryKey(table, pkColumnKey, primaryKeyValue); + } else { + Table.throwDuplicatePrimaryKeyException(primaryKeyValue); + } + cache.put(object, objKey); + + some.test.EmbeddedClass childObj = ((some_test_EmbeddedClassSimpleParentRealmProxyInterface) object).realmGet$child(); + if (childObj != null) { + Long cachechild = cache.get(childObj); + if (cachechild != null) { + throw new IllegalArgumentException("Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: " + cachechild.toString()); + } else { + cachechild = some_test_EmbeddedClassRealmProxy.insert(realm, table, columnInfo.childColKey, objKey, childObj, cache); + } + } + + RealmList childrenList = ((some_test_EmbeddedClassSimpleParentRealmProxyInterface) object).realmGet$children(); + if (childrenList != null) { + OsList childrenOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.childrenColKey); + for (some.test.EmbeddedClass childrenItem : childrenList) { + Long cacheItemIndexchildren = cache.get(childrenItem); + if (cacheItemIndexchildren != null) { + throw new IllegalArgumentException("Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: " + cacheItemIndexchildren.toString()); + } else { + cacheItemIndexchildren = some_test_EmbeddedClassRealmProxy.insert(realm, table, columnInfo.childrenColKey, objKey, childrenItem, cache); + } + } + } + return objKey; + } + + public static void insert(Realm realm, Iterator objects, Map cache) { + Table table = realm.getTable(some.test.EmbeddedClassSimpleParent.class); + long tableNativePtr = table.getNativePtr(); + EmbeddedClassSimpleParentColumnInfo columnInfo = (EmbeddedClassSimpleParentColumnInfo) realm.getSchema().getColumnInfo(some.test.EmbeddedClassSimpleParent.class); + long pkColumnKey = columnInfo.idColKey; + some.test.EmbeddedClassSimpleParent object = null; + while (objects.hasNext()) { + object = (some.test.EmbeddedClassSimpleParent) objects.next(); + if (cache.containsKey(object)) { + continue; + } + if (object instanceof RealmObjectProxy && !RealmObject.isFrozen(object) && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { + cache.put(object, ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getObjectKey()); + continue; + } + String primaryKeyValue = ((some_test_EmbeddedClassSimpleParentRealmProxyInterface) object).realmGet$id(); + long objKey = Table.NO_MATCH; + if (primaryKeyValue == null) { + objKey = Table.nativeFindFirstNull(tableNativePtr, pkColumnKey); + } else { + objKey = Table.nativeFindFirstString(tableNativePtr, pkColumnKey, primaryKeyValue); + } + if (objKey == Table.NO_MATCH) { + objKey = OsObject.createRowWithPrimaryKey(table, pkColumnKey, primaryKeyValue); + } else { + Table.throwDuplicatePrimaryKeyException(primaryKeyValue); + } + cache.put(object, objKey); + + some.test.EmbeddedClass childObj = ((some_test_EmbeddedClassSimpleParentRealmProxyInterface) object).realmGet$child(); + if (childObj != null) { + Long cachechild = cache.get(childObj); + if (cachechild != null) { + throw new IllegalArgumentException("Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: " + cachechild.toString()); + } else { + cachechild = some_test_EmbeddedClassRealmProxy.insert(realm, table, columnInfo.childColKey, objKey, childObj, cache); + } + } + + RealmList childrenList = ((some_test_EmbeddedClassSimpleParentRealmProxyInterface) object).realmGet$children(); + if (childrenList != null) { + OsList childrenOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.childrenColKey); + for (some.test.EmbeddedClass childrenItem : childrenList) { + Long cacheItemIndexchildren = cache.get(childrenItem); + if (cacheItemIndexchildren != null) { + throw new IllegalArgumentException("Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: " + cacheItemIndexchildren.toString()); + } else { + cacheItemIndexchildren = some_test_EmbeddedClassRealmProxy.insert(realm, table, columnInfo.childrenColKey, objKey, childrenItem, cache); + } + } + } + } + } + + public static long insertOrUpdate(Realm realm, some.test.EmbeddedClassSimpleParent object, Map cache) { + if (object instanceof RealmObjectProxy && !RealmObject.isFrozen(object) && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { + return ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getObjectKey(); + } + Table table = realm.getTable(some.test.EmbeddedClassSimpleParent.class); + long tableNativePtr = table.getNativePtr(); + EmbeddedClassSimpleParentColumnInfo columnInfo = (EmbeddedClassSimpleParentColumnInfo) realm.getSchema().getColumnInfo(some.test.EmbeddedClassSimpleParent.class); + long pkColumnKey = columnInfo.idColKey; + String primaryKeyValue = ((some_test_EmbeddedClassSimpleParentRealmProxyInterface) object).realmGet$id(); + long objKey = Table.NO_MATCH; + if (primaryKeyValue == null) { + objKey = Table.nativeFindFirstNull(tableNativePtr, pkColumnKey); + } else { + objKey = Table.nativeFindFirstString(tableNativePtr, pkColumnKey, primaryKeyValue); + } + if (objKey == Table.NO_MATCH) { + objKey = OsObject.createRowWithPrimaryKey(table, pkColumnKey, primaryKeyValue); + } + cache.put(object, objKey); + + some.test.EmbeddedClass childObj = ((some_test_EmbeddedClassSimpleParentRealmProxyInterface) object).realmGet$child(); + if (childObj != null) { + Long cachechild = cache.get(childObj); + if (cachechild != null) { + throw new IllegalArgumentException("Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: " + cachechild.toString()); + } else { + cachechild = some_test_EmbeddedClassRealmProxy.insertOrUpdate(realm, table, columnInfo.childColKey, objKey, childObj, cache); + } + } else { + Table.nativeNullifyLink(tableNativePtr, columnInfo.childColKey, objKey); + } + + OsList childrenOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.childrenColKey); + RealmList childrenList = ((some_test_EmbeddedClassSimpleParentRealmProxyInterface) object).realmGet$children(); + if (childrenList != null && childrenList.size() == childrenOsList.size()) { + // For lists of equal lengths, we need to set each element directly as clearing the receiver list can be wrong if the input and target list are the same. + int objects = childrenList.size(); + for (int i = 0; i < objects; i++) { + some.test.EmbeddedClass childrenItem = childrenList.get(i); + Long cacheItemIndexchildren = cache.get(childrenItem); + if (cacheItemIndexchildren != null) { + throw new IllegalArgumentException("Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: " + cacheItemIndexchildren.toString()); + } else { + cacheItemIndexchildren = some_test_EmbeddedClassRealmProxy.insertOrUpdate(realm, table, columnInfo.childrenColKey, objKey, childrenItem, cache); + } + } + } else { + childrenOsList.removeAll(); + if (childrenList != null) { + for (some.test.EmbeddedClass childrenItem : childrenList) { + Long cacheItemIndexchildren = cache.get(childrenItem); + if (cacheItemIndexchildren != null) { + throw new IllegalArgumentException("Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: " + cacheItemIndexchildren.toString()); + } else { + cacheItemIndexchildren = some_test_EmbeddedClassRealmProxy.insertOrUpdate(realm, table, columnInfo.childrenColKey, objKey, childrenItem, cache); + } + } + } + } + + return objKey; + } + + public static void insertOrUpdate(Realm realm, Iterator objects, Map cache) { + Table table = realm.getTable(some.test.EmbeddedClassSimpleParent.class); + long tableNativePtr = table.getNativePtr(); + EmbeddedClassSimpleParentColumnInfo columnInfo = (EmbeddedClassSimpleParentColumnInfo) realm.getSchema().getColumnInfo(some.test.EmbeddedClassSimpleParent.class); + long pkColumnKey = columnInfo.idColKey; + some.test.EmbeddedClassSimpleParent object = null; + while (objects.hasNext()) { + object = (some.test.EmbeddedClassSimpleParent) objects.next(); + if (cache.containsKey(object)) { + continue; + } + if (object instanceof RealmObjectProxy && !RealmObject.isFrozen(object) && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { + cache.put(object, ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getObjectKey()); + continue; + } + String primaryKeyValue = ((some_test_EmbeddedClassSimpleParentRealmProxyInterface) object).realmGet$id(); + long objKey = Table.NO_MATCH; + if (primaryKeyValue == null) { + objKey = Table.nativeFindFirstNull(tableNativePtr, pkColumnKey); + } else { + objKey = Table.nativeFindFirstString(tableNativePtr, pkColumnKey, primaryKeyValue); + } + if (objKey == Table.NO_MATCH) { + objKey = OsObject.createRowWithPrimaryKey(table, pkColumnKey, primaryKeyValue); + } + cache.put(object, objKey); + + some.test.EmbeddedClass childObj = ((some_test_EmbeddedClassSimpleParentRealmProxyInterface) object).realmGet$child(); + if (childObj != null) { + Long cachechild = cache.get(childObj); + if (cachechild != null) { + throw new IllegalArgumentException("Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: " + cachechild.toString()); + } else { + cachechild = some_test_EmbeddedClassRealmProxy.insertOrUpdate(realm, table, columnInfo.childColKey, objKey, childObj, cache); + } + } else { + Table.nativeNullifyLink(tableNativePtr, columnInfo.childColKey, objKey); + } + + OsList childrenOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.childrenColKey); + RealmList childrenList = ((some_test_EmbeddedClassSimpleParentRealmProxyInterface) object).realmGet$children(); + if (childrenList != null && childrenList.size() == childrenOsList.size()) { + // For lists of equal lengths, we need to set each element directly as clearing the receiver list can be wrong if the input and target list are the same. + int objectCount = childrenList.size(); + for (int i = 0; i < objectCount; i++) { + some.test.EmbeddedClass childrenItem = childrenList.get(i); + Long cacheItemIndexchildren = cache.get(childrenItem); + if (cacheItemIndexchildren != null) { + throw new IllegalArgumentException("Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: " + cacheItemIndexchildren.toString()); + } else { + cacheItemIndexchildren = some_test_EmbeddedClassRealmProxy.insertOrUpdate(realm, table, columnInfo.childrenColKey, objKey, childrenItem, cache); + } + } + } else { + childrenOsList.removeAll(); + if (childrenList != null) { + for (some.test.EmbeddedClass childrenItem : childrenList) { + Long cacheItemIndexchildren = cache.get(childrenItem); + if (cacheItemIndexchildren != null) { + throw new IllegalArgumentException("Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: " + cacheItemIndexchildren.toString()); + } else { + cacheItemIndexchildren = some_test_EmbeddedClassRealmProxy.insertOrUpdate(realm, table, columnInfo.childrenColKey, objKey, childrenItem, cache); + } + } + } + } + + } + } + + public static some.test.EmbeddedClassSimpleParent createDetachedCopy(some.test.EmbeddedClassSimpleParent realmObject, int currentDepth, int maxDepth, Map> cache) { + if (currentDepth > maxDepth || realmObject == null) { + return null; + } + CacheData cachedObject = cache.get(realmObject); + some.test.EmbeddedClassSimpleParent unmanagedObject; + if (cachedObject == null) { + unmanagedObject = new some.test.EmbeddedClassSimpleParent(); + cache.put(realmObject, new RealmObjectProxy.CacheData(currentDepth, unmanagedObject)); + } else { + // Reuse cached object or recreate it because it was encountered at a lower depth. + if (currentDepth >= cachedObject.minDepth) { + return (some.test.EmbeddedClassSimpleParent) cachedObject.object; + } + unmanagedObject = (some.test.EmbeddedClassSimpleParent) cachedObject.object; + cachedObject.minDepth = currentDepth; + } + some_test_EmbeddedClassSimpleParentRealmProxyInterface unmanagedCopy = (some_test_EmbeddedClassSimpleParentRealmProxyInterface) unmanagedObject; + some_test_EmbeddedClassSimpleParentRealmProxyInterface realmSource = (some_test_EmbeddedClassSimpleParentRealmProxyInterface) realmObject; + unmanagedCopy.realmSet$id(realmSource.realmGet$id()); + + // Deep copy of child + unmanagedCopy.realmSet$child(some_test_EmbeddedClassRealmProxy.createDetachedCopy(realmSource.realmGet$child(), currentDepth + 1, maxDepth, cache)); + + // Deep copy of children + if (currentDepth == maxDepth) { + unmanagedCopy.realmSet$children(null); + } else { + RealmList managedchildrenList = realmSource.realmGet$children(); + RealmList unmanagedchildrenList = new RealmList(); + unmanagedCopy.realmSet$children(unmanagedchildrenList); + int nextDepth = currentDepth + 1; + int size = managedchildrenList.size(); + for (int i = 0; i < size; i++) { + some.test.EmbeddedClass item = some_test_EmbeddedClassRealmProxy.createDetachedCopy(managedchildrenList.get(i), nextDepth, maxDepth, cache); + unmanagedchildrenList.add(item); + } + } + + return unmanagedObject; + } + + static some.test.EmbeddedClassSimpleParent update(Realm realm, EmbeddedClassSimpleParentColumnInfo columnInfo, some.test.EmbeddedClassSimpleParent realmObject, some.test.EmbeddedClassSimpleParent newObject, Map cache, Set flags) { + some_test_EmbeddedClassSimpleParentRealmProxyInterface realmObjectTarget = (some_test_EmbeddedClassSimpleParentRealmProxyInterface) realmObject; + some_test_EmbeddedClassSimpleParentRealmProxyInterface realmObjectSource = (some_test_EmbeddedClassSimpleParentRealmProxyInterface) newObject; + Table table = realm.getTable(some.test.EmbeddedClassSimpleParent.class); + OsObjectBuilder builder = new OsObjectBuilder(table, flags); + builder.addString(columnInfo.idColKey, realmObjectSource.realmGet$id()); + + some.test.EmbeddedClass childObj = realmObjectSource.realmGet$child(); + if (childObj == null) { + builder.addNull(columnInfo.childColKey); + } else { + // Embedded objects are created directly instead of using the builder. + some.test.EmbeddedClass cachechild = (some.test.EmbeddedClass) cache.get(childObj); + if (cachechild != null) { + throw new IllegalArgumentException("Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: cachechild.toString()"); + } + + long objKey = ((RealmObjectProxy) realmObject).realmGet$proxyState().getRow$realm().createEmbeddedObject(columnInfo.childColKey); + Row row = realm.getTable(some.test.EmbeddedClass.class).getUncheckedRow(objKey); + some.test.EmbeddedClass proxyObject = some_test_EmbeddedClassRealmProxy.newProxyInstance(realm, row); + cache.put(childObj, (RealmObjectProxy) proxyObject); + some_test_EmbeddedClassRealmProxy.updateEmbeddedObject(realm, childObj, proxyObject, cache, flags); + } + + RealmList childrenUnmanagedList = realmObjectSource.realmGet$children(); + if (childrenUnmanagedList != null) { + RealmList childrenManagedCopy = new RealmList(); + for (int i = 0; i < childrenUnmanagedList.size(); i++) { + some.test.EmbeddedClass childrenUnmanagedItem = childrenUnmanagedList.get(i); + some.test.EmbeddedClass cachechildren = (some.test.EmbeddedClass) cache.get(childrenUnmanagedItem); + if (cachechildren != null) { + throw new IllegalArgumentException("Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: cachechildren.toString()"); + } else { + long objKey = realmObjectTarget.realmGet$children().getOsList().createAndAddEmbeddedObject(); + Row row = realm.getTable(some.test.EmbeddedClass.class).getUncheckedRow(objKey); + some.test.EmbeddedClass proxyObject = some_test_EmbeddedClassRealmProxy.newProxyInstance(realm, row); + cache.put(childrenUnmanagedItem, (RealmObjectProxy) proxyObject); + childrenManagedCopy.add(proxyObject); + some_test_EmbeddedClassRealmProxy.updateEmbeddedObject(realm, childrenUnmanagedItem, proxyObject, new HashMap(), Collections.EMPTY_SET); + } + } + builder.addObjectList(columnInfo.childrenColKey, childrenManagedCopy); + } else { + builder.addObjectList(columnInfo.childrenColKey, new RealmList()); + } + + builder.updateExistingTopLevelObject(); + return realmObject; + } + + @Override + @SuppressWarnings("ArrayToString") + public String toString() { + if (!RealmObject.isValid(this)) { + return "Invalid object"; + } + StringBuilder stringBuilder = new StringBuilder("EmbeddedClassSimpleParent = proxy["); + stringBuilder.append("{id:"); + stringBuilder.append(realmGet$id() != null ? realmGet$id() : "null"); + stringBuilder.append("}"); + stringBuilder.append(","); + stringBuilder.append("{child:"); + stringBuilder.append(realmGet$child() != null ? "EmbeddedClass" : "null"); + stringBuilder.append("}"); + stringBuilder.append(","); + stringBuilder.append("{children:"); + stringBuilder.append("RealmList[").append(realmGet$children().size()).append("]"); + stringBuilder.append("}"); + stringBuilder.append("]"); + return stringBuilder.toString(); + } + + @Override + public ProxyState realmGet$proxyState() { + return proxyState; + } + + @Override + public int hashCode() { + String realmName = proxyState.getRealm$realm().getPath(); + String tableName = proxyState.getRow$realm().getTable().getName(); + long objKey = proxyState.getRow$realm().getObjectKey(); + + int result = 17; + result = 31 * result + ((realmName != null) ? realmName.hashCode() : 0); + result = 31 * result + ((tableName != null) ? tableName.hashCode() : 0); + result = 31 * result + (int) (objKey ^ (objKey >>> 32)); + return result; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + some_test_EmbeddedClassSimpleParentRealmProxy aEmbeddedClassSimpleParent = (some_test_EmbeddedClassSimpleParentRealmProxy)o; + + BaseRealm realm = proxyState.getRealm$realm(); + BaseRealm otherRealm = aEmbeddedClassSimpleParent.proxyState.getRealm$realm(); + String path = realm.getPath(); + String otherPath = otherRealm.getPath(); + if (path != null ? !path.equals(otherPath) : otherPath != null) return false; + if (realm.isFrozen() != otherRealm.isFrozen()) return false; + if (!realm.sharedRealm.getVersionID().equals(otherRealm.sharedRealm.getVersionID())) { + return false; + } + + String tableName = proxyState.getRow$realm().getTable().getName(); + String otherTableName = aEmbeddedClassSimpleParent.proxyState.getRow$realm().getTable().getName(); + if (tableName != null ? !tableName.equals(otherTableName) : otherTableName != null) return false; + + if (proxyState.getRow$realm().getObjectKey() != aEmbeddedClassSimpleParent.proxyState.getRow$realm().getObjectKey()) return false; + + return true; + } +} diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NamePolicyMixedClassSettingsRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NamePolicyMixedClassSettingsRealmProxy.java index b879ae03d8..f7cbbd3b2e 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NamePolicyMixedClassSettingsRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NamePolicyMixedClassSettingsRealmProxy.java @@ -151,7 +151,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } private static OsObjectSchemaInfo createExpectedObjectSchemaInfo() { - OsObjectSchemaInfo.Builder builder = new OsObjectSchemaInfo.Builder("customName", 2, 0); + OsObjectSchemaInfo.Builder builder = new OsObjectSchemaInfo.Builder("customName", false, 2, 0); builder.addPersistedProperty("first_name", RealmFieldType.STRING, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); builder.addPersistedProperty("LastName", RealmFieldType.STRING, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); return builder.build(); @@ -229,7 +229,7 @@ public static some.test.NamePolicyMixedClassSettings createUsingJsonStream(Realm return realm.copyToRealm(obj); } - private static some_test_NamePolicyMixedClassSettingsRealmProxy newProxyInstance(BaseRealm realm, Row row) { + static some_test_NamePolicyMixedClassSettingsRealmProxy newProxyInstance(BaseRealm realm, Row row) { // Ignore default values to avoid creating unexpected objects from RealmModel/RealmList fields final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get(); objectContext.set(realm, row, realm.getSchema().getColumnInfo(some.test.NamePolicyMixedClassSettings.class), false, Collections.emptyList()); @@ -263,22 +263,22 @@ public static some.test.NamePolicyMixedClassSettings copy(Realm realm, NamePolic return (some.test.NamePolicyMixedClassSettings) cachedRealmObject; } - some_test_NamePolicyMixedClassSettingsRealmProxyInterface realmObjectSource = (some_test_NamePolicyMixedClassSettingsRealmProxyInterface) newObject; + some_test_NamePolicyMixedClassSettingsRealmProxyInterface unmanagedSource = (some_test_NamePolicyMixedClassSettingsRealmProxyInterface) newObject; Table table = realm.getTable(some.test.NamePolicyMixedClassSettings.class); OsObjectBuilder builder = new OsObjectBuilder(table, flags); // Add all non-"object reference" fields - builder.addString(columnInfo.firstNameColKey, realmObjectSource.realmGet$firstName()); - builder.addString(columnInfo.lastNameColKey, realmObjectSource.realmGet$lastName()); + builder.addString(columnInfo.firstNameColKey, unmanagedSource.realmGet$firstName()); + builder.addString(columnInfo.lastNameColKey, unmanagedSource.realmGet$lastName()); // Create the underlying object and cache it before setting any object/objectlist references // This will allow us to break any circular dependencies by using the object cache. Row row = builder.createNewObject(); - io.realm.some_test_NamePolicyMixedClassSettingsRealmProxy realmObjectCopy = newProxyInstance(realm, row); - cache.put(newObject, realmObjectCopy); + io.realm.some_test_NamePolicyMixedClassSettingsRealmProxy managedCopy = newProxyInstance(realm, row); + cache.put(newObject, managedCopy); - return realmObjectCopy; + return managedCopy; } public static long insert(Realm realm, some.test.NamePolicyMixedClassSettings object, Map cache) { diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NamePolicyModuleDefaultsRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NamePolicyModuleDefaultsRealmProxy.java index 83bc876281..aef2c2fd5a 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NamePolicyModuleDefaultsRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NamePolicyModuleDefaultsRealmProxy.java @@ -151,7 +151,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } private static OsObjectSchemaInfo createExpectedObjectSchemaInfo() { - OsObjectSchemaInfo.Builder builder = new OsObjectSchemaInfo.Builder("NamePolicyModuleDefaults", 2, 0); + OsObjectSchemaInfo.Builder builder = new OsObjectSchemaInfo.Builder("NamePolicyModuleDefaults", false, 2, 0); builder.addPersistedProperty("FirstName", RealmFieldType.STRING, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); builder.addPersistedProperty("LastName", RealmFieldType.STRING, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); return builder.build(); @@ -229,7 +229,7 @@ public static some.test.NamePolicyModuleDefaults createUsingJsonStream(Realm rea return realm.copyToRealm(obj); } - private static some_test_NamePolicyModuleDefaultsRealmProxy newProxyInstance(BaseRealm realm, Row row) { + static some_test_NamePolicyModuleDefaultsRealmProxy newProxyInstance(BaseRealm realm, Row row) { // Ignore default values to avoid creating unexpected objects from RealmModel/RealmList fields final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get(); objectContext.set(realm, row, realm.getSchema().getColumnInfo(some.test.NamePolicyModuleDefaults.class), false, Collections.emptyList()); @@ -263,22 +263,22 @@ public static some.test.NamePolicyModuleDefaults copy(Realm realm, NamePolicyMod return (some.test.NamePolicyModuleDefaults) cachedRealmObject; } - some_test_NamePolicyModuleDefaultsRealmProxyInterface realmObjectSource = (some_test_NamePolicyModuleDefaultsRealmProxyInterface) newObject; + some_test_NamePolicyModuleDefaultsRealmProxyInterface unmanagedSource = (some_test_NamePolicyModuleDefaultsRealmProxyInterface) newObject; Table table = realm.getTable(some.test.NamePolicyModuleDefaults.class); OsObjectBuilder builder = new OsObjectBuilder(table, flags); // Add all non-"object reference" fields - builder.addString(columnInfo.firstNameColKey, realmObjectSource.realmGet$firstName()); - builder.addString(columnInfo.lastNameColKey, realmObjectSource.realmGet$lastName()); + builder.addString(columnInfo.firstNameColKey, unmanagedSource.realmGet$firstName()); + builder.addString(columnInfo.lastNameColKey, unmanagedSource.realmGet$lastName()); // Create the underlying object and cache it before setting any object/objectlist references // This will allow us to break any circular dependencies by using the object cache. Row row = builder.createNewObject(); - io.realm.some_test_NamePolicyModuleDefaultsRealmProxy realmObjectCopy = newProxyInstance(realm, row); - cache.put(newObject, realmObjectCopy); + io.realm.some_test_NamePolicyModuleDefaultsRealmProxy managedCopy = newProxyInstance(realm, row); + cache.put(newObject, managedCopy); - return realmObjectCopy; + return managedCopy; } public static long insert(Realm realm, some.test.NamePolicyModuleDefaults object, Map cache) { diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NullTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NullTypesRealmProxy.java index 20327c69ca..396c74efbe 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NullTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NullTypesRealmProxy.java @@ -992,6 +992,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { @Override public void realmSet$fieldObjectNull(some.test.NullTypes value) { + Realm realm = (Realm) proxyState.getRealm$realm(); if (proxyState.isUnderConstruction()) { if (!proxyState.getAcceptDefaultValue$realm()) { return; @@ -1000,7 +1001,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { return; } if (value != null && !RealmObject.isManaged(value)) { - value = ((Realm) proxyState.getRealm$realm()).copyToRealm(value); + value = realm.copyToRealm(value); } final Row row = proxyState.getRow$realm(); if (value == null) { @@ -1959,7 +1960,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } private static OsObjectSchemaInfo createExpectedObjectSchemaInfo() { - OsObjectSchemaInfo.Builder builder = new OsObjectSchemaInfo.Builder("NullTypes", 49, 0); + OsObjectSchemaInfo.Builder builder = new OsObjectSchemaInfo.Builder("NullTypes", false, 49, 0); builder.addPersistedProperty("fieldStringNotNull", RealmFieldType.STRING, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); builder.addPersistedProperty("fieldStringNull", RealmFieldType.STRING, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); builder.addPersistedProperty("fieldBooleanNotNull", RealmFieldType.BOOLEAN, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); @@ -2611,7 +2612,7 @@ public static some.test.NullTypes createUsingJsonStream(Realm realm, JsonReader return realm.copyToRealm(obj); } - private static some_test_NullTypesRealmProxy newProxyInstance(BaseRealm realm, Row row) { + static some_test_NullTypesRealmProxy newProxyInstance(BaseRealm realm, Row row) { // Ignore default values to avoid creating unexpected objects from RealmModel/RealmList fields final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get(); objectContext.set(realm, row, realm.getSchema().getColumnInfo(some.test.NullTypes.class), false, Collections.emptyList()); @@ -2645,81 +2646,81 @@ public static some.test.NullTypes copy(Realm realm, NullTypesColumnInfo columnIn return (some.test.NullTypes) cachedRealmObject; } - some_test_NullTypesRealmProxyInterface realmObjectSource = (some_test_NullTypesRealmProxyInterface) newObject; + some_test_NullTypesRealmProxyInterface unmanagedSource = (some_test_NullTypesRealmProxyInterface) newObject; Table table = realm.getTable(some.test.NullTypes.class); OsObjectBuilder builder = new OsObjectBuilder(table, flags); // Add all non-"object reference" fields - builder.addString(columnInfo.fieldStringNotNullColKey, realmObjectSource.realmGet$fieldStringNotNull()); - builder.addString(columnInfo.fieldStringNullColKey, realmObjectSource.realmGet$fieldStringNull()); - builder.addBoolean(columnInfo.fieldBooleanNotNullColKey, realmObjectSource.realmGet$fieldBooleanNotNull()); - builder.addBoolean(columnInfo.fieldBooleanNullColKey, realmObjectSource.realmGet$fieldBooleanNull()); - builder.addByteArray(columnInfo.fieldBytesNotNullColKey, realmObjectSource.realmGet$fieldBytesNotNull()); - builder.addByteArray(columnInfo.fieldBytesNullColKey, realmObjectSource.realmGet$fieldBytesNull()); - builder.addInteger(columnInfo.fieldByteNotNullColKey, realmObjectSource.realmGet$fieldByteNotNull()); - builder.addInteger(columnInfo.fieldByteNullColKey, realmObjectSource.realmGet$fieldByteNull()); - builder.addInteger(columnInfo.fieldShortNotNullColKey, realmObjectSource.realmGet$fieldShortNotNull()); - builder.addInteger(columnInfo.fieldShortNullColKey, realmObjectSource.realmGet$fieldShortNull()); - builder.addInteger(columnInfo.fieldIntegerNotNullColKey, realmObjectSource.realmGet$fieldIntegerNotNull()); - builder.addInteger(columnInfo.fieldIntegerNullColKey, realmObjectSource.realmGet$fieldIntegerNull()); - builder.addInteger(columnInfo.fieldLongNotNullColKey, realmObjectSource.realmGet$fieldLongNotNull()); - builder.addInteger(columnInfo.fieldLongNullColKey, realmObjectSource.realmGet$fieldLongNull()); - builder.addFloat(columnInfo.fieldFloatNotNullColKey, realmObjectSource.realmGet$fieldFloatNotNull()); - builder.addFloat(columnInfo.fieldFloatNullColKey, realmObjectSource.realmGet$fieldFloatNull()); - builder.addDouble(columnInfo.fieldDoubleNotNullColKey, realmObjectSource.realmGet$fieldDoubleNotNull()); - builder.addDouble(columnInfo.fieldDoubleNullColKey, realmObjectSource.realmGet$fieldDoubleNull()); - builder.addDate(columnInfo.fieldDateNotNullColKey, realmObjectSource.realmGet$fieldDateNotNull()); - builder.addDate(columnInfo.fieldDateNullColKey, realmObjectSource.realmGet$fieldDateNull()); - builder.addDecimal128(columnInfo.fieldDecimal128NotNullColKey, realmObjectSource.realmGet$fieldDecimal128NotNull()); - builder.addDecimal128(columnInfo.fieldDecimal128NullColKey, realmObjectSource.realmGet$fieldDecimal128Null()); - builder.addObjectId(columnInfo.fieldObjectIdNotNullColKey, realmObjectSource.realmGet$fieldObjectIdNotNull()); - builder.addObjectId(columnInfo.fieldObjectIdNullColKey, realmObjectSource.realmGet$fieldObjectIdNull()); - builder.addStringList(columnInfo.fieldStringListNotNullColKey, realmObjectSource.realmGet$fieldStringListNotNull()); - builder.addStringList(columnInfo.fieldStringListNullColKey, realmObjectSource.realmGet$fieldStringListNull()); - builder.addByteArrayList(columnInfo.fieldBinaryListNotNullColKey, realmObjectSource.realmGet$fieldBinaryListNotNull()); - builder.addByteArrayList(columnInfo.fieldBinaryListNullColKey, realmObjectSource.realmGet$fieldBinaryListNull()); - builder.addBooleanList(columnInfo.fieldBooleanListNotNullColKey, realmObjectSource.realmGet$fieldBooleanListNotNull()); - builder.addBooleanList(columnInfo.fieldBooleanListNullColKey, realmObjectSource.realmGet$fieldBooleanListNull()); - builder.addLongList(columnInfo.fieldLongListNotNullColKey, realmObjectSource.realmGet$fieldLongListNotNull()); - builder.addLongList(columnInfo.fieldLongListNullColKey, realmObjectSource.realmGet$fieldLongListNull()); - builder.addIntegerList(columnInfo.fieldIntegerListNotNullColKey, realmObjectSource.realmGet$fieldIntegerListNotNull()); - builder.addIntegerList(columnInfo.fieldIntegerListNullColKey, realmObjectSource.realmGet$fieldIntegerListNull()); - builder.addShortList(columnInfo.fieldShortListNotNullColKey, realmObjectSource.realmGet$fieldShortListNotNull()); - builder.addShortList(columnInfo.fieldShortListNullColKey, realmObjectSource.realmGet$fieldShortListNull()); - builder.addByteList(columnInfo.fieldByteListNotNullColKey, realmObjectSource.realmGet$fieldByteListNotNull()); - builder.addByteList(columnInfo.fieldByteListNullColKey, realmObjectSource.realmGet$fieldByteListNull()); - builder.addDoubleList(columnInfo.fieldDoubleListNotNullColKey, realmObjectSource.realmGet$fieldDoubleListNotNull()); - builder.addDoubleList(columnInfo.fieldDoubleListNullColKey, realmObjectSource.realmGet$fieldDoubleListNull()); - builder.addFloatList(columnInfo.fieldFloatListNotNullColKey, realmObjectSource.realmGet$fieldFloatListNotNull()); - builder.addFloatList(columnInfo.fieldFloatListNullColKey, realmObjectSource.realmGet$fieldFloatListNull()); - builder.addDateList(columnInfo.fieldDateListNotNullColKey, realmObjectSource.realmGet$fieldDateListNotNull()); - builder.addDateList(columnInfo.fieldDateListNullColKey, realmObjectSource.realmGet$fieldDateListNull()); - builder.addDecimal128List(columnInfo.fieldDecimal128ListNotNullColKey, realmObjectSource.realmGet$fieldDecimal128ListNotNull()); - builder.addDecimal128List(columnInfo.fieldDecimal128ListNullColKey, realmObjectSource.realmGet$fieldDecimal128ListNull()); - builder.addObjectIdList(columnInfo.fieldObjectIdListNotNullColKey, realmObjectSource.realmGet$fieldObjectIdListNotNull()); - builder.addObjectIdList(columnInfo.fieldObjectIdListNullColKey, realmObjectSource.realmGet$fieldObjectIdListNull()); + builder.addString(columnInfo.fieldStringNotNullColKey, unmanagedSource.realmGet$fieldStringNotNull()); + builder.addString(columnInfo.fieldStringNullColKey, unmanagedSource.realmGet$fieldStringNull()); + builder.addBoolean(columnInfo.fieldBooleanNotNullColKey, unmanagedSource.realmGet$fieldBooleanNotNull()); + builder.addBoolean(columnInfo.fieldBooleanNullColKey, unmanagedSource.realmGet$fieldBooleanNull()); + builder.addByteArray(columnInfo.fieldBytesNotNullColKey, unmanagedSource.realmGet$fieldBytesNotNull()); + builder.addByteArray(columnInfo.fieldBytesNullColKey, unmanagedSource.realmGet$fieldBytesNull()); + builder.addInteger(columnInfo.fieldByteNotNullColKey, unmanagedSource.realmGet$fieldByteNotNull()); + builder.addInteger(columnInfo.fieldByteNullColKey, unmanagedSource.realmGet$fieldByteNull()); + builder.addInteger(columnInfo.fieldShortNotNullColKey, unmanagedSource.realmGet$fieldShortNotNull()); + builder.addInteger(columnInfo.fieldShortNullColKey, unmanagedSource.realmGet$fieldShortNull()); + builder.addInteger(columnInfo.fieldIntegerNotNullColKey, unmanagedSource.realmGet$fieldIntegerNotNull()); + builder.addInteger(columnInfo.fieldIntegerNullColKey, unmanagedSource.realmGet$fieldIntegerNull()); + builder.addInteger(columnInfo.fieldLongNotNullColKey, unmanagedSource.realmGet$fieldLongNotNull()); + builder.addInteger(columnInfo.fieldLongNullColKey, unmanagedSource.realmGet$fieldLongNull()); + builder.addFloat(columnInfo.fieldFloatNotNullColKey, unmanagedSource.realmGet$fieldFloatNotNull()); + builder.addFloat(columnInfo.fieldFloatNullColKey, unmanagedSource.realmGet$fieldFloatNull()); + builder.addDouble(columnInfo.fieldDoubleNotNullColKey, unmanagedSource.realmGet$fieldDoubleNotNull()); + builder.addDouble(columnInfo.fieldDoubleNullColKey, unmanagedSource.realmGet$fieldDoubleNull()); + builder.addDate(columnInfo.fieldDateNotNullColKey, unmanagedSource.realmGet$fieldDateNotNull()); + builder.addDate(columnInfo.fieldDateNullColKey, unmanagedSource.realmGet$fieldDateNull()); + builder.addDecimal128(columnInfo.fieldDecimal128NotNullColKey, unmanagedSource.realmGet$fieldDecimal128NotNull()); + builder.addDecimal128(columnInfo.fieldDecimal128NullColKey, unmanagedSource.realmGet$fieldDecimal128Null()); + builder.addObjectId(columnInfo.fieldObjectIdNotNullColKey, unmanagedSource.realmGet$fieldObjectIdNotNull()); + builder.addObjectId(columnInfo.fieldObjectIdNullColKey, unmanagedSource.realmGet$fieldObjectIdNull()); + builder.addStringList(columnInfo.fieldStringListNotNullColKey, unmanagedSource.realmGet$fieldStringListNotNull()); + builder.addStringList(columnInfo.fieldStringListNullColKey, unmanagedSource.realmGet$fieldStringListNull()); + builder.addByteArrayList(columnInfo.fieldBinaryListNotNullColKey, unmanagedSource.realmGet$fieldBinaryListNotNull()); + builder.addByteArrayList(columnInfo.fieldBinaryListNullColKey, unmanagedSource.realmGet$fieldBinaryListNull()); + builder.addBooleanList(columnInfo.fieldBooleanListNotNullColKey, unmanagedSource.realmGet$fieldBooleanListNotNull()); + builder.addBooleanList(columnInfo.fieldBooleanListNullColKey, unmanagedSource.realmGet$fieldBooleanListNull()); + builder.addLongList(columnInfo.fieldLongListNotNullColKey, unmanagedSource.realmGet$fieldLongListNotNull()); + builder.addLongList(columnInfo.fieldLongListNullColKey, unmanagedSource.realmGet$fieldLongListNull()); + builder.addIntegerList(columnInfo.fieldIntegerListNotNullColKey, unmanagedSource.realmGet$fieldIntegerListNotNull()); + builder.addIntegerList(columnInfo.fieldIntegerListNullColKey, unmanagedSource.realmGet$fieldIntegerListNull()); + builder.addShortList(columnInfo.fieldShortListNotNullColKey, unmanagedSource.realmGet$fieldShortListNotNull()); + builder.addShortList(columnInfo.fieldShortListNullColKey, unmanagedSource.realmGet$fieldShortListNull()); + builder.addByteList(columnInfo.fieldByteListNotNullColKey, unmanagedSource.realmGet$fieldByteListNotNull()); + builder.addByteList(columnInfo.fieldByteListNullColKey, unmanagedSource.realmGet$fieldByteListNull()); + builder.addDoubleList(columnInfo.fieldDoubleListNotNullColKey, unmanagedSource.realmGet$fieldDoubleListNotNull()); + builder.addDoubleList(columnInfo.fieldDoubleListNullColKey, unmanagedSource.realmGet$fieldDoubleListNull()); + builder.addFloatList(columnInfo.fieldFloatListNotNullColKey, unmanagedSource.realmGet$fieldFloatListNotNull()); + builder.addFloatList(columnInfo.fieldFloatListNullColKey, unmanagedSource.realmGet$fieldFloatListNull()); + builder.addDateList(columnInfo.fieldDateListNotNullColKey, unmanagedSource.realmGet$fieldDateListNotNull()); + builder.addDateList(columnInfo.fieldDateListNullColKey, unmanagedSource.realmGet$fieldDateListNull()); + builder.addDecimal128List(columnInfo.fieldDecimal128ListNotNullColKey, unmanagedSource.realmGet$fieldDecimal128ListNotNull()); + builder.addDecimal128List(columnInfo.fieldDecimal128ListNullColKey, unmanagedSource.realmGet$fieldDecimal128ListNull()); + builder.addObjectIdList(columnInfo.fieldObjectIdListNotNullColKey, unmanagedSource.realmGet$fieldObjectIdListNotNull()); + builder.addObjectIdList(columnInfo.fieldObjectIdListNullColKey, unmanagedSource.realmGet$fieldObjectIdListNull()); // Create the underlying object and cache it before setting any object/objectlist references // This will allow us to break any circular dependencies by using the object cache. Row row = builder.createNewObject(); - io.realm.some_test_NullTypesRealmProxy realmObjectCopy = newProxyInstance(realm, row); - cache.put(newObject, realmObjectCopy); + io.realm.some_test_NullTypesRealmProxy managedCopy = newProxyInstance(realm, row); + cache.put(newObject, managedCopy); // Finally add all fields that reference other Realm Objects, either directly or through a list - some.test.NullTypes fieldObjectNullObj = realmObjectSource.realmGet$fieldObjectNull(); + some.test.NullTypes fieldObjectNullObj = unmanagedSource.realmGet$fieldObjectNull(); if (fieldObjectNullObj == null) { - realmObjectCopy.realmSet$fieldObjectNull(null); + managedCopy.realmSet$fieldObjectNull(null); } else { some.test.NullTypes cachefieldObjectNull = (some.test.NullTypes) cache.get(fieldObjectNullObj); if (cachefieldObjectNull != null) { - realmObjectCopy.realmSet$fieldObjectNull(cachefieldObjectNull); + managedCopy.realmSet$fieldObjectNull(cachefieldObjectNull); } else { - realmObjectCopy.realmSet$fieldObjectNull(some_test_NullTypesRealmProxy.copyOrUpdate(realm, (some_test_NullTypesRealmProxy.NullTypesColumnInfo) realm.getSchema().getColumnInfo(some.test.NullTypes.class), fieldObjectNullObj, update, cache, flags)); + managedCopy.realmSet$fieldObjectNull(some_test_NullTypesRealmProxy.copyOrUpdate(realm, (some_test_NullTypesRealmProxy.NullTypesColumnInfo) realm.getSchema().getColumnInfo(some.test.NullTypes.class), fieldObjectNullObj, update, cache, flags)); } } - return realmObjectCopy; + return managedCopy; } public static long insert(Realm realm, some.test.NullTypes object, Map cache) { diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_SimpleRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_SimpleRealmProxy.java index dcec635152..8c5590e1e2 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_SimpleRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_SimpleRealmProxy.java @@ -143,7 +143,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } private static OsObjectSchemaInfo createExpectedObjectSchemaInfo() { - OsObjectSchemaInfo.Builder builder = new OsObjectSchemaInfo.Builder("Simple", 2, 0); + OsObjectSchemaInfo.Builder builder = new OsObjectSchemaInfo.Builder("Simple", false, 2, 0); builder.addPersistedProperty("name", RealmFieldType.STRING, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); builder.addPersistedProperty("age", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); return builder.build(); @@ -221,7 +221,7 @@ public static some.test.Simple createUsingJsonStream(Realm realm, JsonReader rea return realm.copyToRealm(obj); } - private static some_test_SimpleRealmProxy newProxyInstance(BaseRealm realm, Row row) { + static some_test_SimpleRealmProxy newProxyInstance(BaseRealm realm, Row row) { // Ignore default values to avoid creating unexpected objects from RealmModel/RealmList fields final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get(); objectContext.set(realm, row, realm.getSchema().getColumnInfo(some.test.Simple.class), false, Collections.emptyList()); @@ -255,22 +255,22 @@ public static some.test.Simple copy(Realm realm, SimpleColumnInfo columnInfo, so return (some.test.Simple) cachedRealmObject; } - some_test_SimpleRealmProxyInterface realmObjectSource = (some_test_SimpleRealmProxyInterface) newObject; + some_test_SimpleRealmProxyInterface unmanagedSource = (some_test_SimpleRealmProxyInterface) newObject; Table table = realm.getTable(some.test.Simple.class); OsObjectBuilder builder = new OsObjectBuilder(table, flags); // Add all non-"object reference" fields - builder.addString(columnInfo.nameColKey, realmObjectSource.realmGet$name()); - builder.addInteger(columnInfo.ageColKey, realmObjectSource.realmGet$age()); + builder.addString(columnInfo.nameColKey, unmanagedSource.realmGet$name()); + builder.addInteger(columnInfo.ageColKey, unmanagedSource.realmGet$age()); // Create the underlying object and cache it before setting any object/objectlist references // This will allow us to break any circular dependencies by using the object cache. Row row = builder.createNewObject(); - io.realm.some_test_SimpleRealmProxy realmObjectCopy = newProxyInstance(realm, row); - cache.put(newObject, realmObjectCopy); + io.realm.some_test_SimpleRealmProxy managedCopy = newProxyInstance(realm, row); + cache.put(newObject, managedCopy); - return realmObjectCopy; + return managedCopy; } public static long insert(Realm realm, some.test.Simple object, Map cache) { diff --git a/realm/realm-annotations-processor/src/test/resources/some/test/EmbeddedClass.java b/realm/realm-annotations-processor/src/test/resources/some/test/EmbeddedClass.java new file mode 100644 index 0000000000..d3bd25f2dd --- /dev/null +++ b/realm/realm-annotations-processor/src/test/resources/some/test/EmbeddedClass.java @@ -0,0 +1,26 @@ +/* + * Copyright 2020 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package some.test; + +import io.realm.RealmObject; +import io.realm.RealmResults; +import io.realm.annotations.RealmClass; + +@RealmClass(embedded = true) +public class EmbeddedClass extends RealmObject { + public String name; + public int age; +} diff --git a/realm/realm-annotations-processor/src/test/resources/some/test/EmbeddedClassMissingFieldDescription.java b/realm/realm-annotations-processor/src/test/resources/some/test/EmbeddedClassMissingFieldDescription.java new file mode 100644 index 0000000000..2e4895ef6f --- /dev/null +++ b/realm/realm-annotations-processor/src/test/resources/some/test/EmbeddedClassMissingFieldDescription.java @@ -0,0 +1,31 @@ +/* + * Copyright 2020 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package some.test; + +import io.realm.RealmObject; +import io.realm.RealmResults; +import io.realm.annotations.LinkingObjects; +import io.realm.annotations.RealmClass; +import io.realm.annotations.Required; + +@RealmClass(embedded = true) +public class EmbeddedClassMissingFieldDescription extends RealmObject { + public String name; + public int age; + + @LinkingObjects + public final EmbeddedClassParent parent1 = new EmbeddedClassParent(); +} diff --git a/realm/realm-annotations-processor/src/test/resources/some/test/EmbeddedClassMissingFinalOnLinkingObjects.java b/realm/realm-annotations-processor/src/test/resources/some/test/EmbeddedClassMissingFinalOnLinkingObjects.java new file mode 100644 index 0000000000..f870670852 --- /dev/null +++ b/realm/realm-annotations-processor/src/test/resources/some/test/EmbeddedClassMissingFinalOnLinkingObjects.java @@ -0,0 +1,31 @@ +/* + * Copyright 2020 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package some.test; + +import io.realm.RealmObject; +import io.realm.RealmResults; +import io.realm.annotations.LinkingObjects; +import io.realm.annotations.RealmClass; +import io.realm.annotations.Required; + +@RealmClass(embedded = true) +public class EmbeddedClassMissingFinalOnLinkingObjects extends RealmObject { + public String name; + public int age; + + @LinkingObjects("child5") + public EmbeddedClassParent parent; +} diff --git a/realm/realm-annotations-processor/src/test/resources/some/test/EmbeddedClassMultipleRequiredParents.java b/realm/realm-annotations-processor/src/test/resources/some/test/EmbeddedClassMultipleRequiredParents.java new file mode 100644 index 0000000000..e72094ef7f --- /dev/null +++ b/realm/realm-annotations-processor/src/test/resources/some/test/EmbeddedClassMultipleRequiredParents.java @@ -0,0 +1,38 @@ +/* + * Copyright 2020 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package some.test; + +import io.realm.RealmObject; +import io.realm.RealmResults; +import io.realm.annotations.LinkingObjects; +import io.realm.annotations.RealmClass; +import io.realm.annotations.Required; + +@RealmClass(embedded = true) +public class EmbeddedClassMultipleRequiredParents extends RealmObject { + public String name; + public int age; + + // If multiple @LinkingObjects are defined + // the @Required annotation is not allowed. + @Required + @LinkingObjects("child6") + public final EmbeddedClassParent parent1 = new EmbeddedClassParent(); + + @Required + @LinkingObjects("child7") + public final EmbeddedClassParent parent2 = new EmbeddedClassParent(); +} diff --git a/realm/realm-annotations-processor/src/test/resources/some/test/EmbeddedClassOptionalParents.java b/realm/realm-annotations-processor/src/test/resources/some/test/EmbeddedClassOptionalParents.java new file mode 100644 index 0000000000..75eb54c600 --- /dev/null +++ b/realm/realm-annotations-processor/src/test/resources/some/test/EmbeddedClassOptionalParents.java @@ -0,0 +1,37 @@ +/* + * Copyright 2020 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package some.test; + +import io.realm.RealmObject; +import io.realm.RealmResults; +import io.realm.annotations.LinkingObjects; +import io.realm.annotations.RealmClass; +import io.realm.annotations.Required; + +@RealmClass(embedded = true) +public class EmbeddedClassOptionalParents extends RealmObject { + public String name; + public int age; + + // If multiple @LinkingObjects are defined + // They are not treated as @Required. + // This mostly impact Kotlin model classes + @LinkingObjects("child3") + public final EmbeddedClassParent parent1 = new EmbeddedClassParent(); // Field must be final, because parent cannot change once set + + @LinkingObjects("child4") + public final EmbeddedClassParent parent2 = new EmbeddedClassParent(); // Field must be final, because parent cannot change once set +} diff --git a/realm/realm-annotations-processor/src/test/resources/some/test/EmbeddedClassParent.java b/realm/realm-annotations-processor/src/test/resources/some/test/EmbeddedClassParent.java new file mode 100644 index 0000000000..02b1ae2cc7 --- /dev/null +++ b/realm/realm-annotations-processor/src/test/resources/some/test/EmbeddedClassParent.java @@ -0,0 +1,34 @@ +/* + * Copyright 2020 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package some.test; + +import io.realm.RealmObject; +import io.realm.RealmResults; +import io.realm.annotations.LinkingObjects; +import io.realm.annotations.RealmClass; + +// This class is only for creating the correct type hiearchy when testing Embedded Objects +// This class can work as a parent for all legal embedded object classes +public class EmbeddedClassParent extends RealmObject { + public String name; + public int age; + + // Valid single children references + public EmbeddedClass child1; + public EmbeddedClassRequiredParent child2; + public EmbeddedClassOptionalParents child3; + public EmbeddedClassOptionalParents child4; +} diff --git a/realm/realm-annotations-processor/src/test/resources/some/test/EmbeddedClassPrimaryKey.java b/realm/realm-annotations-processor/src/test/resources/some/test/EmbeddedClassPrimaryKey.java new file mode 100644 index 0000000000..174c2e8c21 --- /dev/null +++ b/realm/realm-annotations-processor/src/test/resources/some/test/EmbeddedClassPrimaryKey.java @@ -0,0 +1,29 @@ +/* + * Copyright 2020 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package some.test; + +import io.realm.RealmObject; +import io.realm.RealmResults; +import io.realm.annotations.LinkingObjects; +import io.realm.annotations.PrimaryKey; +import io.realm.annotations.RealmClass; + +@RealmClass(embedded = true) +public class EmbeddedClassPrimaryKey extends RealmObject { + @PrimaryKey // This is not allowed in embedded classes + public String name; + public int age; +} diff --git a/realm/realm-annotations-processor/src/test/resources/some/test/EmbeddedClassRequiredParent.java b/realm/realm-annotations-processor/src/test/resources/some/test/EmbeddedClassRequiredParent.java new file mode 100644 index 0000000000..db7804f876 --- /dev/null +++ b/realm/realm-annotations-processor/src/test/resources/some/test/EmbeddedClassRequiredParent.java @@ -0,0 +1,32 @@ +/* + * Copyright 2020 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package some.test; + +import io.realm.RealmObject; +import io.realm.RealmResults; +import io.realm.annotations.LinkingObjects; +import io.realm.annotations.RealmClass; +import io.realm.annotations.Required; + +@RealmClass(embedded = true) +public class EmbeddedClassRequiredParent extends RealmObject { + public String name; + public int age; + + @Required // Optional, is implied if only a single @LinkingObjects parent is defined + @LinkingObjects("child2") + public final EmbeddedClassParent parent = new EmbeddedClassParent(); // Field must be final, because parent cannot change once set +} diff --git a/realm/realm-annotations-processor/src/test/resources/some/test/EmbeddedClassSimpleParent.java b/realm/realm-annotations-processor/src/test/resources/some/test/EmbeddedClassSimpleParent.java new file mode 100644 index 0000000000..1cc405105c --- /dev/null +++ b/realm/realm-annotations-processor/src/test/resources/some/test/EmbeddedClassSimpleParent.java @@ -0,0 +1,32 @@ +/* + * Copyright 2020 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package some.test; + +import io.realm.RealmList; +import io.realm.RealmObject; +import io.realm.RealmResults; +import io.realm.annotations.LinkingObjects; +import io.realm.annotations.PrimaryKey; +import io.realm.annotations.RealmClass; + +// Simple parent of embedded objects. Used to verify the output of the annotation processor. +public class EmbeddedClassSimpleParent extends RealmObject { + @PrimaryKey + public String id; + public EmbeddedClass child; + public RealmList children; + +} diff --git a/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsManagedTests.java b/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsManagedTests.java index 13ed88e978..4174c14099 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsManagedTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsManagedTests.java @@ -604,11 +604,11 @@ public void migration_backlinkedSourceFieldDoesntExist() throws ClassNotFoundExc // Mock the schema info so the only difference compared with the original schema is that the LinkingObject field // points to BacklinksSource.childNotExist. - OsObjectSchemaInfo targetSchemaInfo = new OsObjectSchemaInfo.Builder("BacklinksTarget", 1, 1) + OsObjectSchemaInfo targetSchemaInfo = new OsObjectSchemaInfo.Builder("BacklinksTarget", false, 1, 1) .addPersistedProperty("id", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED) .addComputedLinkProperty("parents", "BacklinksSource", "childNotExist" /*"child" is the original value*/) .build(); - OsObjectSchemaInfo sourceSchemaInfo = new OsObjectSchemaInfo.Builder("BacklinksSource", 2, 0) + OsObjectSchemaInfo sourceSchemaInfo = new OsObjectSchemaInfo.Builder("BacklinksSource", false, 2, 0) .addPersistedProperty("name", RealmFieldType.STRING, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED) .addPersistedLinkProperty("child", RealmFieldType.OBJECT, "BacklinksTarget") .build(); @@ -647,11 +647,11 @@ public void migration_backlinkedSourceFieldWrongType() { // Mock the schema info so the only difference compared with the original schema is that BacklinksSource.child // type is changed to BacklinksSource from BacklinksTarget. - OsObjectSchemaInfo targetSchemaInfo = new OsObjectSchemaInfo.Builder("BacklinksTarget", 1, 1) + OsObjectSchemaInfo targetSchemaInfo = new OsObjectSchemaInfo.Builder("BacklinksTarget", false, 1, 1) .addPersistedProperty("id", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED) .addComputedLinkProperty("parents", "BacklinksSource", "child") .build(); - OsObjectSchemaInfo sourceSchemaInfo = new OsObjectSchemaInfo.Builder("BacklinksSource", 2, 0) + OsObjectSchemaInfo sourceSchemaInfo = new OsObjectSchemaInfo.Builder("BacklinksSource", false, 2, 0) .addPersistedProperty("name", RealmFieldType.STRING, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED) .addPersistedLinkProperty("child", RealmFieldType.OBJECT, "BacklinksSource"/*"BacklinksTarget" is the original value*/) diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/OsListTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/OsListTests.java index 2b79b9d1b2..5f28f7e53a 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/OsListTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/OsListTests.java @@ -51,7 +51,7 @@ public class OsListTests { @Before public void setUp() { - OsObjectSchemaInfo objectSchemaInfo = new OsObjectSchemaInfo.Builder("TestModel",14, 0) + OsObjectSchemaInfo objectSchemaInfo = new OsObjectSchemaInfo.Builder("TestModel", false,14, 0) .addPersistedValueListProperty("longList", RealmFieldType.INTEGER_LIST, !Property.REQUIRED) .addPersistedValueListProperty("doubleList", RealmFieldType.DOUBLE_LIST, !Property.REQUIRED) .addPersistedValueListProperty("floatList", RealmFieldType.FLOAT_LIST, !Property.REQUIRED) diff --git a/realm/realm-library/src/androidTest/kotlin/io/realm/Decimal128Tests.kt b/realm/realm-library/src/androidTest/kotlin/io/realm/Decimal128Tests.kt index c6b3094613..b47a5d8d6b 100644 --- a/realm/realm-library/src/androidTest/kotlin/io/realm/Decimal128Tests.kt +++ b/realm/realm-library/src/androidTest/kotlin/io/realm/Decimal128Tests.kt @@ -1,3 +1,18 @@ +/* + * Copyright 2020 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package io.realm import androidx.test.ext.junit.runners.AndroidJUnit4 diff --git a/realm/realm-library/src/androidTest/kotlin/io/realm/EmbeddedObjectsTest.kt b/realm/realm-library/src/androidTest/kotlin/io/realm/EmbeddedObjectsTest.kt new file mode 100644 index 0000000000..a6be56f3c9 --- /dev/null +++ b/realm/realm-library/src/androidTest/kotlin/io/realm/EmbeddedObjectsTest.kt @@ -0,0 +1,592 @@ +/* + * Copyright 2020 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.realm + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import io.realm.entities.* +import io.realm.entities.embedded.* +import io.realm.kotlin.addChangeListener +import io.realm.kotlin.createEmbeddedObject +import io.realm.kotlin.createObject +import io.realm.kotlin.where +import io.realm.rule.BlockingLooperThread +import io.realm.rule.TestRealmConfigurationFactory +import org.junit.* +import org.junit.Assert.* +import org.junit.runner.RunWith +import java.util.* +import kotlin.test.assertFailsWith + +/** + * Class testing the Embedded Objects feature. + */ +// FIXME: Move all of these tests out from here. We try to tests by Class, not Feature. +@RunWith(AndroidJUnit4::class) +class EmbeddedObjectsTest { + + @get:Rule + val configFactory = TestRealmConfigurationFactory() + + private val looperThread = BlockingLooperThread() + + private lateinit var realmConfig: RealmConfiguration + private lateinit var realm: Realm + + @Before + fun setUp() { + Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) + realmConfig = configFactory.createConfiguration() + realm = Realm.getInstance(realmConfig) + } + + @After + fun tearDown() { + if (this::realm.isInitialized) { + realm.close() + } + } + + @Test + fun createObject_throwsForEmbeddedClasses() = realm.executeTransaction { realm -> + assertFailsWith { realm.createObject() } + } + + @Test + fun createObjectWithPrimaryKey_throwsForEmbeddedClasses() = realm.executeTransaction { realm -> + assertFailsWith { realm.createObject("foo") } + } + + @Test + fun createEmbeddedObject_nullArgsThrows() = realm.executeTransaction { realm -> + assertFailsWith { realm.createEmbeddedObject(EmbeddedSimpleChild::class.java, TestHelper.getNull(), "foo") } + val parent = realm.createObject("parent") + assertFailsWith { realm.createEmbeddedObject(EmbeddedSimpleChild::class.java, parent, TestHelper.getNull()) } + } + + @Test + fun createEmbeddedObject_nonExistingParentPropertyNameThrows() = realm.executeTransaction { realm -> + val parent = realm.createObject("parent") + assertFailsWith { realm.createEmbeddedObject(parent, "foo") } + } + + @Test + fun createEmbeddedObject_wrongParentPropertyTypeThrows() = realm.executeTransaction { realm -> + val parent = realm.createObject("parent") + + // TODO: Smoke-test for wrong type. Figure out how to test all unsupported types. + assertFailsWith { realm.createEmbeddedObject(parent, "id") } + } + + @Test + @Ignore("FIXME") + fun createEmbeddedObject_wrongParentPropertyObjectTypeThrows() = realm.executeTransaction { realm -> + val parent = realm.createObject("parent") + + assertFailsWith { + // Embedded object is not of the type the parent object links to. + realm.createEmbeddedObject(parent, "child") + } + } + + @Test + @Ignore("FIXME") + fun createEmbeddedObject_wrongParentPropertyListTypeThrows() = realm.executeTransaction { realm -> + val parent = realm.createObject("parent") + + assertFailsWith { + // Embedded object is not of the type the parent object links to. + realm.createEmbeddedObject(parent, "children") + } + } + + @Test + fun createEmbeddedObject_simpleSingleChild() = realm.executeTransaction { realm -> + val parent = realm.createObject("parent") + val child = realm.createEmbeddedObject(parent, "child"); + assertEquals(child.parent, parent) + } + + @Test + fun createEmbeddedObject_simpleChildList() = realm.executeTransaction { realm -> + // Using createEmbeddedObject() with a parent list, will append the object to the end + // of the list + val parent = realm.createObject(UUID.randomUUID().toString()) + val child1 = realm.createEmbeddedObject(parent, "children") + val child2 = realm.createEmbeddedObject(parent, "children") + assertEquals(2, parent.children.size.toLong()) + assertEquals(child1, parent.children.first()!!) + assertEquals(child2, parent.children.last()!!) + } + + @Test + @Ignore("Placeholder for all tests for DynamicRealm.createEmbeddedObject()") + fun dynamicRealm_createEmbeddedObject() { + TODO() + } + + @Test + fun settingParentFieldDeletesChild() = realm.executeTransaction { realm -> + val parent = EmbeddedSimpleParent("parent") + parent.child = EmbeddedSimpleChild("child") + + val managedParent: EmbeddedSimpleParent = realm.copyToRealm(parent) + val managedChild: EmbeddedSimpleChild = managedParent.child!! + managedParent.child = null // Will delete the embedded object + assertFalse(managedChild.isValid) + assertEquals(0, realm.where().count()) + } + + @Test + fun objectAccessor_willAutomaticallyCopyUnmanaged() = realm.executeTransaction { realm -> + // Checks that adding an unmanaged embedded object to a property will automatically copy it. + val parent = EmbeddedSimpleParent("parent") + val managedParent: EmbeddedSimpleParent = realm.copyToRealm(parent) + + assertEquals(0, realm.where().count()) + managedParent.child = EmbeddedSimpleChild("child") // Will copy the object to Realm + assertEquals(1, realm.where().count()) + assertTrue(managedParent.child!!.isValid) + } + + @Test + fun objectAccessor_willAutomaticallyCopyManaged() = realm.executeTransaction { realm -> + // Checks that setting a link to a managed embedded object will automatically copy it unlike + // normal objects that allow multiple parents. Note: This behavior is a bit controversial + // and was subject to a lot of discussion during API design. The problem is that making + // the behavior explicit will result in an extremely annoying API. We need to carefully + // monitor if people understand how this behaves. + val managedParent1: EmbeddedSimpleParent = realm.copyToRealm(EmbeddedSimpleParent("parent1")) + val managedParent2: EmbeddedSimpleParent = realm.copyToRealm(EmbeddedSimpleParent("parent2")) + + assertEquals(0, realm.where().count()) + managedParent1.child = EmbeddedSimpleChild("child") + assertEquals(1, realm.where().count()) + managedParent2.child = managedParent1.child // Will copy the embedded object + assertEquals(2, realm.where().count()) + assertNotEquals(managedParent1.child, managedParent2.child) + } + + @Test + fun objectAccessor_willCopyUnderConstruction() = realm.executeTransaction { realm -> + val unmanagedObj = EmbeddedWithConstructorArgs() + val managedObj = realm.copyToRealm(unmanagedObj) + assertEquals(EmbeddedWithConstructorArgs.INNER_CHILD_ID, managedObj.child!!.id) + } + + @Test + fun realmList_add_willAutomaticallyCopy() = realm.executeTransaction { realm -> + val parent = realm.copyToRealm(EmbeddedSimpleListParent("parent")) + assertTrue(parent.children.add(EmbeddedSimpleChild("child"))) + val child = parent.children.first()!! + assertTrue(child.isValid) + assertEquals("child", child.id) + + // FIXME: How to handle DynamicRealmObject :( + } + + @Test + fun realmList_addIndex_willAutomaticallyCopy() = realm.executeTransaction { realm -> + val parent = realm.copyToRealm(EmbeddedSimpleListParent("parent")) + parent.children.add(EmbeddedSimpleChild("secondChild")) + parent.children.add(0, EmbeddedSimpleChild("firstChild")) + val child = parent.children.first()!! + assertTrue(child.isValid) + assertEquals("firstChild", child.id) + + // FIXME: How to handle DynamicRealmObject :( + } + + @Test + fun realmList_set_willAutomaticallyCopy() = realm.executeTransaction { realm -> + // Checks that adding an unmanaged embedded object to a list will automatically make + // it managed + val parent = realm.copyToRealm(EmbeddedSimpleListParent("parent")) + assertTrue(parent.children.add(EmbeddedSimpleChild("child"))) + assertEquals(1, realm.where().count()) + parent.children[0] = EmbeddedSimpleChild("OtherChild") + assertEquals("OtherChild", parent.children.first()!!.id) + assertEquals(1, realm.where().count()) + + // FIXME: How to handle DynamicRealmObject :( + } + + @Test + fun copyToRealm_noParentThrows() = realm.executeTransaction { + assertFailsWith { + realm.copyToRealm(EmbeddedSimpleChild("child")) + } + } + + @Test + fun copyToRealmOrUpdate_NoParentThrows() = realm.executeTransaction { + assertFailsWith { + realm.copyToRealmOrUpdate(EmbeddedSimpleChild("child")) + } + } + + @Test + fun copyToRealm_simpleSingleChild() { + realm.executeTransaction { + val parent = EmbeddedSimpleParent("parent1") + parent.child = EmbeddedSimpleChild("child1") + it.copyToRealm(parent) + } + + assertEquals(1, realm.where().count()) + assertEquals(1, realm.where().count()) + } + + @Test + fun copyToRealm_simpleChildList() { + realm.executeTransaction { + val parent = EmbeddedSimpleListParent("parent1") + parent.children = RealmList(EmbeddedSimpleChild("child1")) + it.copyToRealm(parent) + } + + assertEquals(1, realm.where().count()) + assertEquals(1, realm.where().count()) + } + + @Test + fun copyToRealm_treeSchema() { + realm.executeTransaction { + val parent = EmbeddedTreeParent("parent1") + + val node1 = EmbeddedTreeNode("node1") + node1.leafNode = EmbeddedTreeLeaf("leaf1") + parent.middleNode = node1 + val node2 = EmbeddedTreeNode("node2") + node2.leafNodeList.add(EmbeddedTreeLeaf("leaf2")) + node2.leafNodeList.add(EmbeddedTreeLeaf("leaf3")) + parent.middleNodeList.add(node2) + + it.copyToRealm(parent) + } + + assertEquals(1, realm.where().count()) + assertEquals(2, realm.where().count()) + assertEquals(3, realm.where().count()) + } + + @Test + fun copyToRealm_circularSchema() { + realm.executeTransaction { + val parent = EmbeddedCircularParent("parent") + val child1 = EmbeddedCircularChild("child1") + val child2 = EmbeddedCircularChild("child2") + child1.singleChild = child2 + parent.singleChild = child1 + it.copyToRealm(parent) + } + + assertEquals(1, realm.where().count()) + assertEquals(2, realm.where().count()) + } + + @Test + fun copyToRealm_throwsIfMultipleRefsToSingleObjectsExists() { + realm.executeTransaction { r -> + val parent = EmbeddedCircularParent("parent") + val child = EmbeddedCircularChild("child") + child.singleChild = child // Create circle between children + parent.singleChild = child + assertFailsWith { r.copyToRealm(parent) } + } + } + + @Test + fun copyToRealm_throwsIfMultipleRefsToListObjectsExists() { + realm.executeTransaction { r -> + val parent = EmbeddedSimpleListParent("parent") + val child = EmbeddedSimpleChild("child") + parent.children = RealmList(child, child) + assertFailsWith { r.copyToRealm(parent) } + } + } + + @Test + @Ignore("FIXME") + fun copyToRealmOrUpdate_deleteReplacedObjects() { + TODO() + + } + + @Test + @Ignore("Add in another PR") + fun insert_noParentThrows() { + TODO() + } + + @Test + @Ignore("Add in another PR") + fun insertOrUpdate_throws() { + TODO() + } + + @Test + fun insert_simpleSingleChild() { + realm.executeTransaction { + val parent = EmbeddedSimpleParent("parent1") + parent.child = EmbeddedSimpleChild("child1") + it.insert(parent) + } + + assertEquals(1, realm.where().count()) + assertEquals(1, realm.where().count()) + } + + @Test + fun insert_simpleChildList() { + realm.executeTransaction { + val parent = EmbeddedSimpleListParent("parent1") + parent.children = RealmList(EmbeddedSimpleChild("child1")) + it.insert(parent) + } + + assertEquals(1, realm.where().count()) + assertEquals(1, realm.where().count()) + } + + @Test + fun insert_treeSchema() { + realm.executeTransaction { + val parent = EmbeddedTreeParent("parent1") + + val node1 = EmbeddedTreeNode("node1") + node1.leafNode = EmbeddedTreeLeaf("leaf1") + parent.middleNode = node1 + val node2 = EmbeddedTreeNode("node2") + node2.leafNodeList.add(EmbeddedTreeLeaf("leaf2")) + node2.leafNodeList.add(EmbeddedTreeLeaf("leaf3")) + parent.middleNodeList.add(node2) + + it.insert(parent) + } + + assertEquals(1, realm.where().count()) + assertEquals(2, realm.where().count()) + assertEquals(3, realm.where().count()) + } + + @Test + fun insert_circularSchema() { + realm.executeTransaction { + val parent = EmbeddedCircularParent("parent") + val child1 = EmbeddedCircularChild("child1") + val child2 = EmbeddedCircularChild("child2") + child1.singleChild = child2 + parent.singleChild = child1 + it.insert(parent) + } + + assertEquals(1, realm.where().count()) + assertEquals(2, realm.where().count()) + } + + @Test + @Ignore("Add in another PR") + fun insertOrUpdate_deletesOldEmbeddedObject() { + TODO() + } + + @Test + @Ignore("Add in another PR") + fun insert_listWithEmbeddedObjects() { + TODO() + } + + @Test + @Ignore("Add in another PR") + fun insertOrUpdate_listWithEmbeddedObjects() { + TODO() + } + + @Test + @Ignore("Add in another PR") + fun createObjectFromJson() { + TODO("Placeholder for all tests regarding importing from JSON") + } + + @Test + @Ignore("Add in another PR") + fun dynamicRealmObject_createEmbeddedObject() { + TODO("Consider which kind of support there should be for embedded objets in DynamicRealm") + } + + + @Test + fun realmObjectSchema_setEmbedded() { + DynamicRealm.getInstance(realm.configuration).use { realm -> + realm.executeTransaction { + val objSchema: RealmObjectSchema = realm.schema[EmbeddedSimpleChild.NAME]!! + assertTrue(objSchema.isEmbedded) + objSchema.isEmbedded = false + assertFalse(objSchema.isEmbedded) + objSchema.isEmbedded = true + assertTrue(objSchema.isEmbedded) + } + } + } + + @Test + fun realmObjectSchema_setEmbedded_throwsWithPrimaryKey() { + DynamicRealm.getInstance(realm.configuration).use { realm -> + realm.executeTransaction { + val objSchema: RealmObjectSchema = realm.schema[AllJavaTypes.CLASS_NAME]!! + assertFailsWith { objSchema.isEmbedded = true } + } + } + } + + @Test + fun realmObjectSchema_setEmbedded_throwsIfBreaksParentInvariants() { + // Classes can only be converted to be embedded if all objects have exactly one other + // object pointing to it. + DynamicRealm.getInstance(realm.configuration).use { realm -> + realm.executeTransaction { + + // Create object with no parents + realm.createObject(Dog.CLASS_NAME) + val dogSchema = realm.schema[Dog.CLASS_NAME]!! + // Succeed by mistake right now. + // See https://github.com/realm/realm-core/issues/3729 + // The correct check is just below + dogSchema.isEmbedded = true + // assertFailsWith { + // dogSchema.isEmbedded = true + // } + + // Create object with two parents + val cat: DynamicRealmObject = realm.createObject(Cat.CLASS_NAME) + val owner1: DynamicRealmObject = realm.createObject(Owner.CLASS_NAME) + owner1.setObject(Owner.FIELD_CAT, cat) + val owner2: DynamicRealmObject = realm.createObject(Owner.CLASS_NAME) + owner2.setObject(Owner.FIELD_CAT, cat) + val catSchema = realm.schema[Cat.CLASS_NAME]!! + assertFailsWith { + catSchema.isEmbedded = true + } + } + } + } + + @Test + fun realmObjectSchema_isEmbedded() { + assertTrue(realm.schema[EmbeddedSimpleChild.NAME]!!.isEmbedded) + assertFalse(realm.schema[AllTypes.CLASS_NAME]!!.isEmbedded) + } + + // Check that deleting a non-embedded parent deletes all embedded children + @Test + fun deleteParentObject_deletesEmbeddedChildren() = realm.executeTransaction { + val parent = EmbeddedSimpleParent("parent") + parent.child = EmbeddedSimpleChild("child") + + val managedParent: EmbeddedSimpleParent = it.copyToRealm(parent) + assertEquals(1, realm.where().count()) + val managedChild: EmbeddedSimpleChild = managedParent.child!! + + managedParent.deleteFromRealm() + assertFalse(managedChild.isValid) + assertEquals(0, realm.where().count()) + assertEquals(0, realm.where().count()) + } + + // Check that deleting a embedded parent deletes all embedded children + @Test + fun deleteParentEmbeddedObject_deletesEmbeddedChildren() = realm.executeTransaction { + val parent = EmbeddedTreeParent("parent1") + val middleNode = EmbeddedTreeNode("node1") + middleNode.leafNode = EmbeddedTreeLeaf("leaf1") + middleNode.leafNodeList.add(EmbeddedTreeLeaf("leaf2")) + middleNode.leafNodeList.add(EmbeddedTreeLeaf("leaf3")) + parent.middleNode = middleNode + + val managedParent: EmbeddedTreeParent = it.copyToRealm(parent) + assertEquals(1, realm.where().count()) + assertEquals(3, realm.where().count()) + managedParent.deleteFromRealm() + assertEquals(0, realm.where().count()) + assertEquals(0, realm.where().count()) + } + + // Cascade deleting an embedded object will trigger its object listener. + @Test + fun deleteParent_triggerChildObjectNotifications() = looperThread.runBlocking { + val realm = Realm.getInstance(realm.configuration) + looperThread.closeAfterTest(realm) + + realm.executeTransaction { + val parent = EmbeddedSimpleParent("parent") + val child = EmbeddedSimpleChild("child") + parent.child = child + it.copyToRealm(parent) + } + + val child = realm.where().findFirst()!!.child!! + child.addChangeListener(RealmChangeListener { + if (!it.isValid) { + looperThread.testComplete() + } + }) + + realm.executeTransaction { + child.parent!!.deleteFromRealm() + } + } + + // Cascade deleting a parent will trigger the listener on any lists in child embedded + // objects + @Test + fun deleteParent_triggerChildListObjectNotifications() = looperThread.runBlocking { + val realm = Realm.getInstance(realm.configuration) + looperThread.closeAfterTest(realm) + + realm.executeTransaction { + val parent = EmbeddedSimpleListParent("parent") + val child1 = EmbeddedSimpleChild("child1") + val child2 = EmbeddedSimpleChild("child2") + parent.children.add(child1) + parent.children.add(child2) + it.copyToRealm(parent) + } + + val children: RealmList = realm.where() + .findFirst()!! + .children + + children.addChangeListener { list -> + if (!list.isValid) { + looperThread.testComplete() + } + } + + realm.executeTransaction { + realm.where().findFirst()!!.deleteFromRealm() + } + } + + + @Test + @Ignore("Add in another PR") + fun results_bulkUpdate() { + // What happens if you bulk update a RealmResults. Should it be allowed to use embeded + // objects here? + TODO() + } +} \ No newline at end of file diff --git a/realm/realm-library/src/androidTest/kotlin/io/realm/ObjectIdTests.kt b/realm/realm-library/src/androidTest/kotlin/io/realm/ObjectIdTests.kt index f1e755a5e1..7162bbb1f2 100644 --- a/realm/realm-library/src/androidTest/kotlin/io/realm/ObjectIdTests.kt +++ b/realm/realm-library/src/androidTest/kotlin/io/realm/ObjectIdTests.kt @@ -1,3 +1,18 @@ +/* + * Copyright 2020 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package io.realm import androidx.test.ext.junit.runners.AndroidJUnit4 diff --git a/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedCircularChild.kt b/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedCircularChild.kt new file mode 100644 index 0000000000..f706aeaab6 --- /dev/null +++ b/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedCircularChild.kt @@ -0,0 +1,29 @@ +/* + * Copyright 2020 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.realm.entities.embedded + +import io.realm.RealmObject +import io.realm.annotations.RealmClass +import java.util.* + +/** + * Embedded object that point to itself. Note, this is only allowed in the schema. The actual + * objects are not allowed to have circular references. + */ +@RealmClass(embedded = true) +open class EmbeddedCircularChild(var id: String = UUID.randomUUID().toString()) : RealmObject() { + var singleChild: EmbeddedCircularChild? = null +} diff --git a/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedCircularParent.kt b/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedCircularParent.kt new file mode 100644 index 0000000000..8209ca9436 --- /dev/null +++ b/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedCircularParent.kt @@ -0,0 +1,26 @@ +/* + * Copyright 2020 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.realm.entities.embedded + +import io.realm.RealmObject +import io.realm.annotations.PrimaryKey +import java.util.* + +// Parent pointing to an embedded object that has a circular schema, i.e. objects can point +// to themselves. Note, this isn't actually allowed at runtime. Only at schema validation time. +open class EmbeddedCircularParent(@PrimaryKey var id: String = UUID.randomUUID().toString()) : RealmObject() { + var singleChild: EmbeddedCircularChild? = null +} diff --git a/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedSimpleChild.kt b/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedSimpleChild.kt new file mode 100644 index 0000000000..345cbbf908 --- /dev/null +++ b/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedSimpleChild.kt @@ -0,0 +1,37 @@ +/* + * Copyright 2020 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.realm.entities.embedded + +import io.realm.RealmObject +import io.realm.annotations.LinkingObjects +import io.realm.annotations.RealmClass +import java.util.* + +/** + * The embedded object part of a simple object graph. This object can have two parents + * [EmbeddedSimpleParent] and [EmbeddedSimpleListParent]. + */ +@RealmClass(embedded = true) +open class EmbeddedSimpleChild(var id: String = UUID.randomUUID().toString()) : RealmObject() { + + @LinkingObjects("child") + val parent = EmbeddedSimpleParent() + + companion object { + const val NAME = "EmbeddedSimpleChild" + } + +} diff --git a/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedSimpleListParent.kt b/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedSimpleListParent.kt new file mode 100644 index 0000000000..507e7f1f22 --- /dev/null +++ b/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedSimpleListParent.kt @@ -0,0 +1,27 @@ +/* + * Copyright 2020 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.realm.entities.embedded + +import io.realm.RealmList +import io.realm.RealmObject +import io.realm.annotations.PrimaryKey +import java.util.* + +// Top-level object describing a simple embedded objects structure consisting of only a +// list of embedded objects. +open class EmbeddedSimpleListParent(@PrimaryKey var id: String = UUID.randomUUID().toString()) : RealmObject() { + var children: RealmList = RealmList() +} diff --git a/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedSimpleParent.kt b/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedSimpleParent.kt new file mode 100644 index 0000000000..1f7f07c15d --- /dev/null +++ b/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedSimpleParent.kt @@ -0,0 +1,25 @@ +/* + * Copyright 2020 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.realm.entities.embedded + +import io.realm.RealmObject +import io.realm.annotations.PrimaryKey +import java.util.* + +// Top-level object describing a simple embedded objects structure consisting of only an object reference. +open class EmbeddedSimpleParent(@PrimaryKey var id: String = UUID.randomUUID().toString()) : RealmObject() { + var child: EmbeddedSimpleChild? = null +} diff --git a/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedTreeLeaf.kt b/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedTreeLeaf.kt new file mode 100644 index 0000000000..6efb4022b3 --- /dev/null +++ b/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedTreeLeaf.kt @@ -0,0 +1,37 @@ +/* + * Copyright 2020 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.realm.entities.embedded + +import io.realm.RealmObject +import io.realm.annotations.LinkingObjects +import io.realm.annotations.PrimaryKey +import io.realm.annotations.RealmClass +import java.util.* + +// Middle-level node in a object-graph that is three-shaped, i.e. no circular references. +// The tree depth can be described as: +// - 1 TreeParent +// - 1 or more TreeNode's. I.e. a TreeNode can be the child of another TreeNode. +// - 1 or more TreeLeaf objects. TreeLeaf objects are always at the bottom of tree. +@RealmClass(embedded = true) +open class EmbeddedTreeLeaf(var id: String = UUID.randomUUID().toString()) : RealmObject() { + + @LinkingObjects("leafNode") + val parentRef: EmbeddedTreeNode? = null + + @LinkingObjects("leafNodeList") + val parentListRef: EmbeddedTreeNode? = null +} diff --git a/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedTreeNode.kt b/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedTreeNode.kt new file mode 100644 index 0000000000..bd70f05c7e --- /dev/null +++ b/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedTreeNode.kt @@ -0,0 +1,35 @@ +/* + * Copyright 2020 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.realm.entities.embedded + +import io.realm.RealmList +import io.realm.RealmObject +import io.realm.annotations.PrimaryKey +import io.realm.annotations.RealmClass +import java.util.* + +// Middle-level node in a object-graph that is three-shaped, i.e. no circular references. +// The tree depth can be described as: +// - 1 TreeParent +// - 1 or more TreeNode's. I.e. a TreeNode can be the child of another TreeNode. +// - 1 or more TreeLeaf objects. TreeLeaf objects are always at the bottom of tree. +@RealmClass(embedded = true) +open class EmbeddedTreeNode(var id: String = UUID.randomUUID().toString()) : RealmObject() { + var middleNode: EmbeddedTreeNode? = null + var leafNode: EmbeddedTreeLeaf? = null + var middleNodeList: RealmList = RealmList() + var leafNodeList: RealmList = RealmList() +} diff --git a/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedTreeParent.kt b/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedTreeParent.kt new file mode 100644 index 0000000000..4f2b035dbf --- /dev/null +++ b/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedTreeParent.kt @@ -0,0 +1,31 @@ +/* + * Copyright 2020 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.realm.entities.embedded + +import io.realm.RealmList +import io.realm.RealmObject +import io.realm.annotations.PrimaryKey +import java.util.* + +// Top-level node in a object-graph that is three-shaped, i.e. no circular references. +// The tree depth can be described as: +// - 1 TreeParent +// - 1 or more TreeNode's. I.e. a TreeNode can be the child of another TreeNode. +// - 1 or more TreeLeaf objects. TreeLeaf objects are always at the bottom of tree. +open class EmbeddedTreeParent(@PrimaryKey var id: String = UUID.randomUUID().toString()) : RealmObject() { + var middleNode: EmbeddedTreeNode? = null + var middleNodeList: RealmList = RealmList() +} diff --git a/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedWithConstructorArgs.kt b/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedWithConstructorArgs.kt new file mode 100644 index 0000000000..7ce1624ba7 --- /dev/null +++ b/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedWithConstructorArgs.kt @@ -0,0 +1,30 @@ +/* + * Copyright 2020 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.realm.entities.embedded + +import io.realm.RealmObject +import io.realm.annotations.Ignore + +open class EmbeddedWithConstructorArgs : RealmObject() { + var child: EmbeddedSimpleChild? = null + init { + child = EmbeddedSimpleChild(INNER_CHILD_ID) + } + + companion object { + const val INNER_CHILD_ID = "innerChild" + } +} diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/rule/BlockingLooperThread.kt b/realm/realm-library/src/androidTest/kotlin/io/realm/rule/BlockingLooperThread.kt similarity index 100% rename from realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/rule/BlockingLooperThread.kt rename to realm/realm-library/src/androidTest/kotlin/io/realm/rule/BlockingLooperThread.kt diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/SyncedRealmMigrationTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/SyncedRealmMigrationTests.kt index 4d1b63393a..937959fe2f 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/SyncedRealmMigrationTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/SyncedRealmMigrationTests.kt @@ -124,7 +124,7 @@ class SyncedRealmMigrationTests { .build() // Setup initial Realm schema (with a different primary key) - val expectedObjectSchema = OsObjectSchemaInfo.Builder(PrimaryKeyAsString.CLASS_NAME, 2, 0) + val expectedObjectSchema = OsObjectSchemaInfo.Builder(PrimaryKeyAsString.CLASS_NAME, false,2, 0) .addPersistedProperty(PrimaryKeyAsString.FIELD_PRIMARY_KEY, RealmFieldType.STRING, false, true, false) .addPersistedProperty(PrimaryKeyAsString.FIELD_ID, RealmFieldType.INTEGER, true, true, true) .build() diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsList.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsList.cpp index c5f6544108..f2023f6563 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsList.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsList.cpp @@ -22,6 +22,7 @@ #include "observable_collection_wrapper.hpp" #include "java_accessor.hpp" +#include "java_object_accessor.hpp" #include "java_exception_def.hpp" #include "jni_util/java_exception_thrower.hpp" #include "util.hpp" @@ -550,6 +551,43 @@ JNIEXPORT jobject JNICALL Java_io_realm_internal_OsList_nativeGetValue(JNIEnv* e return nullptr; } +JNIEXPORT jlong JNICALL Java_io_realm_internal_OsList_nativeCreateAndAddEmbeddedObject(JNIEnv* env, jclass, jlong native_list_ptr, jlong j_index) +{ + try { + List& list = reinterpret_cast(native_list_ptr)->collection(); + auto& realm = list.get_realm(); + auto& object_schema = list.get_object_schema(); + JavaContext ctx(env, realm, object_schema); + // Create dummy object. Properties must be added later. + // TODO CreatePolicy::Skip is a hack right after the object is inserted and before Schemas + // are validated. Figure out a better approach. + auto array_index = static_cast(j_index); + list.insert(ctx, array_index, JavaValue(std::map()), CreatePolicy::Skip); + return reinterpret_cast(list.get(array_index).get_key().value); + } + CATCH_STD() + return reinterpret_cast(nullptr); +} + + +JNIEXPORT jlong JNICALL Java_io_realm_internal_OsList_nativeCreateAndSetEmbeddedObject(JNIEnv* env, jclass, jlong native_list_ptr, jlong j_index) +{ + try { + List& list = reinterpret_cast(native_list_ptr)->collection(); + auto& realm = list.get_realm(); + auto& object_schema = list.get_object_schema(); + JavaContext ctx(env, realm, object_schema); + size_t array_index = static_cast(j_index); + // Create dummy object. Properties must be added later. + // TODO CreatePolicy::Skip is a hack right after the object is inserted and before Schemas + // are validated. Figure out a better approach. + list.set(ctx, array_index, JavaValue(std::map()), CreatePolicy::Skip); + return reinterpret_cast(list.get(list.size() - 1).get_key().value); + } + CATCH_STD() + return reinterpret_cast(nullptr); +} + JNIEXPORT jlong JNICALL Java_io_realm_internal_OsList_nativeFreeze(JNIEnv* env, jclass, jlong native_list_ptr, jlong frozen_realm_native_ptr) { try { diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp index 5bfcdf1439..7e3305fcbb 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp @@ -389,3 +389,24 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_OsObject_nativeCreateNewObjectWit CATCH_STD() return 0; } + +JNIEXPORT jlong JNICALL Java_io_realm_internal_OsObject_nativeCreateEmbeddedObject( + JNIEnv* env, jclass, jlong j_parent_table_ptr, jlong j_parent_object_key, jlong j_parent_column_key) +{ + try { + TableRef table = TBL_REF(j_parent_table_ptr); + ObjKey obj_key(static_cast(j_parent_object_key)); + Obj parent_obj = table->get_object(obj_key); + ColKey col_key(static_cast(j_parent_column_key)); + Obj child_obj; + if (table->get_column_type(col_key) == type_Link) { + child_obj = parent_obj.create_and_set_linked_object(col_key); + } else { + LnkLstPtr list = parent_obj.get_linklist_ptr(col_key); + child_obj = list->create_and_insert_linked_object(list->size()); + } + return to_jlong_or_not_found(child_obj.get_key()); + } + CATCH_STD() + return 0; +} diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsObjectSchemaInfo.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsObjectSchemaInfo.cpp index 8e7841e674..5835ced274 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsObjectSchemaInfo.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsObjectSchemaInfo.cpp @@ -36,12 +36,14 @@ static void finalize_object_schema(jlong ptr) } JNIEXPORT jlong JNICALL Java_io_realm_internal_OsObjectSchemaInfo_nativeCreateRealmObjectSchema(JNIEnv* env, jclass, - jstring j_name_str) + jstring j_name_str, + jboolean j_embedded) { try { JStringAccessor name(env, j_name_str); ObjectSchema* object_schema = new ObjectSchema(); object_schema->name = name; + object_schema->is_embedded = to_bool(j_embedded); return reinterpret_cast(object_schema); } CATCH_STD() @@ -129,3 +131,13 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_OsObjectSchemaInfo_nativeGetPrima CATCH_STD() return reinterpret_cast(nullptr); } + +JNIEXPORT jboolean JNICALL Java_io_realm_internal_OsObjectSchemaInfo_nativeIsEmbedded(JNIEnv* env, jclass, jlong native_ptr) +{ + try { + auto& object_schema = *reinterpret_cast(native_ptr); + return to_jbool(object_schema.is_embedded); + } + CATCH_STD() + return to_jbool(false); +} diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp index 60f3231742..ad93293322 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp @@ -939,3 +939,23 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeFreeze(JNIEnv*, jclas TableRef* frozen_table = new TableRef(shared_realm->import_copy_of(table)); return reinterpret_cast(frozen_table); } + +JNIEXPORT jboolean JNICALL Java_io_realm_internal_Table_nativeIsEmbedded(JNIEnv* env, jclass, jlong j_table_ptr) +{ + try { + TableRef table = TableRef(TBL_REF(j_table_ptr)); + return to_jbool(table->is_embedded()); + } + CATCH_STD() + return false; +} + +JNIEXPORT jboolean JNICALL Java_io_realm_internal_Table_nativeSetEmbedded(JNIEnv* env, jclass, jlong j_table_ptr, jboolean j_embedded) +{ + try { + TableRef table = TableRef(TBL_REF(j_table_ptr)); + return to_jbool(table->set_embedded(to_bool(j_embedded))); + } + CATCH_STD() + return false; +} diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_UncheckedRow.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_UncheckedRow.cpp index fb00218b93..6506ca5216 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_UncheckedRow.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_UncheckedRow.cpp @@ -476,3 +476,18 @@ JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetObjectId(JNI } CATCH_STD() } + +JNIEXPORT jlong JNICALL Java_io_realm_internal_UncheckedRow_nativeCreateEmbeddedObject(JNIEnv* env, jobject, + jlong j_obj_ptr, + jlong j_column_key) +{ + if (!ROW_VALID(env, OBJ(j_obj_ptr))) { + return -1; + } + try { + Obj embedded_object = OBJ(j_obj_ptr)->create_and_set_linked_object(ColKey(j_column_key)); + return reinterpret_cast(embedded_object.get_key().value); + } + CATCH_STD() + return -1; +} diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsObjectBuilder.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsObjectBuilder.cpp index 2427b6c3f3..57bd430a55 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsObjectBuilder.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsObjectBuilder.cpp @@ -176,8 +176,13 @@ static inline const ObjectSchema& get_schema(const Schema& schema, TableRef tabl return *it; } -JNIEXPORT jlong JNICALL Java_io_realm_internal_objectstore_OsObjectBuilder_nativeCreateOrUpdate - (JNIEnv* env, jclass, jlong shared_realm_ptr, jlong table_ref_ptr, jlong builder_ptr, jboolean update_existing, jboolean ignore_same_values) +JNIEXPORT jlong JNICALL Java_io_realm_internal_objectstore_OsObjectBuilder_nativeCreateOrUpdateTopLevelObject(JNIEnv* env, + jclass, + jlong shared_realm_ptr, + jlong table_ref_ptr, + jlong builder_ptr, + jboolean update_existing, + jboolean ignore_same_values) { try { SharedRealm shared_realm = *(reinterpret_cast(shared_realm_ptr)); @@ -202,6 +207,31 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_objectstore_OsObjectBuilder_nativ return realm::npos; } +JNIEXPORT jlong JNICALL Java_io_realm_internal_objectstore_OsObjectBuilder_nativeUpdateEmbeddedObject(JNIEnv* env, + jclass, + jlong shared_realm_ptr, + jlong table_ref_ptr, + jlong builder_ptr, + jlong j_obj_key, + jboolean ignore_same_values) +{ + try { + SharedRealm shared_realm = *(reinterpret_cast(shared_realm_ptr)); + CreatePolicy policy = (ignore_same_values) ? CreatePolicy::UpdateModified : CreatePolicy::UpdateAll; + TableRef table = TBL_REF(table_ref_ptr); + ObjKey embedded_object_key(j_obj_key); + const auto& schema = shared_realm->schema(); + const ObjectSchema& object_schema = get_schema(schema, table); + JavaContext ctx(env, shared_realm, object_schema); + auto list = *reinterpret_cast(builder_ptr); + JavaValue values = JavaValue(list); + Object obj = Object::create(ctx, shared_realm, object_schema, values, policy, embedded_object_key); + return reinterpret_cast(new Obj(obj.obj())); + } + CATCH_STD() + return realm::npos; +} + JNIEXPORT jlong JNICALL Java_io_realm_internal_objectstore_OsObjectBuilder_nativeStartList (JNIEnv* env, jclass, jlong list_size) { diff --git a/realm/realm-library/src/main/java/io/realm/FrozenPendingRow.java b/realm/realm-library/src/main/java/io/realm/FrozenPendingRow.java index 693fe41909..58fc0fb63f 100644 --- a/realm/realm-library/src/main/java/io/realm/FrozenPendingRow.java +++ b/realm/realm-library/src/main/java/io/realm/FrozenPendingRow.java @@ -197,6 +197,11 @@ public void setObjectId(long columnKey, ObjectId value) { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } + @Override + public long createEmbeddedObject(long columnKey) { + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); + } + @Override public boolean isValid() { return false; diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index 364f8d6d2b..716080fa74 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -47,6 +47,7 @@ import javax.annotation.Nullable; import io.reactivex.Flowable; +import io.realm.annotations.RealmClass; import io.realm.exceptions.RealmException; import io.realm.exceptions.RealmFileException; import io.realm.exceptions.RealmMigrationNeededException; @@ -62,6 +63,7 @@ import io.realm.internal.RealmNotifier; import io.realm.internal.RealmObjectProxy; import io.realm.internal.RealmProxyMediator; +import io.realm.internal.Row; import io.realm.internal.Table; import io.realm.internal.Util; import io.realm.internal.annotations.ObjectServer; @@ -158,9 +160,9 @@ private Realm(RealmCache cache, OsSharedRealm.VersionID version) { schema = new ImmutableRealmSchema(this, new ColumnIndices(configuration.getSchemaMediator(), sharedRealm.getSchemaInfo())); // FIXME: This is to work around the different behaviour between the read only Realms in the Object Store and - // in current java implementation. Opening a read only Realm with some missing schemas is allowed by Object - // Store and realm-cocoa. In that case, any query based on the missing schema should just return an empty - // results. Fix this together with https://github.com/realm/realm-java/issues/2953 + // in current java implementation. Opening a read only Realm with some missing schemas is allowed by Object + // Store and realm-cocoa. In that case, any query based on the missing schema should just return an empty + // results. Fix this together with https://github.com/realm/realm-java/issues/2953 if (configuration.isReadOnly()) { RealmProxyMediator mediator = configuration.getSchemaMediator(); Set> classes = mediator.getModelClasses(); @@ -964,6 +966,10 @@ private Scanner getFullStringScanner(InputStream in) { */ public E createObject(Class clazz) { checkIfValid(); + RealmProxyMediator mediator = configuration.getSchemaMediator(); + if (mediator.isEmbedded(clazz)) { + throw new IllegalArgumentException("This class is marked embedded. Use `createEmbeddedObject(class, parent, property)` instead: " + mediator.getSimpleClassName(clazz)); + } return createObjectInternal(clazz, true, Collections.emptyList()); } @@ -1011,9 +1017,63 @@ E createObjectInternal( */ public E createObject(Class clazz, @Nullable Object primaryKeyValue) { checkIfValid(); + RealmProxyMediator mediator = configuration.getSchemaMediator(); + if (mediator.isEmbedded(clazz)) { + throw new IllegalArgumentException("This class is marked embedded. Use `createEmbeddedObject(class, parent, property)` instead: " + mediator.getSimpleClassName(clazz)); + } return createObjectInternal(clazz, primaryKeyValue, true, Collections.emptyList()); } + /** + * Instantiates and adds a new embedded object to the Realm. + *

+ * This method should only be used to created objects of types marked as embedded. + * + * @param clazz the Class of the object to create. It must be marked with {@code \@RealmClass(embedded = true)}. + * @param parent The parent object which should a reference to the embedded object. If the parent property is a list + * the embedded object will be added to the end of that list. + * @param parentProperty the property in the parent class which holds the reference. + * @return the newly created embedded object. + * @throws IllegalArgumentException if {@code clazz} is not an embedded class or if the property + * in the parent class cannot hold objects of the appropriate type. + * @see RealmClass#embedded() + */ + public E createEmbeddedObject(Class clazz, RealmModel parentObject, String parentProperty) { + checkIfValid(); + Util.checkNull(parentObject, "parentObject"); + Util.checkEmpty(parentProperty, "parentProperty"); + if (!RealmObject.isManaged(parentObject) || !RealmObject.isValid(parentObject)) { + throw new IllegalArgumentException("Only valid, managed objects can be a parent to an embedded object."); + } + RealmObjectProxy proxy = (RealmObjectProxy) parentObject; + long parentPropertyColKey = schema.getSchemaForClass(parentObject.getClass()).getColumnKey(parentProperty); + RealmFieldType parentPropertyType = schema.getSchemaForClass(parentObject.getClass()).getFieldType(parentProperty); + Row embeddedObject; + switch(parentPropertyType) { + case OBJECT: { + // FIXME: Check type of link + long objKey = proxy.realmGet$proxyState().getRow$realm().createEmbeddedObject(parentPropertyColKey); + embeddedObject = getTable(clazz).getUncheckedRow(objKey); + break; + } + case LIST: { + // FIXME: Check type of link + long objKey = proxy.realmGet$proxyState().getRow$realm().getModelList(parentPropertyColKey).createAndAddEmbeddedObject(); + embeddedObject = getTable(clazz).getUncheckedRow(objKey); + break; + } + default: + throw new IllegalArgumentException("Parent property is not a reference to embedded objects of the appropriate type: " + parentPropertyType); + } + + //noinspection unchecked + return (E) configuration.getSchemaMediator().newInstance(clazz, + this, + embeddedObject, + schema.getColumnInfo(clazz), + true, Collections.EMPTY_LIST); + } + /** * Same as {@link #createObject(Class, Object)} but this does not check the thread. * @@ -1057,6 +1117,7 @@ E createObjectInternal( */ public E copyToRealm(E object, ImportFlag... flags) { checkNotNullObject(object); + return copyOrUpdate(object, false, new HashMap<>(), Util.toSet(flags)); } @@ -1685,6 +1746,10 @@ private E copyOrUpdate(E object, boolean update, Map E copyToRealmIfNeeded(E object) { + private boolean checkCanObjectBeCopied(BaseRealm realm, RealmModel object) { if (object instanceof RealmObjectProxy) { RealmObjectProxy proxy = (RealmObjectProxy) object; @@ -1559,7 +1604,7 @@ private E copyToRealmIfNeeded(E object) { String objectClassName = ((DynamicRealmObject) object).getType(); if (listClassName.equals(objectClassName)) { // Same Realm instance and same target table - return object; + return false; } else { // Different target table throw new IllegalArgumentException(String.format(Locale.US, @@ -1580,11 +1625,15 @@ private E copyToRealmIfNeeded(E object) { if (realm != proxy.realmGet$proxyState().getRealm$realm()) { throw new IllegalArgumentException("Cannot copy an object from another Realm instance."); } - return object; + return false; } } } + return true; + } + // Transparently copies an unmanaged object or managed object from another Realm to the Realm backing this RealmList. + private E copyToRealm(E object) { // At this point the object can only be a typed object, so the backing Realm cannot be a DynamicRealm. Realm realm = (Realm) this.realm; if (OsObjectStore.getPrimaryKeyForObject(realm.getSharedRealm(), @@ -1594,6 +1643,15 @@ private E copyToRealmIfNeeded(E object) { return realm.copyToRealm(object); } } + + private void updateEmbeddedObject(RealmModel unmanagedObject, long objKey) { + RealmProxyMediator schemaMediator = realm.getConfiguration().getSchemaMediator(); + Class modelClass = Util.getOriginalModelClass(unmanagedObject.getClass()); + Table table = ((Realm) realm).getTable(modelClass); + RealmModel managedObject = schemaMediator.newInstance(modelClass, realm, table.getUncheckedRow(objKey), realm.getSchema().getColumnInfo(modelClass), true, Collections.EMPTY_LIST); + schemaMediator.updateEmbeddedObject((Realm) realm, unmanagedObject, managedObject, new HashMap<>(), Collections.EMPTY_SET); + } + } /** diff --git a/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java b/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java index 85bd1edb88..b96f95632b 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java +++ b/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java @@ -26,6 +26,7 @@ import javax.annotation.Nullable; +import io.realm.annotations.RealmClass; import io.realm.annotations.Required; import io.realm.internal.CheckedRow; import io.realm.internal.ColumnInfo; @@ -419,6 +420,48 @@ public RealmFieldType getFieldType(String fieldName) { return table.getColumnType(columnKey); } + /** + * Returns {@code true} if objects of this type are considered "embedded". + * See {@link RealmClass#embedded()} for further details. + * + * @return {@code true} if objects of this type are embedded. {@code false} if not. + */ + public boolean isEmbedded() { + return table.isEmbedded(); + } + + /** + * Converts the class to be embedded or not. + *

+ * A class can only be marked as embedded if the following invariants are satisfied: + *

+ * + * @throws IllegalStateException if the class could not be converted because it broke some of the Embedded Objects invariants. + * @see RealmClass#embedded() + */ + public void setEmbedded(boolean embedded) { + if (hasPrimaryKey()) { + throw new IllegalStateException("Embedded classes cannot have primary keys. This class " + + "has a primary key defined so cannot be marked as embedded: " + getClassName()); + } + boolean setEmbedded = table.setEmbedded(embedded); + if (!setEmbedded && embedded) { + throw new IllegalStateException("The class could not be marked as embedded as some " + + "objects of this type break some of the Embedded Objects invariants. In order to convert " + + "all objects to be embedded, they must have one and exactly one parent object" + + "pointing to them."); + } + } + /** * Get a parser for a field descriptor. * diff --git a/realm/realm-library/src/main/java/io/realm/internal/InvalidRow.java b/realm/realm-library/src/main/java/io/realm/internal/InvalidRow.java index afd7f74aa6..92545b82bf 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/InvalidRow.java +++ b/realm/realm-library/src/main/java/io/realm/internal/InvalidRow.java @@ -192,6 +192,11 @@ public void setObjectId(long columnKey, ObjectId value) { throw getStubException(); } + @Override + public long createEmbeddedObject(long columnKey) { + throw getStubException(); + } + @Override public boolean isValid() { return false; diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsList.java b/realm/realm-library/src/main/java/io/realm/internal/OsList.java index a95145d955..5e415e00da 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsList.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsList.java @@ -320,6 +320,19 @@ public OsList freeze(OsSharedRealm frozenRealm) { (targetTable != null) ? targetTable.freeze(frozenRealm) : null); } + public long createAndAddEmbeddedObject() { + return nativeCreateAndAddEmbeddedObject(nativePtr, size()); + } + + public long createAndAddEmbeddedObject(long index) { + return nativeCreateAndAddEmbeddedObject(nativePtr, index); + } + + public long createAndSetEmbeddedObject(long index) { + return nativeCreateAndSetEmbeddedObject(nativePtr, index); + } + + private static native long nativeGetFinalizerPtr(); // TODO: nativeTablePtr is not necessary. It is used to create FieldDescriptor which should be generated from @@ -418,4 +431,11 @@ public OsList freeze(OsSharedRealm frozenRealm) { private native void nativeStopListening(long nativePtr); private static native long nativeFreeze(long nativePtr, long sharedRealmNativePtr); + + // Create an "empty" embedded object at the end of the list + private static native long nativeCreateAndAddEmbeddedObject(long nativePtr, long index); + + // Replaces the embedded object and index with a new "empty" embedded object + private static native long nativeCreateAndSetEmbeddedObject(long nativePtr, long index); + } diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsObject.java b/realm/realm-library/src/main/java/io/realm/internal/OsObject.java index ab139b299b..a7b94cd1a3 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsObject.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsObject.java @@ -254,6 +254,10 @@ public static long createRowWithPrimaryKey(Table table, long primaryKeyColumnInd } } + public static long createEmbeddedObject(Table parentTable, long parentObjectKey, long parentColumnKey) { + return nativeCreateEmbeddedObject(parentTable.getNativePtr(), parentObjectKey, parentColumnKey); + } + // Called by JNI @SuppressWarnings("unused") private void notifyChangeListeners(String[] changedFields) { @@ -301,4 +305,6 @@ private static native long nativeCreateNewObjectWithObjectIdPrimaryKey(long shar long tableRefPtr, long pk_column_index, @Nullable String data); + private static native long nativeCreateEmbeddedObject(long parentTablePtr, long parentObjectKey, long parentObjectColumnKey); + } diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsObjectSchemaInfo.java b/realm/realm-library/src/main/java/io/realm/internal/OsObjectSchemaInfo.java index cf2e4de4b5..43e7e32105 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsObjectSchemaInfo.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsObjectSchemaInfo.java @@ -30,6 +30,7 @@ public class OsObjectSchemaInfo implements NativeObject { public static class Builder { private final String className; private final long[] persistedPropertyPtrArray; + private final boolean embedded; private int persistedPropertyPtrCurPos = 0; private final long[] computedPropertyPtrArray; private int computedPropertyPtrCurPos = 0; @@ -40,8 +41,9 @@ public static class Builder { * * @param className name of the class */ - public Builder(String className, int persistedPropertyCapacity, int computedPropertyCapacity) { + public Builder(String className, boolean embedded, int persistedPropertyCapacity, int computedPropertyCapacity) { this.className = className; + this.embedded = embedded; this.persistedPropertyPtrArray = new long[persistedPropertyCapacity]; this.computedPropertyPtrArray = new long[computedPropertyCapacity]; } @@ -125,7 +127,7 @@ public OsObjectSchemaInfo build() { if (persistedPropertyPtrCurPos == -1 || computedPropertyPtrCurPos == -1) { throw new IllegalStateException("'OsObjectSchemaInfo.build()' has been called before on this object."); } - OsObjectSchemaInfo info = new OsObjectSchemaInfo(className); + OsObjectSchemaInfo info = new OsObjectSchemaInfo(className, embedded); nativeAddProperties(info.nativePtr, persistedPropertyPtrArray, computedPropertyPtrArray); persistedPropertyPtrCurPos = -1; computedPropertyPtrCurPos = -1; @@ -142,8 +144,8 @@ public OsObjectSchemaInfo build() { * * @param className name of the class */ - private OsObjectSchemaInfo(String className) { - this(nativeCreateRealmObjectSchema(className)); + private OsObjectSchemaInfo(String className, boolean embedded) { + this(nativeCreateRealmObjectSchema(className, embedded)); } /** @@ -185,6 +187,11 @@ public Property getProperty(String propertyName) { return propertyPtr == 0 ? null : new Property(nativeGetPrimaryKeyProperty(nativePtr)); } + + public boolean isEmbedded() { + return nativeIsEmbedded(nativePtr); + } + @Override public long getNativePtr() { return nativePtr; @@ -195,7 +202,7 @@ public long getNativeFinalizerPtr() { return nativeFinalizerPtr; } - private static native long nativeCreateRealmObjectSchema(String className); + private static native long nativeCreateRealmObjectSchema(String className, boolean embedded); private static native long nativeGetFinalizerPtr(); @@ -210,4 +217,5 @@ public long getNativeFinalizerPtr() { // Return nullptr if it doesn't have a primary key. private static native long nativeGetPrimaryKeyProperty(long nativePtr); + private static native boolean nativeIsEmbedded(long nativePtr); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java b/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java index f95bf2a138..70095a7411 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java +++ b/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java @@ -221,6 +221,11 @@ public void setObjectId(long columnKey, ObjectId value) { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } + @Override + public long createEmbeddedObject(long columnKey) { + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); + } + @Override public boolean isValid() { return false; diff --git a/realm/realm-library/src/main/java/io/realm/internal/RealmProxyMediator.java b/realm/realm-library/src/main/java/io/realm/internal/RealmProxyMediator.java index fc28b88e67..cabc4b1a55 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/RealmProxyMediator.java +++ b/realm/realm-library/src/main/java/io/realm/internal/RealmProxyMediator.java @@ -197,6 +197,23 @@ public abstract E newInstance(Class clazz, */ public abstract E createDetachedCopy(E realmObject, int maxDepth, Map> cache); + /** + * Returns whether or not this class is considered "embedded". + */ + public abstract boolean isEmbedded(Class clazz); + + + /** + * Updates an embedded object with the values from an unmanaged object. + * + * @param realm the reference to the {@link Realm} where the object will be copied. + * @param unmanagedObject the unmanaged objects whose values should be used to update the manged object + * @param managedObject the managed object that should be updated + * @param cache the cache for mapping between unmanaged objects and their {@link RealmObjectProxy} representation. + * @param flags any special flags controlling the behaviour of the import. + */ + public abstract void updateEmbeddedObject(Realm realm, E unmanagedObject, E managedObject, Map cache, Set flags); + /** * Returns whether Realm transformer has been applied or not. Subclasses of this class are * created by the annotation processor and the Realm transformer will add an override of @@ -238,4 +255,8 @@ protected static RealmException getMissingProxyClassException(String className) return new RealmException( String.format("'%s' is not part of the schema for this Realm.", className)); } + + protected static IllegalStateException getNotEmbeddedClassException(String className) { + return new IllegalStateException("This class is not marked embedded: " + className); + } } diff --git a/realm/realm-library/src/main/java/io/realm/internal/Row.java b/realm/realm-library/src/main/java/io/realm/internal/Row.java index 08374fb427..ce5ddeaf3d 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Row.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Row.java @@ -120,6 +120,11 @@ public interface Row { void setObjectId(long columnKey, ObjectId value); + // Creates a new Embedded object in the given property. + // This will replace any existing object which will be + // deleted. The Obj pointer for the new object is returned. + long createEmbeddedObject(long columnKey); + /** * Checks if the row is still valid. * diff --git a/realm/realm-library/src/main/java/io/realm/internal/Table.java b/realm/realm-library/src/main/java/io/realm/internal/Table.java index d388ca1bcc..ad1183feb2 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Table.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Table.java @@ -702,6 +702,18 @@ public Table freeze(OsSharedRealm frozenRealm) { return new Table(frozenRealm, nativeFreeze(frozenRealm.getNativePtr(), nativeTableRefPtr)); } + public boolean isEmbedded() { + return nativeIsEmbedded(nativeTableRefPtr); + } + + /** + * Returns true if the state was changed, false if not. If false was returned, it meant + * some invariant was broken when trying to change the state + */ + public boolean setEmbedded(boolean embedded) { + return nativeSetEmbedded(nativeTableRefPtr, embedded); + } + @Nullable public static String getClassNameForTable(@Nullable String name) { if (name == null) { return null; } @@ -844,4 +856,8 @@ public static String getTableNameForClass(String name) { private static native long nativeGetFinalizerPtr(); private static native long nativeFreeze(long frozenSharedRealmPtr, long nativeTableRefPtr); + + private static native boolean nativeIsEmbedded(long nativeTableRefPtr); + + private static native boolean nativeSetEmbedded(long nativeTableRefPtr, boolean isEmbedded); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java b/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java index 095fb4f9a5..9ad00ab02a 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java +++ b/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java @@ -306,6 +306,12 @@ public void setObjectId(long columnKey, @Nullable ObjectId value) { } } + @Override + public long createEmbeddedObject(long columnKey) { + parent.checkImmutable(); + return nativeCreateEmbeddedObject(nativePtr, columnKey); + } + /** * Converts the unchecked Row to a checked variant. * @@ -410,5 +416,7 @@ public boolean isLoaded() { protected native long nativeFreeze(long nativeRowPtr, long frozenRealmNativePtr); + protected native long nativeCreateEmbeddedObject(long nativeRowPtr, long columnKey); + private static native long nativeGetFinalizerPtr(); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/modules/CompositeMediator.java b/realm/realm-library/src/main/java/io/realm/internal/modules/CompositeMediator.java index b95207f4d6..4f061c4ffb 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/modules/CompositeMediator.java +++ b/realm/realm-library/src/main/java/io/realm/internal/modules/CompositeMediator.java @@ -164,6 +164,18 @@ public E createDetachedCopy(E realmObject, int maxDepth, return mediator.createDetachedCopy(realmObject, maxDepth, cache); } + @Override + public boolean isEmbedded(Class clazz) { + RealmProxyMediator mediator = getMediator(Util.getOriginalModelClass(clazz)); + return mediator.isEmbedded(clazz); + } + + @Override + public void updateEmbeddedObject(Realm realm, E unmanagedObject, E managedObject, Map cache, Set flags) { + RealmProxyMediator mediator = getMediator(Util.getOriginalModelClass(managedObject.getClass())); + mediator.updateEmbeddedObject(realm, unmanagedObject, managedObject, cache, flags); + } + @Override public boolean transformerApplied() { for (Map.Entry, RealmProxyMediator> entry : mediators.entrySet()) { diff --git a/realm/realm-library/src/main/java/io/realm/internal/modules/FilterableMediator.java b/realm/realm-library/src/main/java/io/realm/internal/modules/FilterableMediator.java index ceb23d7c05..5b87416138 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/modules/FilterableMediator.java +++ b/realm/realm-library/src/main/java/io/realm/internal/modules/FilterableMediator.java @@ -162,6 +162,18 @@ public E createDetachedCopy(E realmObject, int maxDepth, return originalMediator.createDetachedCopy(realmObject, maxDepth, cache); } + @Override + public boolean isEmbedded(Class clazz) { + checkSchemaHasClass(Util.getOriginalModelClass(clazz)); + return originalMediator.isEmbedded(clazz); + } + + @Override + public void updateEmbeddedObject(Realm realm, E unmanagedObject, E managedObject, Map cache, Set flags) { + checkSchemaHasClass(Util.getOriginalModelClass(managedObject.getClass())); + originalMediator.updateEmbeddedObject(realm, unmanagedObject, managedObject, cache, flags); + } + @Override public boolean transformerApplied() { //noinspection SimplifiableIfStatement diff --git a/realm/realm-library/src/main/java/io/realm/internal/objectstore/OsObjectBuilder.java b/realm/realm-library/src/main/java/io/realm/internal/objectstore/OsObjectBuilder.java index 201efe62e5..a2daace840 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/objectstore/OsObjectBuilder.java +++ b/realm/realm-library/src/main/java/io/realm/internal/objectstore/OsObjectBuilder.java @@ -39,7 +39,8 @@ * This class is a wrapper around building up object data for calling `Object::create()` *

* Fill the object data by calling the various `addX()` methods, then create a new Object or update - * an existing one by calling {@link #createNewObject()} or {@link #updateExistingObject()}. + * an existing one by calling {@link #createNewObject()}, {@link #updateExistingTopLevelObject()} or. + * {@link #updateExistingEmbeddedObject(RealmObjectProxy)} *

* This class assumes it is only being used from within a write transaction. Using it outside one * will result in undefined behaviour. @@ -403,12 +404,28 @@ private void addEmptyList(long columnKey) { /** * Updates any existing object if it exists, otherwise creates a new one. + *

+ * Updating an existing object requires that the primary key is defined as one of the fields. + *

+ * The builder is automatically closed after calling this method. + */ + public void updateExistingTopLevelObject() { + try { + nativeCreateOrUpdateTopLevelObject(sharedRealmPtr, tablePtr, builderPtr, true, ignoreFieldsWithSameValue); + } finally { + close(); + } + } + + /** + * Updates an existing embedded object. * * The builder is automatically closed after calling this method. */ - public void updateExistingObject() { + public void updateExistingEmbeddedObject(RealmObjectProxy embeddedObject) { try { - nativeCreateOrUpdate(sharedRealmPtr, tablePtr, builderPtr, true, ignoreFieldsWithSameValue); + long objKey = embeddedObject.realmGet$proxyState().getRow$realm().getObjectKey(); + nativeUpdateEmbeddedObject(sharedRealmPtr, tablePtr, builderPtr, objKey, ignoreFieldsWithSameValue); } finally { close(); } @@ -422,7 +439,7 @@ public void updateExistingObject() { public UncheckedRow createNewObject() { UncheckedRow row; try { - long rowPtr = nativeCreateOrUpdate(sharedRealmPtr, tablePtr, builderPtr, false, false); + long rowPtr = nativeCreateOrUpdateTopLevelObject(sharedRealmPtr, tablePtr, builderPtr, false, false); row = new UncheckedRow(context, table, rowPtr); } finally { close(); @@ -451,12 +468,18 @@ private interface ItemCallback { private static native long nativeCreateBuilder(); private static native void nativeDestroyBuilder(long builderPtr); - private static native long nativeCreateOrUpdate(long sharedRealmPtr, + private static native long nativeCreateOrUpdateTopLevelObject(long sharedRealmPtr, long tablePtr, long builderPtr, boolean updateExistingObject, boolean ignoreFieldsWithSameValue); + private static native long nativeUpdateEmbeddedObject(long sharedRealmPtr, + long tablePtr, + long builderPtr, + long objKey, + boolean ignoreFieldsWithSameValue); + // Add simple properties private static native void nativeAddNull(long builderPtr, long columnKey); private static native void nativeAddInteger(long builderPtr, long columnKey, long val);