You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2012/11/30 18:38:20 UTC
svn commit: r1415758 [2/3] - in /sis/trunk: ./ ide-project/NetBeans/
ide-project/NetBeans/nbproject/
sis-utility/src/main/java/org/apache/sis/internal/util/
sis-utility/src/main/java/org/apache/sis/io/
sis-utility/src/main/java/org/apache/sis/math/ sis...
Modified: sis/trunk/sis-utility/src/main/java/org/apache/sis/util/Numbers.java
URL: http://svn.apache.org/viewvc/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/Numbers.java?rev=1415758&r1=1415757&r2=1415758&view=diff
==============================================================================
--- sis/trunk/sis-utility/src/main/java/org/apache/sis/util/Numbers.java (original)
+++ sis/trunk/sis-utility/src/main/java/org/apache/sis/util/Numbers.java Fri Nov 30 17:38:17 2012
@@ -18,11 +18,20 @@ package org.apache.sis.util;
import java.util.Map;
import java.util.HashMap;
+import java.util.List;
+import java.util.Queue;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.Collections;
+import java.lang.reflect.Array;
import java.math.BigDecimal;
import java.math.BigInteger;
import org.apache.sis.util.resources.Errors;
+import static org.apache.sis.util.collection.Collections.emptyQueue;
+import static org.apache.sis.util.collection.Collections.emptySortedSet;
+
/**
* Static methods working with {@link Number} objects, and a few primitive types by extension.
@@ -34,7 +43,7 @@ import org.apache.sis.util.resources.Err
*/
public final class Numbers extends Static {
/**
- * Constants to be used in {@code switch} statements.
+ * Constant of value {@value}Â used in {@code switch} statements or as index in arrays.
*/
public static final byte
DOUBLE=8, FLOAT=7, LONG=6, INTEGER=5, SHORT=4, BYTE=3, CHARACTER=2, BOOLEAN=1, OTHER=0;
@@ -62,7 +71,7 @@ public final class Numbers extends Stati
/** The wrapper for the primitive type. */ private final Class<?> wrapper;
/** {@code true} for floating point number. */ private final boolean isFloat;
/** {@code true} for integer number. */ private final boolean isInteger;
- /** The size in bytes. */ private final byte size;
+ /** The size in bytes, or -1 if variable. */ private final byte size;
/** Constant to be used in switch statement.*/ private final byte ordinal;
/** The internal form of the primitive name.*/ private final char internal;
/** The null, NaN, 0 or false value. */ private final Object nullValue;
@@ -219,7 +228,7 @@ public final class Numbers extends Stati
* @throws IllegalArgumentException If a number is not of a known type.
*
* @see #widestClass(Number, Number)
- * @see #finestClass(Number, Number)
+ * @see #narrowestClass(Number, Number)
*/
public static Class<? extends Number> widestClass(final Number n1, final Number n2)
throws IllegalArgumentException
@@ -250,7 +259,7 @@ public final class Numbers extends Stati
* @throws IllegalArgumentException If one of the given types is unknown.
*
* @see #widestClass(Class, Class)
- * @see #finestClass(Number, Number)
+ * @see #narrowestClass(Number, Number)
*/
public static Class<? extends Number> widestClass(final Class<? extends Number> c1,
final Class<? extends Number> c2)
@@ -270,27 +279,27 @@ public final class Numbers extends Stati
}
/**
- * Returns the finest type of two numbers. Numbers {@code n1} and {@code n2} must be instance
+ * Returns the narrowest type of two numbers. Numbers {@code n1} and {@code n2} must be instance
* of any of {@link Byte}, {@link Short}, {@link Integer}, {@link Long}, {@link Float}
* {@link Double}, {@link BigInteger} or {@link BigDecimal} types.
*
* @param n1 The first number.
* @param n2 The second number.
- * @return The finest type of the given numbers.
+ * @return The narrowest type of the given numbers.
* @throws IllegalArgumentException If a number is not of a known type.
*
- * @see #finestClass(Class, Class)
+ * @see #narrowestClass(Class, Class)
* @see #widestClass(Class, Class)
*/
- public static Class<? extends Number> finestClass(final Number n1, final Number n2)
+ public static Class<? extends Number> narrowestClass(final Number n1, final Number n2)
throws IllegalArgumentException
{
- return finestClass((n1 != null) ? n1.getClass() : null,
+ return narrowestClass((n1 != null) ? n1.getClass() : null,
(n2 != null) ? n2.getClass() : null);
}
/**
- * Returns the finest of the given types. Classes {@code c1} and {@code c2} can be
+ * Returns the narrowest of the given types. Classes {@code c1} and {@code c2} can be
* {@link Byte}, {@link Short}, {@link Integer}, {@link Long}, {@link Float},
* {@link Double}, {@link BigInteger} or {@link BigDecimal} types.
*
@@ -300,21 +309,21 @@ public final class Numbers extends Stati
* Example:
*
* {@preformat java
- * finestClass(Short.class, Long.class);
+ * narrowestClass(Short.class, Long.class);
* }
*
* returns {@code Short.class}.
*
* @param c1 The first number type, or {@code null}.
* @param c2 The second number type, or {@code null}.
- * @return The finest of the given types, or {@code null} if both {@code c1} and {@code c2} are null.
+ * @return The narrowest of the given types, or {@code null} if both {@code c1} and {@code c2} are null.
* @throws IllegalArgumentException If one of the given types is unknown.
*
- * @see #finestClass(Number, Number)
+ * @see #narrowestClass(Number, Number)
* @see #widestClass(Class, Class)
*/
- public static Class<? extends Number> finestClass(final Class<? extends Number> c1,
- final Class<? extends Number> c2)
+ public static Class<? extends Number> narrowestClass(final Class<? extends Number> c1,
+ final Class<? extends Number> c2)
throws IllegalArgumentException
{
final Numbers m1 = MAPPING.get(c1);
@@ -333,37 +342,37 @@ public final class Numbers extends Stati
/**
* Returns the smallest class capable to hold the specified value. If the given value is
* {@code null}, then this method returns {@code null}. Otherwise this method delegates
- * to {@link #finestClass(double)} or {@link #finestClass(long)} depending on the value type.
+ * to {@link #narrowestClass(double)} or {@link #narrowestClass(long)} depending on the value type.
*
* @param value The value to be wrapped in a finer (if possible) {@link Number}.
- * @return The finest type capable to hold the given value.
+ * @return The narrowest type capable to hold the given value.
*
- * @see #finestNumber(Number)
+ * @see #narrowestNumber(Number)
*/
- public static Class<? extends Number> finestClass(final Number value) {
+ public static Class<? extends Number> narrowestClass(final Number value) {
if (value == null) {
return null;
}
if (isPrimitiveInteger(value.getClass())) {
- return finestClass(value.longValue());
+ return narrowestClass(value.longValue());
} else {
- return finestClass(value.doubleValue());
+ return narrowestClass(value.doubleValue());
}
}
/**
* Returns the smallest class capable to hold the specified value.
- * This is similar to {@link #finestClass(long)}, but extended to floating point values.
+ * This is similar to {@link #narrowestClass(long)}, but extended to floating point values.
*
* @param value The value to be wrapped in a {@link Number}.
- * @return The finest type capable to hold the given value.
+ * @return The narrowest type capable to hold the given value.
*
- * @see #finestNumber(double)
+ * @see #narrowestNumber(double)
*/
- public static Class<? extends Number> finestClass(final double value) {
+ public static Class<? extends Number> narrowestClass(final double value) {
final long lg = (long) value;
if (value == lg) {
- return finestClass(lg);
+ return narrowestClass(lg);
}
final float fv = (float) value;
if (Double.doubleToRawLongBits(value) == Double.doubleToRawLongBits(fv)) {
@@ -387,11 +396,11 @@ public final class Numbers extends Stati
* </ul>
*
* @param value The value to be wrapped in a {@link Number}.
- * @return The finest type capable to hold the given value.
+ * @return The narrowest type capable to hold the given value.
*
- * @see #finestNumber(long)
+ * @see #narrowestNumber(long)
*/
- public static Class<? extends Number> finestClass(final long value) {
+ public static Class<? extends Number> narrowestClass(final long value) {
// Tests MAX_VALUE before MIN_VALUE because it is more likely to fail.
if (value <= Byte .MAX_VALUE && value >= Byte .MIN_VALUE) return Byte.class;
if (value <= Short .MAX_VALUE && value >= Short .MIN_VALUE) return Short.class;
@@ -402,23 +411,23 @@ public final class Numbers extends Stati
/**
* Returns the number of the smallest class capable to hold the specified value. If the
* given value is {@code null}, then this method returns {@code null}. Otherwise this
- * method delegates to {@link #finestNumber(double)} or {@link #finestNumber(long)}
+ * method delegates to {@link #narrowestNumber(double)} or {@link #narrowestNumber(long)}
* depending on the value type.
*
* @param value The value to be wrapped in a finer (if possible) {@link Number}.
- * @return The finest type capable to hold the given value.
+ * @return The narrowest type capable to hold the given value.
*
- * @see #finestClass(Number)
+ * @see #narrowestClass(Number)
*/
- public static Number finestNumber(final Number value) {
+ public static Number narrowestNumber(final Number value) {
if (value == null) {
return null;
}
final Number candidate;
if (isPrimitiveInteger(value.getClass())) {
- candidate = finestNumber(value.longValue());
+ candidate = narrowestNumber(value.longValue());
} else {
- candidate = finestNumber(value.doubleValue());
+ candidate = narrowestNumber(value.doubleValue());
}
// Keep the existing instance if possible.
return value.equals(candidate) ? value : candidate;
@@ -426,17 +435,17 @@ public final class Numbers extends Stati
/**
* Returns the number of the smallest class capable to hold the specified value.
- * This is similar to {@link #finestNumber(long)}, but extended to floating point values.
+ * This is similar to {@link #narrowestNumber(long)}, but extended to floating point values.
*
* @param value The value to be wrapped in a {@link Number}.
- * @return The finest type capable to hold the given value.
+ * @return The narrowest type capable to hold the given value.
*
- * @see #finestClass(double)
+ * @see #narrowestClass(double)
*/
- public static Number finestNumber(final double value) {
+ public static Number narrowestNumber(final double value) {
final long lg = (long) value;
if (value == lg) {
- return finestNumber(lg);
+ return narrowestNumber(lg);
}
final float fv = (float) value;
if (Double.doubleToRawLongBits(value) == Double.doubleToRawLongBits(fv)) {
@@ -460,11 +469,11 @@ public final class Numbers extends Stati
* </ul>
*
* @param value The value to be wrapped in a {@link Number}.
- * @return The given value as a number of the finest type capable to hold it.
+ * @return The given value as a number of the narrowest type capable to hold it.
*
- * @see #finestClass(long)
+ * @see #narrowestClass(long)
*/
- public static Number finestNumber(final long value) {
+ public static Number narrowestNumber(final long value) {
// Tests MAX_VALUE before MIN_VALUE because it is more likely to fail.
if (value <= Byte .MAX_VALUE && value >= Byte .MIN_VALUE) return Byte .valueOf((byte) value);
if (value <= Short .MAX_VALUE && value >= Short .MIN_VALUE) return Short .valueOf((short) value);
@@ -476,23 +485,23 @@ public final class Numbers extends Stati
* Returns the smallest number capable to hold the specified value.
*
* @param value The value to be wrapped in a {@link Number}.
- * @return The finest type capable to hold the given value.
+ * @return The narrowest type capable to hold the given value.
* @throws NumberFormatException if the given value can not be parsed as a number.
*
- * @see #finestNumber(Number)
- * @see #finestNumber(double)
- * @see #finestNumber(long)
+ * @see #narrowestNumber(Number)
+ * @see #narrowestNumber(double)
+ * @see #narrowestNumber(long)
*/
- public static Number finestNumber(String value) throws NumberFormatException {
- value = value.trim();
+ public static Number narrowestNumber(String value) throws NumberFormatException {
+ value = CharSequences.trimWhitespaces(value);
final int length = value.length();
for (int i=0; i<length; i++) {
final char c = value.charAt(i);
if (c == '.' || c == 'e' || c == 'E') {
- return finestNumber(Double.parseDouble(value));
+ return narrowestNumber(Double.parseDouble(value));
}
}
- return finestNumber(Long.parseLong(value));
+ return narrowestNumber(Long.parseLong(value));
}
/**
@@ -562,19 +571,12 @@ public final class Numbers extends Stati
* string value is not parseable as a number of the specified type.
*/
@SuppressWarnings("unchecked")
- public static <T> T valueOf(final Class<T> type, final String value)
+ public static <T> T valueOf(final Class<T> type, String value)
throws IllegalArgumentException, NumberFormatException
{
- if (value == null) {
- return null;
+ if (value == null || type == String.class) {
+ return (T) value;
}
- if (type == Double .class) return (T) Double .valueOf(value);
- if (type == Float .class) return (T) Float .valueOf(value);
- if (type == Long .class) return (T) Long .valueOf(value);
- if (type == Integer.class) return (T) Integer.valueOf(value);
- if (type == Short .class) return (T) Short .valueOf(value);
- if (type == Byte .class) return (T) Byte .valueOf(value);
- if (type == Boolean.class) return (T) Boolean.valueOf(value);
if (type == Character.class) {
/*
* If the string is empty, returns 0 which means "end of string" in C/C++
@@ -585,13 +587,75 @@ public final class Numbers extends Stati
*/
return (T) Character.valueOf(value.isEmpty() ? 0 : value.charAt(0));
}
- if (type == String.class) {
- return (T) value;
- }
+ value = CharSequences.trimWhitespaces(value);
+ if (type == Double .class) return (T) Double .valueOf(value);
+ if (type == Float .class) return (T) Float .valueOf(value);
+ if (type == Long .class) return (T) Long .valueOf(value);
+ if (type == Integer.class) return (T) Integer.valueOf(value);
+ if (type == Short .class) return (T) Short .valueOf(value);
+ if (type == Byte .class) return (T) Byte .valueOf(value);
+ if (type == Boolean.class) return (T) Boolean.valueOf(value);
throw unknownType(type);
}
/**
+ * Returns a {@code NaN}, zero, empty or {@code null} value of the given type. This method
+ * tries to return the closest value that can be interpreted as "<cite>none</cite>", which
+ * is usually not the same than "<cite>zero</cite>". More specifically:
+ *
+ * <ul>
+ * <li>If the given type is a floating point <strong>primitive</strong> type ({@code float}
+ * or {@code double}), then this method returns {@link Float#NaN} or {@link Double#NaN}
+ * depending on the given type.</li>
+ *
+ * <li>If the given type is an integer <strong>primitive</strong> type or the character type
+ * ({@code long}, {@code int}, {@code short}, {@code byte} or {@code char}), then this
+ * method returns the zero value of the given type.</li>
+ *
+ * <li>If the given type is the {@code boolean} <strong>primitive</strong> type, then this
+ * method returns {@link Boolean#FALSE}.</li>
+ *
+ * <li>If the given type is an array or a collection, then this method returns an empty
+ * array or collection. The given type is honored on a <cite>best effort</cite> basis.</li>
+ *
+ * <li>For all other cases, including the wrapper classes of primitive types, this method
+ * returns {@code null}.</li>
+ * </ul>
+ *
+ * Despite being defined in the {@code Numbers} class, the scope of this method has been
+ * extended to array and collection types because those objects can also be seen as
+ * mathematical concepts.
+ *
+ * @param <T> The compile-time type of the requested object.
+ * @param type The type of the object for which to get a nil value.
+ * @return An object of the given type which represents a nil value, or {@code null}.
+ *
+ * @see org.apache.sis.xml.NilObject
+ */
+ @SuppressWarnings("unchecked")
+ public static <T> T valueOfNil(final Class<T> type) {
+ final Numbers mapping = MAPPING.get(type);
+ if (mapping != null) {
+ if (type.isPrimitive()) {
+ return (T) mapping.nullValue;
+ }
+ } else if (type != null && type != Object.class) {
+ if (type == Map .class) return (T) Collections.EMPTY_MAP;
+ if (type == List .class) return (T) Collections.EMPTY_LIST;
+ if (type == Queue .class) return (T) emptyQueue();
+ if (type == SortedSet.class) return (T) emptySortedSet();
+ if (type.isAssignableFrom(Set.class)) {
+ return (T) Collections.EMPTY_SET;
+ }
+ final Class<?> element = type.getComponentType();
+ if (element != null) {
+ return (T) Array.newInstance(element, 0);
+ }
+ }
+ return null;
+ }
+
+ /**
* Returns one of {@link #DOUBLE}, {@link #FLOAT}, {@link #LONG}, {@link #INTEGER},
* {@link #SHORT}, {@link #BYTE}, {@link #CHARACTER}, {@link #BOOLEAN} or {@link #OTHER}
* constants for the given type. This is a commodity for usage in {@code switch} statements.
Modified: sis/trunk/sis-utility/src/main/java/org/apache/sis/util/collection/DefaultTreeTable.java
URL: http://svn.apache.org/viewvc/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/collection/DefaultTreeTable.java?rev=1415758&r1=1415757&r2=1415758&view=diff
==============================================================================
--- sis/trunk/sis-utility/src/main/java/org/apache/sis/util/collection/DefaultTreeTable.java (original)
+++ sis/trunk/sis-utility/src/main/java/org/apache/sis/util/collection/DefaultTreeTable.java Fri Nov 30 17:38:17 2012
@@ -21,12 +21,19 @@ import java.util.Map;
import java.util.LinkedHashMap;
import java.util.Collections;
import java.io.Serializable;
+import java.text.Format;
import net.jcip.annotations.NotThreadSafe;
import org.apache.sis.util.ArgumentChecks;
import org.apache.sis.util.resources.Errors;
+import org.apache.sis.internal.util.Cloner;
+import static org.apache.sis.util.CharSequences.trimWhitespaces;
+import static org.apache.sis.util.collection.Collections.isNullOrEmpty;
import static org.apache.sis.util.collection.Collections.hashMapCapacity;
+// Related to JDK7
+import org.apache.sis.internal.util.Objects;
+
/**
* A {@link TreeTable} implementation with a {@linkplain #getColumns() list of columns} given at
@@ -36,12 +43,12 @@ import static org.apache.sis.util.collec
* Example:
*
* {@preformat java
- * class CityLocation {
- * public static final TableColumn<String> CITY_NAME = new MyColumn<>(String.class);
- * public static final TableColumn<Float> LATITUDE = new MyColumn<>(Float .class);
- * public static final TableColumn<Float> LONGTITUDE = new MyColumn<>(Float .class);
+ * public class CityLocation {
+ * public static final TableColumn<String> CITY_NAME = new TableColumn<>(String.class);
+ * public static final TableColumn<Float> LATITUDE = new TableColumn<>(Float .class);
+ * public static final TableColumn<Float> LONGTITUDE = new TableColumn<>(Float .class);
*
- * TreeTable createTable() {
+ * public TreeTable createTable() {
* DefaultTreeTable table = new DefaultTreeTable(CITY_NAME, LATITUDE, LONGITUDE);
* TreeTable.Node city = new DefaultTreeTable.Node(table);
* city.setValue(CITY_NAME, "Rimouski");
@@ -63,13 +70,18 @@ import static org.apache.sis.util.collec
* @module
*/
@NotThreadSafe
-public class DefaultTreeTable implements TreeTable, Serializable {
+public class DefaultTreeTable implements TreeTable, Cloneable, Serializable {
/**
* For cross-version compatibility.
*/
private static final long serialVersionUID = 1951201018202846555L;
/**
+ * Shared {@code TreeTableFormat} instance for {@link #toString()} implementation.
+ */
+ private static Format format;
+
+ /**
* The root node, or {@code null} if not yet specified.
*
* @see #getRoot()
@@ -112,7 +124,7 @@ public class DefaultTreeTable implements
* <p>The {@linkplain #getRoot() root} node is initially {@code null}. Callers can initialize
* it after construction time by a call to the {@link #setRoot(TreeTable.Node)} method.</p>
*
- * @param columns The table columns.
+ * @param columns The list of table columns.
*/
public DefaultTreeTable(TableColumn<?>... columns) {
ArgumentChecks.ensureNonNull("columns", columns);
@@ -174,6 +186,9 @@ public class DefaultTreeTable implements
/**
* Returns the table columns given at construction time.
* The returned list is never null neither empty.
+ *
+ * @see Node#getValue(TableColumn)
+ * @see Node#setValue(TableColumn, Object)
*/
@Override
public final List<TableColumn<?>> getColumns() {
@@ -218,8 +233,61 @@ public class DefaultTreeTable implements
}
/**
+ * Returns a clone of this table. This method clones the {@linkplain #getRoot() root} node.
+ * If the root is an instance of {@link Node}, then cloning the root will recursively clone
+ * all its {@linkplain Node#getChildren() children}.
+ *
+ * @return A clone of this table.
+ * @throws CloneNotSupportedException If this table, the root node or one of its children
+ * can not be cloned.
+ *
+ * @see Node#clone()
+ */
+ @Override
+ public DefaultTreeTable clone() throws CloneNotSupportedException {
+ final DefaultTreeTable clone = (DefaultTreeTable) super.clone();
+ clone.root = (TreeTable.Node) new Cloner().clone(clone.root);
+ return clone;
+ }
+
+ /**
+ * Compares the given object with this tree table for equality. This method compares the
+ * {@linkplain #getColumns() columns} and the {@linkplain #getRoot() root node}. If the
+ * later is an instance of the {@link Node} inner class, then all node values and children
+ * will be compared recursively.
+ *
+ * @param other The object to compare with this table.
+ * @return {@code true} if the two objects are equal.
+ *
+ * @see Node#equals(Object)
+ */
+ @Override
+ public boolean equals(final Object other) {
+ if (other == this) {
+ return true;
+ }
+ if (other != null && other.getClass() == getClass()) {
+ final DefaultTreeTable that = (DefaultTreeTable) other;
+ return columnIndices.equals(that.columnIndices) &&
+ Objects.equals(root, that.root);
+ }
+ return false;
+ }
+
+ /**
+ * Returns a hash code value for this table.
+ * This method is defined for consistency with {@link #equals(Object)} contract.
+ *
+ * @see Node#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ return (columnIndices.hashCode() + 31*Objects.hashCode(root)) ^ (int) serialVersionUID;
+ }
+
+ /**
* Returns a string representation of this tree table.
- * The default implementation delegates to {@link TreeTables#toString(TreeTable)}.
+ * The default implementation delegates to {@link #toString(TreeTable)}.
* This is okay for debugging or occasional usages. However for more extensive usages,
* developers are encouraged to create and configure their own {@link TreeTableFormat}
* instance.
@@ -228,7 +296,25 @@ public class DefaultTreeTable implements
*/
@Override
public String toString() {
- return TreeTables.toString(this);
+ return toString(this);
+ }
+
+ /**
+ * Returns a string representation of the given tree table.
+ * The default implementation uses a shared instance of {@link TreeTableFormat}.
+ * This is okay for debugging or occasional usages. However for more extensive usages,
+ * developers are encouraged to create and configure their own {@code TreeTableFormat}
+ * instance.
+ *
+ * @param table The tree table to format.
+ * @return A string representation of the given tree table.
+ */
+ public static synchronized String toString(final TreeTable table) {
+ ArgumentChecks.ensureNonNull("table", table);
+ if (format == null) {
+ format = new TreeTableFormat(null, null);
+ }
+ return format.format(table);
}
@@ -239,9 +325,12 @@ public class DefaultTreeTable implements
* of columns. The list of columns is specified by a {@link TreeTable}, or inherited from
* a parent node.
*
- * <p>The {@linkplain #getChildren() list of children} provided by this class is <cite>live</cite>:
- * adding a {@code Node} child to that list will automatically set its parent to {@code this},
- * and removing a {@code Node} from that list will set its parent to {@code null}.</p>
+ * {@section Note on the parent node}
+ * The value returned by the {@link #getParent()} method is updated automatically when
+ * this node is <em>added to</em> or <em>removed from</em> the {@linkplain #getChildren()
+ * list of children} of another {@code Node} instance - there is no {@code setParent(Node)}
+ * method. As a derived value, the parent is ignored by the {@link #clone()},
+ * {@link #equals(Object)} and {@link #hashCode()} methods.
*
* @author Martin Desruisseaux (Geomatys)
* @since 0.3
@@ -249,7 +338,7 @@ public class DefaultTreeTable implements
* @module
*/
@NotThreadSafe
- public static class Node implements TreeTable.Node, Serializable {
+ public static class Node implements TreeTable.Node, Cloneable, Serializable {
/**
* For cross-version compatibility.
*/
@@ -329,14 +418,6 @@ public class DefaultTreeTable implements
private Object[] values;
/**
- * Creates a new node with the given shared map of columns and the given values.
- */
- Node(final Map<TableColumn<?>,Integer> columnIndices, final Object[] values) {
- this.columnIndices = columnIndices;
- this.values = values;
- }
-
- /**
* Creates a new node for the given table. The new node will be able to store a value
* for each {@linkplain TreeTable#getColumns() columns} defined in the given table.
*
@@ -390,9 +471,30 @@ public class DefaultTreeTable implements
}
/**
+ * Creates a node with a single column for object names (c<cite>convenience constructor</cite>).
+ * The node will have the following columns:
+ *
+ * <table class="sis">
+ * <tr><th>Header</th> <th>Type</th> <th>Initial value</th></tr>
+ * <tr><td>"Name"</td> <td>{@link CharSequence}</td> <td>{@code name}</td></tr>
+ * </table>
+ *
+ * @param name The initial value for the "Name" column (can be {@code null}).
+ */
+ public Node(final CharSequence name) {
+ columnIndices = TableColumn.NAME_MAP;
+ if (name != null) {
+ values = new CharSequence[] {name};
+ }
+ }
+
+ /**
* Returns the parent of this node. On {@code Node} creation, this value may be initially
* {@code null}. It will be automatically set to a non-null value when this node will be
* added as a child of another {@code Node} instance.
+ *
+ * <p>Note that the parent is intentionally ignored by the {@link #clone()},
+ * {@link #equals(Object)} and {@link #hashCode()} methods.</p>
*/
@Override
public final TreeTable.Node getParent() {
@@ -495,23 +597,134 @@ public class DefaultTreeTable implements
}
/**
- * Returns a string representation of this node, for identification in error message
- * or in debugger.
+ * Returns a clone of this node without parent.
+ * This method recursively clones all {@linkplain #getChildren() children},
+ * but does not clone the column {@linkplain #getValue(TableColumn) values}.
+ * The parent of the cloned node is set to {@code null}.
+ *
+ * @return A clone of this node without parent.
+ * @throws CloneNotSupportedException If this node or one of its children can not be cloned.
+ */
+ @Override
+ public Node clone() throws CloneNotSupportedException {
+ final Node clone = (Node) super.clone();
+ clone.parent = null;
+ if (clone.values != null) {
+ clone.values = clone.values.clone();
+ }
+ if (clone.children != null) {
+ clone.children = new Children(clone);
+ for (final TreeTable.Node child : children) {
+ /*
+ * Implementation note: we could have used the Cloner for cloning arbitrary
+ * node implementations, but children.add(...) would fail anyway because it
+ * can not set the parent of unknown implementation.
+ */
+ if (!(child instanceof Node)) {
+ throw new CloneNotSupportedException(Errors.format(
+ Errors.Keys.CloneNotSupported_1, child.getClass()));
+ }
+ clone.children.add(((Node) child).clone());
+ }
+ }
+ return clone;
+ }
+
+ /**
+ * Compares the given object with this node for {@linkplain #getValue(TableColumn) values}
+ * and {@linkplain #getChildren() children} equality, ignoring the {@linkplain #getParent()
+ * parent}. This method can be used for determining if two branches of a same tree or of two
+ * different trees are identical.
+ *
+ * {@note This method ignores the parent because:
+ * <ul>
+ * <li>When comparing the children recursively, comparing the parents would cause infinite recursivity.</li>
+ * <li>For consistency with the <code>clone()</code> method, which can not clone the parent.</li>
+ * <li>For making possible to compare branches instead than only whole trees.</li>
+ * </ul>}
+ *
+ * @param other The object to compare with this node.
+ * @return {@code true} if the two objects are equal, ignoring the parent node.
+ */
+ @Override
+ public boolean equals(final Object other) {
+ if (other == this) {
+ return true;
+ }
+ if (other != null && other.getClass() == getClass()) {
+ final Node that = (Node) other;
+ if (columnIndices.equals(that.columnIndices)) {
+ final Object[] v1 = this.values;
+ final Object[] v2 = that.values;
+ if (v1 != v2) { // For skipping the loop if v1 and v2 are null.
+ for (int i=columnIndices.size(); --i>=0;) {
+ if (!Objects.equals((v1 != null) ? v1[i] : null,
+ (v2 != null) ? v2[i] : null))
+ {
+ return false;
+ }
+ }
+ }
+ final List<TreeTable.Node> c1 = this.children;
+ final List<TreeTable.Node> c2 = that.children;
+ final int n = (c1 != null) ? c1.size() : 0;
+ if (((c2 != null) ? c2.size() : 0) == n) {
+ for (int i=0; i<n; i++) {
+ if (!c1.get(i).equals(c2.get(i))) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns a hash-code value computed from the {@linkplain #getValue(TableColumn) values}
+ * and {@linkplain #getChildren() children}, ignoring the {@linkplain #getParent() parent}.
+ * This method is defined for consistency with {@link #equals(Object)} contract.
+ */
+ @Override
+ public int hashCode() {
+ int hash = 0;
+ final Object[] values = this.values;
+ if (values != null) {
+ // Do not use Objects.hashCode(...) because we want the result of array
+ // containing only null elements to be the same than null array (zero).
+ for (int i=values.length; --i>=0;) {
+ hash = 31*hash + Objects.hash(values[i]);
+ }
+ }
+ // Do not use Objects.hashCode(...) because we
+ // want the same result for null and empty list.
+ if (!isNullOrEmpty(children)) {
+ hash += 37 * children.hashCode();
+ }
+ return hash ^ (int) serialVersionUID;
+ }
+
+ /**
+ * Returns a string representation of this node for identification in error message or in debugger.
+ * The default implementation returns the {@code toString()} value of the first non-empty
+ * {@link CharSequence} found in the {@linkplain #getValue(TableColumn) values}, if any.
+ * If no such value is found, then this method returns "<var>Node</var>-<var>i</var>"
+ * where <var>Node</var> is the {@linkplain Class#getSimpleName() simple classname}
+ * and <var>i</var> is the index of this node in the parent node.
*
* @return A string representation of this node.
*/
@Override
public String toString() {
- Object value = getUserObject();
- if (value instanceof CharSequence) {
- return value.toString();
- }
final Object[] values = this.values;
if (values != null) {
- for (int i=0; i<values.length; i++) {
- value = values[i];
+ for (final Object value : values) {
if (value instanceof CharSequence) {
- return value.toString();
+ final String text = trimWhitespaces(value.toString());
+ if (text != null && !text.isEmpty()) {
+ return text;
+ }
}
}
}
Modified: sis/trunk/sis-utility/src/main/java/org/apache/sis/util/collection/TableColumn.java
URL: http://svn.apache.org/viewvc/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/collection/TableColumn.java?rev=1415758&r1=1415757&r2=1415758&view=diff
==============================================================================
--- sis/trunk/sis-utility/src/main/java/org/apache/sis/util/collection/TableColumn.java (original)
+++ sis/trunk/sis-utility/src/main/java/org/apache/sis/util/collection/TableColumn.java Fri Nov 30 17:38:17 2012
@@ -16,7 +16,14 @@
*/
package org.apache.sis.util.collection;
+import java.util.Map;
+import java.util.Collections;
+import java.io.Serializable;
+import java.io.InvalidObjectException;
import org.opengis.util.InternationalString;
+import org.apache.sis.util.type.Types;
+import org.apache.sis.util.ArgumentChecks;
+import org.apache.sis.util.resources.Vocabulary;
/**
@@ -26,23 +33,63 @@ import org.opengis.util.InternationalStr
* as in the following example:
*
* {@preformat java
- * class CityLocation {
- * public static final TableColumn<String> CITY_NAME = new MyColumn<>(String.class);
- * public static final TableColumn<Float> LATITUDE = new MyColumn<>(Float .class);
- * public static final TableColumn<Float> LONGTITUDE = new MyColumn<>(Float .class);
+ * public class CityLocation {
+ * public static final ColumnTable<String> CITY_NAME = new ColumnTable<>(String.class, "City name");
+ * public static final ColumnTable<Float> LATITUDE = new ColumnTable<>(Float.class, "Latitude");
+ * public static final ColumnTable<Float> LONGITUDE = new ColumnTable<>(Float.class, "Longitude");
*
- * private String city;
+ * private String name;
* private float latitude;
* private float longitude;
*
* CityLocation(TreeTable.Node myNode) {
- * city = myNode.getValue(CITY_NAME);
- * latitude = myNode.getValue(LATITUDE );
+ * name = myNode.getValue(CITY_NAME);
+ * latitude = myNode.getValue(LATITUDE);
* longitude = myNode.getValue(LONGITUDE);
* }
* }
* }
*
+ * {@section Identity comparisons and serialization}
+ * This base class relies on <cite>identity comparisons</cite> instead than defining the
+ * {@code equals(Object)} method, because the {@linkplain #getElementType() element type}
+ * is not a sufficient criterion for differentiating the columns (many columns have values
+ * of the same type) and the {@linkplain #getHeader() header} is arbitrary. Consequently
+ * developers who create their own instances are encouraged to declare them as static final
+ * constants as in the above example, and use those constants consistently.
+ *
+ * <p>This base class is not serializable because the default deserialization mechanism does
+ * not resolve automatically the deserialized instances to the above-cited singleton instances.
+ * Developers who need serialization support for their own instances have to resolve them in
+ * their own subclass. The following example is one possible way to achieve that goal:</p>
+ *
+ * {@preformat java
+ * public class CityLocation {
+ * public static final ColumnTable<String> CITY_NAME = new Column<>("CITY_NAME", String.class, "City name");
+ * public static final ColumnTable<Float> LATITUDE = new Column<>("LATITUDE", Float.class, "Latitude");
+ * public static final ColumnTable<Float> LONGITUDE = new Column<>("LONGITUDE", Float.class, "Longitude");
+ *
+ * private static final class Column<V> extends TableColumn<V> implements Serializable {
+ * private final String field;
+ *
+ * private Column(String field, Class<V> type, CharSequence header) {
+ * super(type, header);
+ * this.field = field;
+ * }
+ *
+ * private Object readResolve() throws InvalidObjectException {
+ * try {
+ * return CityLocation.class.getField(field).get(null);
+ * } catch (Exception cause) { // Many exceptions, including unchecked ones.
+ * throw new InvalidObjectException(cause.toString());
+ * }
+ * }
+ * }
+ * }
+ * }
+ *
+ * The constants defined in this class use a similar approach for providing serialization support.
+ *
* @param <V> Base type of all values in the column identified by this instance.
*
* @author Martin Desruisseaux (Geomatys)
@@ -50,18 +97,168 @@ import org.opengis.util.InternationalStr
* @version 0.3
* @module
*/
-public interface TableColumn<V> extends CheckedContainer<V> {
+public class TableColumn<V> implements CheckedContainer<V> {
+ /**
+ * Frequently-used constant for a column of object names.
+ * The values are typically instances of {@link String} or {@link InternationalString},
+ * depending on whether the data provide localization support or not.
+ */
+ public static final TableColumn<CharSequence> NAME = new Constant<CharSequence>("NAME",
+ CharSequence.class, Vocabulary.Keys.Name);
+
+ /**
+ * Frequently-used constant for a column of object types.
+ */
+ @SuppressWarnings("unchecked")
+ public static final TableColumn<Class<?>> TYPE = new Constant<Class<?>>("TYPE",
+ (Class) Class.class, Vocabulary.Keys.Type);
+
+ /**
+ * A map containing only the {@link #NAME} column.
+ * This is the default set of columns when parsing a table tree.
+ */
+ static final Map<TableColumn<?>,Integer> NAME_MAP =
+ Collections.<TableColumn<?>,Integer>singletonMap(NAME, 0);
+
+ /**
+ * Base type of all values in the column identified by this {@code ColumnTable} instance.
+ */
+ private final Class<V> type;
+
+ /**
+ * The column header, or {@code null} if not yet created.
+ */
+ CharSequence header;
+
+ /**
+ * Implementation of {@link TableColumn} for the pre-defined constants.
+ * This implementation differs resource bundle loading until first needed,
+ * and resolves deserialized instances to the singleton instances.
+ *
+ * @param <V> Base type of all values in the column identified by this instance.
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ * @since 0.3
+ * @version 0.3
+ * @module
+ */
+ private static final class Constant<V> extends TableColumn<V> implements Serializable {
+ /**
+ * For cross-version compatibility.
+ */
+ private static final long serialVersionUID = -2486202389234601560L;
+
+ /**
+ * The programmatic name of the static final field holding this constant.
+ */
+ private final String field;
+
+ /**
+ * The resource key for the column header.
+ */
+ private final transient int resourceKey;
+
+ /**
+ * Creates a new instance for a build-in constant.
+ *
+ * @param field The programmatic name of the static final field holding this constant.
+ * @param type Base type of all values in the column identified by this instance.
+ * @param header The resource key for the column header.
+ */
+ Constant(final String field, final Class<V> type, final int header) {
+ super(type);
+ this.field = field;
+ this.resourceKey = header;
+ }
+
+ /**
+ * Returns the text to display as column header.
+ */
+ @Override
+ public synchronized InternationalString getHeader() {
+ InternationalString i18n = (InternationalString) header;
+ if (i18n == null) {
+ header = i18n = Vocabulary.formatInternational(resourceKey);
+ }
+ return i18n;
+ }
+
+ /**
+ * Invoked on deserialization for resolving this instance to one of the predefined constants.
+ *
+ * @return One of the predefined constants.
+ * @throws InvalidObjectException If this instance can not be resolved.
+ */
+ private Object readResolve() throws InvalidObjectException {
+ try {
+ return TableColumn.class.getField(field).get(null);
+ } catch (Exception cause) { // Many exceptions, including unchecked ones.
+ InvalidObjectException e = new InvalidObjectException(cause.toString());
+ e.initCause(cause);
+ throw e;
+ }
+ }
+ }
+
+ /**
+ * Invoked on deserialization for creating an initially empty instance.
+ * This constructor has {@code protected} visibility only because the Java deserialization
+ * mechanism requires so; this constructor shall not be invoked in any other context.
+ * See the <cite>Identity comparisons and serialization</cite> section in the class
+ * javadoc for more information.
+ */
+ protected TableColumn() {
+ type = null;
+ }
+
+ /**
+ * Creates a new instance for a build-in constant.
+ *
+ * @param type Base type of all values in the column identified by this instance.
+ */
+ TableColumn(final Class<V> type) {
+ this.type = type;
+ }
+
+ /**
+ * Creates a new instance for the given type of values.
+ *
+ * @param type Base type of all values in the column identified by this instance.
+ * @param header The text to display as column header.
+ */
+ public TableColumn(final Class<V> type, final CharSequence header) {
+ ArgumentChecks.ensureNonNull("type", this.type = type);
+ ArgumentChecks.ensureNonNull("header", this.header = header);
+ this.header = Types.toInternationalString(header);
+ }
+
/**
* Returns the text to display as column header.
*
* @return The text to display as column header.
*/
- InternationalString getHeader();
+ public synchronized InternationalString getHeader() {
+ final InternationalString i18n = Types.toInternationalString(header);
+ header = i18n;
+ return i18n;
+ }
/**
* Returns the base type of all values in any column identified by this {@code TableColumn}
* instance.
*/
@Override
- Class<V> getElementType();
+ public final Class<V> getElementType() {
+ return type;
+ }
+
+ /**
+ * Returns a string representation of this table column.
+ * The default implementation returns the {@linkplain #getHeader() header}
+ * in its default locale.
+ */
+ @Override
+ public String toString() {
+ return String.valueOf(getHeader());
+ }
}
Modified: sis/trunk/sis-utility/src/main/java/org/apache/sis/util/collection/TreeTable.java
URL: http://svn.apache.org/viewvc/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/collection/TreeTable.java?rev=1415758&r1=1415757&r2=1415758&view=diff
==============================================================================
--- sis/trunk/sis-utility/src/main/java/org/apache/sis/util/collection/TreeTable.java (original)
+++ sis/trunk/sis-utility/src/main/java/org/apache/sis/util/collection/TreeTable.java Fri Nov 30 17:38:17 2012
@@ -20,10 +20,46 @@ import java.util.List;
/**
- * The root of a tree of nodes, together with the definition of table columns.
- * The {@link #getColumns()} method gives the list of all columns that can be found
- * in a {@code TreeTable}. Usually some or all of those columns are also available as
- * {@link TableColumn} constants defined in {@link TreeTables}.
+ * Defines the structure (list of columns) of a table and provides the root of the tree
+ * containing the data. {@code TreeTable} can be seen as a table in which the first
+ * column contains a tree. Every row in this table is a {@link Node} instance, and each
+ * node can have an arbitrary number of {@linkplain Node#getChildren() children} nodes.
+ *
+ * <p>Below is an example of what a two-columns {@code TreeTable} instance may look like
+ * when {@linkplain TreeTableFormat formatted as a text}:</p>
+ *
+ * {@preformat text
+ * Citation
+ * ââââTitleâ¦â¦â¦â¦â¦â¦â¦â¦â¦â¦â¦â¦â¦â¦â¦â¦â¦â¦â¦â¦â¦â¦â¦ Open Geospatial Consortium
+ * ââââPresentation Formsâ¦â¦â¦â¦â¦â¦â¦â¦â¦â¦ document digital
+ * ââââCited Responsible Parties
+ * â   ââââOrganisation Nameâ¦â¦â¦â¦â¦â¦â¦ Open Geospatial Consortium
+ * â   ââââRoleâ¦â¦â¦â¦â¦â¦â¦â¦â¦â¦â¦â¦â¦â¦â¦â¦â¦â¦â¦â¦ resource provider
+ * â   ââââContact Info
+ * â       ââââOnline Resource
+ * â           ââââLinkageâ¦â¦â¦â¦â¦â¦â¦â¦â¦ http://www.opengeospatial.org/
+ * â           ââââFunctionâ¦â¦â¦â¦â¦â¦â¦â¦ information
+ * ââââIdentifiers
+ *     ââââCodeâ¦â¦â¦â¦â¦â¦â¦â¦â¦â¦â¦â¦â¦â¦â¦â¦â¦â¦â¦â¦ OGC
+ * }
+ *
+ * <p>In many cases, the columns are known in advance as hard-coded static constants.
+ * Those column constants are typically documented close to the class producing the
+ * {@code TreeTable} instance. Using directly those static constants provides type
+ * safety, as in the following example:</p>
+ *
+ * {@preformat java
+ * TreeTable table = ...; // Put here a TreeTable instance.
+ * TreeTable.Node node = table.getRoot();
+ * CharSequence name = node.getValue(TableColumn.NAME);
+ * Class<?> type = node.getValue(TableColumn.TYPE);
+ * }
+ *
+ * In the above example, the type of value returned by the {@link Node#getValue(TableColumn)}
+ * method is determined by the column constant. However this approach is possible only when
+ * the table structure is known in advance. If a method needs to work with arbitrary tables,
+ * then that method can get the list of columns by a call to {@link #getColumns()}. However
+ * this column list does not provide the above type-safety.
*
* @author Martin Desruisseaux (Geomatys)
* @since 0.3
@@ -40,6 +76,7 @@ public interface TreeTable {
* @return The union of all table columns in every tree node.
*
* @see Node#getValue(TableColumn)
+ * @see Node#setValue(TableColumn, Object)
*/
List<TableColumn<?>> getColumns();
@@ -84,6 +121,11 @@ public interface TreeTable {
/**
* Returns the parent node, or {@code null} if this node is the root of the tree.
*
+ * <p>There is intentionally no {@code setParent(Node)} method, as children and parent
+ * managements are highly implementation-dependant. If the {@linkplain #getChildren()
+ * children list} is modifiable, then implementations are encouraged to update automatically
+ * the parent when a child is <em>added to</em> or <em>removed from</em> the children list.</p>
+ *
* @return The parent, or {@code null} if none.
* @category tree
*/
@@ -93,6 +135,12 @@ public interface TreeTable {
* Returns the children of this node. The returned list may or may not be modifiable, at
* implementation choice. If the list is modifiable, then it shall be <cite>live</cite>,
* i.e. any modification to the returned list are reflected immediately in the tree.
+ * This allows addition or removal of child nodes as below:
+ *
+ * {@preformat java
+ * TreeTable.Node newNode = new ...; // Create a new node here.
+ * parent.getChildren().add(newNode);
+ * }
*
* @return The children, or an empty list if none.
* @category tree
Modified: sis/trunk/sis-utility/src/main/java/org/apache/sis/util/collection/TreeTableFormat.java
URL: http://svn.apache.org/viewvc/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/collection/TreeTableFormat.java?rev=1415758&r1=1415757&r2=1415758&view=diff
==============================================================================
--- sis/trunk/sis-utility/src/main/java/org/apache/sis/util/collection/TreeTableFormat.java (original)
+++ sis/trunk/sis-utility/src/main/java/org/apache/sis/util/collection/TreeTableFormat.java Fri Nov 30 17:38:17 2012
@@ -25,12 +25,15 @@ import java.io.IOException;
import java.text.Format;
import java.text.ParsePosition;
import java.text.ParseException;
+import java.util.regex.Pattern;
+import java.util.regex.Matcher;
import net.jcip.annotations.NotThreadSafe;
import org.apache.sis.io.LineFormatter;
import org.apache.sis.io.TableFormatter;
import org.apache.sis.io.CompoundFormat;
import org.apache.sis.util.Workaround;
import org.apache.sis.util.CharSequences;
+import org.apache.sis.util.StringBuilders;
import org.apache.sis.util.ArgumentChecks;
import org.apache.sis.util.resources.Errors;
import org.apache.sis.internal.util.LocalizedParseException;
@@ -114,12 +117,35 @@ public class TreeTableFormat extends Com
private int verticalLinePosition;
/**
+ * The string to write before and after the {@link #columnSeparator},
+ * or an empty string if none.
+ */
+ String separatorPrefix, separatorSuffix;
+
+ /**
* The column separator to use at formatting time if there is more than one column.
+ * This character will be repeated as many time as needed.
*
- * @see #getColumnSeparator()
- * @see #setColumnSeparator(String)
+ * @see #getColumnSeparatorPattern()
+ * @see #setColumnSeparatorPattern(String)
+ */
+ char columnSeparator;
+
+ /**
+ * {@code true} if the trailing {@code null} values shall be omitted at formatting time.
*/
- String columnSeparator;
+ boolean omitTrailingNulls;
+
+ /**
+ * {@code true} if the user defined the parsing pattern explicitely.
+ */
+ boolean isParsePatternDefined;
+
+ /**
+ * The pattern used at parsing time for finding the column separators, or {@code null}
+ * if not yet constructed. This field is serialized because it may be a user-specified pattern.
+ */
+ private Pattern parsePattern;
/**
* The line separator to use for formatting the tree.
@@ -153,9 +179,12 @@ public class TreeTableFormat extends Com
*/
public TreeTableFormat(final Locale locale, final TimeZone timezone) {
super(locale, timezone);
- indentation = 4;
- columnSeparator = "â¦â¦";
- lineSeparator = JDK7.lineSeparator();
+ indentation = 4;
+ separatorPrefix = "â¦â¦";
+ columnSeparator = 'â¦';
+ separatorSuffix = " ";
+ omitTrailingNulls = true;
+ lineSeparator = JDK7.lineSeparator();
}
/**
@@ -214,41 +243,149 @@ public class TreeTableFormat extends Com
}
/**
- * Returns the characters used in column separators. This character will be used
- * only if more than one column is formatted. The default value is {@code "â¦â¦"}.
- *
- * @return The current column separator.
- */
- public String getColumnSeparator() {
- return columnSeparator;
+ * Returns the pattern of characters used in column separators. Those characters will be used
+ * only if more than one column is formatted. See {@link #setColumnSeparatorPattern(String)}
+ * for a description of the pattern syntax.
+ *
+ * <p>The default pattern is {@code "?â¦â¦[â¦] "}, which means "<cite>If the next value is
+ * non-null, then insert the {@code "â¦â¦"} string, repeat the {@code 'â¦'} character as many
+ * time as needed (may be zero), and finally insert a space</cite>".</p>
+ *
+ * @return The pattern of the current column separator.
+ */
+ public String getColumnSeparatorPattern() {
+ final StringBuilder buffer = new StringBuilder(8);
+ buffer.append(separatorPrefix).append('\uFFFF').append(separatorSuffix);
+ StringBuilders.replace(buffer, "\\", "\\\\");
+ StringBuilders.replace(buffer, "?", "\\?");
+ StringBuilders.replace(buffer, "[", "\\[");
+ StringBuilders.replace(buffer, "]", "\\]");
+ StringBuilders.replace(buffer, "/", "\\/");
+ if (omitTrailingNulls) {
+ buffer.insert(0, '?');
+ }
+ final int insertAt = buffer.indexOf("\uFFFF");
+ buffer.replace(insertAt, insertAt+1, "[\uFFFF]").setCharAt(insertAt+1, columnSeparator);
+ if (isParsePatternDefined) {
+ buffer.append('/').append(parsePattern.pattern());
+ }
+ return buffer.toString();
}
/**
- * Sets the characters to insert between the columns. The separator shall be non-empty and
- * contains at least one non-space character. The last character will be repeated as many
- * time as needed for columns alignment.
+ * Sets the pattern of the characters to insert between the columns. The pattern shall contain
+ * exactly one occurrence of the {@code "[ ]"} pair of bracket, with exactly one character
+ * between them. This character will be repeated as many time as needed for columns alignment.
+ *
+ * <p>The formatting pattern can optionally be followed by a regular expression to be used at
+ * parsing time. If omitted, the parsing pattern will be inferred from the formatting pattern.
+ * If specified, then the {@link #parse(CharSequence, ParsePosition) parse} method will invoke
+ * the {@link Matcher#find()} method for determining the column boundaries.</p>
+ *
+ * <p>The characters listed below have special meaning in the pattern.
+ * Other characters are appended <cite>as-is</cite> between the columns.</p>
+ *
+ * <table class="sis">
+ * <tr><th>Character(s)</th> <th>Meaning</th></tr>
+ * <tr><td>{@code '?'}</td> <td>Omit the column separator for trailing null values.</td></tr>
+ * <tr><td>{@code "[ ]"}</td><td>Repeat the character between bracket as needed.</td></tr>
+ * <tr><td>{@code '/'}</td> <td>Separate the formatting pattern from the parsing pattern.</td></tr>
+ * <tr><td>{@code '\\'}</td> <td>Escape any of the characters listed in this table.</td></tr>
+ * </table>
*
- * <p>In current implementation, the above-cited repeated character must be in the
- * {@linkplain Character#isBmpCodePoint(int) Basic Multilanguage Plane}.</p>
+ * Restrictions:
+ * <ul>
+ * <li>If present, {@code '?'} shall be the first character in the pattern.</li>
+ * <li>The repeated character (specified inside the pair of brackets) is mandatory.</li>
+ * <li>In the current implementation, the repeated character must be in the
+ * {@linkplain Character#isBmpCodePoint(int) Basic Multilanguage Plane}.</li>
+ * <li>If {@code '/'} is present, anything on its right must be compliant
+ * with the {@link Pattern} syntax.</li>
+ * </ul>
*
- * @param separator The new column separator.
- * @throws IllegalArgumentException If the given separator does not contains at least one
- * non-space character, or the last character is not in the BMP plane.
+ * @param pattern The pattern of the new column separator.
+ * @throws IllegalArgumentException If the given pattern is illegal.
*/
- public void setColumnSeparator(final String separator) throws IllegalArgumentException {
- ArgumentChecks.ensureNonEmpty("separator", separator);
- final int length = separator.length();
- if (JDK7.isBmpCodePoint(separator.codePointBefore(length))) {
- for (int i=0; i<length; i++) {
- final int c = separator.codePointAt(i);
- if (!Character.isSpaceChar(c) && !Character.isISOControl(c)) {
- columnSeparator = separator;
- return;
+ public void setColumnSeparatorPattern(final String pattern) throws IllegalArgumentException {
+ ArgumentChecks.ensureNonEmpty("pattern", pattern);
+ final int length = pattern.length();
+ final StringBuilder buffer = new StringBuilder(length);
+ boolean escape = false;
+ boolean trim = false;
+ String prefix = null;
+ String regex = null;
+ int separatorIndex = -1;
+scan: for (int i=0; i<length; i++) {
+ final char c = pattern.charAt(i);
+ switch (c) {
+ case '\uFFFF': { // This "character" is reserved.
+ prefix = null;
+ break scan; // This will cause IllegalArgumentException to be thrown.
+ }
+ case '\\': {
+ if (i != separatorIndex) {
+ if (escape) break;
+ escape = true;
+ }
+ continue;
+ }
+ case '?': {
+ if (i != 0) {
+ prefix = null;
+ break scan;
+ }
+ trim = true;
+ continue;
+ }
+ case '[': {
+ if (escape) break;
+ if (i != separatorIndex) {
+ if (separatorIndex >= 0) {
+ prefix = null;
+ break scan; // This will cause IllegalArgumentException to be thrown.
+ }
+ separatorIndex = i+1;
+ }
+ continue;
+ }
+ case ']': {
+ if (escape) break;
+ switch (i - separatorIndex) {
+ case 0: continue;
+ case 1: prefix = buffer.toString(); buffer.setLength(0); continue;
+ default: prefix = null; break scan;
+ }
}
+ case '/': {
+ if (escape) break;
+ regex = pattern.substring(i+1);
+ break scan;
+ }
+ }
+ if (i != separatorIndex) {
+ buffer.append(c);
}
}
- throw new IllegalArgumentException(Errors.format(
- Errors.Keys.IllegalArgumentValue_2, "separator", separator));
+ if (prefix == null) {
+ throw new IllegalArgumentException(Errors.format(
+ Errors.Keys.IllegalFormatPatternForClass_2, pattern, TreeTable.class));
+ }
+ /*
+ * Finally store the result. The parsing pattern must be first because the call to
+ * Pattern.compile(regex) may thrown PatternSyntaxException. In such case, we want
+ * it to happen before we modified anything else.
+ */
+ if (regex != null) {
+ parsePattern = Pattern.compile(regex);
+ isParsePatternDefined = true;
+ } else {
+ parsePattern = null;
+ isParsePatternDefined = false;
+ }
+ omitTrailingNulls = trim;
+ separatorPrefix = prefix;
+ separatorSuffix = buffer.toString();
+ columnSeparator = pattern.charAt(separatorIndex);
}
/**
@@ -356,7 +493,8 @@ public class TreeTableFormat extends Com
* <li>Each node shall be represented by a single line made of two parts, in that order:
* <ol>
* <li>white spaces and tree drawing characters ({@code 'â'}, {@code 'â'}, {@code 'â'} or {@code 'â'});</li>
- * <li>string representations of node values, separated by the {@linkplain #getColumnSeparator() colunm separator}.</li>
+ * <li>string representations of node values, separated by the
+ * {@linkplain #getColumnSeparatorPattern() colunm separator}.</li>
* </ol>
* </li>
* <li>The number of spaces and drawing characters before the node values determines the node
@@ -373,27 +511,38 @@ public class TreeTableFormat extends Com
*/
@Override
public TreeTable parse(final CharSequence text, final ParsePosition pos) throws ParseException {
- final char separator = columnSeparator.charAt(columnSeparator.length() - 1);
+ if (parsePattern == null) {
+ parsePattern = Pattern.compile(
+ Pattern.quote(separatorPrefix)
+ + Pattern.quote(String.valueOf(columnSeparator)) + '*'
+ + Pattern.quote(separatorSuffix));
+ }
+ final Matcher matcher = parsePattern.matcher(text);
final int length = text.length();
int indexOfLineStart = pos.getIndex();
int indentationLevel = 0; // Current index in the 'indentations' array.
int[] indentations = new int[16]; // Number of spaces (ignoring drawing characters) for each level.
TreeTable.Node lastNode = null; // Last parsed node, having 'indentation[level]' characters before its content.
TreeTable.Node root = null; // First node found while parsing.
- final DefaultTreeTable table = new DefaultTreeTable(columnIndices != null ? columnIndices : ColumnConstant.NAME_MAP);
+ final DefaultTreeTable table = new DefaultTreeTable(columnIndices != null ? columnIndices : TableColumn.NAME_MAP);
final TableColumn<?>[] columns = DefaultTreeTable.getColumns(table.columnIndices);
final Format[] formats = getFormats(columns, true);
do {
final int startNextLine = CharSequences.indexOfLineStart(text, 1, indexOfLineStart);
- int endPosition = startNextLine;
- while (endPosition > indexOfLineStart) {
- final int c = text.charAt(endPosition-1);
+ int endOfLine = startNextLine;
+ while (endOfLine > indexOfLineStart) {
+ final int c = text.charAt(endOfLine-1);
if (c != '\r' && c != '\n') break;
- endPosition--; // Skip trailing '\r' and '\n'.
+ endOfLine--; // Skip trailing '\r' and '\n'.
}
+ /*
+ * Skip leading spaces using Character.isSpaceChar(â¦) instead than isWhitespace(â¦)
+ * because we need to skip non-breaking spaces as well as ordinary space. We don't
+ * need to consider line feeds since they were handled by the lines just above.
+ */
boolean hasChar = false;
int i; // The indentation of current line.
- for (i=indexOfLineStart; i<endPosition;) {
+ for (i=indexOfLineStart; i<endOfLine;) {
final int c = Character.codePointAt(text, i);
if (!Character.isSpaceChar(c)) {
hasChar = true;
@@ -412,12 +561,7 @@ public class TreeTableFormat extends Com
* user-spaces to interfer with the calculation of indentation.
*/
int indexOfValue = i;
- while (i > indexOfLineStart) {
- final int c = Character.codePointBefore(text, i);
- if (!Character.isSpaceChar(c)) break;
- i -= Character.charCount(c);
- }
- i -= indexOfLineStart;
+ i = CharSequences.skipTrailingWhitespaces(text, indexOfLineStart, i) - indexOfLineStart;
/*
* Found the first character which is not part of the indentation. Create a new root
* (without parent for now) and parse the values for each column. Columns with empty
@@ -425,24 +569,19 @@ public class TreeTableFormat extends Com
*/
final TreeTable.Node node = new DefaultTreeTable.Node(table);
try {
-parseColumns: for (int ci=0; ci<columns.length; ci++) {
- int endOfColumn = CharSequences.indexOf(text, columnSeparator, indexOfValue, endPosition);
- if (endOfColumn < 0) {
- endOfColumn = endPosition;
- }
- for (int endOfValue = endOfColumn; endOfValue > indexOfValue;) {
- final int c = Character.codePointBefore(text, endOfValue);
- if (!Character.isSpaceChar(c)) {
- parseValue(node, columns[ci], formats[ci], text.subSequence(indexOfValue, endOfValue).toString());
- break;
- }
- endOfValue -= Character.charCount(c);
- }
- indexOfValue = endOfColumn;
- do if (++indexOfValue >= endPosition) {
- break parseColumns;
+ matcher.region(indexOfValue, endOfLine);
+ for (int ci=0; ci<columns.length; ci++) {
+ final boolean found = matcher.find();
+ int endOfColumn = found ? matcher.start() : endOfLine;
+ indexOfValue = CharSequences.skipLeadingWhitespaces (text, indexOfValue, endOfColumn);
+ int endOfValue = CharSequences.skipTrailingWhitespaces(text, indexOfValue, endOfColumn);
+ if (endOfValue > indexOfValue) {
+ parseValue(node, columns[ci], formats[ci], text.subSequence(indexOfValue, endOfValue).toString());
}
- while (text.charAt(indexOfValue) == separator);
+ if (!found) break;
+ // The end of this column will be the beginning of the next column,
+ // after skipping the last character of the column separator.
+ indexOfValue = matcher.end();
}
} catch (ParseException e) {
pos.setErrorIndex(indexOfValue);
@@ -584,7 +723,7 @@ parseColumns: for (int ci=0; ci<column
* For each indentation level, {@code true} if the previous levels are writing the last node.
* This array will growth as needed.
*/
- private boolean[] last;
+ private boolean[] isLast;
/**
* The columns to write.
@@ -597,9 +736,9 @@ parseColumns: for (int ci=0; ci<column
private final Format[] formats;
/**
- * The column separator character to repeat until the columns are aligned.
+ * The node values to format.
*/
- private final char toRepeat;
+ private final Object[] values;
/**
* Creates a new instance which will write in the given appendable.
@@ -608,8 +747,8 @@ parseColumns: for (int ci=0; ci<column
super(columns.length >= 2 ? new TableFormatter(out, "") : out);
this.columns = columns;
this.formats = getFormats(columns, false);
- this.last = new boolean[8];
- this.toRepeat = columnSeparator.charAt(columnSeparator.length() - 1);
+ this.values = new Object[columns.length];
+ this.isLast = new boolean[8];
setTabulationExpanded(true);
setLineSeparator(" ¶ ");
}
@@ -653,24 +792,34 @@ parseColumns: for (int ci=0; ci<column
*/
final void format(final TreeTable.Node node, final int level) throws IOException {
for (int i=0; i<level; i++) {
- out.append(getTreeSymbols(i != level-1, last[i]));
+ out.append(getTreeSymbols(i != level-1, isLast[i]));
}
+ int n = 0;
for (int i=0; i<columns.length; i++) {
+ if ((values[i] = node.getValue(columns[i])) != null) {
+ n = i;
+ }
+ }
+ if (!omitTrailingNulls) {
+ n = values.length - 1;
+ }
+ for (int i=0; i<=n; i++) {
if (i != 0) {
// We have a TableFormatter instance if and only if there is 2 or more columns.
- ((TableFormatter) out.append(columnSeparator)).nextColumn(toRepeat);
+ ((TableFormatter) out.append(separatorPrefix)).nextColumn(columnSeparator);
+ out.append(separatorSuffix);
}
- formatValue(formats[i], node.getValue(columns[i]));
+ formatValue(formats[i], values[i]);
clear();
}
out.append(lineSeparator);
- if (level >= last.length) {
- last = Arrays.copyOf(last, level*2);
+ if (level >= isLast.length) {
+ isLast = Arrays.copyOf(isLast, level*2);
}
final List<? extends TreeTable.Node> children = node.getChildren();
final int count = children.size();
for (int i=0; i<count; i++) {
- last[level] = (i == count-1);
+ isLast[level] = (i == count-1);
format(children.get(i), level+1);
}
}
Modified: sis/trunk/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java
URL: http://svn.apache.org/viewvc/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java?rev=1415758&r1=1415757&r2=1415758&view=diff
==============================================================================
--- sis/trunk/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java (original)
+++ sis/trunk/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java Fri Nov 30 17:38:17 2012
@@ -41,11 +41,34 @@ public final class Errors extends Indexe
* @version 0.3
* @module
*/
- public static final class Keys {
+ public static final class Keys extends KeyConstants {
+ /**
+ * The unique instance of key constants handler.
+ */
+ static final Keys INSTANCE = new Keys();
+
+ /**
+ * For {@link #INSTANCE} creation only.
+ */
private Keys() {
}
/**
+ * Can not compute the derivative.
+ */
+ public static final int CanNotComputeDerivative = 44;
+
+ /**
+ * Can not clone an object of type â{0}â.
+ */
+ public static final int CloneNotSupported_1 = 42;
+
+ /**
+ * Thread â{0}â is dead.
+ */
+ public static final int DeadThread_1 = 43;
+
+ /**
* Value â{0}â is duplicated.
*/
public static final int DuplicatedValue_1 = 38;
@@ -127,6 +150,11 @@ public final class Errors extends Indexe
public static final int IndexOutOfBounds_1 = 4;
/**
+ * Argument â{0}â can not take an infinite value.
+ */
+ public static final int InfiniteArgumentValue_1 = 45;
+
+ /**
* A different value is already associated to the â{0}â key.
*/
public static final int KeyCollision_1 = 19;
@@ -267,12 +295,11 @@ public final class Errors extends Indexe
}
/**
- * Returns the {@code Keys} class.
+ * Returns the handle for the {@code Keys} constants.
*/
@Override
- final Class<?> getKeysClass() throws ClassNotFoundException {
- assert super.getKeysClass() == Keys.class;
- return Keys.class;
+ final KeyConstants getKeyConstants() {
+ return Keys.INSTANCE;
}
/**
@@ -375,9 +402,10 @@ public final class Errors extends Indexe
private static final class International extends ResourceInternationalString {
private static final long serialVersionUID = -229348959712294902L;
- International(int key) {super(key);}
- International(int key, Object args) {super(key, args);}
- @Override IndexedResourceBundle getBundle(Locale locale) {
+ International(int key) {super(key);}
+ International(int key, Object args) {super(key, args);}
+ @Override KeyConstants getKeyConstants() {return Keys.INSTANCE;}
+ @Override IndexedResourceBundle getBundle(final Locale locale) {
return getResources(locale);
}
}
Modified: sis/trunk/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties
URL: http://svn.apache.org/viewvc/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties?rev=1415758&r1=1415757&r2=1415758&view=diff
==============================================================================
--- sis/trunk/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties (original)
+++ sis/trunk/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties Fri Nov 30 17:38:17 2012
@@ -14,6 +14,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
+CanNotComputeDerivative = Can not compute the derivative.
+CloneNotSupported_1 = Can not clone an object of type \u2018{0}\u2019.
+DeadThread_1 = Thread \u201c{0}\u201d is dead.
DuplicatedValue_1 = Value \u201c{0}\u201d is duplicated.
ElementAlreadyPresent_1 = Element \u201c{0}\u201d is already present.
EmptyArgument_1 = Argument \u2018{0}\u2019 shall not be empty.
@@ -30,6 +33,7 @@ IllegalRange_2 = Range
InconsistentAttribute_2 = Value \u201c{1}\u201d of attribute \u2018{0}\u2019 is inconsistent with other attributes.
InconsistentTableColumns = Inconsistent table columns.
IndexOutOfBounds_1 = Index {0} is out of bounds.
+InfiniteArgumentValue_1 = Argument \u2018{0}\u2019 can not take an infinite value.
KeyCollision_1 = A different value is already associated to the \u201c{0}\u201d key.
MandatoryAttribute_2 = Attribute \u201c{0}\u201d is mandatory for an object of type \u2018{1}\u2019.
NegativeArgument_2 = Argument \u2018{0}\u2019 shall not be negative. The given value was {1}.
Modified: sis/trunk/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties
URL: http://svn.apache.org/viewvc/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties?rev=1415758&r1=1415757&r2=1415758&view=diff
==============================================================================
--- sis/trunk/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties (original)
+++ sis/trunk/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties Fri Nov 30 17:38:17 2012
@@ -14,6 +14,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
+CanNotComputeDerivative = La d\u00e9riv\u00e9 ne peut pas \u00eatre calcul\u00e9e.
+CloneNotSupported_1 = Un objet de type \u2018{0}\u2019 ne peut pas \u00eatre clon\u00e9.
+DeadThread_1 = La t\u00e2che \u201c{0}\u201d est morte.
DuplicatedValue_1 = La valeur \u201c{0}\u201d est dupliqu\u00e9e.
ElementAlreadyPresent_1 = L\u2019\u00e9lement \u201c{0}\u201d est d\u00e9j\u00e0 pr\u00e9sent.
EmptyArgument_1 = L\u2019argument \u2018{0}\u2019 ne doit pas \u00eatre vide.
@@ -30,6 +33,7 @@ IllegalRange_2 = La pla
InconsistentAttribute_2 = La valeur \u201c{1}\u201d de l\u2019attribut \u2018{0}\u2019 n\u2019est pas coh\u00e9rente avec celles des autres attributs.
InconsistentTableColumns = Les colonnes des tables ne sont pas coh\u00e9rentes.
IndexOutOfBounds_1 = L\u2019index {0} est en dehors des limites permises.
+InfiniteArgumentValue_1 = L\u2019argument \u2018{0}\u2019 ne peut pas prendre une valeur infinie.
KeyCollision_1 = Une valeur diff\u00e9rente est d\u00e9j\u00e0 associ\u00e9e \u00e0 la cl\u00e9 \u201c{0}\u201d.
MandatoryAttribute_2 = L\u2019attribut \u201c{0}\u201d est obligatoire pour un objet de type \u2018{1}\u2019.
NegativeArgument_2 = L\u2019argument \u2018{0}\u2019 ne doit pas \u00eatre n\u00e9gatif. La valeur donn\u00e9e \u00e9tait {1}.
Modified: sis/trunk/sis-utility/src/main/java/org/apache/sis/util/resources/IndexedResourceBundle.java
URL: http://svn.apache.org/viewvc/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/resources/IndexedResourceBundle.java?rev=1415758&r1=1415757&r2=1415758&view=diff
==============================================================================
--- sis/trunk/sis-utility/src/main/java/org/apache/sis/util/resources/IndexedResourceBundle.java (original)
+++ sis/trunk/sis-utility/src/main/java/org/apache/sis/util/resources/IndexedResourceBundle.java Fri Nov 30 17:38:17 2012
@@ -22,7 +22,6 @@ import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.text.MessageFormat;
-import java.util.Arrays;
import java.util.Enumeration;
import java.util.Locale;
import java.util.MissingResourceException;
@@ -30,8 +29,6 @@ import java.util.NoSuchElementException;
import java.util.ResourceBundle;
import java.util.logging.Level;
import java.util.logging.LogRecord;
-import java.lang.reflect.Field;
-import java.lang.reflect.Modifier;
import net.jcip.annotations.ThreadSafe;
import org.opengis.util.InternationalString;
@@ -42,8 +39,6 @@ import org.apache.sis.util.Exceptions;
import org.apache.sis.util.CharSequences;
import org.apache.sis.util.logging.Logging;
-import static org.apache.sis.util.Arrays.resize;
-
// Related to JDK7
import org.apache.sis.internal.util.JDK7;
@@ -81,15 +76,6 @@ public class IndexedResourceBundle exten
private final String filename;
/**
- * The key names. This is usually not needed, but may be created from the {@code Keys}
- * inner class in some occasions.
- *
- * @see #getKeyNames()
- * @see #getKeyName(int)
- */
- private transient String[] keys;
-
- /**
* The array of resources. Keys are an array index. For example, the value for key "14" is
* {@code values[14]}. This array will be loaded only when first needed. We should not load
* it at construction time, because some {@code ResourceBundle} objects will never ask for
@@ -175,54 +161,22 @@ public class IndexedResourceBundle exten
}
/**
- * Returns the inner {@code Keys} class which declare the key constants.
+ * Returns a handler for the constants declared in the inner {@code Keys} class.
* Subclasses defined in the {@code org.apache.sis.util.resources} package
* override this method for efficiency. However the default implementation
* should work for other cases (we don't want to expose too much internal API).
*
- * @return The inner {@code Keys} class.
- * @throws ClassNotFoundException If the inner class has not been found.
+ * @return A handler for the constants declared in the inner {@code Keys} class.
*/
- Class<?> getKeysClass() throws ClassNotFoundException {
+ KeyConstants getKeyConstants() {
+ Class<?> keysClass = KeyConstants.class;
for (final Class<?> inner : getClass().getClasses()) {
if ("Keys".equals(inner.getSimpleName())) {
- return inner;
- }
- }
- throw new ClassNotFoundException();
- }
-
- /**
- * Returns the internal array of key names. <strong>Do not modify the returned array.</strong>
- * This method should usually not be invoked, in order to avoid loading the inner Keys class.
- * The keys names are used only in rare situation, like {@link #list(Writer)} or in log records.
- */
- private synchronized String[] getKeyNames() {
- if (keys == null) {
- String[] names;
- int length = 0;
- try {
- final Field[] fields = getKeysClass().getFields();
- names = new String[fields.length];
- for (final Field field : fields) {
- if (Modifier.isStatic(field.getModifiers()) && field.getType() == Integer.TYPE) {
- final int index = (Integer) field.get(null);
- if (index >= length) {
- length = index + 1;
- if (length > names.length) {
- // Usually don't happen, except for incomplete bundles.
- names = Arrays.copyOf(names, length*2);
- }
- }
- names[index] = field.getName();
- }
- }
- } catch (Exception e) {
- names = CharSequences.EMPTY_ARRAY;
+ keysClass = inner;
+ break;
}
- keys = resize(names, length);
}
- return keys;
+ return new KeyConstants(keysClass);
}
/**
@@ -232,7 +186,7 @@ public class IndexedResourceBundle exten
*/
@Override
public final Enumeration<String> getKeys() {
- return new KeyEnum(getKeyNames());
+ return new KeyEnum(getKeyConstants().getKeyNames());
}
/**
@@ -272,22 +226,6 @@ public class IndexedResourceBundle exten
}
/**
- * Returns the name of the key at the given index. If there is no name at that given
- * index, format the index as a decimal number. Those decimal numbers are parsed by
- * our {@link #handleGetObject(String)} implementation.
- */
- private String getKeyNameAt(final int index) {
- final String[] keys = getKeyNames();
- if (index < keys.length) {
- final String key = keys[index];
- if (key != null) {
- return key;
- }
- }
- return String.valueOf(index);
- }
-
- /**
* Lists resources to the specified stream. If a resource has more than one line, only
* the first line will be written. This method is used mostly for debugging purposes.
*
@@ -297,7 +235,7 @@ public class IndexedResourceBundle exten
@Debug
public final void list(final Appendable out) throws IOException {
int keyLength = 0;
- final String[] keys = getKeyNames();
+ final String[] keys = getKeyConstants().getKeyNames();
for (final String key : keys) {
if (key != null) {
keyLength = Math.max(keyLength, key.length());
@@ -429,7 +367,7 @@ public class IndexedResourceBundle exten
* LogRecords, for easier debugging if the message has not been properly formatted.
*/
try {
- keyID = (Integer) getKeysClass().getField(key).get(null);
+ keyID = getKeyConstants().getKeyValue(key);
} catch (Exception e) {
Logging.recoverableException(getClass(), "handleGetObject", e);
return null; // This is okay as of 'handleGetObject' contract.
@@ -668,7 +606,7 @@ public class IndexedResourceBundle exten
* @return The log record.
*/
public final LogRecord getLogRecord(final Level level, final int key) {
- final LogRecord record = new LogRecord(level, getKeyNameAt(key));
+ final LogRecord record = new LogRecord(level, getKeyConstants().getKeyName(key));
record.setResourceBundleName(getClass().getName());
record.setResourceBundle(this);
return record;