diff --git a/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java b/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java index 20b9a54a79..8553ff0f24 100644 --- a/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java +++ b/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java @@ -16,8 +16,13 @@ package io.realm; import java.util.Arrays; +import java.util.Collections; import java.util.Date; +import java.util.EnumMap; +import java.util.HashMap; +import java.util.Iterator; import java.util.Locale; +import java.util.Map; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -40,6 +45,43 @@ public class DynamicRealmObject extends RealmObject implements RealmObjectProxy { static final String MSG_LINK_QUERY_NOT_SUPPORTED = "Queries across relationships are not supported"; + private static final Map, RealmFieldType> VALUE_CLASS_TO_LIST_FIELD_TYPE; + static { + final Map, RealmFieldType> map = new HashMap<>(); + map.put(String.class, RealmFieldType.STRING_LIST); + map.put(byte[].class, RealmFieldType.BINARY_LIST); + map.put(Boolean.class, RealmFieldType.BOOLEAN_LIST); + map.put(Long.class, RealmFieldType.INTEGER_LIST); + map.put(Integer.class, RealmFieldType.INTEGER_LIST); + map.put(Short.class, RealmFieldType.INTEGER_LIST); + map.put(Byte.class, RealmFieldType.INTEGER_LIST); + map.put(Double.class, RealmFieldType.DOUBLE_LIST); + map.put(Float.class, RealmFieldType.FLOAT_LIST); + map.put(Date.class, RealmFieldType.DATE_LIST); + VALUE_CLASS_TO_LIST_FIELD_TYPE = Collections.unmodifiableMap(map); + } + + private static final Map> LIST_FIELD_TYPE_TO_VALUE_CLASS; + static { + final Map> map = new EnumMap<>(RealmFieldType.class); + map.put(RealmFieldType.STRING_LIST, String.class); + map.put(RealmFieldType.BINARY_LIST, byte[].class); + map.put(RealmFieldType.BOOLEAN_LIST, Boolean.class); + map.put(RealmFieldType.INTEGER_LIST, Long.class); + map.put(RealmFieldType.DOUBLE_LIST, Double.class); + map.put(RealmFieldType.FLOAT_LIST, Float.class); + map.put(RealmFieldType.DATE_LIST, Date.class); + LIST_FIELD_TYPE_TO_VALUE_CLASS = Collections.unmodifiableMap(map); + } + + private static RealmFieldType getFieldTypeForValueClass(Class valueClass) { + final RealmFieldType expectedFieldType = VALUE_CLASS_TO_LIST_FIELD_TYPE.get(valueClass); + if (expectedFieldType == null) { + throw new IllegalArgumentException("Unsupported value type: " + valueClass.getName()); + } + return expectedFieldType; + } + private final ProxyState proxyState = new ProxyState<>(this); /** @@ -342,16 +384,23 @@ public DynamicRealmObject getObject(String fieldName) { * @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 objects. + * @see #getValueList(String, Class) */ public RealmList getList(String fieldName) { proxyState.getRealm$realm().checkIfValid(); long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); try { - OsList osList = proxyState.getRow$realm().getList(columnIndex); - //noinspection ConstantConditions - @Nonnull - String className = osList.getTargetTable().getClassName(); + OsList osList = proxyState.getRow$realm().getModelList(columnIndex); + final Table targetTable = osList.getTargetTable(); + if (targetTable == null) { + throw new IllegalArgumentException("The field is value list."); + } + + final String className = targetTable.getClassName(); + if (className == null) { + throw new IllegalStateException("Unexpectedly target class name is null."); + } return new RealmList<>(className, osList, proxyState.getRealm$realm()); } catch (IllegalArgumentException e) { checkFieldType(fieldName, columnIndex, RealmFieldType.LIST); @@ -364,11 +413,29 @@ public RealmList getList(String fieldName) { * * @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 values. + * @throws IllegalArgumentException if field name doesn't exist or it doesn't contain a list of values or {@code valueClass} is not valid for the field type. + * @see #getList(String) */ public RealmList getValueList(String fieldName, Class valueClass) { - // TODO implement this - return null; + proxyState.getRealm$realm().checkIfValid(); + + final RealmFieldType expectedFieldType = getFieldTypeForValueClass(valueClass); + final OsList osList; + long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); + try { + osList = proxyState.getRow$realm().getValueList(columnIndex, expectedFieldType); + final Table targetTable = osList.getTargetTable(); + if (targetTable != null) { + throw new IllegalArgumentException("The field is object list."); + } + } catch (IllegalArgumentException e) { + checkFieldType(fieldName, columnIndex, expectedFieldType); + throw e; + } + + checkFieldType(fieldName, columnIndex, expectedFieldType); + //noinspection ConstantConditions + return new RealmList(valueClass, osList, proxyState.getRealm$realm()); } /** @@ -395,8 +462,17 @@ public boolean isNull(String fieldName) { case DATE: return proxyState.getRow$realm().isNull(columnIndex); case LIST: + case BINARY_LIST: + case BOOLEAN_LIST: + case DATE_LIST: + case DOUBLE_LIST: + case FLOAT_LIST: + case STRING_LIST: + case INTEGER_LIST: + case LINKING_OBJECTS: case UNSUPPORTED_TABLE: case UNSUPPORTED_MIXED: + case UNSUPPORTED_DATE: default: return false; } @@ -744,9 +820,17 @@ public void setList(String fieldName, RealmList list) { throw new IllegalArgumentException("Null values not allowed for lists"); } - long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); - OsList osList = proxyState.getRow$realm().getList(columnIndex); + final Row row = proxyState.getRow$realm(); + long columnIndex = row.getColumnIndex(fieldName); + OsList osList = row.getModelList(columnIndex); Table linkTargetTable = osList.getTargetTable(); + if (linkTargetTable == null) { + // designated field was value list, not object list. + throw new IllegalArgumentException(String.format(Locale.US, + "Unexpected field type. Was %s, expected %s.", + row.getColumnType(columnIndex).name(), RealmFieldType.LIST.name())); + } + //noinspection ConstantConditions @Nonnull final String linkTargetTableName = linkTargetTable.getClassName(); @@ -772,6 +856,9 @@ public void setList(String fieldName, RealmList list) { for (int i = 0; i < listLength; i++) { RealmObjectProxy obj = list.get(i); + if (obj == null) { + throw new IllegalArgumentException("Null element is not allowed."); + } if (obj.realmGet$proxyState().getRealm$realm() != proxyState.getRealm$realm()) { throw new IllegalArgumentException("Each element in 'list' must belong to the same Realm instance."); } @@ -792,6 +879,37 @@ public void setList(String fieldName, RealmList list) { } } + private ManagedListOperator getOperator(BaseRealm realm, OsList osList, RealmFieldType valueListType, Class valueClass) { + if (valueListType == RealmFieldType.STRING_LIST) { + //noinspection unchecked + return (ManagedListOperator) new StringListOperator(realm, osList, (Class) valueClass); + } + if (valueListType == RealmFieldType.INTEGER_LIST) { + return new LongListOperator<>(realm, osList, valueClass); + } + if (valueListType == RealmFieldType.BOOLEAN_LIST) { + //noinspection unchecked + return (ManagedListOperator) new BooleanListOperator(realm, osList, (Class) valueClass); + } + if (valueListType == RealmFieldType.BINARY_LIST) { + //noinspection unchecked + return (ManagedListOperator) new BinaryListOperator(realm, osList, (Class) valueClass); + } + if (valueListType == RealmFieldType.DOUBLE_LIST) { + //noinspection unchecked + return (ManagedListOperator) new DoubleListOperator(realm, osList, (Class) valueClass); + } + if (valueListType == RealmFieldType.FLOAT_LIST) { + //noinspection unchecked + return (ManagedListOperator) new FloatListOperator(realm, osList, (Class) valueClass); + } + if (valueListType == RealmFieldType.DATE_LIST) { + //noinspection unchecked + return (ManagedListOperator) new DateListOperator(realm, osList, (Class) valueClass); + } + throw new IllegalArgumentException("Unexpected list type: " + valueListType.name()); + } + /** * Sets the reference to a {@link RealmList} on the given field. * @@ -802,7 +920,47 @@ public void setList(String fieldName, RealmList list) { * different Realm. */ public void setValueList(String fieldName, RealmList list) { - // TODO implement this + proxyState.getRealm$realm().checkIfValid(); + + //noinspection ConstantConditions + if (list == null) { + throw new IllegalArgumentException("Null values not allowed for lists"); + } + + final Row row = proxyState.getRow$realm(); + final long columnIndex = row.getColumnIndex(fieldName); + final RealmFieldType fieldType = proxyState.getRow$realm().getColumnType(columnIndex); + final OsList targetOsList = row.getValueList(columnIndex, fieldType); + final Table linkTargetTable = targetOsList.getTargetTable(); + if (linkTargetTable != null) { + // designated field was object list, not value list. + throw new IllegalArgumentException(String.format(Locale.US, + "Unexpected field type. Was %s, expected %s.", + row.getColumnType(columnIndex).name(), "one of the value list")); + } + + final Class valueClass = LIST_FIELD_TYPE_TO_VALUE_CLASS.get(fieldType); + + final ManagedListOperator operator = getOperator(proxyState.getRealm$realm(), targetOsList, fieldType, valueClass); + + if (list.isManaged() && targetOsList.size() == list.size()) { + /* + * There is a chance that the source list and the target list are the same list in the same object. + * In this case, we can't use removeAll(). + */ + final int size = list.size(); + final Iterator iterator = list.iterator(); + for (int i = 0; i < size; i++) { + @Nullable + final Object value = iterator.next(); + operator.set(i, value); + } + } else { + targetOsList.removeAll(); + for (Object value : list) { + operator.add(value); + } + } } /** @@ -968,10 +1126,25 @@ public String toString() { break; case LIST: String targetClassName = proxyState.getRow$realm().getTable().getLinkTarget(columnIndex).getClassName(); - sb.append(String.format(Locale.US, "RealmList<%s>[%s]", targetClassName, proxyState.getRow$realm().getList(columnIndex).size())); + sb.append(String.format(Locale.US, "RealmList<%s>[%s]", targetClassName, proxyState.getRow$realm().getModelList(columnIndex).size())); + break; + case BOOLEAN_LIST: // fall-through + case STRING_LIST: // fall-through + case BINARY_LIST: // fall-through + case INTEGER_LIST: // fall-through + case DOUBLE_LIST: // fall-through + case FLOAT_LIST: // fall-through + case DATE_LIST: + final String valueClassName = LIST_FIELD_TYPE_TO_VALUE_CLASS.get(type).getSimpleName(); + final OsList modelList = proxyState.getRow$realm().getValueList(columnIndex, type); + sb.append(String.format(Locale.US, "RealmList<%s>[%s]", valueClassName, modelList.size())); + break; + case LINKING_OBJECTS: + // nothing to print break; case UNSUPPORTED_TABLE: case UNSUPPORTED_MIXED: + case UNSUPPORTED_DATE: default: sb.append("?"); break; diff --git a/realm/realm-library/src/main/java/io/realm/internal/CheckedRow.java b/realm/realm-library/src/main/java/io/realm/internal/CheckedRow.java index f3a3e1f97b..49575b80a0 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/CheckedRow.java +++ b/realm/realm-library/src/main/java/io/realm/internal/CheckedRow.java @@ -16,7 +16,9 @@ package io.realm.internal; +import java.util.EnumSet; import java.util.Locale; +import java.util.Set; import io.realm.RealmFieldType; @@ -97,17 +99,6 @@ public void setNull(long columnIndex) { } } - @Override - public OsList getList(long columnIndex) { - RealmFieldType fieldType = getTable().getColumnType(columnIndex); - if (fieldType != RealmFieldType.LIST) { - throw new IllegalArgumentException( - String.format(Locale.US, "Field '%s' is not a 'RealmList'.", - getTable().getColumnName(columnIndex))); - } - return super.getList(columnIndex); - } - @Override public OsList getModelList(long columnIndex) { RealmFieldType fieldType = getTable().getColumnType(columnIndex); @@ -119,8 +110,18 @@ public OsList getModelList(long columnIndex) { return super.getModelList(columnIndex); } + private static final Set VALUE_LIST_TYPES = EnumSet.of(RealmFieldType.STRING_LIST, + RealmFieldType.BINARY_LIST, RealmFieldType.BOOLEAN_LIST, RealmFieldType.INTEGER_LIST, + RealmFieldType.DOUBLE_LIST, RealmFieldType.FLOAT_LIST, RealmFieldType.DATE_LIST); + @Override public OsList getValueList(long columnIndex, RealmFieldType fieldType) { + if (!VALUE_LIST_TYPES.contains(fieldType)) { + throw new IllegalArgumentException( + String.format(Locale.US, "The type of field '%1$s' (RealmFieldType.%2$s) is not a value list type.", + getTable().getColumnName(columnIndex), fieldType.name())); + } + final RealmFieldType actualFieldType = getTable().getColumnType(columnIndex); if (fieldType != actualFieldType) { throw new IllegalArgumentException( 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 79510c7e08..42f160d1d9 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 @@ -104,11 +104,6 @@ public boolean isNullLink(long columnIndex) { throw getStubException(); } - @Override - public OsList getList(long columnIndex) { - throw getStubException(); - } - @Override public OsList getModelList(long columnIndex) { throw getStubException(); 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 88ace3a27b..638fc3d5f8 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 @@ -133,11 +133,6 @@ public boolean isNullLink(long columnIndex) { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } - @Override - public OsList getList(long columnIndex) { - throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); - } - @Override public OsList getModelList(long columnIndex) { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); 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 f1a556f057..681d818999 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 @@ -81,9 +81,6 @@ public interface Row { boolean isNullLink(long columnIndex); - // FIXME remove this in DynamicRealm PR - OsList getList(long columnIndex); - OsList getModelList(long columnIndex); OsList getValueList(long columnIndex, RealmFieldType fieldType); 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 5c98952c7d..c7e035b89c 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 @@ -172,11 +172,6 @@ public boolean isNullLink(long columnIndex) { return nativeIsNullLink(nativePtr, columnIndex); } - @Override - public OsList getList(long columnIndex) { - return new OsList(this, columnIndex); - } - @Override public OsList getModelList(long columnIndex) { return new OsList(this, columnIndex);