You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pivot.apache.org by tv...@apache.org on 2009/03/26 00:12:17 UTC
svn commit: r758461 [6/47] - in /incubator/pivot/branches: ./ 1.1/
1.1/charts-test/ 1.1/charts-test/src/ 1.1/charts-test/src/pivot/
1.1/charts-test/src/pivot/charts/ 1.1/charts-test/src/pivot/charts/test/
1.1/charts/ 1.1/charts/lib/ 1.1/charts/src/ 1.1...
Added: incubator/pivot/branches/1.1/core/src/pivot/serialization/CSVSerializer.java
URL: http://svn.apache.org/viewvc/incubator/pivot/branches/1.1/core/src/pivot/serialization/CSVSerializer.java?rev=758461&view=auto
==============================================================================
--- incubator/pivot/branches/1.1/core/src/pivot/serialization/CSVSerializer.java (added)
+++ incubator/pivot/branches/1.1/core/src/pivot/serialization/CSVSerializer.java Wed Mar 25 23:08:38 2009
@@ -0,0 +1,340 @@
+/*
+ * Copyright (c) 2008 VMware, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package pivot.serialization;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.io.Writer;
+import java.nio.charset.Charset;
+
+import pivot.beans.BeanDictionary;
+import pivot.collections.ArrayList;
+import pivot.collections.Dictionary;
+import pivot.collections.HashMap;
+import pivot.collections.List;
+import pivot.collections.Sequence;
+
+/**
+ * Implementation of the {@link Serializer} interface that reads data from
+ * and writes data to a comma-separated value (CSV) file.
+ * <p>
+ * TODO Add "firstLineContainsKeys" flag.
+ * <p>
+ * TODO Add support for variable delimiters.
+ *
+ * @author gbrown
+ */
+public class CSVSerializer implements Serializer<List<?>> {
+ /**
+ * Class representing the serializers key sequence.
+ */
+ public class KeySequence implements Sequence<String> {
+ public int add(String item) {
+ return keys.add(item);
+ }
+
+ public void insert(String item, int index) {
+ keys.insert(item, index);
+ }
+
+ public String update(int index, String item) {
+ return keys.update(index, item);
+ }
+
+ public int remove(String item) {
+ return keys.remove(item);
+ }
+
+ public Sequence<String> remove(int index, int count) {
+ return keys.remove(index, count);
+ }
+
+ public String get(int index) {
+ return keys.get(index);
+ }
+
+ public int indexOf(String item) {
+ return keys.indexOf(item);
+ }
+
+ public int getLength() {
+ return keys.getLength();
+ }
+ }
+
+ private Charset charset;
+
+ private ArrayList<String> keys = new ArrayList<String>();
+ private KeySequence keySequence = new KeySequence();
+
+ public static final String MIME_TYPE = "text/csv";
+ public static final int BUFFER_SIZE = 2048;
+
+ int c = -1;
+ private Class<?> itemClass = HashMap.class;
+
+ public CSVSerializer() {
+ this(Charset.defaultCharset());
+ }
+
+ public CSVSerializer(String charsetName) {
+ this(charsetName == null ? Charset.defaultCharset() : Charset.forName(charsetName));
+ }
+
+ public CSVSerializer(Charset charset) {
+ if (charset == null) {
+ throw new IllegalArgumentException("charset is null.");
+ }
+
+ this.charset = charset;
+ }
+
+ /**
+ * Returns a sequence representing the fields that will be read or written
+ * by this serializer.
+ */
+ public KeySequence getKeys() {
+ return keySequence;
+ }
+
+ /**
+ * Returns the item class that will be instantiated by the serializer during
+ * a read operation.
+ */
+ public Class<?> getItemClass() {
+ return itemClass;
+ }
+
+ /**
+ * Sets the item class that will be instantiated by the serializer during
+ * a read operation. The class must implement the {@link Dictionary}
+ * interface.
+ */
+ public void setItemClass(Class<?> itemClass) {
+ if (itemClass == null) {
+ throw new IllegalArgumentException("itemClass is null.");
+ }
+
+ this.itemClass = itemClass;
+ }
+
+ /**
+ * Reads values from a comma-separated value stream.
+ *
+ * @param inputStream
+ * The input stream from which data will be read.
+ *
+ * @see #readObject(Reader)
+ */
+ public List<?> readObject(InputStream inputStream)
+ throws IOException, SerializationException {
+ Reader reader = new BufferedReader(new InputStreamReader(inputStream, charset),
+ BUFFER_SIZE);
+ return readObject(reader);
+ }
+
+ /**
+ * Reads values from a comma-separated value stream.
+ *
+ * @param reader
+ * The reader from which data will be read.
+ *
+ * @return
+ * A list containing the data read from the CSV file.
+ * The list items are instances of Dictionary<String, Object> populated by
+ * mapping columns in the CSV file to keys in the key sequence.
+ */
+ public List<?> readObject(Reader reader)
+ throws IOException, SerializationException {
+ ArrayList<Object> items = new ArrayList<Object>();
+
+ // Move to the first character
+ c = reader.read();
+
+ while (c != -1) {
+ Object item = readItem(reader);
+ while (item != null) {
+ items.add(item);
+
+ // Move to next line
+ while (c != -1
+ && (c == '\r' || c == '\n')) {
+ c = reader.read();
+ }
+
+ // Read the next item
+ item = readItem(reader);
+ }
+ }
+
+ return items;
+ }
+
+ @SuppressWarnings("unchecked")
+ private Object readItem(Reader reader)
+ throws IOException, SerializationException {
+ Object item = null;
+
+ if (c != -1) {
+ // Instantiate the item
+ Dictionary<String, Object> itemDictionary;
+
+ try {
+ item = itemClass.newInstance();
+
+ if (item instanceof Dictionary) {
+ itemDictionary = (Dictionary<String, Object>)item;
+ } else {
+ itemDictionary = new BeanDictionary(item);
+ }
+ } catch(IllegalAccessException exception) {
+ throw new SerializationException(exception);
+ } catch(InstantiationException exception) {
+ throw new SerializationException(exception);
+ }
+
+ // Add values to the item
+ for (int i = 0, n = keys.getLength(); i < n; i++) {
+ String key = keys.get(i);
+ String value = readValue(reader);
+ if (value == null) {
+ throw new SerializationException("Error reading value for "
+ + key + " from input stream.");
+ }
+
+ itemDictionary.put(key, value);
+ }
+ }
+
+ return item;
+ }
+
+ private String readValue(Reader reader)
+ throws IOException, SerializationException {
+ String value = null;
+
+ // Read the next value from this line, returning null if there are
+ // no more values on the line
+ if (c != -1
+ && (c != '\r' && c != '\n')) {
+ // Read the value
+ StringBuilder valueBuilder = new StringBuilder();
+
+ // Values may be bounded in quotes; the double-quote character is
+ // escaped by two successive occurrences
+ boolean quoted = (c == '"');
+ if (quoted) {
+ c = reader.read();
+ }
+
+ while (c != -1
+ && (quoted || c != ',')
+ && (c != '\r' && c != '\n')) {
+ if (c == '"') {
+ c = reader.read();
+ quoted &= (c == '"');
+ }
+
+ if (quoted || c != ',') {
+ valueBuilder.append((char)c);
+ c = reader.read();
+ }
+ }
+
+ if (quoted) {
+ throw new SerializationException("Unterminated string.");
+ }
+
+ value = valueBuilder.toString();
+
+ // Move to the next character after ','
+ c = reader.read();
+ }
+
+ return value;
+ }
+
+ /**
+ * Writes values to a comma-separated value stream.
+ *
+ * @param items
+ *
+ * @param outputStream
+ * The output stream to which data will be written.
+ *
+ * @see #writeObject(List, Writer)
+ */
+ public void writeObject(List<?> items, OutputStream outputStream)
+ throws IOException, SerializationException {
+ Writer writer = new BufferedWriter(new OutputStreamWriter(outputStream, charset),
+ BUFFER_SIZE);
+ writeObject(items, writer);
+ }
+
+ /**
+ * Writes values to a comma-separated value stream.
+ *
+ * @param items
+ * A list containing the data to write to the CSV
+ * file. List items must be instances of Dictionary<String, Object>. The
+ * dictionary values will be written out in the order specified by the
+ * key sequence.
+ *
+ * @param writer
+ * The writer to which data will be written.
+ */
+ @SuppressWarnings("unchecked")
+ public void writeObject(List<?> items, Writer writer)
+ throws IOException, SerializationException {
+ if (writer == null) {
+ throw new IllegalArgumentException("writer is null.");
+ }
+
+ for (Object item : items) {
+ Dictionary<String, Object> itemDictionary;
+ if (item instanceof Dictionary) {
+ itemDictionary = (Dictionary<String, Object>)item;
+ } else {
+ itemDictionary = new BeanDictionary(item);
+ }
+
+ for (int i = 0, n = keys.getLength(); i < n; i++) {
+ String key = keys.get(i);
+
+ if (i > 0) {
+ writer.append(",");
+ }
+
+ Object value = itemDictionary.get(key);
+ writer.append(value.toString());
+ }
+
+ writer.append("\r\n");
+ }
+
+ writer.flush();
+ }
+
+ public String getMIMEType(List<?> objects) {
+ return MIME_TYPE + "; charset=" + charset.name();
+ }
+}
Added: incubator/pivot/branches/1.1/core/src/pivot/serialization/JSONSerializer.java
URL: http://svn.apache.org/viewvc/incubator/pivot/branches/1.1/core/src/pivot/serialization/JSONSerializer.java?rev=758461&view=auto
==============================================================================
--- incubator/pivot/branches/1.1/core/src/pivot/serialization/JSONSerializer.java (added)
+++ incubator/pivot/branches/1.1/core/src/pivot/serialization/JSONSerializer.java Wed Mar 25 23:08:38 2009
@@ -0,0 +1,883 @@
+/*
+ * Copyright (c) 2008 VMware, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package pivot.serialization;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.Writer;
+import java.nio.charset.Charset;
+
+import pivot.collections.ArrayList;
+import pivot.collections.Dictionary;
+import pivot.collections.HashMap;
+import pivot.collections.List;
+import pivot.collections.Map;
+import pivot.collections.Sequence;
+
+/**
+ * Implementation of the {@link Serializer} interface that reads data from
+ * and writes data to a JavaScript Object Notation (JSON) file.
+ * <p>
+ * TODO Wrap reader in a CountingReader that tracks line/character index.
+ *
+ * @author gbrown
+ */
+public class JSONSerializer implements Serializer<Object> {
+ private Charset charset = null;
+
+ private int c = -1;
+ private boolean alwaysDelimitMapKeys = false;
+
+ public static final String MIME_TYPE = "application/json";
+ public static final int BUFFER_SIZE = 2048;
+
+ public JSONSerializer() {
+ this(Charset.defaultCharset());
+ }
+
+ public JSONSerializer(String charsetName) {
+ this(charsetName == null ? Charset.defaultCharset() : Charset.forName(charsetName));
+ }
+
+ public JSONSerializer(Charset charset) {
+ if (charset == null) {
+ throw new IllegalArgumentException("charset is null.");
+ }
+
+ this.charset = charset;
+ }
+
+ /**
+ * Reads data from a JSON stream.
+ *
+ * @param inputStream
+ * The input stream from which data will be read.
+ *
+ * @see #readObject(Reader)
+ */
+ public Object readObject(InputStream inputStream)
+ throws IOException, SerializationException {
+ Reader reader = new BufferedReader(new InputStreamReader(inputStream, charset), BUFFER_SIZE);
+ Object object = readObject(reader);
+
+ return object;
+ }
+
+ /**
+ * Reads data from a JSON stream.
+ *
+ * @param reader
+ * The reader from which data will be read.
+ *
+ * @return
+ * One of the following types, depending on the content of the stream:
+ *
+ * <ul>
+ * <li>java.lang.String</li>
+ * <li>java.lang.Number</li>
+ * <li>java.lang.Boolean</li>
+ * <li>pivot.collections.List</li>
+ * <li>pivot.collections.Map</li>
+ * </ul>
+ */
+ public Object readObject(Reader reader)
+ throws IOException, SerializationException {
+ // Move to the first character
+ c = reader.read();
+
+ // Read the root value
+ Object object = readValue(reader);
+
+ return object;
+ }
+
+ private Object readValue(Reader reader)
+ throws IOException, SerializationException {
+ Object object = null;
+
+ skipWhitespace(reader);
+
+ if (c == -1) {
+ throw new SerializationException("Unexpected end of input stream.");
+ }
+
+ if (c == 'n') {
+ object = readNull(reader);
+ } else if (c == '"' || c == '\'') {
+ object = readString(reader);
+ } else if (c == '+' || c == '-' || Character.isDigit(c)) {
+ object = readNumber(reader);
+ } else if (c == 't' || c == 'f') {
+ object = readBoolean(reader);
+ } else if (c == '[') {
+ object = readList(reader);
+ } else if (c == '{') {
+ object = readMap(reader);
+ } else {
+ throw new SerializationException("Unexpected character in input stream.");
+ }
+
+ return object;
+ }
+
+ private void skipWhitespace(Reader reader)
+ throws IOException {
+ while (c != -1 && Character.isWhitespace(c)) {
+ c = reader.read();
+ }
+ }
+
+ private Object readNull(Reader reader)
+ throws IOException, SerializationException {
+ String nullString = "null";
+
+ int n = nullString.length();
+ int i = 0;
+
+ while (c != -1 && i < n) {
+ if (nullString.charAt(i) != c) {
+ throw new SerializationException("Unexpected character in input stream.");
+ }
+
+ c = reader.read();
+ i++;
+ }
+
+ if (i < n) {
+ throw new SerializationException("Incomplete null value in input stream.");
+ }
+
+ return null;
+ }
+
+ private String readString(Reader reader)
+ throws IOException, SerializationException {
+ StringBuilder stringBuilder = new StringBuilder();
+
+ // Use the same delimiter to close the string
+ int t = c;
+
+ // Move to the next character after the delimiter
+ c = reader.read();
+
+ while (c != -1 && c != t) {
+ if (c == '\\') {
+ c = reader.read();
+
+ if (c == 't') {
+ c = '\t';
+ } else if (c == 'n') {
+ c = '\n';
+ } else if (c == 'u') {
+ StringBuilder unicodeBuilder = new StringBuilder();
+ while (unicodeBuilder.length() < 4) {
+ c = reader.read();
+ unicodeBuilder.append((char)c);
+ }
+
+ String unicode = unicodeBuilder.toString();
+ c = (char)Integer.parseInt(unicode, 16);
+ } else {
+ if (!(c == '\\'
+ || c == '/'
+ || c == '\"'
+ || c == '\''
+ || c == t)) {
+ throw new SerializationException("Unsupported escape sequence in input stream.");
+ }
+ }
+ }
+
+ stringBuilder.append((char)c);
+ c = reader.read();
+ }
+
+ if (c != t) {
+ throw new SerializationException("Unterminated string in input stream.");
+ }
+
+ // Move to the next character after the delimiter
+ c = reader.read();
+
+ return stringBuilder.toString();
+ }
+
+ private Number readNumber(Reader reader)
+ throws IOException, SerializationException {
+ Number number = null;
+
+ StringBuilder stringBuilder = new StringBuilder();
+ boolean negative = false;
+ boolean integer = true;
+
+ if (c == '+' || c == '-') {
+ negative = (c == '-');
+ c = reader.read();
+ }
+
+ while (c != -1 && (Character.isDigit(c) || c == '.'
+ || c == 'e' || c == 'E')) {
+ stringBuilder.append((char)c);
+ integer &= !(c == '.');
+ c = reader.read();
+ }
+
+ if (integer) {
+ // TODO 5/28/2008 Remove 32-bit optimization when 64-bit processors
+ // are more prevalent
+ long value = Long.parseLong(stringBuilder.toString()) * (negative ? -1 : 1);
+
+ if (value > Integer.MAX_VALUE
+ || value < Integer.MIN_VALUE) {
+ number = value;
+ } else {
+ number = (int)value;
+ }
+ } else {
+ number = Double.parseDouble(stringBuilder.toString()) * (negative ? -1.0d : 1.0d);
+ }
+
+ return number;
+ }
+
+ private Boolean readBoolean(Reader reader)
+ throws IOException, SerializationException {
+ String booleanString = (c == 't') ? "true" : "false";
+ int n = booleanString.length();
+ int i = 0;
+
+ while (c != -1 && i < n) {
+ if (booleanString.charAt(i) != c) {
+ throw new SerializationException("Unexpected character in input stream.");
+ }
+
+ c = reader.read();
+ i++;
+ }
+
+ if (i < n) {
+ throw new SerializationException("Incomplete boolean value in input stream.");
+ }
+
+ return Boolean.parseBoolean(booleanString);
+ }
+
+ private List<Object> readList(Reader reader)
+ throws IOException, SerializationException {
+ List<Object> list = new ArrayList<Object>();
+
+ // Move to the next character after '['
+ c = reader.read();
+
+ while (c != -1 && c != ']') {
+ list.add(readValue(reader));
+ skipWhitespace(reader);
+
+ if (c == ',') {
+ c = reader.read();
+ skipWhitespace(reader);
+ } else if (c == -1) {
+ throw new SerializationException("Unexpected end of input stream.");
+ } else {
+ if (c != ']') {
+ throw new SerializationException("Unexpected character in input stream.");
+ }
+ }
+ }
+
+ // Move to the next character after ']'
+ c = reader.read();
+
+ return list;
+ }
+
+ private Map<String, Object> readMap(Reader reader)
+ throws IOException, SerializationException {
+ Map<String, Object> map = new HashMap<String, Object>();
+
+ // Move to the next character after '{'
+ c = reader.read();
+ skipWhitespace(reader);
+
+ while (c != -1 && c != '}') {
+ String key = null;
+
+ if (c == '"' || c == '\'') {
+ // The key is a delimited string
+ key = readString(reader);
+ } else {
+ // The key is an undelimited string; it must adhere to Java
+ // identifier syntax
+ StringBuilder keyStringBuilder = new StringBuilder();
+
+ if (!Character.isJavaIdentifierStart(c)) {
+ throw new SerializationException("Illegal identifier start character.");
+ }
+
+ while (c != -1
+ && c != ':' && !Character.isWhitespace(c)) {
+ if (!Character.isJavaIdentifierPart(c)) {
+ throw new SerializationException("Illegal identifier character.");
+ }
+
+ keyStringBuilder.append((char)c);
+ c = reader.read();
+ }
+
+ if (c == -1) {
+ throw new SerializationException("Unexpected end of input stream.");
+ }
+
+ key = keyStringBuilder.toString();
+ }
+
+ if (key == null
+ || key.length() == 0) {
+ throw new SerializationException("\"" + key + "\" is not a valid key.");
+ }
+
+ skipWhitespace(reader);
+
+ if (c != ':') {
+ throw new SerializationException("Unexpected character in input stream.");
+ }
+
+ // Move to the first character after ':'
+ c = reader.read();
+
+ map.put(key, readValue(reader));
+ skipWhitespace(reader);
+
+ if (c == ',') {
+ c = reader.read();
+ skipWhitespace(reader);
+ } else if (c == -1) {
+ throw new SerializationException("Unexpected end of input stream.");
+ } else {
+ if (c != '}') {
+ throw new SerializationException("Unexpected character in input stream.");
+ }
+ }
+ }
+
+ // Move to the first character after '}'
+ c = reader.read();
+
+ return map;
+ }
+
+ /**
+ * Writes data to a JSON stream.
+ *
+ * @param object
+ *
+ * @param outputStream
+ * The output stream to which data will be written.
+ *
+ * @see #writeObject(Object, Writer)
+ */
+ public void writeObject(Object object, OutputStream outputStream)
+ throws IOException, SerializationException {
+ Writer writer = new BufferedWriter(new OutputStreamWriter(outputStream, charset),
+ BUFFER_SIZE);
+ writeObject(object, writer);
+ }
+
+ /**
+ * Writes data to a JSON stream.
+ *
+ * @param object
+ * The object to serialize. Must be one of the following types:
+ *
+ * <ul>
+ * <li>java.lang.String</li>
+ * <li>java.lang.Number</li>
+ * <li>java.lang.Boolean</li>
+ * <li>pivot.collections.List</li>
+ * <li>pivot.collections.Map</li>
+ * </ul>
+ *
+ * @param writer
+ * The writer to which data will be written.
+ */
+ @SuppressWarnings("unchecked")
+ public void writeObject(Object object, Writer writer)
+ throws IOException, SerializationException {
+ if (writer == null) {
+ throw new IllegalArgumentException("writer is null.");
+ }
+
+ if (object == null) {
+ writer.append("null");
+ } else if (object instanceof String) {
+ String string = (String)object;
+ StringBuilder stringBuilder = new StringBuilder();
+
+ for (int i = 0, n = string.length(); i < n; i++) {
+ char c = string.charAt(i);
+
+ switch(c) {
+ case '\t': {
+ stringBuilder.append("\\t");
+ break;
+ }
+
+ case '\n': {
+ stringBuilder.append("\\n");
+ break;
+ }
+
+ case '\\':
+ case '\"':
+ case '\'': {
+ stringBuilder.append("\\" + c);
+ break;
+ }
+
+ default: {
+ if (charset.name().startsWith("UTF")
+ || c <= 0xFF) {
+ stringBuilder.append(c);
+ } else {
+ stringBuilder.append("\\u");
+ stringBuilder.append(String.format("%04x", (short)c));
+ }
+ }
+ }
+
+ }
+
+ writer.append("\"" + stringBuilder.toString() + "\"");
+ } else if (object instanceof Number) {
+ writer.append(object.toString());
+ } else if (object instanceof Boolean) {
+ writer.append(object.toString());
+ } else if (object instanceof List<?>) {
+ List<Object> list = (List<Object>)object;
+ writer.append("[");
+
+ int i = 0;
+ for (Object item : list) {
+ if (i > 0) {
+ writer.append(", ");
+ }
+
+ writeObject(item, writer);
+ i++;
+ }
+
+ writer.append("]");
+ } else if (object instanceof Map<?, ?>) {
+ Map<String, Object> map = (Map<String, Object>)object;
+ writer.append("{");
+
+ int i = 0;
+ for (String key : map) {
+ Object value = map.get(key);
+
+ boolean identifier = true;
+ StringBuilder keyStringBuilder = new StringBuilder();
+
+ for (int j = 0, n = key.length(); j < n; j++) {
+ char c = key.charAt(j);
+ identifier &= Character.isJavaIdentifierPart(c);
+
+ if (c == '"') {
+ keyStringBuilder.append('\\');
+ }
+
+ keyStringBuilder.append(c);
+ }
+
+ key = keyStringBuilder.toString();
+
+ if (i > 0) {
+ writer.append(", ");
+ }
+
+ // Write the key
+ if (!identifier || alwaysDelimitMapKeys) {
+ writer.append('"');
+ }
+
+ writer.append(key);
+
+ if (!identifier || alwaysDelimitMapKeys) {
+ writer.append('"');
+ }
+
+ writer.append(": ");
+
+ // Write the value
+ writeObject(value, writer);
+
+ i++;
+ }
+
+ writer.append("}");
+ } else {
+ throw new IllegalArgumentException(object.getClass()
+ + " is not a supported type.");
+ }
+
+ writer.flush();
+ }
+
+ public String getMIMEType(Object object) {
+ return MIME_TYPE + "; charset=" + charset.name();
+ }
+
+ /**
+ * Returns a flag indicating whether or not map keys will always be
+ * quote-delimited.
+ */
+ public boolean getAlwaysDelimitMapKeys() {
+ return alwaysDelimitMapKeys;
+ }
+
+ /**
+ * Sets a flag indicating that map keys should always be quote-delimited.
+ *
+ * @param alwaysDelimitMapKeys
+ * <tt>true</tt> to bound map keys in double quotes; <tt>false</tt> to
+ * only quote-delimit keys as necessary.
+ */
+ public void setAlwaysDelimitMapKeys(boolean alwaysDelimitMapKeys) {
+ this.alwaysDelimitMapKeys = alwaysDelimitMapKeys;
+ }
+
+ /**
+ * Returns the value at the given path.
+ *
+ * @param root
+ * The root object; must be an instance of {@link pivot.collections.Map}
+ * or {@link pivot.collections.List}.
+ *
+ * @param path
+ * The path to the value, in JavaScript path notation.
+ *
+ * @return
+ * The value at the given path.
+ */
+ @SuppressWarnings("unchecked")
+ public static Object getValue(Object root, String path) {
+ Object value = root;
+
+ int i = 0;
+ int n = path.length();
+
+ while (i < n) {
+ char c = path.charAt(i++);
+
+ boolean keyed = true;
+ StringBuilder identifierBuilder = new StringBuilder();
+
+ boolean bracketed = (c == '[');
+ if (bracketed
+ && i < n) {
+ c = path.charAt(i++);
+
+ char quote = Character.UNASSIGNED;
+
+ boolean quoted = (c == '"'
+ || c == '\'');
+ if (quoted
+ && i < n) {
+ quote = c;
+ c = path.charAt(i++);
+ }
+
+ keyed = quoted;
+
+ while (i <= n
+ && bracketed) {
+ bracketed = quoted || (c != ']');
+
+ if (bracketed) {
+ if (c == quote) {
+ if (i < n) {
+ c = path.charAt(i++);
+ quoted = (c == quote);
+ }
+ }
+
+ if (quoted || c != ']') {
+ if (Character.isISOControl(c)) {
+ throw new IllegalArgumentException("Illegal identifier character.");
+ }
+
+ identifierBuilder.append(c);
+
+ if (i < n) {
+ c = path.charAt(i++);
+ }
+ }
+ }
+ }
+
+ if (quoted) {
+ throw new IllegalArgumentException("Unterminated quoted identifier.");
+ }
+
+ if (bracketed) {
+ throw new IllegalArgumentException("Unterminated bracketed identifier.");
+ }
+
+ if (i < n) {
+ c = path.charAt(i);
+
+ if (c == '.') {
+ i++;
+ }
+ }
+ } else {
+ keyed = true;
+
+ while(i <= n
+ && c != '.'
+ && c != '[') {
+ if (!Character.isJavaIdentifierPart(c)) {
+ throw new IllegalArgumentException("Illegal identifier character.");
+ }
+
+ identifierBuilder.append(c);
+
+ if (i < n) {
+ c = path.charAt(i);
+ }
+
+ i++;
+ }
+
+ if (c == '[') {
+ i--;
+ }
+ }
+
+ if (c == '.'
+ && i == n) {
+ throw new IllegalArgumentException("Path cannot end with a '.' character.");
+ }
+
+ if (identifierBuilder.length() == 0) {
+ throw new IllegalArgumentException("Missing identifier.");
+ }
+
+ String identifier = identifierBuilder.toString();
+
+ if (keyed) {
+ if (!(value instanceof Dictionary<?, ?>)){
+ throw new IllegalArgumentException("Invalid path.");
+ }
+
+ String key = identifier;
+ Dictionary<String, Object> dictionary = (Dictionary<String, Object>)value;
+ value = dictionary.get(key);
+ } else {
+ if (!(value instanceof Sequence<?>)){
+ throw new IllegalArgumentException("Invalid path.");
+ }
+
+ int index = Integer.parseInt(identifier);
+ Sequence<Object> sequence = (Sequence<Object>)value;
+ value = sequence.get(index);
+ }
+ }
+
+ return value;
+ }
+
+ /**
+ * Returns the value at the given path as a string.
+ *
+ * @param root
+ * @param path
+ *
+ * @see #getValue(Object, String)
+ */
+ public static String getString(Object root, String path) {
+ return (String)getValue(root, path);
+ }
+
+ /**
+ * Returns the value at the given path as a number.
+ *
+ * @param root
+ * @param path
+ *
+ * @see #getValue(Object, String)
+ */
+ public static Number getNumber(Object root, String path) {
+ return (Number)getValue(root, path);
+ }
+
+ /**
+ * Returns the value at the given path as a short.
+ *
+ * @param root
+ * @param path
+ *
+ * @see #getValue(Object, String)
+ */
+ public static Short getShort(Object root, String path) {
+ return (Short)getValue(root, path);
+ }
+
+ /**
+ * Returns the value at the given path as an integer.
+ *
+ * @param root
+ * @param path
+ *
+ * @see #getValue(Object, String)
+ */
+ public static Integer getInteger(Object root, String path) {
+ return (Integer)getValue(root, path);
+ }
+
+ /**
+ * Returns the value at the given path as a long.
+ *
+ * @param root
+ * @param path
+ *
+ * @see #getValue(Object, String)
+ */
+ public static Long getLong(Object root, String path) {
+ return (Long)getValue(root, path);
+ }
+
+ /**
+ * Returns the value at the given path as a float.
+ *
+ * @param root
+ * @param path
+ *
+ * @see #getValue(Object, String)
+ */
+ public static Float getFloat(Object root, String path) {
+ return (Float)getValue(root, path);
+ }
+
+ /**
+ * Returns the value at the given path as a double.
+ *
+ * @param root
+ * @param path
+ *
+ * @see #getValue(Object, String)
+ */
+ public static Double getDouble(Object root, String path) {
+ return (Double)getValue(root, path);
+ }
+
+ /**
+ * Returns the value at the given path as a boolean.
+ *
+ * @param root
+ * @param path
+ *
+ * @see #getValue(Object, String)
+ */
+ public static Boolean getBoolean(Object root, String path) {
+ return (Boolean)getValue(root, path);
+ }
+
+ /**
+ * Returns the value at the given path as a list.
+ *
+ * @param root
+ * @param path
+ *
+ * @see #getValue(Object, String)
+ */
+ @SuppressWarnings("unchecked")
+ public static List<?> getList(Object root, String path) {
+ return (List<?>)getValue(root, path);
+ }
+
+ /**
+ * Returns the value at the given path as a map.
+ *
+ * @param root
+ * @param path
+ *
+ * @see #getValue(Object, String)
+ */
+ @SuppressWarnings("unchecked")
+ public static Map<String, ?> getMap(Object root, String path) {
+ return (Map<String, ?>)getValue(root, path);
+ }
+
+ /**
+ * Parses a JSON-formatted array value into a list.
+ *
+ * @param string
+ * A string containing a JSON array (e.g. "[1, 2, 3]").
+ *
+ * @return
+ * A {@link List} instance containing the parsed JSON data.
+ */
+ @SuppressWarnings("unchecked")
+ public static List<?> parseList(String string) {
+ List<?> list = null;
+ JSONSerializer jsonSerializer = new JSONSerializer();
+
+ Object object = null;
+ try {
+ object = jsonSerializer.readObject(new StringReader(string));
+ } catch(Exception exception) {
+ throw new RuntimeException(exception);
+ }
+
+ list = (List<?>)object;
+
+ return list;
+ }
+
+ /**
+ * Parses a JSON-formatted object value into a map.
+ *
+ * @param string
+ * A string containing a JSON object (e.g. "{a:1, b:2, c:3}").
+ *
+ * @return
+ * A {@link Map} instance containing the parsed JSON data.
+ */
+ @SuppressWarnings("unchecked")
+ public static Map<String, ?> parseMap(String string) {
+ Map<String, ?> map = null;
+ JSONSerializer jsonSerializer = new JSONSerializer();
+
+ Object object = null;
+ try {
+ object = jsonSerializer.readObject(new StringReader(string));
+ } catch(Exception exception) {
+ throw new RuntimeException(exception);
+ }
+
+ map = (Map<String, ?>)object;
+
+ return map;
+ }
+}
Added: incubator/pivot/branches/1.1/core/src/pivot/serialization/PropertiesSerializer.java
URL: http://svn.apache.org/viewvc/incubator/pivot/branches/1.1/core/src/pivot/serialization/PropertiesSerializer.java?rev=758461&view=auto
==============================================================================
--- incubator/pivot/branches/1.1/core/src/pivot/serialization/PropertiesSerializer.java (added)
+++ incubator/pivot/branches/1.1/core/src/pivot/serialization/PropertiesSerializer.java Wed Mar 25 23:08:38 2009
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2008 VMware, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package pivot.serialization;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Properties;
+
+import pivot.collections.Map;
+import pivot.collections.adapter.MapAdapter;
+
+/**
+ * Implementation of the {@link Serializer} interface that reads data from
+ * and writes data to the Java properties file format.
+ *
+ * @author smartini
+ * @author gbrown
+ */
+public class PropertiesSerializer implements Serializer<Map<?, ?>> {
+ public static final String MIME_TYPE = "text/plain";
+
+ /**
+ * Reads data from a properties stream.
+ *
+ * @param inputStream
+ * The input stream from which data will be read.
+ *
+ * @return
+ * An instance of {@link Map} containing the data read from the properties
+ * file. Both keys and values are strings.
+ */
+ public Map<?, ?> readObject(InputStream inputStream) throws IOException,
+ SerializationException {
+ if (inputStream == null) {
+ throw new IllegalArgumentException("inputStream is null.");
+ }
+
+ Properties properties = new Properties();
+ properties.load(inputStream);
+
+ return new MapAdapter<Object, Object>(properties);
+ }
+
+ /**
+ * Writes data to a properties stream.
+ *
+ * @param object
+ * An instance of {@link Map} containing the data to be written to the
+ * properties file. Keys must be strings, and values will be converted to
+ * strings.
+ *
+ * @param outputStream
+ * The output stream to which data will be written.
+ */
+ @SuppressWarnings("unchecked")
+ public void writeObject(Map<?, ?> object, OutputStream outputStream) throws IOException,
+ SerializationException {
+ if (outputStream == null) {
+ throw new IllegalArgumentException("outputStream is null.");
+ }
+
+ Map<Object, Object> map = (Map<Object, Object>)object;
+
+ Properties properties = new Properties();
+
+ for (Object key : map) {
+ Object value = map.get(key);
+ if (value != null) {
+ value = value.toString();
+ }
+
+ properties.put(key, value);
+ }
+
+ properties.store(outputStream, null);
+ }
+
+ public String getMIMEType(Map<?, ?> object) {
+ return MIME_TYPE;
+ }
+}
Added: incubator/pivot/branches/1.1/core/src/pivot/serialization/SerializationException.java
URL: http://svn.apache.org/viewvc/incubator/pivot/branches/1.1/core/src/pivot/serialization/SerializationException.java?rev=758461&view=auto
==============================================================================
--- incubator/pivot/branches/1.1/core/src/pivot/serialization/SerializationException.java (added)
+++ incubator/pivot/branches/1.1/core/src/pivot/serialization/SerializationException.java Wed Mar 25 23:08:38 2009
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2008 VMware, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package pivot.serialization;
+
+/**
+ * Thrown when an error is encountered during serialization.
+ *
+ * @author gbrown
+ */
+public class SerializationException extends Exception {
+ private static final long serialVersionUID = 0;
+
+ public SerializationException() {
+ this(null, null);
+ }
+
+ public SerializationException(String message) {
+ this(message, null);
+ }
+
+ public SerializationException(Throwable cause) {
+ this(null, cause);
+ }
+
+ public SerializationException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
Added: incubator/pivot/branches/1.1/core/src/pivot/serialization/Serializer.java
URL: http://svn.apache.org/viewvc/incubator/pivot/branches/1.1/core/src/pivot/serialization/Serializer.java?rev=758461&view=auto
==============================================================================
--- incubator/pivot/branches/1.1/core/src/pivot/serialization/Serializer.java (added)
+++ incubator/pivot/branches/1.1/core/src/pivot/serialization/Serializer.java Wed Mar 25 23:08:38 2009
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2008 VMware, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package pivot.serialization;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.IOException;
+
+/**
+ * Defines an interface for writing objects to and reading objects from a data
+ * stream.
+ *
+ * @param <T>
+ * The type of data being read and written.
+ *
+ * @author gbrown
+ * @author tvolkert
+ */
+public interface Serializer<T> {
+ /**
+ * Reads an object from an input stream.
+ *
+ * @param inputStream
+ * The data stream from which the object will be read.
+ *
+ * @return
+ * The deserialized object.
+ */
+ public T readObject(InputStream inputStream) throws IOException, SerializationException;
+
+ /**
+ * Writes an object to an output stream.
+ *
+ * @param object
+ * The object to serialize.
+ *
+ * @param outputStream
+ * The data stream to which the object will be written.
+ */
+ public void writeObject(T object, OutputStream outputStream) throws IOException, SerializationException;
+
+ /**
+ * Returns the MIME type of the data read and written by this serializer.
+ *
+ * @param object
+ * If provided, allows the serializer to attach parameters to the returned
+ * MIME type containing more detailed information about the data. If
+ * <tt>null</tt>, the base MIME type is returned.
+ */
+ public String getMIMEType(T object);
+}
Added: incubator/pivot/branches/1.1/core/src/pivot/serialization/package.html
URL: http://svn.apache.org/viewvc/incubator/pivot/branches/1.1/core/src/pivot/serialization/package.html?rev=758461&view=auto
==============================================================================
--- incubator/pivot/branches/1.1/core/src/pivot/serialization/package.html (added)
+++ incubator/pivot/branches/1.1/core/src/pivot/serialization/package.html Wed Mar 25 23:08:38 2009
@@ -0,0 +1,6 @@
+<html>
+<head></head>
+<body>
+<p>Contains a set of classes for use in data serialization.</p>
+</body>
+</html>
Added: incubator/pivot/branches/1.1/core/src/pivot/util/Base64.java
URL: http://svn.apache.org/viewvc/incubator/pivot/branches/1.1/core/src/pivot/util/Base64.java?rev=758461&view=auto
==============================================================================
--- incubator/pivot/branches/1.1/core/src/pivot/util/Base64.java (added)
+++ incubator/pivot/branches/1.1/core/src/pivot/util/Base64.java Wed Mar 25 23:08:38 2009
@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) 2008 VMware, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package pivot.util;
+
+/**
+ * Implements the "base64" binary encoding scheme as defined by
+ * <a href="http://tools.ietf.org/html/rfc2045">RFC 2045</a>.
+ *
+ * @author tvolkert
+ */
+public final class Base64 {
+ private static final char[] lookup = new char[64];
+ private static final byte[] reverseLookup = new byte[256];
+
+ static {
+ // Populate the lookup array
+
+ for (int i = 0; i < 26; i++) {
+ lookup[i] = (char)('A' + i);
+ }
+
+ for (int i = 26, j = 0; i < 52; i++, j++) {
+ lookup[i] = (char)('a' + j);
+ }
+
+ for (int i = 52, j = 0; i < 62; i++, j++) {
+ lookup[i] = (char)('0' + j);
+ }
+
+ lookup[62] = '+';
+ lookup[63] = '/';
+
+ // Populate the reverse lookup array
+
+ for (int i = 0; i < 256; i++) {
+ reverseLookup[i] = -1;
+ }
+
+ for (int i = 'Z'; i >= 'A'; i--) {
+ reverseLookup[i] = (byte)(i - 'A');
+ }
+
+ for (int i = 'z'; i >= 'a'; i--) {
+ reverseLookup[i] = (byte)(i - 'a' + 26);
+ }
+
+ for (int i = '9'; i >= '0'; i--) {
+ reverseLookup[i] = (byte)(i - '0' + 52);
+ }
+
+ reverseLookup['+'] = 62;
+ reverseLookup['/'] = 63;
+ reverseLookup['='] = 0;
+ }
+
+ /**
+ * This class is not instantiable.
+ */
+ private Base64() {
+ }
+
+ /**
+ * Encodes the specified data into a base64 string.
+ *
+ * @param bytes
+ * The unencoded raw data.
+ */
+ public static String encode(byte[] bytes) {
+ StringBuilder buf = new StringBuilder(4 * (bytes.length / 3 + 1));
+
+ for (int i = 0, n = bytes.length; i < n; ) {
+ byte byte0 = bytes[i++];
+ byte byte1 = (i++ < n) ? bytes[i - 1] : 0;
+ byte byte2 = (i++ < n) ? bytes[i - 1] : 0;
+
+ buf.append(lookup[byte0 >> 2]);
+ buf.append(lookup[((byte0 << 4) | byte1 >> 4) & 63]);
+ buf.append(lookup[((byte1 << 2) | byte2 >> 6) & 63]);
+ buf.append(lookup[byte2 & 63]);
+
+ if (i > n) {
+ for (int m = buf.length(), j = m - (i - n); j < m; j++) {
+ buf.setCharAt(j, '=');
+ }
+ }
+ }
+
+ return buf.toString();
+ }
+
+ /**
+ * Decodes the specified base64 string back into its raw data.
+ *
+ * @param encoded
+ * The base64 encoded string.
+ */
+ public static byte[] decode(String encoded) {
+ int padding = 0;
+
+ for (int i = encoded.length() - 1; encoded.charAt(i) == '='; i--) {
+ padding++;
+ }
+
+ int length = encoded.length() * 6 / 8 - padding;
+ byte[] bytes = new byte[length];
+
+ for (int i = 0, index = 0, n = encoded.length(); i < n; i += 4) {
+ int word = reverseLookup[encoded.charAt(i)] << 18;
+ word += reverseLookup[encoded.charAt(i + 1)] << 12;
+ word += reverseLookup[encoded.charAt(i + 2)] << 6;
+ word += reverseLookup[encoded.charAt(i + 3)];
+
+ for (int j = 0; j < 3 && index + j < length; j++) {
+ bytes[index + j] = (byte)(word >> (8 * (2 - j)));
+ }
+
+ index += 3;
+ }
+
+ return bytes;
+ }
+}
Added: incubator/pivot/branches/1.1/core/src/pivot/util/CalendarDate.java
URL: http://svn.apache.org/viewvc/incubator/pivot/branches/1.1/core/src/pivot/util/CalendarDate.java?rev=758461&view=auto
==============================================================================
--- incubator/pivot/branches/1.1/core/src/pivot/util/CalendarDate.java (added)
+++ incubator/pivot/branches/1.1/core/src/pivot/util/CalendarDate.java Wed Mar 25 23:08:38 2009
@@ -0,0 +1,337 @@
+/*
+ * Copyright (c) 2008 VMware, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package pivot.util;
+
+import java.io.Serializable;
+import java.text.NumberFormat;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * <tt>CalendarDate</tt> allows a specific day to be identified within the
+ * gregorian calendar system. This identification has no association with any
+ * particular time zone and no notion of the time of day.
+ *
+ * @author tvolkert
+ */
+public class CalendarDate implements Comparable<CalendarDate>, Serializable {
+ private static final long serialVersionUID = 3974393986540543704L;
+
+ private int year;
+ private int month;
+ private int day;
+
+ private static final int[] MONTH_LENGTHS = {
+ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
+ };
+
+ private static final int GREGORIAN_CUTOVER_YEAR = 1582;
+
+ /**
+ * Creates a new <tt>CalendarDate</tt> representing the current day in the
+ * default timezone and the default locale.
+ */
+ public CalendarDate() {
+ this(new GregorianCalendar());
+ }
+
+ /**
+ * Creates a new <tt>CalendarDate</tt> representing the day contained in
+ * the specified gregorian calendar (assuming the default locale and the
+ * default timezone).
+ *
+ * @param calendar
+ * The calendar containing the year, month, and day fields.
+ */
+ public CalendarDate(GregorianCalendar calendar) {
+ this(calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH),
+ calendar.get(Calendar.DAY_OF_MONTH) - 1);
+ }
+
+ /**
+ * Creates a new <tt>CalendarDate</tt> representing the specified year,
+ * month, and day of month.
+ *
+ * @param year
+ * The year field. (e.g. <tt>2008</tt>)
+ *
+ * @param month
+ * The month field, 0-based. (e.g. <tt>2</tt> for March)
+ *
+ * @param day
+ * The day of the month, 0-based. (e.g. <tt>14</tt> for the 15th)
+ */
+ public CalendarDate(int year, int month, int day) {
+ set(year, month, day);
+ }
+
+ /**
+ * Creates a new date representing the specified date string. The date
+ * string must be in the <tt>ISO 8601</tt> "calendar date" format,
+ * which is <tt>[YYYY]-[MM]-[DD]</tt>.
+ *
+ * @param date
+ * A string in the form of <tt>[YYYY]-[MM]-[DD]</tt>. (e.g. 2008-07-23)
+ */
+ public CalendarDate(String date) {
+ Pattern pattern = Pattern.compile("^(\\d{4})-(\\d{2})-(\\d{2})$");
+ Matcher matcher = pattern.matcher(date);
+
+ if (!matcher.matches()) {
+ throw new IllegalArgumentException("Invalid date format: " + date);
+ }
+
+ String year = matcher.group(1);
+ String month = matcher.group(2);
+ String day = matcher.group(3);
+
+ set(Integer.parseInt(year), Integer.parseInt(month) - 1,
+ Integer.parseInt(day) - 1);
+ }
+
+ /**
+ * Sets the date.
+ *
+ * @param year
+ * @param month
+ * @param day
+ */
+ public void set(int year, int month, int day) {
+ if (year <= GREGORIAN_CUTOVER_YEAR || year > 9999) {
+ throw new IllegalArgumentException("Invalid year: " + year);
+ }
+
+ if (month < 0 || month > 11) {
+ throw new IllegalArgumentException("Invalid month: " + month);
+ }
+
+ int daysInMonth = MONTH_LENGTHS[month];
+
+ boolean isLeapYear = ((year & 3) == 0 && (year % 100 != 0 || year % 400 == 0));
+ if (isLeapYear && month == 1) {
+ daysInMonth++;
+ }
+
+ if (day < 0 || day >= daysInMonth) {
+ throw new IllegalArgumentException("Invalid day: " + day);
+ }
+
+ this.year = year;
+ this.month = month;
+ this.day = day;
+ }
+
+ /**
+ * Gets the year field. (e.g. <tt>2008</tt>).
+ *
+ * @return
+ * This calendar date's <tt>year</tt> field
+ */
+ public int getYear() {
+ return year;
+ }
+
+ /**
+ * Sets the year field.
+ *
+ * @param year
+ */
+ public void setYear(int year) {
+ set(year, month, day);
+ }
+
+ /**
+ * Gets the month field, 0-based. (e.g. <tt>2</tt> for March).
+ *
+ * @return
+ * This calendar date's <tt>month</tt> field
+ */
+ public int getMonth() {
+ return month;
+ }
+
+ /**
+ * Sets the month field.
+ *
+ * @param month
+ */
+ public void setMonth(int month) {
+ set(year, month, day);
+ }
+
+ /**
+ * Gets the day of the month, 0-based. (e.g. <tt>14</tt> for the 15th).
+ *
+ * @return
+ * This calendar date's <tt>day</tt> field
+ */
+ public int getDay() {
+ return day;
+ }
+
+ /**
+ * Sets the day field.
+ *
+ * @param day
+ */
+ public void setDay(int day) {
+ set(year, month, day);
+ }
+
+ /**
+ * Compares this calendar date with another calendar date.
+ *
+ * @param calendarDate
+ * The calendar date against which to compare
+ *
+ * @return
+ * A negative number, zero, or a positive number if the specified calendar
+ * date is less than, equal to, or greater than this calendar date,
+ * respectively.
+ */
+ public int compareTo(CalendarDate calendarDate) {
+ int result = year - calendarDate.year;
+
+ if (result == 0) {
+ result = month - calendarDate.month;
+
+ if (result == 0) {
+ result = day - calendarDate.day;
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Adds the specified number of days to this calendar date and returns the
+ * resulting calendar date. The number of days may be negative, in which
+ * case the result will be a date before this calendar date.
+ * <p>
+ * More formally, it is defined that given calendar dates <tt>c1</tt> and
+ * <tt>c2</tt>, the following will return <tt>true</tt>:
+ * <pre>
+ * c1.add(c2.subtract(c1)).equals(c2);
+ * </pre>
+ *
+ * @param days
+ * The number of days to add to (or subtract from if negative) this
+ * calendar date
+ *
+ * @return
+ * The resulting calendar date
+ */
+ public CalendarDate add(int days) {
+ GregorianCalendar calendar = toCalendar();
+ calendar.add(Calendar.DAY_OF_YEAR, days);
+ return new CalendarDate(calendar);
+ }
+
+ /**
+ * Gets the number of days in between this calendar date and the specified
+ * calendar date. If this calendar date represents a day after the
+ * specified calendar date, the difference will be positive. If this
+ * calendardate represents a day before the specified calendar date, the
+ * difference will be negative. If the two calendar dates represent the
+ * same day, the difference will be zero.
+ * <p>
+ * More formally, it is defined that given calendar dates <tt>c1</tt> and
+ * <tt>c2</tt>, the following will return <tt>true</tt>:
+ * <pre>
+ * c1.add(c2.subtract(c1)).equals(c2);
+ * </pre>
+ *
+ * @param calendarDate
+ * The calendar date to subtract from this calendar date
+ *
+ * @return
+ * The number of days in between this calendar date and
+ * <tt>calendarDate</tt>
+ */
+ public int subtract(CalendarDate calendarDate) {
+ GregorianCalendar c1 = toCalendar();
+ GregorianCalendar c2 = calendarDate.toCalendar();
+
+ long t1 = c1.getTimeInMillis();
+ long t2 = c2.getTimeInMillis();
+
+ return (int)((t1 - t2) / (1000l * 60 * 60 * 24));
+ }
+
+ /**
+ * Translates this calendar date to an instance of
+ * <tt>GregorianCalendar</tt>, with the <tt>year</tt>, <tt>month</tt>, and
+ * <tt>dayOfMonth</tt> fields set in the default time zone with the default
+ * locale.
+ *
+ * @return
+ * This calendar date as a <tt>GregorianCalendar</tt>
+ */
+ public GregorianCalendar toCalendar() {
+ return new GregorianCalendar(year, month, day + 1);
+ }
+
+ /**
+ * Indicates whether some other object is "equal to" this one.
+ * This is the case if the object is a calendar date that represents the
+ * same day as this one.
+ *
+ * @param o
+ * Reference to the object against which to compare
+ */
+ @Override
+ public boolean equals(Object o) {
+ return (o instanceof CalendarDate
+ && ((CalendarDate)o).year == year
+ && ((CalendarDate)o).month == month
+ && ((CalendarDate)o).day == day);
+ }
+
+ /**
+ * Returns a hash code value for the object.
+ */
+ @Override
+ public int hashCode() {
+ Integer hashKey = year + month + day;
+ return hashKey.hashCode();
+ }
+
+ /**
+ * Returns a string representation of this calendar date in the <tt>ISO
+ * 8601</tt> "calendar date" format, which is <tt>[YYYY]-[MM]-[DD]</tt>.
+ */
+ @Override
+ public String toString() {
+ StringBuilder buf = new StringBuilder();
+
+ NumberFormat format = NumberFormat.getIntegerInstance();
+ format.setGroupingUsed(false);
+ format.setMinimumIntegerDigits(4);
+
+ buf.append(format.format(year));
+ buf.append("-");
+
+ format.setMinimumIntegerDigits(2);
+
+ buf.append(format.format(month + 1));
+ buf.append("-");
+ buf.append(format.format(day + 1));
+
+ return buf.toString();
+ }
+}
Added: incubator/pivot/branches/1.1/core/src/pivot/util/ImmutableIterator.java
URL: http://svn.apache.org/viewvc/incubator/pivot/branches/1.1/core/src/pivot/util/ImmutableIterator.java?rev=758461&view=auto
==============================================================================
--- incubator/pivot/branches/1.1/core/src/pivot/util/ImmutableIterator.java (added)
+++ incubator/pivot/branches/1.1/core/src/pivot/util/ImmutableIterator.java Wed Mar 25 23:08:38 2009
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2008 VMware, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package pivot.util;
+
+import java.util.Iterator;
+
+/**
+ * Immutable implementation of the {@link Iterator} interface.
+ *
+ * @author gbrown
+ */
+public class ImmutableIterator<T> implements Iterator<T> {
+ Iterator<T> iterator;
+
+ public ImmutableIterator(Iterator<T> iterator) {
+ if (iterator == null) {
+ throw new IllegalArgumentException("iterator is null.");
+ }
+
+ this.iterator = iterator;
+ }
+
+ public boolean hasNext() {
+ return iterator.hasNext();
+ }
+
+ public T next() {
+ return iterator.next();
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+}
Added: incubator/pivot/branches/1.1/core/src/pivot/util/ListenerList.java
URL: http://svn.apache.org/viewvc/incubator/pivot/branches/1.1/core/src/pivot/util/ListenerList.java?rev=758461&view=auto
==============================================================================
--- incubator/pivot/branches/1.1/core/src/pivot/util/ListenerList.java (added)
+++ incubator/pivot/branches/1.1/core/src/pivot/util/ListenerList.java Wed Mar 25 23:08:38 2009
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2008 VMware, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package pivot.util;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ * Abstract base class for listener lists.
+ * <p>
+ * NOTE This class is not thread safe. For thread-safe management of events,
+ * use {@link pivot.util.concurrent.SynchronizedListenerList}.
+ *
+ * @author gbrown
+ */
+public abstract class ListenerList<T> implements Iterable<T> {
+ /**
+ * Represents a node in the linked list of event listeners.
+ *
+ * @author gbrown
+ */
+ private class Node {
+ private Node previous;
+ private Node next;
+ private T listener;
+
+ public Node(Node previous, Node next, T listener) {
+ this.previous = previous;
+ this.next = next;
+ this.listener = listener;
+ }
+ }
+
+ /**
+ * Listener list iterator.
+ *
+ * @author gbrown
+ */
+ private class NodeIterator implements Iterator<T> {
+ private Node node;
+
+ public NodeIterator(Node node) {
+ this.node = node;
+ }
+
+ public boolean hasNext() {
+ return (node != null);
+ }
+
+ public T next() {
+ if (node == null) {
+ throw new NoSuchElementException();
+ }
+
+ T listener = node.listener;
+ node = node.next;
+
+ return listener;
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ /**
+ * The first node in the list, or <tt>null</tt> if the list is empty.
+ */
+ private Node first = null;
+
+ /**
+ * Adds a listener to the list, if it has not previously been added.
+ *
+ * @param listener
+ */
+ public void add(T listener) {
+ if (listener == null) {
+ throw new IllegalArgumentException("listener is null.");
+ }
+
+ Node node = first;
+
+ if (node == null) {
+ first = new Node(null, null, listener);
+ } else {
+ while (node.next != null
+ && node.listener != listener) {
+ node = node.next;
+ }
+
+ if (node.next == null
+ && node.listener != listener) {
+ node.next = new Node(node, null, listener);
+ } else {
+ System.err.println("Duplicate listener " + listener + " added to " + this);
+ }
+ }
+ }
+
+ /**
+ * Removes a listener from the list, if it has previously been added.
+ *
+ * @param listener
+ */
+ public void remove(T listener) {
+ if (listener == null) {
+ throw new IllegalArgumentException("listener is null.");
+ }
+
+ Node node = first;
+ while (node != null
+ && node.listener != listener) {
+ node = node.next;
+ }
+
+ if (node == null) {
+ System.err.println("Nonexistent listener " + listener + " removed from " + this);
+ } else {
+ if (node.previous == null) {
+ first = node.next;
+
+ if (first != null) {
+ first.previous = null;
+ }
+ } else {
+ node.previous.next = node.next;
+
+ if (node.next != null) {
+ node.next.previous = node.previous;
+ }
+ }
+ }
+ }
+
+ public Iterator<T> iterator() {
+ return new NodeIterator(first);
+ }
+}
Added: incubator/pivot/branches/1.1/core/src/pivot/util/MIMEType.java
URL: http://svn.apache.org/viewvc/incubator/pivot/branches/1.1/core/src/pivot/util/MIMEType.java?rev=758461&view=auto
==============================================================================
--- incubator/pivot/branches/1.1/core/src/pivot/util/MIMEType.java (added)
+++ incubator/pivot/branches/1.1/core/src/pivot/util/MIMEType.java Wed Mar 25 23:08:38 2009
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) 2008 VMware, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package pivot.util;
+
+import java.util.Iterator;
+
+import pivot.collections.Dictionary;
+import pivot.collections.HashMap;
+
+/**
+ * Utility class for introspecting a MIME type string.
+ *
+ * @author gbrown
+ */
+public class MIMEType implements Dictionary<String, String>, Iterable<String> {
+ private String baseType;
+ private HashMap<String, String> parameters = new HashMap<String, String>();
+
+ public MIMEType(String baseType) {
+ this.baseType = baseType;
+ }
+
+ /**
+ * Returns the base type of this MIME type (i.e. the type string minus
+ * parameter values).
+ */
+ public String getBaseType() {
+ return baseType;
+ }
+
+ public String get(String key) {
+ return parameters.get(key);
+ }
+
+ public String put(String key, String value) {
+ return parameters.put(key, value);
+ }
+
+ public String remove(String key) {
+ return parameters.remove(key);
+ }
+
+ public boolean containsKey(String key) {
+ return parameters.containsKey(key);
+ }
+
+ public boolean isEmpty() {
+ return parameters.isEmpty();
+ }
+
+ public Iterator<String> iterator() {
+ return new ImmutableIterator<String>(parameters.iterator());
+ }
+
+ public String toString() {
+ StringBuilder stringBuilder = new StringBuilder();
+
+ stringBuilder.append(baseType);
+ if (!parameters.isEmpty()) {
+ for (String parameter : parameters) {
+ stringBuilder.append("; " + parameter + "=" + parameters.get(parameter));
+ }
+ }
+
+ return stringBuilder.toString();
+ }
+
+ public static MIMEType decode(String value) {
+ if (value == null) {
+ throw new IllegalArgumentException("value is null.");
+ }
+
+ MIMEType mimeType;
+
+ int i = value.indexOf(";");
+ if (i == -1) {
+ mimeType = new MIMEType(value);
+ } else {
+ mimeType = new MIMEType(value.substring(0, i));
+
+ int n = value.length();
+ do {
+ // Get the index of the assignment delimiter for
+ // this parameter
+ int j = value.indexOf("=", i);
+ if (j == -1) {
+ throw new IllegalArgumentException("Parameter list is invalid.");
+ }
+
+ String parameterKey = value.substring(i + 1, j).trim();
+ if (parameterKey.length() == 0) {
+ throw new IllegalArgumentException("Missing parameter name.");
+ }
+
+ // Get the index of the next parameter delimiter
+ i = value.indexOf(";", j);
+ if (i == -1) {
+ i = n;
+ }
+
+ String parameterValue = value.substring(j + 1, i).trim();
+ if (parameterValue.length() == 0) {
+ throw new IllegalArgumentException("Missing parameter value.");
+ }
+
+ // Add the parameter to the dictionary
+ mimeType.put(parameterKey, parameterValue);
+ } while (i < n);
+ }
+
+ return mimeType;
+ }
+}
Added: incubator/pivot/branches/1.1/core/src/pivot/util/Preferences.java
URL: http://svn.apache.org/viewvc/incubator/pivot/branches/1.1/core/src/pivot/util/Preferences.java?rev=758461&view=auto
==============================================================================
--- incubator/pivot/branches/1.1/core/src/pivot/util/Preferences.java (added)
+++ incubator/pivot/branches/1.1/core/src/pivot/util/Preferences.java Wed Mar 25 23:08:38 2009
@@ -0,0 +1,108 @@
+/*
+ * Copyright (c) 2009 VMware, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package pivot.util;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.Iterator;
+
+import pivot.collections.Dictionary;
+import pivot.serialization.SerializationException;
+
+/**
+ * Provides access to application preference data. Preferences are modeled as
+ * JSON data and are stored using the JNLP Persistence API. One top-level JSON
+ * object exists per URL. The dictionary methods allow a caller to access and
+ * modify this data.
+ * <p>
+ * Keys may be dot-delimited, allowing callers to get/set nested values.
+ *
+ * @author gbrown
+ */
+public class Preferences implements Dictionary<String, Object>, Iterable<String> {
+ private static class PreferencesListenerList extends ListenerList<PreferencesListener>
+ implements PreferencesListener {
+ public void valueAdded(Preferences preferences, String key) {
+ for (PreferencesListener listener : this) {
+ listener.valueAdded(preferences, key);
+ }
+ }
+
+ public void valueUpdated(Preferences preferences, String key, Object previousValue) {
+ for (PreferencesListener listener : this) {
+ listener.valueUpdated(preferences, key, previousValue);
+ }
+ }
+
+ public void valueRemoved(Preferences preferences, String key, Object value) {
+ for (PreferencesListener listener : this) {
+ listener.valueRemoved(preferences, key, value);
+ }
+ }
+ }
+
+ private PreferencesListenerList preferencesListeners = new PreferencesListenerList();
+
+ /**
+ * Creates a preferences object that provides access to the preferences for
+ * a given URL.
+ *
+ * @param url
+ * @param defaults
+ */
+ public Preferences(URL url, Dictionary<String, Object> defaults)
+ throws IOException, SerializationException {
+ // TODO
+ }
+
+ public Object get(String key) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public Object put(String key, Object value) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public Object remove(String key) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public boolean containsKey(String key) {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ public boolean isEmpty() {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ public void save() throws IOException, SerializationException {
+ // TODO
+ }
+
+ public Iterator<String> iterator() {
+ // TODO
+ return null;
+ }
+
+ public ListenerList<PreferencesListener> getPreferencesListeners() {
+ return preferencesListeners;
+ }
+}
Added: incubator/pivot/branches/1.1/core/src/pivot/util/PreferencesListener.java
URL: http://svn.apache.org/viewvc/incubator/pivot/branches/1.1/core/src/pivot/util/PreferencesListener.java?rev=758461&view=auto
==============================================================================
--- incubator/pivot/branches/1.1/core/src/pivot/util/PreferencesListener.java (added)
+++ incubator/pivot/branches/1.1/core/src/pivot/util/PreferencesListener.java Wed Mar 25 23:08:38 2009
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2009 VMware, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package pivot.util;
+
+/**
+ * Preferences listener interface.
+ *
+ * @author gbrown
+ */
+public interface PreferencesListener {
+ /**
+ * Called when a preference has been added.
+ *
+ * @param preferences
+ * The source of the event.
+ *
+ * @param key
+ * The key of the value that was added.
+ */
+ public void valueAdded(Preferences preferences, String key);
+
+ /**
+ * Called when a preference value has been updated.
+ *
+ * @param preferences
+ * The source of the event.
+ *
+ * @param key
+ * The key whose value was updated.
+ *
+ * @param previousValue
+ * The value that was previously associated with the key.
+ */
+ public void valueUpdated(Preferences preferences, String key, Object previousValue);
+
+ /**
+ * Called when a preference value has been removed.
+ *
+ * @param preferences
+ * The source of the event.
+ *
+ * @param key
+ * The key of the value that was removed.
+ *
+ * @param value
+ * The value that was removed.
+ */
+ public void valueRemoved(Preferences preferences, String key, Object value);
+}
Added: incubator/pivot/branches/1.1/core/src/pivot/util/Resources.java
URL: http://svn.apache.org/viewvc/incubator/pivot/branches/1.1/core/src/pivot/util/Resources.java?rev=758461&view=auto
==============================================================================
--- incubator/pivot/branches/1.1/core/src/pivot/util/Resources.java (added)
+++ incubator/pivot/branches/1.1/core/src/pivot/util/Resources.java Wed Mar 25 23:08:38 2009
@@ -0,0 +1,206 @@
+/*
+ * Copyright (c) 2008 VMware, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package pivot.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.Charset;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.MissingResourceException;
+
+import pivot.collections.Dictionary;
+import pivot.collections.Map;
+import pivot.serialization.JSONSerializer;
+import pivot.serialization.SerializationException;
+
+/**
+ * Reads a JSON resource at {@link #baseName} using
+ * {@link ClassLoader#getResourceAsStream(String)}. It applies localization to
+ * the resource using a method similar to that of
+ * {@link java.util.ResourceBundle} in that it loads the base resource, then
+ * applies a country specified resource over-writing the values in the base
+ * using the country specified. It then does the same for country/language
+ * specific.
+ *
+ * @see java.util.ResourceBundle
+ *
+ * @author brindy
+ * @author gbrown
+ */
+public class Resources implements Dictionary<String, Object>, Iterable<String> {
+ private String baseName = null;
+ private Locale locale = null;
+ private Charset charset = null;
+
+ private Map<String, Object> resourceMap = null;
+
+ public Resources(String baseName)
+ throws IOException, SerializationException {
+ this(baseName, Locale.getDefault(), Charset.defaultCharset());
+ }
+
+ public Resources(String baseName, Locale locale)
+ throws IOException, SerializationException {
+ this(baseName, locale, Charset.defaultCharset());
+ }
+
+ public Resources(String baseName, String charsetName)
+ throws IOException, SerializationException {
+ this(baseName, Locale.getDefault(), charsetName);
+ }
+
+ public Resources(String baseName, Charset charset)
+ throws IOException, SerializationException {
+ this(baseName, Locale.getDefault(), charset);
+ }
+
+ public Resources(String baseName, Locale locale, String charsetName)
+ throws IOException, SerializationException {
+ this(baseName, locale, Charset.forName(charsetName));
+ }
+
+ /**
+ * Creates a new resource bundle.
+ *
+ * @param baseName
+ * The base name of this resource as a fully qualified class name.
+ *
+ * @param locale
+ * The locale to use when reading this resource.
+ *
+ * @param charset
+ * The character encoding to use when reading this resource.
+ *
+ * @throws IOException
+ * If there is a problem when reading the resource.
+ *
+ * @throws SerializationException
+ * If there is a problem deserializing the resource from its JSON format.
+ *
+ * @throws IllegalArgumentException
+ * If baseName or locale is null.
+ *
+ * @throws MissingResourceException
+ * If no resource for the specified base name can be found.
+ */
+ public Resources(String baseName, Locale locale, Charset charset) throws IOException,
+ SerializationException {
+
+ if (locale == null) {
+ throw new IllegalArgumentException("locale is null");
+ }
+
+ if (charset == null) {
+ throw new IllegalArgumentException("charset is null.");
+ }
+
+ this.baseName = baseName;
+ this.locale = locale;
+ this.charset = charset;
+
+ String resourceName = baseName.replace('.', '/');
+ resourceMap = readJSONResource(resourceName + ".json");
+ if (resourceMap == null) {
+ throw new MissingResourceException("Can't find resource for base name "
+ + baseName + ", locale " + locale, baseName, "");
+ }
+
+ // try to find resource for the language (e.g. resourceName_en)
+ Map<String, Object> overrideMap = readJSONResource(resourceName + "_"
+ + locale.getLanguage() + ".json");
+ if (overrideMap != null) {
+ applyOverrides(resourceMap, overrideMap);
+ }
+
+ // try to find resource for the entire locale (e.g. resourceName_en_GB)
+ overrideMap = readJSONResource(resourceName + "_" + locale.toString() + ".json");
+ if (null != overrideMap) {
+ applyOverrides(resourceMap, overrideMap);
+ }
+ }
+
+ public String getBaseName() {
+ return baseName;
+ }
+
+ public Locale getLocale() {
+ return locale;
+ }
+
+ public Object get(String key) {
+ return JSONSerializer.getValue(resourceMap, key);
+ }
+
+ public Object put(String key, Object value) {
+ throw new UnsupportedOperationException("Resources instances are immutable");
+ }
+
+ public Object remove(String key) {
+ throw new UnsupportedOperationException("Resources instances are immutable");
+ }
+
+ public boolean containsKey(String key) {
+ return resourceMap.containsKey(key);
+ }
+
+ public boolean isEmpty() {
+ return resourceMap.isEmpty();
+ }
+
+ @SuppressWarnings("unchecked")
+ private void applyOverrides(Map<String, Object> sourceMap,
+ Map<String, Object> overridesMap) {
+
+ for (String key : overridesMap) {
+ if (sourceMap.containsKey(key)) {
+ Object source = sourceMap.get(key);
+ Object override = overridesMap.get(key);
+
+ if (source instanceof Map && override instanceof Map) {
+ applyOverrides((Map<String, Object>) source,
+ (Map<String, Object>) override);
+ } else {
+ sourceMap.put(key, overridesMap.get(key));
+ }
+ }
+ }
+
+ }
+
+ @SuppressWarnings("unchecked")
+ private Map<String, Object> readJSONResource(String name) throws IOException,
+ SerializationException {
+ InputStream in = getClass().getClassLoader().getResourceAsStream(name);
+ if (in == null) {
+ return null;
+ }
+
+ JSONSerializer serializer = new JSONSerializer(charset);
+ Map<String, Object> resourceMap = null;
+ try {
+ resourceMap = (Map<String, Object>) serializer.readObject(in);
+ } finally {
+ in.close();
+ }
+
+ return resourceMap;
+ }
+
+ public Iterator<String> iterator() {
+ return new ImmutableIterator<String>(resourceMap.iterator());
+ }
+}