Author: desruisseaux Date: Wed Jul 9 14:06:19 2014 New Revision: 1609161 URL: http://svn.apache.org/r1609161 Log: DefaultRecord uses array of primitive type when possible.
Modified: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/iso/DefaultRecord.java sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/iso/RecordDefinition.java sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/util/iso/DefaultRecordTest.java sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/util/iso/DefaultRecordTypeTest.java sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/util/iso/SerializableRecordSchema.java Modified: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/iso/DefaultRecord.java URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/iso/DefaultRecord.java?rev=1609161&r1=1609160&r2=1609161&view=diff ============================================================================== --- sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/iso/DefaultRecord.java [UTF-8] (original) +++ sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/iso/DefaultRecord.java [UTF-8] Wed Jul 9 14:06:19 2014 @@ -22,18 +22,26 @@ import java.util.AbstractMap; import java.util.AbstractSet; import java.util.Iterator; import java.util.NoSuchElementException; -import java.util.Arrays; import java.io.Serializable; +import java.lang.reflect.Array; import org.opengis.util.MemberName; import org.opengis.util.Record; import org.opengis.util.RecordType; import org.apache.sis.util.Debug; +import org.apache.sis.util.Utilities; import org.apache.sis.util.ArgumentChecks; import org.apache.sis.util.resources.Errors; +// Branch-dependent imports +import java.util.Objects; + /** * A list of logically related elements as (<var>name</var>, <var>value</var>) pairs in a dictionary. + * By definition, all record members have a [1 … 1] cardinality + * (for a more flexible construct, see {@linkplain org.apache.sis.feature features}). + * Since all members are expected to be assigned a value, the initial values on {@code DefaultRecord} + * instantiation are undetermined. Some may be null, or some may be zero. * * {@section Limitations} * <ul> @@ -65,12 +73,15 @@ public class DefaultRecord implements Re final RecordDefinition definition; /** - * The record values. + * The record values in an array. May be an array of primitive type for compactness, + * which is why the type is not {@code Object[]}. */ - private final Object[] values; + private final Object values; /** * Creates a new record for the given record type. + * The initial values are undetermined - they may be null or zero. + * Callers can assign values by a call to {@link #setAll(Object[])}. * * @param type The type definition of the new record. */ @@ -81,7 +92,7 @@ public class DefaultRecord implements Re } else { definition = new RecordDefinition.Adapter(type); } - values = new Object[definition.size()]; + values = Array.newInstance(definition.baseValueClass(), definition.size()); } /** @@ -222,7 +233,7 @@ public class DefaultRecord implements Re @Override public Object locate(final MemberName name) { final Integer index = definition.indexOf(name); - return (index != null) ? values[index] : null; + return (index != null) ? Array.get(values, index) : null; } /** @@ -247,7 +258,7 @@ public class DefaultRecord implements Re name, value.getClass())); } } - values[index] = value; + Array.set(values, index, value); } /** @@ -257,11 +268,12 @@ public class DefaultRecord implements Re * @throws IllegalArgumentException if the given number of values does not match the expected number. * @throws ClassCastException if a value is not an instance of the expected type for this record. */ - public void setValues(final Object... newValues) { + public void setAll(final Object... newValues) { ArgumentChecks.ensureNonNull("values", newValues); - if (newValues.length != values.length) { + final int length = Array.getLength(values); + if (newValues.length != length) { throw new IllegalArgumentException(Errors.format( - Errors.Keys.UnexpectedArrayLength_2, values.length, newValues.length)); + Errors.Keys.UnexpectedArrayLength_2, length, newValues.length)); } for (int i=0; i<newValues.length; i++) { final Object value = newValues[i]; @@ -272,7 +284,7 @@ public class DefaultRecord implements Re definition.getName(i), value.getClass())); } } - values[i] = value; + Array.set(values, i, value); } } @@ -290,7 +302,7 @@ public class DefaultRecord implements Re if (object != null && object.getClass() == getClass()) { final DefaultRecord that = (DefaultRecord) object; return definition.getRecordType().equals(that.definition.getRecordType()) && - Arrays.equals(values, that.values); + Objects.deepEquals(values, that.values); } return false; } @@ -302,7 +314,7 @@ public class DefaultRecord implements Re */ @Override public int hashCode() { - return Arrays.hashCode(values) ^ definition.getRecordType().hashCode(); + return Utilities.deepHashCode(values) ^ definition.getRecordType().hashCode(); } /** Modified: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/iso/RecordDefinition.java URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/iso/RecordDefinition.java?rev=1609161&r1=1609160&r2=1609161&view=diff ============================================================================== --- sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/iso/RecordDefinition.java [UTF-8] (original) +++ sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/iso/RecordDefinition.java [UTF-8] Wed Jul 9 14:06:19 2014 @@ -21,12 +21,15 @@ import java.util.LinkedHashMap; import java.io.Serializable; import java.io.IOException; import java.io.ObjectInputStream; +import java.lang.reflect.Array; import javax.xml.bind.annotation.XmlTransient; import org.opengis.util.Type; import org.opengis.util.RecordType; import org.opengis.util.MemberName; import org.opengis.feature.AttributeType; import org.apache.sis.util.Debug; +import org.apache.sis.util.Classes; +import org.apache.sis.util.Numbers; import org.apache.sis.util.CharSequences; import org.apache.sis.util.collection.Containers; import org.apache.sis.internal.util.CollectionsExt; @@ -116,6 +119,11 @@ abstract class RecordDefinition { // Int private transient Class<?>[] valueClasses; /** + * The common parent of all value classes. May be a primitive type. + */ + private transient Class<?> baseValueClass; + + /** * Creates a new instance. Subclasses shall invoke {@link #computeTransientFields(Map)} in their constructor. */ RecordDefinition() { @@ -148,6 +156,7 @@ abstract class RecordDefinition { // Int valueClasses = new Class<?>[size]; } valueClasses[i] = c; + baseValueClass = Classes.findCommonClass(baseValueClass, c); } } final MemberName name = entry.getKey(); @@ -157,10 +166,18 @@ abstract class RecordDefinition { // Int i++; } memberIndices = CollectionsExt.unmodifiableOrCopy(memberIndices); + baseValueClass = (baseValueClass != null) ? Numbers.wrapperToPrimitive(baseValueClass) : Object.class; return types; } /** + * Returns the common parent of all value classes. May be a primitive type. + */ + final Class<?> baseValueClass() { + return baseValueClass; + } + + /** * Read-only access to the map of member indices. */ final Map<MemberName,Integer> memberIndices() { @@ -211,10 +228,10 @@ abstract class RecordDefinition { // Int * Returns a string representation of a {@code Record} or {@code RecordType}. * * @param head Either {@code "Record"} or {@code "RecordType"}. - * @param values The values, or {@code null} for writing the types instead. + * @param values The values as an array, or {@code null} for writing the types instead. * @return The string representation. */ - final String toString(final String head, final Object[] values) { + final String toString(final String head, final Object values) { final StringBuilder buffer = new StringBuilder(250); final String lineSeparator = System.lineSeparator(); final String[] names = new String[members.length]; @@ -226,7 +243,7 @@ abstract class RecordDefinition { // Int for (int i=0; i<names.length; i++) { final String name = names[i]; buffer.append(" ").append(name); - final Object value = (values != null) ? values[i] : members[i].getAttributeType(); + final Object value = (values != null) ? Array.get(values, i) : members[i].getAttributeType(); if (value != null) { buffer.append(CharSequences.spaces(width - name.length())).append(" : ").append(value); } Modified: sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/util/iso/DefaultRecordTest.java URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/util/iso/DefaultRecordTest.java?rev=1609161&r1=1609160&r2=1609161&view=diff ============================================================================== --- sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/util/iso/DefaultRecordTest.java [UTF-8] (original) +++ sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/util/iso/DefaultRecordTest.java [UTF-8] Wed Jul 9 14:06:19 2014 @@ -16,9 +16,8 @@ */ package org.apache.sis.util.iso; - -import java.util.LinkedHashMap; import java.util.Map; +import java.util.LinkedHashMap; import org.opengis.util.MemberName; import org.opengis.util.RecordType; @@ -73,28 +72,35 @@ public final strictfp class DefaultRecor } /** - * Tests {@link DefaultRecord#setValues(Object[])}. + * Sets all values in the given record using the {@link DefaultRecord#setAll(Object[])} method, + * then checks that the values were correctly stored. + */ + private static void setAllAndCompare(final DefaultRecord record, final Object... values) { + record.setAll(values); + assertArrayEquals("attributes.values", values, record.getAttributes().values().toArray()); + } + + /** + * Tests {@link DefaultRecord#setAll(Object[])}. */ @Test - public void testSetValues() { + public void testSetAll() { final DefaultRecord record = new DefaultRecord(recordType); try { - record.setValues("Machu Picchu", -13.1639, -72.5468); + record.setAll("Machu Picchu", -13.1639, -72.5468); fail("Shall not accept array of illegal length."); } catch (IllegalArgumentException e) { assertNotNull(e.getMessage()); } try { - record.setValues("Machu Picchu", -13.1639, -72.5468, "Unknown"); + record.setAll("Machu Picchu", -13.1639, -72.5468, "Unknown"); fail("Shall not accept 'population' value of class String."); } catch (ClassCastException e) { final String message = e.getMessage(); assertTrue(message, message.contains("population")); assertTrue(message, message.contains("String")); } - final Object[] values = {"Machu Picchu", -13.1639, -72.5468, null}; - record.setValues(values); - assertArrayEquals(values, record.getAttributes().values().toArray()); + setAllAndCompare(record, "Machu Picchu", -13.1639, -72.5468, null); } /** @@ -128,10 +134,10 @@ public final strictfp class DefaultRecor * Tests {@link DefaultRecord#toString()}. */ @Test - @DependsOnMethod("testSetValues") + @DependsOnMethod("testSetAll") public void testToString() { final DefaultRecord record = new DefaultRecord(recordType); - record.setValues("Machu Picchu", -13.1639, -72.5468, null); + record.setAll("Machu Picchu", -13.1639, -72.5468, null); assertMultilinesEquals( "Record[“MyRecordType”] {\n" + " city : Machu Picchu\n" + @@ -158,10 +164,43 @@ public final strictfp class DefaultRecor * Tests serialization of a {@link DefaultRecord}. */ @Test - @DependsOnMethod("testSetValues") + @DependsOnMethod("testSetAll") public void testSerialization() { final DefaultRecord record = new DefaultRecord(recordType); - record.setValues("Machu Picchu", -13.1639, -72.5468, null); + record.setAll("Machu Picchu", -13.1639, -72.5468, null); + assertNotSame(record, assertSerializedEquals(record)); + } + + /** + * Tests a record where all members are the same primitive type. This method performs tests + * similar to {@link #testSetAll()}, {@link #testToString()} and {@link #testSerialization()}. + */ + @Test + @DependsOnMethod({"testSetAll", "testToString", "testSerialization"}) + public void testPrimitiveType() { + final Map<CharSequence,Class<?>> members = new LinkedHashMap<>(8); + assertNull(members.put("latitude", Double.class)); + assertNull(members.put("longitude", Double.class)); + final DefaultRecord record = new DefaultRecord( + SerializableRecordSchema.INSTANCE.createRecordType("MyRecordType", members)); + /* + * As a side effect of the fact that DefaultRecord uses an array of primitive type, + * initial values should be zero instead than null. We use this trick as a way to + * detect that we really got an array of primitive type. + */ + assertEquals("baseValueClass", Double.TYPE, record.definition.baseValueClass()); + assertArrayEquals("attributes.values", new Double[] {0.0, 0.0}, + record.getAttributes().values().toArray()); + /* + * Combines tests similar to 3 other test methods in this class. + */ + setAllAndCompare(record, -13.1639, -72.5468); + assertMultilinesEquals( + "Record[“MyRecordType”] {\n" + + " latitude : -13.1639\n" + + " longitude : -72.5468\n" + + "}\n", + record.toString()); assertNotSame(record, assertSerializedEquals(record)); } } Modified: sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/util/iso/DefaultRecordTypeTest.java URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/util/iso/DefaultRecordTypeTest.java?rev=1609161&r1=1609160&r2=1609161&view=diff ============================================================================== --- sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/util/iso/DefaultRecordTypeTest.java [UTF-8] (original) +++ sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/util/iso/DefaultRecordTypeTest.java [UTF-8] Wed Jul 9 14:06:19 2014 @@ -18,10 +18,8 @@ package org.apache.sis.util.iso; import java.util.Collections; import org.opengis.util.Type; -import org.opengis.util.TypeName; import org.opengis.util.MemberName; import org.opengis.util.NameSpace; -import org.opengis.util.RecordSchema; import org.apache.sis.internal.simple.SimpleAttributeType; // Test imports @@ -44,10 +42,10 @@ import static org.apache.sis.test.TestUt */ @DependsOn(AbstractNameTest.class) public final strictfp class DefaultRecordTypeTest extends TestCase { - /** Value of {@link DefaultRecordType#getContainer()}. */ private RecordSchema container; - /** Value of {@link DefaultRecordType#getTypeName()}. */ private TypeName recordTypeName; - /** Value of {@link DefaultRecordType#getMembers()}. */ private MemberName memberName; - /** Value of {@link DefaultRecordType#getMemberTypes()}. */ private TypeName memberTypeName; + /** Value of {@link DefaultRecordType#getContainer()}. */ private DefaultRecordSchema container; + /** Value of {@link DefaultRecordType#getTypeName()}. */ private DefaultTypeName recordTypeName; + /** Value of {@link DefaultRecordType#getMembers()}. */ private DefaultMemberName memberName; + /** Value of {@link DefaultRecordType#getMemberTypes()}. */ private DefaultTypeName memberTypeName; /** * Initializes the private fields. @@ -82,6 +80,10 @@ public final strictfp class DefaultRecor public void testConstructor() { init(); final DefaultRecordType type = create(); + assertEquals("size", 1, type.size()); + assertEquals("baseValueClass", Integer.TYPE, type.baseValueClass()); + + // Public properties assertSame("container", container, type.getContainer()); assertSame("typeName", recordTypeName, type.getTypeName()); assertSame("members", memberName, getSingleton(type.getMembers())); @@ -99,7 +101,7 @@ public final strictfp class DefaultRecor @DependsOnMethod("testConstructor") public void testArgumentChecks() { init(); - final TypeName correctRecordName = recordTypeName; + final DefaultTypeName correctRecordName = recordTypeName; final NameSpace correctMemberNamespace = memberName.scope(); final DefaultNameSpace wrongNamespace = new DefaultNameSpace(null, "WrongNameSpace", ":", ":"); /* Modified: sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/util/iso/SerializableRecordSchema.java URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/util/iso/SerializableRecordSchema.java?rev=1609161&r1=1609160&r2=1609161&view=diff ============================================================================== --- sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/util/iso/SerializableRecordSchema.java [UTF-8] (original) +++ sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/util/iso/SerializableRecordSchema.java [UTF-8] Wed Jul 9 14:06:19 2014 @@ -18,7 +18,6 @@ package org.apache.sis.util.iso; import java.io.Serializable; import java.io.ObjectStreamException; -import org.opengis.util.RecordSchema; /** @@ -35,7 +34,7 @@ final class SerializableRecordSchema ext /** * The unique instance for the shema. */ - static RecordSchema INSTANCE; + static DefaultRecordSchema INSTANCE; /** * Construct a new record schema.