package com.revolsys.record; import java.sql.Clob; import java.sql.SQLException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import com.revolsys.collection.list.ListByIndexIterator; import com.revolsys.collection.map.MapEx; import com.revolsys.datatype.DataType; import com.revolsys.datatype.DataTypes; import com.revolsys.geometry.model.Geometry; import com.revolsys.identifier.Identifiable; import com.revolsys.identifier.Identifier; import com.revolsys.identifier.ListIdentifier; import com.revolsys.identifier.TypedIdentifier; import com.revolsys.logging.Logs; import com.revolsys.record.code.CodeTable; import com.revolsys.record.query.Value; import com.revolsys.record.schema.FieldDefinition; import com.revolsys.record.schema.RecordDefinition; import com.revolsys.record.schema.RecordDefinitionProxy; import com.revolsys.util.CompareUtil; import com.revolsys.util.Property; import com.revolsys.util.Strings; public interface Record extends MapEx, Comparable<Object>, Identifiable, RecordDefinitionProxy { String EVENT_RECORD_CHANGED = "_recordChanged"; String EXCLUDE_GEOMETRY = Record.class.getName() + ".excludeGeometry"; String EXCLUDE_ID = Record.class.getName() + ".excludeId"; Comparator<Record> IDENTIFIER_COMPARATOR = (final Record record1, final Record record2) -> { final Identifier identifier1 = record1.getIdentifier(); final Identifier identifier2 = record2.getIdentifier(); if (identifier1 == null) { if (identifier2 == null) { return 0; } else { return 1; } } else if (identifier2 == null) { return -1; } else { return identifier1.compareTo(identifier2); } }; @SuppressWarnings({ "unchecked", "rawtypes" }) static boolean equalsNotNull(final Object object1, final Object object2) { return ((Record)object1).equalValuesAll((Map)object2); } @SuppressWarnings({ "unchecked", "rawtypes" }) static boolean equalsNotNull(final Object object1, final Object object2, final Collection<String> excludeFieldNames) { return ((Record)object1).equalValuesExclude((Map)object2, excludeFieldNames); } static Comparator<Record> newComparatorIdentifier(final String fieldName) { return (record1, record2) -> { final Identifier value1 = record1.getIdentifier(fieldName); final Identifier value2 = record2.getIdentifier(fieldName); if (value1 == null) { if (value2 == null) { return 0; } else { return 1; } } else if (value2 == null) { return -1; } else { final int compare = CompareUtil.compare(value1, value2); return compare; } }; } static String toString(final Record record) { final RecordDefinition recordDefinition = record.getRecordDefinition(); final StringBuilder s = new StringBuilder(); s.append(recordDefinition.getPath()).append("(\n"); for (int i = 0; i < recordDefinition.getFieldCount(); i++) { final Object value = record.getValue(i); if (value != null) { final String fieldName = recordDefinition.getFieldName(i); s.append(fieldName).append('=').append(value).append('\n'); } } s.append(')'); return s.toString(); } @SuppressWarnings("unchecked") default <R extends Record> int addTo(final List<R> records) { if (!contains(records)) { final int index = records.size(); records.add((R)this); return index; } return -1; } Record clone(); @Override default int compareTo(final Object other) { if (other instanceof Record) { final Record record = (Record)other; return compareTo(record); } else { return -1; } } default int compareTo(final Record other) { if (other == null) { return -1; } else if (this == other) { return 0; } else { final int recordDefinitionCompare = getRecordDefinition() .compareTo(other.getRecordDefinition()); if (recordDefinitionCompare == 0) { final Identifier id1 = getIdentifier(); final Identifier id2 = other.getIdentifier(); if (id1 == null) { if (id2 != null) { return -1; } } else { final int idCompare = id1.compareTo(id2); if (idCompare != 0) { return idCompare; } } final Geometry geometry1 = getGeometry(); final Geometry geometry2 = other.getGeometry(); if (geometry1 != null && geometry2 != null) { final int geometryComparison = geometry1.compareTo(geometry2); if (geometryComparison != 0) { return geometryComparison; } } final Integer hash1 = hashCode(); final int hash2 = other.hashCode(); final int hashCompare = hash1.compareTo(hash2); if (hashCompare != 0) { return hashCompare; } return -1; } else { return recordDefinitionCompare; } } } default int compareValue(final CharSequence fieldName, final Object value) { final FieldDefinition fieldDefinition = getFieldDefinition(fieldName); if (fieldDefinition == null) { return -1; } else { final int fieldIndex = fieldDefinition.getIndex(); final Object fieldValue = getValue(fieldIndex); return CompareUtil.compare(fieldValue, value); } } default int compareValue(final Map<String, Object> map, final CharSequence fieldName) { if (map != null) { final Object value = map.get(fieldName); return compareValue(fieldName, value); } return -1; } default boolean contains(final Iterable<? extends Record> records) { for (final Record record : records) { if (isSame(record)) { return true; } } return false; } default void delete() { getRecordDefinition().deleteRecord(this); } default double distance(final Geometry geometry) { final Geometry recordGeometry = getGeometry(); if (Property.isEmpty(geometry) || Property.isEmpty(recordGeometry)) { return Double.NaN; } else { final double distance = recordGeometry.distance(geometry); return distance; } } @Override default Set<Entry<String, Object>> entrySet() { return new RecordEntrySet(this); } default boolean equalPathValue(final CharSequence fieldPath, final Object value) { final Object fieldValue = getValueByPath(fieldPath); final boolean hasValue1 = Property.hasValue(value); final boolean hasValue2 = Property.hasValue(fieldValue); if (hasValue1) { if (hasValue2) { return DataType.equal(fieldValue, value); } else { return false; } } else { if (hasValue2) { return false; } else { return true; } } } default boolean equalValue(final CharSequence fieldName, Object value) { final FieldDefinition fieldDefinition = getFieldDefinition(fieldName); if (fieldDefinition == null) { return false; } else { final int fieldIndex = fieldDefinition.getIndex(); final Object fieldValue = getValue(fieldIndex); final CodeTable codeTable = fieldDefinition.getCodeTable(); if (codeTable != null) { value = codeTable.getIdentifier(value); } return fieldDefinition.equals(fieldValue, value); } } default boolean equalValue(final Map<String, ? extends Object> map, final CharSequence fieldName) { if (map != null) { final Object value = map.get(fieldName); return equalValue(fieldName, value); } return false; } default boolean equalValueExclude(final CharSequence fieldName, final Object value, final CharSequence... excludeFieldNames) { final FieldDefinition fieldDefinition = getFieldDefinition(fieldName); if (fieldDefinition == null) { return false; } else { final int fieldIndex = fieldDefinition.getIndex(); final Object fieldValue = getValue(fieldIndex); return fieldDefinition.equals(fieldValue, value, excludeFieldNames); } } default boolean equalValueExclude(final CharSequence fieldName, final Object value, final Collection<? extends CharSequence> excludeFieldNames) { final FieldDefinition fieldDefinition = getFieldDefinition(fieldName); if (fieldDefinition == null) { return false; } else { final int fieldIndex = fieldDefinition.getIndex(); final Object fieldValue = getValue(fieldIndex); return fieldDefinition.equals(fieldValue, value, excludeFieldNames); } } default boolean equalValueExclude(final Map<String, ? extends Object> map, final CharSequence fieldName, final CharSequence... excludeFieldNames) { if (map != null) { final Object value = map.get(fieldName); return equalValueExclude(fieldName, value, excludeFieldNames); } return false; } default boolean equalValueExclude(final Map<String, ? extends Object> map, final CharSequence fieldName, final Collection<? extends CharSequence> excludeFieldNames) { if (isFieldExcluded(excludeFieldNames, fieldName)) { return true; } else { if (map != null) { final Object value = map.get(fieldName); return equalValueExclude(fieldName, value, excludeFieldNames); } return false; } } default boolean equalValueExclude(final Record otherRecord, final CharSequence fieldName, final CharSequence... excludeFieldNames) { if (otherRecord != null) { final Object value = otherRecord.getValue(fieldName); return equalValueExclude(fieldName, value, excludeFieldNames); } return false; } /** * Equal if the the keys and values in the map are equal to those field values on the record. * @param map */ default boolean equalValues(final Map<String, ? extends Object> map) { if (map != null) { for (final String fieldName : map.keySet()) { if (hasField(fieldName)) { if (!equalValue(map, fieldName)) { return false; } } } } return true; } /** * Equal if the map has all the fields and values of this record * @param map */ default boolean equalValuesAll(final Map<String, ? extends Object> map) { if (map == null) { return false; } else { if (map instanceof Record) { final Record record = (Record)map; if (!record.getPathName().equals(getPathName())) { return false; } } final List<String> fieldNames = getFieldNames(); for (final String fieldName : fieldNames) { if (!equalValue(map, fieldName)) { return false; } } return true; } } /** * Equal if the map has all the fields and values of this record * @param map */ default boolean equalValuesExclude(final Map<String, Object> map, final Collection<? extends CharSequence> excludeFieldNames) { final List<String> fieldNames = getFieldNames(); for (final String fieldName : fieldNames) { if (!equalValueExclude(map, fieldName, excludeFieldNames)) { return false; } } return true; } @Override default Object get(final Object key) { if (key instanceof CharSequence) { final CharSequence name = (String)key; return getValue(name); } else { return null; } } @Override default Byte getByte(final CharSequence name) { final Object value = getValue(name); if (Property.hasValue(value)) { if (value instanceof Number) { final Number number = (Number)value; return number.byteValue(); } else { return Byte.valueOf(value.toString()); } } else { return null; } } @SuppressWarnings("unchecked") default <T> T getCodeValue(final CharSequence fieldName) { Object value = getValue(fieldName); if (Property.hasValue(value)) { final FieldDefinition fieldDefinition = getFieldDefinition(fieldName); final CodeTable codeTable = fieldDefinition.getCodeTable(); if (codeTable != null) { value = codeTable.getValue(value); } } return (T)value; } @SuppressWarnings("unchecked") default <T> T getCodeValue(final int fieldIndex) { Object value = getValue(fieldIndex); if (Property.hasValue(value)) { final FieldDefinition fieldDefinition = getFieldDefinition(fieldIndex); final CodeTable codeTable = fieldDefinition.getCodeTable(); if (codeTable != null) { value = codeTable.getValue(value); } } return (T)value; } /** * Return the list of field names that are different between this record and the other map. * Compares all the field names from this record. * * @param map The map to compare */ default List<String> getDifferentFieldNames(final Map<String, Object> map) { final List<String> fieldNames = getFieldNames(); return getDifferentFieldNames(map, fieldNames); } /** * Return the list of field names that are different between this record and the other map. * * @param map The map to compare * @param fieldNames The field names to compare */ default List<String> getDifferentFieldNames(final Map<String, Object> map, final Collection<? extends CharSequence> fieldNames) { List<String> differentFieldNames = new ArrayList<>(); for (final CharSequence fieldName : fieldNames) { if (!equalValue(map, fieldName)) { if (differentFieldNames == Collections.<String> emptyList()) { differentFieldNames = new ArrayList<>(); } differentFieldNames.add(fieldName.toString()); } } return differentFieldNames; } /** * Return the list of field names that are different between this record and the other map. * * @param map The map to compare * @param excludeFieldNames The field names to not compare */ default List<String> getDifferentFieldNamesExclude(final Map<String, Object> map, final Collection<? extends CharSequence> excludeFieldNames) { List<String> differentFieldNames = new ArrayList<>(); final List<String> fieldNames = getFieldNames(); for (final CharSequence fieldName : fieldNames) { if (!excludeFieldNames.contains(fieldName) && !equalValue(map, fieldName)) { if (differentFieldNames == Collections.<String> emptyList()) { differentFieldNames = new ArrayList<>(); } differentFieldNames.add(fieldName.toString()); } } return differentFieldNames; } @Override default Double getDouble(final CharSequence name) { final Object value = getValue(name); if (Property.hasValue(value)) { if (value instanceof Number) { final Number number = (Number)value; return number.doubleValue(); } else { return Double.valueOf(value.toString()); } } else { return null; } } @Override default <E extends Enum<E>> E getEnum(final Class<E> enumType, final CharSequence fieldName) { final String value = getString(fieldName); if (Property.hasValue(value)) { return Enum.valueOf(enumType, value); } else { return null; } } @Override default Float getFloat(final CharSequence name) { final Object value = getValue(name); if (Property.hasValue(value)) { if (value instanceof Number) { final Number number = (Number)value; return number.floatValue(); } else { return Float.valueOf(value.toString()); } } else { return null; } } /** * Get the value of the primary geometry field. * * @return The primary geometry. */ @SuppressWarnings("unchecked") default <T extends Geometry> T getGeometry() { final RecordDefinition recordDefinition = getRecordDefinition(); if (recordDefinition == null) { return null; } else { final int index = recordDefinition.getGeometryFieldIndex(); return (T)getValue(index); } } @Override default Identifier getIdentifier() { final RecordDefinition recordDefinition = getRecordDefinition(); final List<Integer> idFieldIndexes = recordDefinition.getIdFieldIndexes(); final int idCount = idFieldIndexes.size(); if (idCount == 0) { return null; } else if (idCount == 1) { final Integer idFieldIndex = idFieldIndexes.get(0); final Object idValue = getValue(idFieldIndex); if (idValue == null) { return null; } else { return Identifier.newIdentifier(idValue); } } else { boolean notNull = false; final Object[] idValues = new Object[idCount]; for (int i = 0; i < idValues.length; i++) { final Integer idFieldIndex = idFieldIndexes.get(i); final Object value = getValue(idFieldIndex); if (value != null) { notNull = true; } idValues[i] = value; } if (notNull) { return new ListIdentifier(idValues); } else { return null; } } } @Override default Identifier getIdentifier(final CharSequence fieldName) { final Object value = getValue(fieldName); return Identifier.newIdentifier(value); } default Identifier getIdentifier(final int index) { final Object value = getValue(index); return Identifier.newIdentifier(value); } default Identifier getIdentifier(final List<? extends CharSequence> fieldNames) { final int idCount = fieldNames.size(); if (idCount == 0) { return null; } else if (idCount == 1) { final CharSequence idFieldName = fieldNames.get(0); final Object idValue = getValue(idFieldName); if (idValue == null) { return null; } else { return Identifier.newIdentifier(idValue); } } else { boolean notNull = false; final Object[] idValues = new Object[idCount]; for (int i = 0; i < idValues.length; i++) { final CharSequence idFieldName = fieldNames.get(i); final Object value = getValue(idFieldName); if (value != null) { notNull = true; } idValues[i] = value; } if (notNull) { return new ListIdentifier(idValues); } else { return null; } } } @Override default Integer getInteger(final CharSequence name) { final Object value = getValue(name); if (Property.hasValue(value)) { if (value instanceof Number) { final Number number = (Number)value; return number.intValue(); } else { return Integer.valueOf(value.toString()); } } else { return null; } } @Override default int getInteger(final CharSequence name, final int defaultValue) { final Integer value = getInteger(name); if (value == null) { return defaultValue; } else { return value; } } @Override default Long getLong(final CharSequence name) { final Object value = getValue(name); if (Property.hasValue(value)) { if (value instanceof Number) { final Number number = (Number)value; return number.longValue(); } else { return Long.valueOf(value.toString()); } } else { return null; } } @Override RecordDefinition getRecordDefinition(); @Override default Short getShort(final CharSequence name) { final Object value = getValue(name); if (Property.hasValue(value)) { if (value instanceof Number) { final Number number = (Number)value; return number.shortValue(); } else { return Short.valueOf(value.toString()); } } else { return null; } } default RecordState getState() { return RecordState.NEW; } @Override default String getString(final CharSequence fieldName) { final Object value = getValue(fieldName); if (value == null) { return null; } else if (value instanceof String) { return value.toString(); } else if (value instanceof Clob) { final Clob clob = (Clob)value; try { return clob.getSubString(1, (int)clob.length()); } catch (final SQLException e) { throw new RuntimeException("Unable to read clob", e); } } else { return getFieldDefinition(fieldName).toString(value); } } @Override default String getString(final CharSequence name, final String defaultValue) { final String value = getString(name); if (Property.hasValue(value)) { return value; } else { return defaultValue; } } default TypedIdentifier getTypedIdentifier(final String type) { final Identifier identifier = getIdentifier(); return TypedIdentifier.newIdentifier(type, identifier); } /** * Get the value of the field with the specified name. * * @param name The name of the field. * @return The field value. */ @Override @SuppressWarnings("unchecked") default <T extends Object> T getValue(final CharSequence name) { final RecordDefinition recordDefinition = getRecordDefinition(); try { final int index = recordDefinition.getFieldIndex(name); return (T)getValue(index); } catch (final NullPointerException e) { Logs.warn(this, "Field " + recordDefinition.getPath() + "." + name + " does not exist", e); return null; } } @Override default <T extends Object> T getValue(final CharSequence name, final DataType dataType) { final Object value = getValue(name); return dataType.toObject(value); } /** * Get the value of the field with the specified index. * * @param index The index of the field. * @return The field value. */ default <T extends Object> T getValue(final int index) { final String fieldName = getFieldName(index); return Property.getSimple(this, fieldName); } default <T extends Object> T getValue(final int index, final DataType dataType) { final Object value = getValue(index); return dataType.toObject(value); } @SuppressWarnings("unchecked") default <T> T getValueByPath(final CharSequence path) { final int fieldIndex = getFieldIndex(path); if (fieldIndex == -1) { final RecordDefinition recordDefinition = getRecordDefinition(); final String[] propertyPath = path.toString().split("\\."); Object propertyValue = this; for (int i = 0; i < propertyPath.length && propertyValue != null; i++) { final String propertyName = propertyPath[i]; if (propertyValue instanceof Record) { final Record record = (Record)propertyValue; if (record.hasField(propertyName)) { propertyValue = record.getValue(propertyName); if (propertyValue == null) { return null; } else if (i + 1 < propertyPath.length) { final CodeTable codeTable = recordDefinition.getCodeTableByFieldName(propertyName); if (codeTable != null) { propertyValue = codeTable.getMap(propertyValue); } } } else { return null; } } else if (propertyValue instanceof Map) { final Map<String, Object> map = (Map<String, Object>)propertyValue; propertyValue = map.get(propertyName); if (propertyValue == null) { return null; } else if (i + 1 < propertyPath.length) { final CodeTable codeTable = recordDefinition.getCodeTableByFieldName(propertyName); if (codeTable != null) { propertyValue = codeTable.getMap(propertyValue); } } } else { try { final Object object = propertyValue; propertyValue = Property.getSimple(object, propertyName); } catch (final IllegalArgumentException e) { Logs.debug(this, "Path does not exist " + path, e); return null; } } } return (T)propertyValue; } else { return getValue(fieldIndex); } } default Map<String, Object> getValueMap(final Collection<? extends CharSequence> fieldNames) { final Map<String, Object> values = new HashMap<>(); for (final CharSequence name : fieldNames) { final Object value = getValue(name); if (value != null) { values.put(name.toString(), value); } } return values; } default List<Object> getValues() { final RecordDefinition recordDefinition = getRecordDefinition(); final List<Object> values = new ArrayList<>(); for (int i = 0; i < recordDefinition.getFieldCount(); i++) { final Object value = getValue(i); values.add(value); } return values; } default List<Object> getValues(final Iterable<? extends CharSequence> fieldNames) { final List<Object> values = new ArrayList<>(); for (final CharSequence fieldName : fieldNames) { final Object value = getValue(fieldName); values.add(value); } return values; } default boolean hasGeometry() { final Geometry geometry = getGeometry(); return Property.hasValue(geometry); } default boolean hasValue(final CharSequence name) { final Object value = getValue(name); return Property.hasValue(value); } default boolean hasValuesAll(final CharSequence... fieldNames) { for (final CharSequence fieldName : fieldNames) { if (!hasValue(fieldName)) { return false; } } return true; } /** * Check if any of the fields have a value. * * @param fieldNames * @return True if any of the fields have a value, false otherwise. */ default boolean hasValuesAny(final CharSequence... fieldNames) { for (final CharSequence fieldName : fieldNames) { if (hasValue(fieldName)) { return true; } } return false; } default int indexOf(final Iterable<? extends Record> records) { int index = 0; for (final Record record : records) { if (isSame(record)) { return index; } index++; } return -1; } default int indexOf(final List<? extends Record> records) { int index = 0; try ( ListByIndexIterator<? extends Record> iterable = new ListByIndexIterator<>(records)) { for (final Record record : iterable) { if (isSame(record)) { return index; } index++; } } return -1; } default boolean isChanged() { final RecordState state = getState(); if (state == RecordState.PERSISTED) { return false; } else { return true; } } default boolean isDeleted() { final RecordState state = getState(); if (state == RecordState.DELETED) { return true; } else { return false; } } default boolean isFieldExcluded(final Collection<? extends CharSequence> excludeFieldNames, CharSequence fieldName) { fieldName = fieldName.toString(); final RecordDefinition recordDefinition = getRecordDefinition(); if (excludeFieldNames.contains(fieldName)) { return true; } else if (excludeFieldNames.contains(Record.EXCLUDE_ID) && ("OBJECTID".equals(fieldName) || recordDefinition.getIdFieldNames().contains(fieldName))) { return true; } else if (excludeFieldNames.contains(Record.EXCLUDE_GEOMETRY) && ("OBJECTID".equals(fieldName) || recordDefinition.getGeometryFieldNames().contains(fieldName))) { return true; } else { return false; } } default boolean isModified() { final RecordState state = getState(); if (state == RecordState.NEW) { return true; } else if (state == RecordState.MODIFIED) { return true; } else { return false; } } default boolean isSame(final Record record) { if (record == null) { return false; } else { if (this == record) { return true; } else { synchronized (this) { if (record.getRecordDefinition() == getRecordDefinition()) { final Identifier id = getIdentifier(); final Identifier otherId = record.getIdentifier(); if (id == null || otherId == null) { return false; } else if (DataType.equal(id, otherId)) { return true; } else { return false; } } else { return false; } } } } } boolean isState(RecordState state); default boolean isValid(final CharSequence fieldName) { return true; } default boolean isValid(final int index) { return true; } @Override default Set<String> keySet() { return getRecordDefinition().getFieldNamesSet(); } default Record newRecordGeometry(final Geometry geometry) { final Record record = clone(); record.setGeometryValue(geometry); return record; } @Override default Object put(final String key, final Object value) { final Object oldValue = getValue(key); setValue(key, value); return oldValue; } @Override default void putAll(final Map<? extends String, ? extends Object> values) { setValues(values); } @Override default Object remove(final Object key) { if (key instanceof CharSequence) { final CharSequence name = (CharSequence)key; final Object value = getValue(name); setValue(name, null); return value; } return null; } /** * Remove the first record in the collection of records that is {{@link #isSame(Record)}} as this record. * * @param records * @return The index of the removed record. */ default int removeFrom(final Iterable<? extends Record> records) { int index = 0; for (final Iterator<? extends Record> iterator = records.iterator(); iterator.hasNext();) { final Record record = iterator.next(); if (record.isSame(this)) { iterator.remove(); return index; } else { index++; } } return -1; } /** * Set the value of the primary geometry field. * * @param geometry The primary geometry. */ default void setGeometryValue(final Geometry geometry) { final RecordDefinition recordDefinition = getRecordDefinition(); final int index = recordDefinition.getGeometryFieldIndex(); if (index > -1) { setValue(index, geometry); } } default void setIdentifier(final Identifier identifier) { final RecordDefinition recordDefinition = getRecordDefinition(); final RecordState state = getState(); if (state == RecordState.NEW || state == RecordState.INITIALIZING) { final List<String> idFieldNames = recordDefinition.getIdFieldNames(); Identifier.setIdentifier(this, idFieldNames, identifier); } else { final Identifier oldIdentifier = getIdentifier(); if (!DataTypes.IDENTIFIER.equals(oldIdentifier, identifier)) { throw new IllegalStateException( "Cannot change the ID on a persisted record: " + identifier + "!=" + oldIdentifier); } } } default RecordState setState(final RecordState state) { return getState(); } /** * Set the value of the field with the specified name. * * @param name The name of the field. * @param value The new value. */ default boolean setValue(final CharSequence name, final Object value) { final boolean updated = false; final RecordDefinition recordDefinition = getRecordDefinition(); final int index = recordDefinition.getFieldIndex(name); if (index != -1) { return setValue(index, value); } else { if (Strings.contains(name, '.')) { throw new IllegalArgumentException("name cannot contain a '.' " + name + "=" + value); } } return updated; } default <T> T setValue(final CharSequence fieldName, final Record source, final String sourceFieldName) { @SuppressWarnings("unchecked") final T value = (T)source.getValue(sourceFieldName); setValueByPath(fieldName, value); return value; } /** * Set the value of the field with the specified name. * * @param index The index of the field. * @param value The new value; */ default boolean setValue(final int index, final Object value) { final String fieldName = getFieldName(index); final Object oldValue = getValue(index); Property.set(this, fieldName, value); return DataType.equal(oldValue, value); } @SuppressWarnings("rawtypes") default boolean setValueByPath(final CharSequence path, final Object value) { boolean updated = false; final String name = path.toString(); final int dotIndex = name.indexOf("."); String codeTableFieldName; String codeTableValueName = null; final RecordDefinition recordDefinition = getRecordDefinition(); if (dotIndex == -1) { final String idFieldName = recordDefinition.getIdFieldName(); if (name.equals(idFieldName)) { codeTableFieldName = null; } else { codeTableFieldName = name; } } else { codeTableFieldName = name.substring(0, dotIndex); codeTableValueName = name.substring(dotIndex + 1); } final CodeTable codeTable = recordDefinition.getCodeTableByFieldName(codeTableFieldName); if (codeTable == null) { if (dotIndex != -1) { Logs.debug(this, "Cannot get code table for " + recordDefinition.getPath() + "." + name); return false; } updated = setValue(name, value); } else if (!Property.hasValue(value)) { updated = setValue(codeTableFieldName, null); } else { Object targetValue; if (codeTableValueName == null) { Identifier id; if (value instanceof List) { final List list = (List)value; id = codeTable.getIdentifier(list.toArray()); } else { id = codeTable.getIdentifier(value); } if (id == null) { targetValue = value; } else { targetValue = Value.getValue(id); } } else { targetValue = codeTable.getIdentifier(Collections.singletonMap(codeTableValueName, value)); } if (targetValue == null) { targetValue = value; } updated = setValue(codeTableFieldName, targetValue); } return updated; } default <T> T setValueByPath(final CharSequence fieldPath, final Record source, final String sourceFieldPath) { @SuppressWarnings("unchecked") final T value = (T)source.getValueByPath(sourceFieldPath); setValueByPath(fieldPath, value); return value; } default void setValues(final Iterable<? extends Object> values) { int fieldIndex = 0; for (final Object value : values) { setValue(fieldIndex, value); fieldIndex++; } } default void setValues(final Map<? extends CharSequence, ? extends Object> values) { if (values instanceof Record) { final Record record = (Record)values; setValues(record); } else if (values != null) { setValues(values, new ArrayList<>(values.keySet())); } } default void setValues(final Map<? extends CharSequence, ? extends Object> values, final Collection<? extends CharSequence> fieldNames) { for (final CharSequence fieldName : fieldNames) { final Object newValue = values.get(fieldName); final FieldDefinition fieldDefinition = getFieldDefinition(fieldName); if (fieldDefinition != null) { fieldDefinition.setValue(this, newValue); } } } default void setValues(final Map<? extends String, ? extends Object> values, final String... fieldNames) { setValues(values, Arrays.asList(fieldNames)); } default void setValues(final Object... values) { for (int fieldIndex = 0; fieldIndex < values.length; fieldIndex++) { final Object value = values[fieldIndex]; setValue(fieldIndex, value); } } default void setValues(final Record record) { if (record != null) { final List<FieldDefinition> idFields = getRecordDefinition().getIdFields(); final List<FieldDefinition> fields = getFieldDefinitions(); for (final FieldDefinition fieldDefintion : fields) { if (!idFields.contains(fieldDefintion)) { final String name = fieldDefintion.getName(); final Object value = record.getValue(name); fieldDefintion.setValue(this, value); } } } } default void setValuesByPath(final Map<? extends CharSequence, ? extends Object> values) { if (values != null) { for (final Entry<? extends CharSequence, ? extends Object> defaultValue : new ArrayList<>( values.entrySet())) { final CharSequence name = defaultValue.getKey(); final Object value = defaultValue.getValue(); setValueByPath(name, value); } } } default void setValuesClone(final Record record) { final List<FieldDefinition> fields = getFieldDefinitions(); for (final FieldDefinition fieldDefintion : fields) { final String name = fieldDefintion.getName(); final Object value = record.getValue(name); fieldDefintion.setValueClone(this, value); } } @Override default int size() { final RecordDefinition recordDefinition = getRecordDefinition(); return recordDefinition.getFieldCount(); } default void validateField(final int fieldIndex) { final FieldDefinition field = getFieldDefinition(fieldIndex); if (field != null) { final Object value = getValue(fieldIndex); field.validate(this, value); } } }