Author: desruisseaux
Date: Thu Mar 28 11:04:14 2013
New Revision: 1462033
URL: http://svn.apache.org/r1462033
Log:
PropertyAccessor shall uses the ordering defined by XmlType.propOrder
annotation.
Modified:
sis/branches/JDK7/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyAccessor.java
sis/branches/JDK7/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyComparator.java
sis/branches/JDK7/sis-metadata/src/test/java/org/apache/sis/metadata/PropertyAccessorTest.java
Modified:
sis/branches/JDK7/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyAccessor.java
URL:
http://svn.apache.org/viewvc/sis/branches/JDK7/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyAccessor.java?rev=1462033&r1=1462032&r2=1462033&view=diff
==============================================================================
---
sis/branches/JDK7/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyAccessor.java
[UTF-8] (original)
+++
sis/branches/JDK7/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyAccessor.java
[UTF-8] Thu Mar 28 11:04:14 2013
@@ -49,6 +49,7 @@ import org.apache.sis.xml.IdentifiedObje
import static org.apache.sis.util.collection.CollectionsExt.modifiableCopy;
import static org.apache.sis.util.collection.CollectionsExt.hashMapCapacity;
import static org.apache.sis.internal.util.Utilities.floatEpsilonEqual;
+import static org.apache.sis.metadata.PropertyComparator.*;
/**
@@ -79,21 +80,6 @@ import static org.apache.sis.internal.ut
@ThreadSafe
final class PropertyAccessor {
/**
- * The prefix for getters on boolean values.
- */
- private static final String IS = "is";
-
- /**
- * The prefix for getters (general case).
- */
- private static final String GET = "get";
-
- /**
- * The prefix for setters.
- */
- private static final String SET = "set";
-
- /**
* Getters shared between many instances of this class. Two different
implementations
* may share the same getters but different setters.
*
@@ -236,7 +222,7 @@ final class PropertyAccessor {
this.standard = standard;
this.type = type;
this.implementation = implementation;
- this.getters = getGetters(type);
+ this.getters = getGetters(type, implementation);
int allCount = getters.length;
int standardCount = allCount;
if (allCount != 0 && getters[allCount-1] == EXTRA_GETTER) {
@@ -375,9 +361,10 @@ final class PropertyAccessor {
* since it may be shared among many instances of {@code PropertyAccessor}.
*
* @param type The metadata interface.
+ * @param implementation The class of metadata implementations.
* @return The getters declared in the given interface (never {@code
null}).
*/
- private static Method[] getGetters(final Class<?> type) {
+ private static Method[] getGetters(final Class<?> type, final Class<?>
implementation) {
synchronized (SHARED_GETTERS) {
Method[] getters = SHARED_GETTERS.get(type);
if (getters == null) {
@@ -425,7 +412,7 @@ final class PropertyAccessor {
* keep the extra methods last. The code checking for the
extra methods require
* them to be last.
*/
- Arrays.sort(getters, 0, count, PropertyComparator.INSTANCE);
+ Arrays.sort(getters, 0, count, new
PropertyComparator(implementation));
if (!hasExtraGetter) {
if (getters.length == count) {
getters = Arrays.copyOf(getters, count+1);
@@ -440,70 +427,6 @@ final class PropertyAccessor {
}
/**
- * Returns the prefix of the specified method name. If the method name
doesn't starts with
- * a prefix (for example {@link
org.opengis.metadata.quality.ConformanceResult#pass()}),
- * then this method returns an empty string.
- */
- private static String prefix(final String name) {
- if (name.startsWith(GET)) {
- return GET;
- }
- if (name.startsWith(IS)) {
- return IS;
- }
- if (name.startsWith(SET)) {
- return SET;
- }
- return "";
- }
-
- /**
- * Returns {@code true} if the specified string starting at the specified
index contains
- * no lower case characters. The characters don't have to be in upper case
however (e.g.
- * non-alphabetic characters)
- */
- private static boolean isAcronym(final String name, int offset) {
- final int length = name.length();
- while (offset < length) {
- final int c = name.codePointAt(offset);
- if (Character.isLowerCase(c)) {
- return false;
- }
- offset += Character.charCount(c);
- }
- return true;
- }
-
- /**
- * Removes the {@code "get"} or {@code "is"} prefix and turn the first
character after the
- * prefix into lower case. For example the method name {@code "getTitle"}
will be replaced
- * by the property name {@code "title"}. We will perform this operation
only if there is
- * at least 1 character after the prefix.
- *
- * @param name The method name (can not be {@code null}).
- * @param base Must be the result of {@code prefix(name).length()}.
- * @return The property name (never {@code null}).
- */
- private static String toPropertyName(String name, final int base) {
- final int length = name.length();
- if (length > base) {
- if (isAcronym(name, base)) {
- name = name.substring(base);
- } else {
- final int up = name.codePointAt(base);
- final int lo = Character.toLowerCase(up);
- if (up != lo) {
- name = new StringBuilder(length - base).appendCodePoint(lo)
- .append(name, base + Character.charCount(up),
length).toString();
- } else {
- name = name.substring(base);
- }
- }
- }
- return name.intern();
- }
-
- /**
* Returns the number of properties that can be read.
*/
final int count() {
Modified:
sis/branches/JDK7/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyComparator.java
URL:
http://svn.apache.org/viewvc/sis/branches/JDK7/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyComparator.java?rev=1462033&r1=1462032&r2=1462033&view=diff
==============================================================================
---
sis/branches/JDK7/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyComparator.java
[UTF-8] (original)
+++
sis/branches/JDK7/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyComparator.java
[UTF-8] Thu Mar 28 11:04:14 2013
@@ -17,33 +17,78 @@
package org.apache.sis.metadata;
import java.util.Comparator;
+import java.util.Map;
+import java.util.IdentityHashMap;
import java.lang.reflect.Method;
-import net.jcip.annotations.Immutable;
+import javax.xml.bind.annotation.XmlType;
import org.opengis.annotation.UML;
import org.opengis.annotation.Obligation;
/**
- * The comparator for sorting method order. This comparator puts mandatory
methods first,
- * which is necessary for reducing the risk of ambiguity in {@link
MetadataTreeFormat#parse}.
+ * The comparator for sorting the properties in a metadata object.
+ * Since the comparator uses (among other criterion) the property names, this
class
+ * incidentally defines static methods for inferring those names from the
methods.
+ *
+ * <p>This comparator uses the following criterion, in priority order:</p>
+ * <ol>
+ * <li>If the property order is specified by a {@link XmlType} annotation,
+ * then this comparator complies to that order.</li>
+ * <li>Otherwise this comparator sorts mandatory methods first, followed by
+ * conditional methods, then optional ones.</li>
+ * <li>If the order can not be inferred from the above, then the comparator
+ * fallbacks on alphabetical order.</li>
+ * </ol>
+ *
+ * The first criterion (mandatory methods first) is necessary for reducing the
risk
+ * of ambiguity in the {@link MetadataTreeTable#parse} method.
*
* @author Martin Desruisseaux (Geomatys)
* @since 0.3 (derived from geotk-2.4)
* @version 0.3
* @module
*/
-@Immutable
final class PropertyComparator implements Comparator<Method> {
/**
- * The singleton instance.
+ * The prefix for getters on boolean values.
+ */
+ private static final String IS = "is";
+
+ /**
+ * The prefix for getters (general case).
+ */
+ private static final String GET = "get";
+
+ /**
+ * The prefix for setters.
+ */
+ static final String SET = "set";
+
+ /**
+ * Methods specified in the {@link XmlType} annotation, or {@code null} if
none.
*/
- static final Comparator<Method> INSTANCE = new PropertyComparator();
+ private final String[] order;
/**
- * Do not allow instantiation of this class, except for the singleton.
+ * Indices of methods in the {@link #order} array, created when first
needed.
*/
- private PropertyComparator() {
+ private Map<Method,Integer> indices;
+
+ /**
+ * Creates a new comparator for the given implementation class.
+ *
+ * @param implementation The implementation class, or {@code null} if
unknown.
+ */
+ PropertyComparator(final Class<?> implementation) {
+ if (implementation != null) {
+ final XmlType xml = implementation.getAnnotation(XmlType.class);
+ if (xml != null) {
+ order = xml.propOrder();
+ return;
+ }
+ }
+ order = null;
}
/**
@@ -51,27 +96,31 @@ final class PropertyComparator implement
*/
@Override
public int compare(final Method m1, final Method m2) {
- final UML a1 = m1.getAnnotation(UML.class);
- final UML a2 = m2.getAnnotation(UML.class);
- if (a1 != null) {
- if (a2 == null) return +1; // Sort annotated elements first.
- int c = order(a1) - order(a2); // Mandatory elements must be
first.
- if (c == 0) {
- // Fallback on alphabetical order.
- c = a1.identifier().compareToIgnoreCase(a2.identifier());
- }
- return c;
- } else if (a2 != null) {
- return -1; // Sort annotated elements first.
+ int c = indexOf(m1) - indexOf(m2);
+ if (c == 0) {
+ final UML a1 = m1.getAnnotation(UML.class);
+ final UML a2 = m2.getAnnotation(UML.class);
+ if (a1 != null) {
+ if (a2 == null) return +1; // Sort annotated elements first.
+ c = order(a1) - order(a2); // Mandatory elements must be
first.
+ if (c == 0) {
+ // Fallback on alphabetical order.
+ c = a1.identifier().compareToIgnoreCase(a2.identifier());
+ }
+ return c;
+ } else if (a2 != null) {
+ return -1; // Sort annotated elements first.
+ }
+ // Fallback on alphabetical order.
+ c = m1.getName().compareToIgnoreCase(m2.getName());
}
- // Fallback on alphabetical order.
- return m1.getName().compareToIgnoreCase(m2.getName());
+ return c;
}
/**
* Returns a higher number for obligation which should be first.
*/
- private int order(final UML uml) {
+ private static int order(final UML uml) {
final Obligation obligation = uml.obligation();
if (obligation != null) {
switch (obligation) {
@@ -83,4 +132,95 @@ final class PropertyComparator implement
}
return 5;
}
+
+ /**
+ * Returns the index of the given method, or {@code order.length} if the
method is not found.
+ */
+ private int indexOf(final Method method) {
+ int i = 0;
+ if (order != null) {
+ if (indices == null) {
+ indices = new IdentityHashMap<>();
+ } else {
+ Integer index = indices.get(method);
+ if (index != null) {
+ return index;
+ }
+ }
+ String name = method.getName();
+ name = toPropertyName(name, prefix(name).length());
+ while (i < order.length) {
+ if (name.equals(order[i])) {
+ break;
+ }
+ i++;
+ }
+ indices.put(method, i);
+ }
+ return i;
+ }
+
+ /**
+ * Returns the prefix of the specified method name. If the method name
doesn't starts with
+ * a prefix (for example {@link
org.opengis.metadata.quality.ConformanceResult#pass()}),
+ * then this method returns an empty string.
+ */
+ static String prefix(final String name) {
+ if (name.startsWith(GET)) {
+ return GET;
+ }
+ if (name.startsWith(IS)) {
+ return IS;
+ }
+ if (name.startsWith(SET)) {
+ return SET;
+ }
+ return "";
+ }
+
+ /**
+ * Returns {@code true} if the specified string starting at the specified
index contains
+ * no lower case characters. The characters don't have to be in upper case
however (e.g.
+ * non-alphabetic characters)
+ */
+ private static boolean isAcronym(final String name, int offset) {
+ final int length = name.length();
+ while (offset < length) {
+ final int c = name.codePointAt(offset);
+ if (Character.isLowerCase(c)) {
+ return false;
+ }
+ offset += Character.charCount(c);
+ }
+ return true;
+ }
+
+ /**
+ * Removes the {@code "get"} or {@code "is"} prefix and turn the first
character after the
+ * prefix into lower case. For example the method name {@code "getTitle"}
will be replaced
+ * by the property name {@code "title"}. We will perform this operation
only if there is
+ * at least 1 character after the prefix.
+ *
+ * @param name The method name (can not be {@code null}).
+ * @param base Must be the result of {@code prefix(name).length()}.
+ * @return The property name (never {@code null}).
+ */
+ static String toPropertyName(String name, final int base) {
+ final int length = name.length();
+ if (length > base) {
+ if (isAcronym(name, base)) {
+ name = name.substring(base);
+ } else {
+ final int up = name.codePointAt(base);
+ final int lo = Character.toLowerCase(up);
+ if (up != lo) {
+ name = new StringBuilder(length - base).appendCodePoint(lo)
+ .append(name, base + Character.charCount(up),
length).toString();
+ } else {
+ name = name.substring(base);
+ }
+ }
+ }
+ return name.intern();
+ }
}
Modified:
sis/branches/JDK7/sis-metadata/src/test/java/org/apache/sis/metadata/PropertyAccessorTest.java
URL:
http://svn.apache.org/viewvc/sis/branches/JDK7/sis-metadata/src/test/java/org/apache/sis/metadata/PropertyAccessorTest.java?rev=1462033&r1=1462032&r2=1462033&view=diff
==============================================================================
---
sis/branches/JDK7/sis-metadata/src/test/java/org/apache/sis/metadata/PropertyAccessorTest.java
[UTF-8] (original)
+++
sis/branches/JDK7/sis-metadata/src/test/java/org/apache/sis/metadata/PropertyAccessorTest.java
[UTF-8] Thu Mar 28 11:04:14 2013
@@ -140,26 +140,27 @@ public final strictfp class PropertyAcce
}
/**
- * Tests the constructor. This test may need to be updated if a future
GeoAPI release
- * modifies the {@link Citation} interface.
+ * Tests the constructor with the {@link DefaultCitation} implementation.
+ * The order of properties shall be the order declared in the {@code
XmlType.propOrder} annotation.
+ * This test may need to be updated if a future GeoAPI release modifies
the {@link Citation} interface.
*/
@Test
public void testConstructor() {
assertMappingEquals(createPropertyAccessor(),
//……Declaring
type………Method………………………………………………………………JavaBeans………………………………………………UML
identifier……………………………Sentence………………………………………………………Type………………………………………………………………
-/*Required*/Citation.class, "getDates", "dates",
"date", "Dates",
CitationDate[].class,
Citation.class, "getTitle", "title",
"title", "Title",
InternationalString.class,
-/*Optional*/Citation.class, "getAlternateTitles", "alternateTitles",
"alternateTitle", "Alternate titles",
InternationalString[].class,
- Citation.class, "getCitedResponsibleParties",
"citedResponsibleParties", "citedResponsibleParty", "Cited responsible
parties", ResponsibleParty[].class,
- Citation.class, "getCollectiveTitle", "collectiveTitle",
"collectiveTitle", "Collective title",
InternationalString.class,
+ Citation.class, "getAlternateTitles", "alternateTitles",
"alternateTitle", "Alternate titles",
InternationalString[].class,
+ Citation.class, "getDates", "dates",
"date", "Dates",
CitationDate[].class,
Citation.class, "getEdition", "edition",
"edition", "Edition",
InternationalString.class,
Citation.class, "getEditionDate", "editionDate",
"editionDate", "Edition date", Date.class,
Citation.class, "getIdentifiers", "identifiers",
"identifier", "Identifiers", Identifier[].class,
- Citation.class, "getISBN", "ISBN",
"ISBN", "ISBN", String.class,
- Citation.class, "getISSN", "ISSN",
"ISSN", "ISSN", String.class,
- Citation.class, "getOtherCitationDetails",
"otherCitationDetails", "otherCitationDetails", "Other citation details",
InternationalString.class,
+ Citation.class, "getCitedResponsibleParties",
"citedResponsibleParties", "citedResponsibleParty", "Cited responsible
parties", ResponsibleParty[].class,
Citation.class, "getPresentationForms", "presentationForms",
"presentationForm", "Presentation forms",
PresentationForm[].class,
- Citation.class, "getSeries", "series",
"series", "Series", Series.class);
+ Citation.class, "getSeries", "series",
"series", "Series", Series.class,
+ Citation.class, "getOtherCitationDetails",
"otherCitationDetails", "otherCitationDetails", "Other citation details",
InternationalString.class,
+ Citation.class, "getCollectiveTitle", "collectiveTitle",
"collectiveTitle", "Collective title",
InternationalString.class,
+ Citation.class, "getISBN", "ISBN",
"ISBN", "ISBN", String.class,
+ Citation.class, "getISSN", "ISSN",
"ISSN", "ISSN", String.class);
}
/**