/* * Copyright 2015 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 java.util.Arrays; import java.util.Date; import java.util.Locale; import io.realm.exceptions.RealmException; import io.realm.internal.CheckedRow; import io.realm.internal.LinkView; import io.realm.internal.RealmObjectProxy; import io.realm.internal.Row; import io.realm.internal.Table; import io.realm.internal.UncheckedRow; import io.realm.internal.android.JsonUtils; /** * Class that wraps a normal RealmObject in order to allow dynamic access instead of a typed interface. * Using a DynamicRealmObject is slower than using the regular RealmObject class. */ @SuppressWarnings("WeakerAccess") public class DynamicRealmObject extends RealmObject implements RealmObjectProxy { static final String MSG_LINK_QUERY_NOT_SUPPORTED = "Queries across relationships are not supported"; private final ProxyState<DynamicRealmObject> proxyState = new ProxyState<>(this); /** * Creates a dynamic Realm object based on an existing object. * * @param obj the Realm object to convert to a dynamic object. Only objects managed by {@link Realm} can be used. * @throws IllegalArgumentException if object isn't managed by Realm or is a {@link DynamicRealmObject} already. */ public DynamicRealmObject(RealmModel obj) { if (obj == null) { throw new IllegalArgumentException("A non-null object must be provided."); } if (obj instanceof DynamicRealmObject) { throw new IllegalArgumentException("The object is already a DynamicRealmObject: " + obj); } if (!RealmObject.isManaged(obj)) { throw new IllegalArgumentException("An object managed by Realm must be provided. This " + "is an unmanaged object."); } if (!RealmObject.isValid(obj)) { throw new IllegalArgumentException("A valid object managed by Realm must be provided. " + "This object was deleted."); } RealmObjectProxy proxy = (RealmObjectProxy) obj; Row row = proxy.realmGet$proxyState().getRow$realm(); proxyState.setRealm$realm(proxy.realmGet$proxyState().getRealm$realm()); proxyState.setRow$realm(((UncheckedRow) row).convertToChecked()); proxyState.setConstructionFinished(); } // row must be an instance of CheckedRow or InvalidRow DynamicRealmObject(BaseRealm realm, Row row) { proxyState.setRealm$realm(realm); proxyState.setRow$realm(row); proxyState.setConstructionFinished(); } /** * Returns the value for the given field. * * @param fieldName name of the field. * @return the field value. * @throws ClassCastException if the field doesn't contain a field of the defined return type. */ @SuppressWarnings({"unchecked", "TypeParameterUnusedInFormals"}) public <E> E get(String fieldName) { proxyState.getRealm$realm().checkIfValid(); long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); RealmFieldType type = proxyState.getRow$realm().getColumnType(columnIndex); switch (type) { case BOOLEAN: return (E) Boolean.valueOf(proxyState.getRow$realm().getBoolean(columnIndex)); case INTEGER: return (E) Long.valueOf(proxyState.getRow$realm().getLong(columnIndex)); case FLOAT: return (E) Float.valueOf(proxyState.getRow$realm().getFloat(columnIndex)); case DOUBLE: return (E) Double.valueOf(proxyState.getRow$realm().getDouble(columnIndex)); case STRING: return (E) proxyState.getRow$realm().getString(columnIndex); case BINARY: return (E) proxyState.getRow$realm().getBinaryByteArray(columnIndex); case DATE: return (E) proxyState.getRow$realm().getDate(columnIndex); case OBJECT: return (E) getObject(fieldName); case LIST: return (E) getList(fieldName); case UNSUPPORTED_TABLE: case UNSUPPORTED_MIXED: default: throw new IllegalStateException("Field type not supported: " + type); } } /** * Returns the {@code boolean} value for a given field. * <p> * If the field is nullable, use {@link #isNull(String)} to check for {@code null} instead of using * this method. * * @param fieldName the name of the field. * @return the boolean value. * @throws IllegalArgumentException if field name doesn't exist or it doesn't contain booleans. * @throws io.realm.exceptions.RealmException if the return value would be {@code null}. */ public boolean getBoolean(String fieldName) { proxyState.getRealm$realm().checkIfValid(); long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); try { return proxyState.getRow$realm().getBoolean(columnIndex); } catch (IllegalArgumentException e) { checkFieldType(fieldName, columnIndex, RealmFieldType.BOOLEAN); throw e; } } /** * Returns the {@code int} value for a given field. * <p> * If the field is nullable, use {@link #isNull(String)} to check for {@code null} instead of using * this method. * * @param fieldName the name of the field. * @return the int value. Integer values exceeding {@code Integer.MAX_VALUE} will wrap. * @throws IllegalArgumentException if field name doesn't exist or it doesn't contain integers. * @throws io.realm.exceptions.RealmException if the return value would be {@code null}. */ public int getInt(String fieldName) { return (int) getLong(fieldName); } /** * Returns the {@code short} value for a given field. * <p> * If the field is nullable, use {@link #isNull(String)} to check for {@code null} instead of using * this method. * * @param fieldName the name of the field. * @return the short value. Integer values exceeding {@code Short.MAX_VALUE} will wrap. * @throws IllegalArgumentException if field name doesn't exist or it doesn't contain integers. * @throws io.realm.exceptions.RealmException if the return value would be {@code null}. */ public short getShort(String fieldName) { return (short) getLong(fieldName); } /** * Returns the {@code long} value for a given field. * <p> * If the field is nullable, use {@link #isNull(String)} to check for {@code null} instead of using * this method. * * @param fieldName the name of the field. * @return the long value. Integer values exceeding {@code Long.MAX_VALUE} will wrap. * @throws IllegalArgumentException if field name doesn't exist or it doesn't contain integers. * @throws io.realm.exceptions.RealmException if the return value would be {@code null}. */ public long getLong(String fieldName) { proxyState.getRealm$realm().checkIfValid(); long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); try { return proxyState.getRow$realm().getLong(columnIndex); } catch (IllegalArgumentException e) { checkFieldType(fieldName, columnIndex, RealmFieldType.INTEGER); throw e; } } /** * Returns the {@code byte} value for a given field. * <p> * If the field is nullable, use {@link #isNull(String)} to check for {@code null} instead of using * this method. * * @param fieldName the name of the field. * @return the byte value. * @throws IllegalArgumentException if field name doesn't exist or it doesn't contain integers. * @throws io.realm.exceptions.RealmException if the return value would be {@code null}. */ public byte getByte(String fieldName) { return (byte) getLong(fieldName); } /** * Returns the {@code float} value for a given field. * <p> * If the field is nullable, use {@link #isNull(String)} to check for {@code null} instead of using * this method. * * @param fieldName the name of the field. * @return the float value. * @throws IllegalArgumentException if field name doesn't exist or it doesn't contain floats. * @throws io.realm.exceptions.RealmException if the return value would be {@code null}. */ public float getFloat(String fieldName) { proxyState.getRealm$realm().checkIfValid(); long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); try { return proxyState.getRow$realm().getFloat(columnIndex); } catch (IllegalArgumentException e) { checkFieldType(fieldName, columnIndex, RealmFieldType.FLOAT); throw e; } } /** * Returns the {@code double} value for a given field. * <p> * If the field is nullable, use {@link #isNull(String)} to check for {@code null} instead of using * this method. * * @param fieldName the name of the field. * @return the double value. * @throws IllegalArgumentException if field name doesn't exist or it doesn't contain doubles. * @throws io.realm.exceptions.RealmException if the return value would be {@code null}. */ public double getDouble(String fieldName) { proxyState.getRealm$realm().checkIfValid(); long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); try { return proxyState.getRow$realm().getDouble(columnIndex); } catch (IllegalArgumentException e) { checkFieldType(fieldName, columnIndex, RealmFieldType.DOUBLE); throw e; } } /** * Returns the {@code byte[]} value for a given field. * * @param fieldName the name of the field. * @return the byte[] value. * @throws IllegalArgumentException if field name doesn't exist or it doesn't contain binary data. */ public byte[] getBlob(String fieldName) { proxyState.getRealm$realm().checkIfValid(); long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); try { return proxyState.getRow$realm().getBinaryByteArray(columnIndex); } catch (IllegalArgumentException e) { checkFieldType(fieldName, columnIndex, RealmFieldType.BINARY); throw e; } } /** * Returns the {@code String} value for a given field. * * @param fieldName the name of the field. * @return the String value. * @throws IllegalArgumentException if field name doesn't exist or it doesn't contain Strings. */ public String getString(String fieldName) { proxyState.getRealm$realm().checkIfValid(); long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); try { return proxyState.getRow$realm().getString(columnIndex); } catch (IllegalArgumentException e) { checkFieldType(fieldName, columnIndex, RealmFieldType.STRING); throw e; } } /** * Returns the {@code Date} value for a given field. * * @param fieldName the name of the field. * @return the Date value. * @throws IllegalArgumentException if field name doesn't exist or it doesn't contain Dates. */ public Date getDate(String fieldName) { proxyState.getRealm$realm().checkIfValid(); long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); checkFieldType(fieldName, columnIndex, RealmFieldType.DATE); if (proxyState.getRow$realm().isNull(columnIndex)) { return null; } else { return proxyState.getRow$realm().getDate(columnIndex); } } /** * Returns the object being linked to from this field. * * @param fieldName the name of the field. * @return the {@link DynamicRealmObject} representation of the linked object or {@code null} if no object is linked. * @throws IllegalArgumentException if field name doesn't exist or it doesn't contain links to other objects. */ public DynamicRealmObject getObject(String fieldName) { proxyState.getRealm$realm().checkIfValid(); long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); checkFieldType(fieldName, columnIndex, RealmFieldType.OBJECT); if (proxyState.getRow$realm().isNullLink(columnIndex)) { return null; } else { long linkRowIndex = proxyState.getRow$realm().getLink(columnIndex); CheckedRow linkRow = proxyState.getRow$realm().getTable().getLinkTarget(columnIndex).getCheckedRow(linkRowIndex); return new DynamicRealmObject(proxyState.getRealm$realm(), linkRow); } } /** * Returns the {@link RealmList} of objects being linked to from this field. * * @param fieldName the name of the field. * @return the {@link RealmList} data for this field. * @throws IllegalArgumentException if field name doesn't exist or it doesn't contain a list of links. */ public RealmList<DynamicRealmObject> getList(String fieldName) { proxyState.getRealm$realm().checkIfValid(); long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); try { LinkView linkView = proxyState.getRow$realm().getLinkList(columnIndex); String className = linkView.getTargetTable().getClassName(); return new RealmList<>(className, linkView, proxyState.getRealm$realm()); } catch (IllegalArgumentException e) { checkFieldType(fieldName, columnIndex, RealmFieldType.LIST); throw e; } } /** * Checks if the value of a given field is {@code null}. * * @param fieldName the name of the field. * @return {@code true} if field value is null, {@code false} otherwise. * @throws IllegalArgumentException if field name doesn't exist. */ public boolean isNull(String fieldName) { proxyState.getRealm$realm().checkIfValid(); long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); RealmFieldType type = proxyState.getRow$realm().getColumnType(columnIndex); switch (type) { case OBJECT: return proxyState.getRow$realm().isNullLink(columnIndex); case BOOLEAN: case INTEGER: case FLOAT: case DOUBLE: case STRING: case BINARY: case DATE: return proxyState.getRow$realm().isNull(columnIndex); case LIST: case UNSUPPORTED_TABLE: case UNSUPPORTED_MIXED: default: return false; } } /** * Checks whether an object has the given field or not. * * @param fieldName field name to check. * @return {@code true} if the object has a field with the given name, {@code false} otherwise. */ public boolean hasField(String fieldName) { proxyState.getRealm$realm().checkIfValid(); //noinspection SimplifiableIfStatement if (fieldName == null || fieldName.isEmpty()) { return false; } return proxyState.getRow$realm().hasColumn(fieldName); } /** * Returns the list of field names on this object. * * @return list of field names on this objects or the empty list if the object doesn't have any fields. */ public String[] getFieldNames() { proxyState.getRealm$realm().checkIfValid(); String[] keys = new String[(int) proxyState.getRow$realm().getColumnCount()]; for (int i = 0; i < keys.length; i++) { keys[i] = proxyState.getRow$realm().getColumnName(i); } return keys; } /** * Sets the value for the given field. This method will automatically try to convert numbers and * booleans that are given as {@code String} to their appropriate type. For example {@code "10"} * will be converted to {@code 10} if the field type is {@code int}. * <p> * Using the typed setters will be faster than using this method. * * @throws IllegalArgumentException if field name doesn't exist or if the input value cannot be converted * to the appropriate input type. * @throws NumberFormatException if a String based number cannot be converted properly. * @throws RealmException if the field is a {@link io.realm.annotations.PrimaryKey} field. */ @SuppressWarnings("unchecked") public void set(String fieldName, Object value) { proxyState.getRealm$realm().checkIfValid(); boolean isString = (value instanceof String); String strValue = isString ? (String) value : null; // Does implicit conversion if needed. long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); RealmFieldType type = proxyState.getRow$realm().getColumnType(columnIndex); if (isString && type != RealmFieldType.STRING) { switch (type) { case BOOLEAN: value = Boolean.parseBoolean(strValue); break; case INTEGER: value = Long.parseLong(strValue); break; case FLOAT: value = Float.parseFloat(strValue); break; case DOUBLE: value = Double.parseDouble(strValue); break; case DATE: value = JsonUtils.stringToDate(strValue); break; default: throw new IllegalArgumentException(String.format("Field %s is not a String field, " + "and the provide value could not be automatically converted: %s. Use a typed" + "setter instead", fieldName, value)); } } if (value == null) { setNull(fieldName); } else { setValue(fieldName, value); } } // Automatically finds the appropriate setter based on the objects type. private void setValue(String fieldName, Object value) { Class<?> valueClass = value.getClass(); if (valueClass == Boolean.class) { setBoolean(fieldName, (Boolean) value); } else if (valueClass == Short.class) { setShort(fieldName, (Short) value); } else if (valueClass == Integer.class) { setInt(fieldName, (Integer) value); } else if (valueClass == Long.class) { setLong(fieldName, (Long) value); } else if (valueClass == Byte.class) { setByte(fieldName, (Byte) value); } else if (valueClass == Float.class) { setFloat(fieldName, (Float) value); } else if (valueClass == Double.class) { setDouble(fieldName, (Double) value); } else if (valueClass == String.class) { setString(fieldName, (String) value); } else if (value instanceof Date) { setDate(fieldName, (Date) value); } else if (value instanceof byte[]) { setBlob(fieldName, (byte[]) value); } else if (valueClass == DynamicRealmObject.class) { setObject(fieldName, (DynamicRealmObject) value); } else if (valueClass == RealmList.class) { @SuppressWarnings("unchecked") RealmList<DynamicRealmObject> list = (RealmList<DynamicRealmObject>) value; setList(fieldName, list); } else { throw new IllegalArgumentException("Value is of an type not supported: " + value.getClass()); } } /** * Sets the {@code boolean} value of the given field. * * @param fieldName field name to update. * @param value value to insert. * @throws IllegalArgumentException if field name doesn't exist or field isn't a boolean field. */ public void setBoolean(String fieldName, boolean value) { proxyState.getRealm$realm().checkIfValid(); long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); proxyState.getRow$realm().setBoolean(columnIndex, value); } /** * Sets the {@code short} value of the given field. * * @param fieldName field name. * @param value value to insert. * @throws IllegalArgumentException if field name doesn't exist or field isn't an integer field. * @throws RealmException if the field is a {@link io.realm.annotations.PrimaryKey} field. */ public void setShort(String fieldName, short value) { proxyState.getRealm$realm().checkIfValid(); checkIsPrimaryKey(fieldName); long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); proxyState.getRow$realm().setLong(columnIndex, value); } /** * Sets the {@code int} value of the given field. * * @param fieldName field name to update. * @param value value to insert. * @throws IllegalArgumentException if field name doesn't exist or field isn't an integer field. * @throws RealmException if the field is a {@link io.realm.annotations.PrimaryKey} field. */ public void setInt(String fieldName, int value) { proxyState.getRealm$realm().checkIfValid(); checkIsPrimaryKey(fieldName); long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); proxyState.getRow$realm().setLong(columnIndex, value); } /** * Sets the {@code long} value of the given field. * * @param fieldName field name. * @param value value to insert. * @throws IllegalArgumentException if field name doesn't exist or field isn't an integer field. * @throws RealmException if the field is a {@link io.realm.annotations.PrimaryKey} field. */ public void setLong(String fieldName, long value) { proxyState.getRealm$realm().checkIfValid(); checkIsPrimaryKey(fieldName); long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); proxyState.getRow$realm().setLong(columnIndex, value); } /** * Sets the {@code byte} value of the given field. * * @param fieldName field name. * @param value value to insert. * @throws IllegalArgumentException if field name doesn't exist or field isn't an integer field. * @throws RealmException if the field is a {@link io.realm.annotations.PrimaryKey} field. */ public void setByte(String fieldName, byte value) { proxyState.getRealm$realm().checkIfValid(); checkIsPrimaryKey(fieldName); long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); proxyState.getRow$realm().setLong(columnIndex, value); } /** * Sets the {@code float} value of the given field. * * @param fieldName field name. * @param value value to insert. * @throws IllegalArgumentException if field name doesn't exist or field isn't a float field. */ public void setFloat(String fieldName, float value) { proxyState.getRealm$realm().checkIfValid(); long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); proxyState.getRow$realm().setFloat(columnIndex, value); } /** * Sets the {@code double} value of the given field. * * @param fieldName field name. * @param value value to insert. * @throws IllegalArgumentException if field name doesn't exist or field isn't a double field. */ public void setDouble(String fieldName, double value) { proxyState.getRealm$realm().checkIfValid(); long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); proxyState.getRow$realm().setDouble(columnIndex, value); } /** * Sets the {@code String} value of the given field. * * @param fieldName field name. * @param value value to insert. * @throws IllegalArgumentException if field name doesn't exist or field isn't a String field. * @throws RealmException if the field is a {@link io.realm.annotations.PrimaryKey} field. */ public void setString(String fieldName, String value) { proxyState.getRealm$realm().checkIfValid(); checkIsPrimaryKey(fieldName); long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); proxyState.getRow$realm().setString(columnIndex, value); } /** * Sets the binary value of the given field. * * @param fieldName field name. * @param value value to insert. * @throws IllegalArgumentException if field name doesn't exist or field isn't a binary field. */ public void setBlob(String fieldName, byte[] value) { proxyState.getRealm$realm().checkIfValid(); long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); proxyState.getRow$realm().setBinaryByteArray(columnIndex, value); } /** * Sets the {@code Date} value of the given field. * * @param fieldName field name. * @param value value to insert. * @throws IllegalArgumentException if field name doesn't exist or field isn't a Date field. */ public void setDate(String fieldName, Date value) { proxyState.getRealm$realm().checkIfValid(); long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); if (value == null) { proxyState.getRow$realm().setNull(columnIndex); } else { proxyState.getRow$realm().setDate(columnIndex, value); } } /** * Sets a reference to another object on the given field. * * @param fieldName field name. * @param value object to link to. * @throws IllegalArgumentException if field name doesn't exist, it doesn't link to other Realm objects, the type * of DynamicRealmObject doesn't match or it belongs to a different Realm. */ public void setObject(String fieldName, DynamicRealmObject value) { proxyState.getRealm$realm().checkIfValid(); long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); if (value == null) { proxyState.getRow$realm().nullifyLink(columnIndex); } else { if (value.proxyState.getRealm$realm() == null || value.proxyState.getRow$realm() == null) { throw new IllegalArgumentException("Cannot link to objects that are not part of the Realm."); } if (proxyState.getRealm$realm() != value.proxyState.getRealm$realm()) { throw new IllegalArgumentException("Cannot add an object from another Realm instance."); } Table table = proxyState.getRow$realm().getTable().getLinkTarget(columnIndex); Table inputTable = value.proxyState.getRow$realm().getTable(); if (!table.hasSameSchema(inputTable)) { throw new IllegalArgumentException(String.format("Type of object is wrong. Was %s, expected %s", inputTable.getName(), table.getName())); } proxyState.getRow$realm().setLink(columnIndex, value.proxyState.getRow$realm().getIndex()); } } /** * Sets the reference to a {@link RealmList} on the given field. * * @param fieldName field name. * @param list list of references. * @throws IllegalArgumentException if field name doesn't exist, it is not a list field, the type * of the object represented by the DynamicRealmObject doesn't match or any element in the list belongs to a * different Realm. */ public void setList(String fieldName, RealmList<DynamicRealmObject> list) { proxyState.getRealm$realm().checkIfValid(); if (list == null) { throw new IllegalArgumentException("Null values not allowed for lists"); } long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); LinkView links = proxyState.getRow$realm().getLinkList(columnIndex); Table linkTargetTable = links.getTargetTable(); final String linkTargetTableName = linkTargetTable.getClassName(); boolean typeValidated; if (list.className == null && list.clazz == null) { // Unmanaged lists don't know anything about the types they contain. They might even hold objects of // multiple types :(, so we have to check each item in the list. typeValidated = false; } else { String listType = list.className != null ? list.className : proxyState.getRealm$realm().getSchema().getTable(list.clazz).getClassName(); if (!linkTargetTableName.equals(listType)) { throw new IllegalArgumentException(String.format(Locale.ENGLISH, "The elements in the list are not the proper type. " + "Was %s expected %s.", listType, linkTargetTableName)); } typeValidated = true; } final int listLength = list.size(); final long[] indices = new long[listLength]; for (int i = 0; i < listLength; i++) { RealmObjectProxy obj = list.get(i); if (obj.realmGet$proxyState().getRealm$realm() != proxyState.getRealm$realm()) { throw new IllegalArgumentException("Each element in 'list' must belong to the same Realm instance."); } if (!typeValidated && !linkTargetTable.hasSameSchema(obj.realmGet$proxyState().getRow$realm().getTable())) { throw new IllegalArgumentException(String.format(Locale.ENGLISH, "Element at index %d is not the proper type. " + "Was '%s' expected '%s'.", i, obj.realmGet$proxyState().getRow$realm().getTable().getClassName(), linkTargetTableName)); } indices[i] = obj.realmGet$proxyState().getRow$realm().getIndex(); } links.clear(); for (int i = 0; i < listLength; i++) { links.add(indices[i]); } } /** * Sets the value to {@code null} for the given field. * * @param fieldName field name. * @throws IllegalArgumentException if field name doesn't exist, or the field isn't nullable. * @throws RealmException if the field is a {@link io.realm.annotations.PrimaryKey} field. */ public void setNull(String fieldName) { proxyState.getRealm$realm().checkIfValid(); long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); RealmFieldType type = proxyState.getRow$realm().getColumnType(columnIndex); if (type == RealmFieldType.OBJECT) { proxyState.getRow$realm().nullifyLink(columnIndex); } else { checkIsPrimaryKey(fieldName); proxyState.getRow$realm().setNull(columnIndex); } } /** * Returns the type of object. This will normally correspond to the name of a class that is extending * {@link RealmObject}. * * @return this objects type. */ public String getType() { proxyState.getRealm$realm().checkIfValid(); return proxyState.getRow$realm().getTable().getClassName(); } /** * Returns the type used by the underlying storage engine to represent this field. * * @return the underlying type used by Realm to represent this field. */ public RealmFieldType getFieldType(String fieldName) { proxyState.getRealm$realm().checkIfValid(); long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); return proxyState.getRow$realm().getColumnType(columnIndex); } private void checkFieldType(String fieldName, long columnIndex, RealmFieldType expectedType) { RealmFieldType columnType = proxyState.getRow$realm().getColumnType(columnIndex); if (columnType != expectedType) { String expectedIndefiniteVowel = ""; if (expectedType == RealmFieldType.INTEGER || expectedType == RealmFieldType.OBJECT) { expectedIndefiniteVowel = "n"; } String columnTypeIndefiniteVowel = ""; if (columnType == RealmFieldType.INTEGER || columnType == RealmFieldType.OBJECT) { columnTypeIndefiniteVowel = "n"; } throw new IllegalArgumentException(String.format("'%s' is not a%s '%s', but a%s '%s'.", fieldName, expectedIndefiniteVowel, expectedType, columnTypeIndefiniteVowel, columnType)); } } /** * Returns a hash code value for the {@link DynamicRealmObject} object. * <p> * By the general contract of {@link Object#hashCode()}, any two objects for which {@link #equals} * returns {@code true} must return the same hash code value. * <p> * Note that a {@link RealmObject} is a live object, and it might be updated by changes from * other threads. This means that a hash code value of the object is not stable, and the value * should be neither used as a key in HashMap nor saved in HashSet. * * @return a hash code value for the object. * @see #equals */ @Override public int hashCode() { proxyState.getRealm$realm().checkIfValid(); String realmName = proxyState.getRealm$realm().getPath(); String tableName = proxyState.getRow$realm().getTable().getName(); long rowIndex = proxyState.getRow$realm().getIndex(); int result = 17; result = 31 * result + ((realmName != null) ? realmName.hashCode() : 0); result = 31 * result + ((tableName != null) ? tableName.hashCode() : 0); result = 31 * result + (int) (rowIndex ^ (rowIndex >>> 32)); return result; } @Override public boolean equals(Object o) { proxyState.getRealm$realm().checkIfValid(); if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } DynamicRealmObject other = (DynamicRealmObject) o; String path = proxyState.getRealm$realm().getPath(); String otherPath = other.proxyState.getRealm$realm().getPath(); if (path != null ? !path.equals(otherPath) : otherPath != null) { return false; } String tableName = proxyState.getRow$realm().getTable().getName(); String otherTableName = other.proxyState.getRow$realm().getTable().getName(); //noinspection SimplifiableIfStatement if (tableName != null ? !tableName.equals(otherTableName) : otherTableName != null) { return false; } return proxyState.getRow$realm().getIndex() == other.proxyState.getRow$realm().getIndex(); } @Override public String toString() { proxyState.getRealm$realm().checkIfValid(); if (!proxyState.getRow$realm().isAttached()) { return "Invalid object"; } final String className = proxyState.getRow$realm().getTable().getClassName(); StringBuilder sb = new StringBuilder(className + " = dynamic["); String[] fields = getFieldNames(); for (String field : fields) { long columnIndex = proxyState.getRow$realm().getColumnIndex(field); RealmFieldType type = proxyState.getRow$realm().getColumnType(columnIndex); sb.append("{"); sb.append(field).append(":"); switch (type) { case BOOLEAN: sb.append(proxyState.getRow$realm().isNull(columnIndex) ? "null" : proxyState.getRow$realm().getBoolean(columnIndex)); break; case INTEGER: sb.append(proxyState.getRow$realm().isNull(columnIndex) ? "null" : proxyState.getRow$realm().getLong(columnIndex)); break; case FLOAT: sb.append(proxyState.getRow$realm().isNull(columnIndex) ? "null" : proxyState.getRow$realm().getFloat(columnIndex)); break; case DOUBLE: sb.append(proxyState.getRow$realm().isNull(columnIndex) ? "null" : proxyState.getRow$realm().getDouble(columnIndex)); break; case STRING: sb.append(proxyState.getRow$realm().getString(columnIndex)); break; case BINARY: sb.append(Arrays.toString(proxyState.getRow$realm().getBinaryByteArray(columnIndex))); break; case DATE: sb.append(proxyState.getRow$realm().isNull(columnIndex) ? "null" : proxyState.getRow$realm().getDate(columnIndex)); break; case OBJECT: sb.append(proxyState.getRow$realm().isNullLink(columnIndex) ? "null" : proxyState.getRow$realm().getTable().getLinkTarget(columnIndex).getClassName()); break; case LIST: String targetClassName = proxyState.getRow$realm().getTable().getLinkTarget(columnIndex).getClassName(); sb.append(String.format("RealmList<%s>[%s]", targetClassName, proxyState.getRow$realm().getLinkList(columnIndex).size())); break; case UNSUPPORTED_TABLE: case UNSUPPORTED_MIXED: default: sb.append("?"); break; } sb.append("},"); } sb.replace(sb.length() - 1, sb.length(), ""); sb.append("]"); return sb.toString(); } /** * Returns {@link RealmResults} containing all {@code srcClassName} class objects that have a relationship * to this object from {@code srcFieldName} field. * <p> * An entry is added for each reference, e.g. if the same reference is in a list multiple times, * the src object will show up here multiple times. * * @param srcClassName name of the class returned objects belong to. * @param srcFieldName name of the field in the source class that holds a reference to this object. * Field type must be either {@code io.realm.RealmFieldType.OBJECT} or {@code io.realm.RealmFieldType.LIST}. * @return the result. * @throws IllegalArgumentException if the {@code srcClassName} is {@code null} or does not exist, * the {@code srcFieldName} is {@code null} or does not exist, * type of the source field is not supported. */ public RealmResults<DynamicRealmObject> linkingObjects(String srcClassName, String srcFieldName) { final DynamicRealm realm = (DynamicRealm) proxyState.getRealm$realm(); realm.checkIfValid(); proxyState.getRow$realm().checkIfAttached(); final RealmSchema schema = realm.getSchema(); final RealmObjectSchema realmObjectSchema = schema.get(srcClassName); if (realmObjectSchema == null) { throw new IllegalArgumentException("Class not found: " + srcClassName); } if (srcFieldName == null) { throw new IllegalArgumentException("Non-null 'srcFieldName' required."); } if (srcFieldName.contains(".")) { throw new IllegalArgumentException(MSG_LINK_QUERY_NOT_SUPPORTED); } final RealmFieldType fieldType = realmObjectSchema.getFieldType(srcFieldName); // throws IAE if not found if (fieldType != RealmFieldType.OBJECT && fieldType != RealmFieldType.LIST) { throw new IllegalArgumentException(String.format(Locale.ENGLISH, "Unexpected field type: %1$s. Field type should be either %2$s.%3$s or %2$s.%4$s.", fieldType.name(), RealmFieldType.class.getSimpleName(), RealmFieldType.OBJECT.name(), RealmFieldType.LIST.name())); } return RealmResults.createDynamicBacklinkResults(realm, (CheckedRow) proxyState.getRow$realm(), realmObjectSchema.getTable(), srcFieldName); } @Override public void realm$injectObjectContext() { // nothing to do for DynamicRealmObject } @Override public ProxyState realmGet$proxyState() { return proxyState; } // Checks if the given field is primary key field. Throws if it is a PK field. private void checkIsPrimaryKey(String fieldName) { RealmObjectSchema objectSchema = proxyState.getRealm$realm().getSchema().getSchemaForClass(getType()); if (objectSchema.hasPrimaryKey() && objectSchema.getPrimaryKey().equals(fieldName)) { throw new IllegalArgumentException(String.format( "Primary key field '%s' cannot be changed after object was created.", fieldName)); } } }