You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pivot.apache.org by gb...@apache.org on 2011/01/10 00:19:29 UTC

svn commit: r1057053 [4/12] - in /pivot/branches/3.x: ./ core/ core/src/ core/src/org/ core/src/org/apache/ core/src/org/apache/pivot/ core/src/org/apache/pivot/beans/ core/src/org/apache/pivot/bxml/ core/src/org/apache/pivot/csv/ core/src/org/apache/p...

Added: pivot/branches/3.x/core/src/org/apache/pivot/util/ListenerList.java
URL: http://svn.apache.org/viewvc/pivot/branches/3.x/core/src/org/apache/pivot/util/ListenerList.java?rev=1057053&view=auto
==============================================================================
--- pivot/branches/3.x/core/src/org/apache/pivot/util/ListenerList.java (added)
+++ pivot/branches/3.x/core/src/org/apache/pivot/util/ListenerList.java Sun Jan  9 23:19:19 2011
@@ -0,0 +1,161 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you 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 org.apache.pivot.util;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ * Provides support for firing events to registered listeners.
+ */
+public abstract class ListenerList<T> {
+    // Node containing a listener in the list
+    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;
+        }
+    }
+
+    // Node iterator
+    private class NodeIterator implements Iterator<T> {
+        private Node node;
+
+        public NodeIterator() {
+            this.node = first;
+        }
+
+        @Override
+        public boolean hasNext() {
+            return (node != null);
+        }
+
+        @Override
+        public T next() {
+            if (node == null) {
+                throw new NoSuchElementException();
+            }
+
+            T listener = node.listener;
+            node = node.next;
+
+            return listener;
+        }
+
+        @Override
+        public void remove() {
+            throw new UnsupportedOperationException();
+        }
+    }
+
+    // First node in the list (we don't maintain a reference to the last
+    // node, since we need to walk the list looking for duplicates on add)
+    private Node first = null;
+
+    private Iterable<T> iterable = new Iterable<T>() {
+        public Iterator<T> iterator() {
+            return new NodeIterator();
+        }
+    };
+
+    /**
+     * 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;
+                }
+            }
+        }
+    }
+
+    /**
+     * Tests the emptiness of the list.
+     *
+     * @return
+     * <tt>true</tt> if the list contains no listeners; <tt>false</tt>,
+     * otherwise.
+     */
+    public boolean isEmpty() {
+        return (first == null);
+    }
+
+    /**
+     * Protected access to list contents; used by subclasses to fire events.
+     */
+    protected Iterable<T> listeners() {
+        return iterable;
+    }
+}

Added: pivot/branches/3.x/core/src/org/apache/pivot/util/MessageBus.java
URL: http://svn.apache.org/viewvc/pivot/branches/3.x/core/src/org/apache/pivot/util/MessageBus.java?rev=1057053&view=auto
==============================================================================
--- pivot/branches/3.x/core/src/org/apache/pivot/util/MessageBus.java (added)
+++ pivot/branches/3.x/core/src/org/apache/pivot/util/MessageBus.java Sun Jan  9 23:19:19 2011
@@ -0,0 +1,90 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you 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 org.apache.pivot.util;
+
+import java.util.HashMap;
+
+/**
+ * Provides support for basic intra-application message passing.
+ */
+public class MessageBus {
+    private static class TopicListenerList<T> extends ListenerList<MessageBusListener<T>>
+        implements MessageBusListener<T> {
+        @Override
+        public void messageSent(T message) {
+            for (MessageBusListener<T> listener : listeners()) {
+                listener.messageSent(message);
+            }
+        }
+    }
+
+    private static HashMap<Class<?>, TopicListenerList<?>> messageTopics =
+        new HashMap<Class<?>, TopicListenerList<?>>();
+
+    /**
+     * Subscribes a listener to a message topic.
+     *
+     * @param topic
+     * @param messageListener
+     */
+    @SuppressWarnings("unchecked")
+    public static <T> void subscribe(Class<? super T> topic, MessageBusListener<T> messageListener) {
+        TopicListenerList<T> topicListeners = (TopicListenerList<T>)messageTopics.get(topic);
+
+        if (topicListeners == null) {
+            topicListeners = new TopicListenerList<T>() {};
+            messageTopics.put(topic, topicListeners);
+        }
+
+        topicListeners.add(messageListener);
+    }
+
+    /**
+     * Unsubscribes a listener from a message topic.
+     *
+     * @param topic
+     * @param messageListener
+     */
+    @SuppressWarnings("unchecked")
+    public static <T> void unsubscribe(Class<? super T> topic, MessageBusListener<T> messageListener) {
+        TopicListenerList<T> topicListeners = (TopicListenerList<T>)messageTopics.get(topic);
+
+        if (topicListeners == null) {
+            throw new IllegalArgumentException(topic.getName() + " does not exist.");
+        }
+
+        topicListeners.remove(messageListener);
+        if (topicListeners.isEmpty()) {
+            messageTopics.remove(topic);
+        }
+    }
+
+    /**
+     * Sends a message to subscribed topic listeners.
+     *
+     * @param message
+     */
+    @SuppressWarnings("unchecked")
+    public static <T> void sendMessage(T message) {
+        Class<?> topic = message.getClass();
+        TopicListenerList<T> topicListeners = (TopicListenerList<T>)messageTopics.get(topic);
+
+        if (topicListeners != null) {
+            topicListeners.messageSent(message);
+        }
+    }
+}

Added: pivot/branches/3.x/core/src/org/apache/pivot/util/MessageBusListener.java
URL: http://svn.apache.org/viewvc/pivot/branches/3.x/core/src/org/apache/pivot/util/MessageBusListener.java?rev=1057053&view=auto
==============================================================================
--- pivot/branches/3.x/core/src/org/apache/pivot/util/MessageBusListener.java (added)
+++ pivot/branches/3.x/core/src/org/apache/pivot/util/MessageBusListener.java Sun Jan  9 23:19:19 2011
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you 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 org.apache.pivot.util;
+
+/**
+ * Message bus listener interface.
+ */
+public interface MessageBusListener<T> {
+    /**
+     * Called when a message has been sent via {@link MessageBus#sendMessage(Object)}.
+     *
+     * @param message
+     */
+    public void messageSent(T message);
+}

Added: pivot/branches/3.x/core/src/org/apache/pivot/util/ObservableList.java
URL: http://svn.apache.org/viewvc/pivot/branches/3.x/core/src/org/apache/pivot/util/ObservableList.java?rev=1057053&view=auto
==============================================================================
--- pivot/branches/3.x/core/src/org/apache/pivot/util/ObservableList.java (added)
+++ pivot/branches/3.x/core/src/org/apache/pivot/util/ObservableList.java Sun Jan  9 23:19:19 2011
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you 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 org.apache.pivot.util;
+
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * A list that fires events when its content changes.
+ */
+public interface ObservableList<E> extends List<E> {
+    /**
+     * Observable list listener list.
+     */
+    public static class ObservableListListenerList<E>
+        extends ListenerList<ObservableListListener<E>>
+        implements ObservableListListener<E> {
+        @Override
+        public void elementsAdded(ObservableList<E> list, int fromIndex, int toIndex) {
+            for (ObservableListListener<E> listener : listeners()) {
+                listener.elementsAdded(list, fromIndex, toIndex);
+            }
+        }
+
+        @Override
+        public void elementsRemoved(ObservableList<E> list, int fromIndex, List<E> elements) {
+            for (ObservableListListener<E> listener : listeners()) {
+                listener.elementsRemoved(list, fromIndex, elements);
+            }
+        }
+
+        @Override
+        public void elementsUpdated(ObservableList<E> list, int fromIndex, List<E> previousElements) {
+            for (ObservableListListener<E> listener : listeners()) {
+                listener.elementsUpdated(list, fromIndex, previousElements);
+            }
+        }
+    }
+
+    public List<E> setAll(int index, Collection<? extends E> collection);
+
+    public void sort(Comparator<E> comparator);
+
+    public ListenerList<ObservableListListener<E>> getObservableListListeners();
+}

Added: pivot/branches/3.x/core/src/org/apache/pivot/util/ObservableListAdapter.java
URL: http://svn.apache.org/viewvc/pivot/branches/3.x/core/src/org/apache/pivot/util/ObservableListAdapter.java?rev=1057053&view=auto
==============================================================================
--- pivot/branches/3.x/core/src/org/apache/pivot/util/ObservableListAdapter.java (added)
+++ pivot/branches/3.x/core/src/org/apache/pivot/util/ObservableListAdapter.java Sun Jan  9 23:19:19 2011
@@ -0,0 +1,147 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you 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 org.apache.pivot.util;
+
+import java.util.AbstractList;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Observable list that is backed by an instance of {@link List}.
+ */
+public class ObservableListAdapter<E> extends AbstractList<E>
+    implements ObservableList<E> {
+    private List<E> list;
+    private ObservableListListenerList<E> observableListListeners =
+        new ObservableListListenerList<E>();
+
+    public ObservableListAdapter(List<E> list) {
+        if (list == null) {
+            throw new IllegalArgumentException();
+        }
+
+        this.list = list;
+    }
+
+    public List<E> getList() {
+        return list;
+    }
+
+    @Override
+    public void add(int index, E element) {
+        list.add(index, element);
+
+        observableListListeners.elementsAdded(this, index, index + 1);
+    }
+
+    @Override
+    public boolean addAll(int index, Collection<? extends E> collection) {
+        boolean added = list.addAll(index, collection);
+
+        if (added) {
+            observableListListeners.elementsAdded(this, index,
+                index + collection.size());
+        }
+
+        return added;
+    }
+
+    @Override
+    public E remove(int index) {
+        E element = list.remove(index);
+        observableListListeners.elementsRemoved(this, index,
+            Collections.singletonList(element));
+
+        return element;
+    }
+
+    @Override
+    protected void removeRange(int fromIndex, int toIndex) {
+        List<E> subList = list.subList(fromIndex, toIndex);
+        List<E> removed = new ArrayList<E>(subList);
+        subList.clear();
+
+        observableListListeners.elementsRemoved(this, fromIndex, removed);
+    }
+
+    @Override
+    public E get(int index) {
+        return list.get(index);
+    }
+
+    @Override
+    public E set(int index, E element) {
+        E previousElement = list.set(index, element);
+        observableListListeners.elementsUpdated(this, index,
+            Collections.singletonList(previousElement));
+
+        return previousElement;
+    }
+
+    @Override
+    public List<E> setAll(int index, Collection<? extends E> collection) {
+        ArrayList<E> previousElements = new ArrayList<E>(collection.size());
+
+        int i = index;
+        for (E element : collection) {
+            list.set(i++, element);
+            previousElements.add(element);
+        }
+
+        observableListListeners.elementsUpdated(this, index, previousElements);
+
+        return previousElements;
+    }
+
+    @Override
+    public void sort(Comparator<E> comparator) {
+        Collections.sort(list, comparator);
+        observableListListeners.elementsUpdated(this, 0, this);
+    }
+
+    @Override
+    public int size() {
+        return list.size();
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        return list.equals(object);
+    }
+
+    @Override
+    public int hashCode() {
+        return list.hashCode();
+    }
+
+    @Override
+    public ListenerList<ObservableListListener<E>> getObservableListListeners() {
+        return observableListListeners;
+    }
+
+    public static <E> ObservableList<E> observableArrayList() {
+        return new ObservableListAdapter<E>(new ArrayList<E>());
+    }
+
+    public static <E> ObservableList<E> observableLinkedList() {
+        return new ObservableListAdapter<E>(new LinkedList<E>());
+    }
+}

Added: pivot/branches/3.x/core/src/org/apache/pivot/util/ObservableListListener.java
URL: http://svn.apache.org/viewvc/pivot/branches/3.x/core/src/org/apache/pivot/util/ObservableListListener.java?rev=1057053&view=auto
==============================================================================
--- pivot/branches/3.x/core/src/org/apache/pivot/util/ObservableListListener.java (added)
+++ pivot/branches/3.x/core/src/org/apache/pivot/util/ObservableListListener.java Sun Jan  9 23:19:19 2011
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you 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 org.apache.pivot.util;
+
+import java.util.List;
+
+/**
+ * Observable list listener interface.
+ *
+ * @param <E>
+ */
+public interface ObservableListListener<E> {
+    /**
+     * Observable list listener adapter.
+     */
+    public static class Adapter<E> implements ObservableListListener<E> {
+        @Override
+        public void elementsAdded(ObservableList<E> list, int fromIndex, int toIndex) {
+        }
+
+        @Override
+        public void elementsRemoved(ObservableList<E> list, int fromIndex, List<E> elements) {
+        }
+
+        @Override
+        public void elementsUpdated(ObservableList<E> list, int fromIndex, List<E> previousElements) {
+        }
+    }
+
+    public void elementsAdded(ObservableList<E> list, int fromIndex, int toIndex);
+    public void elementsRemoved(ObservableList<E> list, int fromIndex, List<E> elements);
+    public void elementsUpdated(ObservableList<E> list, int fromIndex, List<E> previousElements);
+}

Added: pivot/branches/3.x/core/src/org/apache/pivot/util/ObservableMap.java
URL: http://svn.apache.org/viewvc/pivot/branches/3.x/core/src/org/apache/pivot/util/ObservableMap.java?rev=1057053&view=auto
==============================================================================
--- pivot/branches/3.x/core/src/org/apache/pivot/util/ObservableMap.java (added)
+++ pivot/branches/3.x/core/src/org/apache/pivot/util/ObservableMap.java Sun Jan  9 23:19:19 2011
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you 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 org.apache.pivot.util;
+
+import java.util.Map;
+
+/**
+ * A map that fires events when its content changes.
+ */
+public interface ObservableMap<K, V> extends Map<K, V> {
+    /**
+     * Observable map listener list.
+     */
+    public static class ObservableMapListenerList<K, V>
+        extends ListenerList<ObservableMapListener<K, V>>
+        implements ObservableMapListener<K, V> {
+        @Override
+        public void valueAdded(ObservableMap<K, V> map, K key) {
+            for (ObservableMapListener<K, V> listener : listeners()) {
+                listener.valueAdded(map, key);
+            }
+        }
+
+        @Override
+        public void valueUpdated(ObservableMap<K, V> map, K key, V previousValue) {
+            for (ObservableMapListener<K, V> listener : listeners()) {
+                listener.valueUpdated(map, key, previousValue);
+            }
+        }
+
+        @Override
+        public void valueRemoved(ObservableMap<K, V> map, Object key, V value) {
+            for (ObservableMapListener<K, V> listener : listeners()) {
+                listener.valueRemoved(map, key, value);
+            }
+        }
+    }
+
+    public ListenerList<ObservableMapListener<K, V>> getObservableMapListeners();
+}

Added: pivot/branches/3.x/core/src/org/apache/pivot/util/ObservableMapAdapter.java
URL: http://svn.apache.org/viewvc/pivot/branches/3.x/core/src/org/apache/pivot/util/ObservableMapAdapter.java?rev=1057053&view=auto
==============================================================================
--- pivot/branches/3.x/core/src/org/apache/pivot/util/ObservableMapAdapter.java (added)
+++ pivot/branches/3.x/core/src/org/apache/pivot/util/ObservableMapAdapter.java Sun Jan  9 23:19:19 2011
@@ -0,0 +1,196 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you 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 org.apache.pivot.util;
+
+import java.util.AbstractMap;
+import java.util.AbstractSet;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Observable map that is backed by an instance of {@link Map}.
+ */
+public class ObservableMapAdapter<K, V> extends AbstractMap<K, V>
+    implements ObservableMap<K, V> {
+    private class ObservableMapEntrySet extends AbstractSet<Entry<K, V>> {
+        @Override
+        public int size() {
+            return map.size();
+        }
+
+        @Override
+        public Iterator<Entry<K, V>> iterator() {
+            return new ObservableMapEntrySetIterator(map.entrySet().iterator());
+        }
+    }
+
+    private class ObservableMapEntrySetIterator implements Iterator<Entry<K, V>> {
+        private Iterator<Entry<K, V>> iterator;
+        private ObservableMapEntry entry = null;
+
+        public ObservableMapEntrySetIterator(Iterator<Entry<K, V>> iterator) {
+            this.iterator = iterator;
+        }
+
+        @Override
+        public boolean hasNext() {
+            return iterator.hasNext();
+        }
+
+        @Override
+        public Entry<K, V> next() {
+            entry = new ObservableMapEntry(iterator.next());
+            return entry;
+        }
+
+        @Override
+        public void remove() {
+            if (entry == null) {
+                throw new IllegalStateException();
+            }
+
+            iterator.remove();
+
+            observableMapListeners.valueRemoved(ObservableMapAdapter.this,
+                entry.getKey(), entry.getValue());
+
+            entry = null;
+        }
+    }
+
+    private class ObservableMapEntry implements Entry<K, V> {
+        private Entry<K, V> entry;
+
+        public ObservableMapEntry(Entry<K, V> entry) {
+            this.entry = entry;
+        }
+
+        @Override
+        public K getKey() {
+            return entry.getKey();
+        }
+
+        @Override
+        public V getValue() {
+            return entry.getValue();
+        }
+
+        @Override
+        public V setValue(V value) {
+            V previousValue = entry.setValue(value);
+
+            observableMapListeners.valueUpdated(ObservableMapAdapter.this,
+                getKey(), previousValue);
+
+            return previousValue;
+        }
+
+        @Override
+        public boolean equals(Object object) {
+            return entry.equals(object);
+        }
+
+        @Override
+        public int hashCode() {
+            return entry.hashCode();
+        }
+    }
+
+    private Map<K, V> map;
+    private ObservableMapEntrySet entrySet = new ObservableMapEntrySet();
+    private ObservableMapListenerList<K, V> observableMapListeners =
+        new ObservableMapListenerList<K, V>();
+
+    public ObservableMapAdapter(Map<K, V> map) {
+        if (map == null) {
+            throw new IllegalArgumentException();
+        }
+
+        this.map = map;
+    }
+
+    public Map<K, V> getMap() {
+        return map;
+    }
+
+    @Override
+    public V get(Object key) {
+        return map.get(key);
+    }
+
+    @Override
+    public V put(K key, V value) {
+        boolean update = containsKey(key);
+        V previousValue = map.put(key, value);
+
+        if (update) {
+            observableMapListeners.valueUpdated(this, key, previousValue);
+        } else {
+            observableMapListeners.valueAdded(this, key);
+        }
+
+        return previousValue;
+    }
+
+    @Override
+    public V remove(Object key) {
+        V value = null;
+
+        if (containsKey(key)) {
+            value = map.remove(key);
+            observableMapListeners.valueRemoved(this, key, value);
+        }
+
+        return value;
+    }
+
+    @Override
+    public boolean containsKey(Object key) {
+        return map.containsKey(key);
+    }
+
+    @Override
+    public int size() {
+        return map.size();
+    }
+
+    @Override
+    public Set<Entry<K, V>> entrySet() {
+        return entrySet;
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        return map.equals(object);
+    }
+
+    @Override
+    public int hashCode() {
+        return map.hashCode();
+    }
+
+    @Override
+    public ListenerList<ObservableMapListener<K, V>> getObservableMapListeners() {
+        return observableMapListeners;
+    }
+
+    public static <K, V> ObservableMapAdapter<K, V> observableHashMap() {
+        return new ObservableMapAdapter<K, V>(new HashMap<K, V>());
+    }
+}

Added: pivot/branches/3.x/core/src/org/apache/pivot/util/ObservableMapListener.java
URL: http://svn.apache.org/viewvc/pivot/branches/3.x/core/src/org/apache/pivot/util/ObservableMapListener.java?rev=1057053&view=auto
==============================================================================
--- pivot/branches/3.x/core/src/org/apache/pivot/util/ObservableMapListener.java (added)
+++ pivot/branches/3.x/core/src/org/apache/pivot/util/ObservableMapListener.java Sun Jan  9 23:19:19 2011
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you 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 org.apache.pivot.util;
+
+/**
+ * Observable map listener interface.
+ */
+public interface ObservableMapListener<K, V> {
+    /**
+     * Observable map listener adapter.
+     */
+    public static class Adapter<K, V> implements ObservableMapListener<K, V> {
+        @Override
+        public void valueAdded(ObservableMap<K, V> map, K key) {
+        }
+
+        @Override
+        public void valueUpdated(ObservableMap<K, V> map, K key, V previousValue) {
+        }
+
+        @Override
+        public void valueRemoved(ObservableMap<K, V> map, Object key, V value) {
+        }
+    }
+
+    public void valueAdded(ObservableMap<K, V> map, K key);
+    public void valueUpdated(ObservableMap<K, V> map, K key, V previousValue);
+    public void valueRemoved(ObservableMap<K, V> map, Object key, V value);
+}

Added: pivot/branches/3.x/core/src/org/apache/pivot/util/ObservableSet.java
URL: http://svn.apache.org/viewvc/pivot/branches/3.x/core/src/org/apache/pivot/util/ObservableSet.java?rev=1057053&view=auto
==============================================================================
--- pivot/branches/3.x/core/src/org/apache/pivot/util/ObservableSet.java (added)
+++ pivot/branches/3.x/core/src/org/apache/pivot/util/ObservableSet.java Sun Jan  9 23:19:19 2011
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you 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 org.apache.pivot.util;
+
+import java.util.Set;
+
+/**
+ * A set that fires events when its content changes.
+ */
+public interface ObservableSet<E> extends Set<E> {
+    /**
+     * Observable set listener list.
+     */
+    public static class ObservableSetListenerList<E>
+        extends ListenerList<ObservableSetListener<E>>
+        implements ObservableSetListener<E> {
+        public void elementAdded(ObservableSet<E> set, E element) {
+            for (ObservableSetListener<E> listener : listeners()) {
+                listener.elementAdded(set, element);
+            }
+        }
+
+        public void elementRemoved(ObservableSet<E> set, Object element) {
+            for (ObservableSetListener<E> listener : listeners()) {
+                listener.elementRemoved(set, element);
+            }
+        }
+    }
+
+    public ListenerList<ObservableSetListener<E>> getObservableSetListeners();
+}

Added: pivot/branches/3.x/core/src/org/apache/pivot/util/ObservableSetAdapter.java
URL: http://svn.apache.org/viewvc/pivot/branches/3.x/core/src/org/apache/pivot/util/ObservableSetAdapter.java?rev=1057053&view=auto
==============================================================================
--- pivot/branches/3.x/core/src/org/apache/pivot/util/ObservableSetAdapter.java (added)
+++ pivot/branches/3.x/core/src/org/apache/pivot/util/ObservableSetAdapter.java Sun Jan  9 23:19:19 2011
@@ -0,0 +1,134 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you 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 org.apache.pivot.util;
+
+import java.util.AbstractSet;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+/**
+ * Observable set that is backed by an instance of {@link Set}.
+ */
+public class ObservableSetAdapter<E> extends AbstractSet<E>
+    implements ObservableSet<E> {
+    private class ObservableSetIterator implements Iterator<E> {
+        private Iterator<E> iterator;
+        private E element = null;
+
+        public ObservableSetIterator(Iterator<E> iterator) {
+            this.iterator = iterator;
+        }
+
+        @Override
+        public boolean hasNext() {
+            return iterator.hasNext();
+        }
+
+        @Override
+        public E next() {
+            element = iterator.next();
+            return element;
+        }
+
+        @Override
+        public void remove() {
+            if (element == null) {
+                throw new IllegalStateException();
+            }
+
+            iterator.remove();
+
+            observableSetListeners.elementRemoved(ObservableSetAdapter.this,
+                element);
+
+            element = null;
+        }
+    }
+
+    private Set<E> set;
+    private ObservableSetListenerList<E> observableSetListeners =
+        new ObservableSetListenerList<E>();
+
+    public ObservableSetAdapter(Set<E> set) {
+        if (set == null) {
+            throw new IllegalArgumentException();
+        }
+
+        this.set = set;
+    }
+
+    public Set<E> getSet() {
+        return set;
+    }
+
+    @Override
+    public boolean add(E element) {
+        boolean added = set.add(element);
+
+        if (added) {
+            observableSetListeners.elementAdded(this, element);
+        }
+
+        return added;
+    }
+
+    @Override
+    public boolean remove(Object element) {
+        boolean removed = set.remove(element);
+
+        if (removed) {
+            observableSetListeners.elementRemoved(this, element);
+        }
+
+        return removed;
+    }
+
+    @Override
+    public boolean contains(Object element) {
+        return set.contains(element);
+    }
+
+    @Override
+    public int size() {
+        return set.size();
+    }
+
+    @Override
+    public Iterator<E> iterator() {
+        return new ObservableSetIterator(set.iterator());
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        return set.equals(object);
+    }
+
+    @Override
+    public int hashCode() {
+        return set.hashCode();
+    }
+
+    @Override
+    public ListenerList<ObservableSetListener<E>> getObservableSetListeners() {
+        return observableSetListeners;
+    }
+
+    public static <E> ObservableSet<E> observableHashSet() {
+        return new ObservableSetAdapter<E>(new HashSet<E>());
+    }
+}

Added: pivot/branches/3.x/core/src/org/apache/pivot/util/ObservableSetListener.java
URL: http://svn.apache.org/viewvc/pivot/branches/3.x/core/src/org/apache/pivot/util/ObservableSetListener.java?rev=1057053&view=auto
==============================================================================
--- pivot/branches/3.x/core/src/org/apache/pivot/util/ObservableSetListener.java (added)
+++ pivot/branches/3.x/core/src/org/apache/pivot/util/ObservableSetListener.java Sun Jan  9 23:19:19 2011
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you 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 org.apache.pivot.util;
+
+/**
+ * Observable set listener interface.
+ */
+public interface ObservableSetListener<E> {
+    /**
+     * Observable set listener adapter.
+     */
+    public static class Adapter<E> implements ObservableSetListener<E> {
+        @Override
+        public void elementAdded(ObservableSet<E> set, E element) {
+        }
+
+        @Override
+        public void elementRemoved(ObservableSet<E> set, Object element) {
+        }
+    }
+
+    public void elementAdded(ObservableSet<E> set, E element);
+    public void elementRemoved(ObservableSet<E> set, Object element);
+}

Added: pivot/branches/3.x/core/src/org/apache/pivot/util/ReferenceCountedMap.java
URL: http://svn.apache.org/viewvc/pivot/branches/3.x/core/src/org/apache/pivot/util/ReferenceCountedMap.java?rev=1057053&view=auto
==============================================================================
--- pivot/branches/3.x/core/src/org/apache/pivot/util/ReferenceCountedMap.java (added)
+++ pivot/branches/3.x/core/src/org/apache/pivot/util/ReferenceCountedMap.java Sun Jan  9 23:19:19 2011
@@ -0,0 +1,249 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you 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 org.apache.pivot.util;
+
+import java.util.AbstractMap;
+import java.util.AbstractSet;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Set;
+
+/**
+ * Map that maintains a reference-counted list of entries. When values are
+ * retrieved from the map, the reference count is incremented. When values
+ * are removed, the reference count is decremented. The value is removed
+ * from the map when the reference count reaches zero.
+ * <p>
+ * Internally, the map is backed by an instance of {@link HashMap}.
+ *
+ * @param <K>
+ * @param <V>
+ */
+public class ReferenceCountedMap<K, V> extends AbstractMap<K, V> {
+    /**
+     * Internal reference counter.
+     *
+     * @param <V>
+     */
+    private static class ReferenceCounter<V> {
+        public V value;
+        public int referenceCount = 0;
+
+        public ReferenceCounter(V value) {
+            this.value = value;
+        }
+    }
+
+    /**
+     * Reference-counted entry set.
+     */
+    private class ReferenceCountedMapEntrySet extends AbstractSet<Entry<K, V>> {
+        @Override
+        public int size() {
+            return map.size();
+        }
+
+        @Override
+        public Iterator<Entry<K, V>> iterator() {
+            return new ReferenceCountedEntrySetIterator(map.entrySet().iterator());
+        }
+    }
+
+    /**
+     * Reference-counted entry set iterator.
+     */
+    private class ReferenceCountedEntrySetIterator implements Iterator<Entry<K, V>> {
+        private Iterator<Entry<K, ReferenceCounter<V>>> iterator;
+        private ReferenceCountedMapEntry entry = null;
+
+        public ReferenceCountedEntrySetIterator(Iterator<Entry<K, ReferenceCounter<V>>> iterator) {
+            this.iterator = iterator;
+        }
+
+        @Override
+        public boolean hasNext() {
+            return iterator.hasNext();
+        }
+
+        @Override
+        public Entry<K, V> next() {
+            entry = new ReferenceCountedMapEntry(iterator.next());
+            return entry;
+        }
+
+        @Override
+        public void remove() {
+            if (entry == null) {
+                throw new IllegalStateException();
+            }
+
+            iterator.remove();
+
+            entry = null;
+        }
+    }
+
+    /**
+     * Reference-counted entry.
+     */
+    private class ReferenceCountedMapEntry implements Entry<K, V> {
+        private Entry<K, ReferenceCounter<V>> entry;
+
+        public ReferenceCountedMapEntry(Entry<K, ReferenceCounter<V>> entry) {
+            this.entry = entry;
+        }
+
+        @Override
+        public K getKey() {
+            return entry.getKey();
+        }
+
+        @Override
+        public V getValue() {
+            ReferenceCounter<V> referenceCounter = entry.getValue();
+            referenceCounter.referenceCount++;
+
+            return referenceCounter.value;
+        }
+
+        @Override
+        public V setValue(V value) {
+            ReferenceCounter<V> referenceCounter = entry.getValue();
+            referenceCounter.referenceCount = 0;
+
+            V previousValue = referenceCounter.value;
+            referenceCounter.value = value;
+
+            return previousValue;
+        }
+
+        @Override
+        public boolean equals(Object object) {
+            return entry.equals(object);
+        }
+
+        @Override
+        public int hashCode() {
+            return entry.hashCode();
+        }
+    }
+
+    private HashMap<K, ReferenceCounter<V>> map = new HashMap<K, ReferenceCounter<V>>();
+    private ReferenceCountedMapEntrySet entrySet = new ReferenceCountedMapEntrySet();
+
+    @Override
+    public V get(Object key) {
+        // Return the value and increment the reference count
+        ReferenceCounter<V> referenceCounter = map.get(key);
+
+        V value;
+        if (referenceCounter != null) {
+            referenceCounter.referenceCount++;
+            value = referenceCounter.value;
+        } else {
+            value = null;
+        }
+
+        return value;
+    }
+
+    @Override
+    public V put(K key, V value) {
+        // Add a new reference counter with no initial references
+        ReferenceCounter<V> previousReferenceCounter = map.put(key, new ReferenceCounter<V>(value));
+
+        V previousValue;
+        if (previousReferenceCounter != null) {
+            previousValue = previousReferenceCounter.value;
+        } else {
+            previousValue = null;
+        }
+
+        return previousValue;
+    }
+
+    @Override
+    public V remove(Object key) {
+        // Decremement the reference count, or remove the entry if no
+        // references remain; only return a value when the entry has
+        // actually been removed
+        ReferenceCounter<V> referenceCounter = map.get(key);
+
+        V value;
+        if (referenceCounter != null) {
+            if (referenceCounter.referenceCount == 0) {
+                map.remove(key);
+                value = referenceCounter.value;
+            } else {
+                referenceCounter.referenceCount--;
+                value = null;
+            }
+        } else {
+            value = null;
+        }
+
+        return value;
+    }
+
+    @Override
+    public boolean containsKey(Object key) {
+        return map.containsKey(key);
+    }
+
+    /**
+     * Returns the reference count for an entry.
+     *
+     * @param key
+     *
+     * @return
+     * The current reference count for the given entry, or <tt>0</tt> if the
+     * entry does not exist in the map. {@link #containsKey(Object)} can be
+     * used to distinguish between these two cases.
+     */
+    public int countOf(K key) {
+        ReferenceCounter<V> referenceCounter = map.get(key);
+
+        int count;
+        if (referenceCounter != null) {
+            count = referenceCounter.referenceCount;
+        } else {
+            count = 0;
+        }
+
+        return count;
+    }
+
+    @Override
+    public int size() {
+        return map.size();
+    }
+
+    @Override
+    public Set<Entry<K, V>> entrySet() {
+        return entrySet;
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        return map.equals(object);
+    }
+
+    @Override
+    public int hashCode() {
+        return map.hashCode();
+    }
+}

Added: pivot/branches/3.x/core/src/org/apache/pivot/util/Resources.java
URL: http://svn.apache.org/viewvc/pivot/branches/3.x/core/src/org/apache/pivot/util/Resources.java?rev=1057053&view=auto
==============================================================================
--- pivot/branches/3.x/core/src/org/apache/pivot/util/Resources.java (added)
+++ pivot/branches/3.x/core/src/org/apache/pivot/util/Resources.java Sun Jan  9 23:19:19 2011
@@ -0,0 +1,212 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you 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 org.apache.pivot.util;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.nio.charset.Charset;
+import java.util.AbstractMap;
+import java.util.Collections;
+import java.util.Locale;
+import java.util.Map;
+import java.util.MissingResourceException;
+import java.util.Properties;
+import java.util.Set;
+
+/**
+ * Represents a set of localizable resources.
+ */
+public class Resources extends AbstractMap<String, String> {
+    private final Resources parent;
+    private final String baseName;
+    private final Locale locale;
+    private final Charset charset;
+
+    private Properties properties = null;
+
+    public static final String DEFAULT_CHARSET_NAME = "UTF-8";
+    public static final String PROPERTIES_EXTENSION = "properties";
+    public static final int BUFFER_SIZE = 2048;
+
+    public Resources(String baseName) throws IOException {
+        this(baseName, Locale.getDefault());
+    }
+
+    public Resources(String baseName, Locale locale) throws IOException {
+        this(baseName, locale, Charset.forName(DEFAULT_CHARSET_NAME));
+    }
+
+    public Resources(String baseName, Charset charset) throws IOException {
+        this(baseName, Locale.getDefault(), charset);
+    }
+
+    public Resources(String baseName, Locale locale, Charset charset) throws IOException {
+        this(null, baseName, locale, charset);
+    }
+
+    public Resources(Resources parent, String baseName) throws IOException {
+        this(parent, baseName, Locale.getDefault());
+    }
+
+    public Resources(Resources parent, String baseName, Locale locale) throws IOException {
+        this(parent, baseName, locale, Charset.forName(DEFAULT_CHARSET_NAME));
+    }
+
+    public Resources(Resources parent, String baseName, Charset charset) throws IOException {
+        this(parent, baseName, Locale.getDefault(), charset);
+    }
+
+    /**
+     * Creates a new resource bundle.
+     *
+     * @param parent
+     * The parent resource bundle. If a resource value cannot be located in
+     * this resource bundle, the parent bundle will be searched. May be
+     * <tt>null</tt> to specify no parent.
+     *
+     * @param baseName
+     * The base name of this resource bundle, as a fully qualified class name.
+     *
+     * @param locale
+     * The locale to use when loading this resource bundle.
+     *
+     * @param charset
+     * The character encoding to use when reading this resource bundle.
+     */
+    public Resources(Resources parent, String baseName, Locale locale, Charset charset)
+        throws IOException {
+        if (baseName == null) {
+            throw new IllegalArgumentException("baseName is null");
+        }
+
+        if (locale == null) {
+            throw new IllegalArgumentException("locale is null");
+        }
+
+        if (charset == null) {
+            throw new IllegalArgumentException("charset is null.");
+        }
+
+        if (parent != null
+            && !parent.locale.equals(locale)) {
+            throw new IllegalArgumentException("Parent locale is not the same as locale.");
+        }
+
+        this.parent = parent;
+        this.baseName = baseName;
+        this.locale = locale;
+        this.charset = charset;
+
+        String resourceName = baseName.replace('.', '/');
+        properties = readProperties(resourceName + "." + PROPERTIES_EXTENSION);
+
+        // Look for language-specific properties
+        Properties languageOverrides = readProperties(resourceName + "_" + locale.getLanguage()
+            + "." + PROPERTIES_EXTENSION);
+        if (languageOverrides != null) {
+            applyOverrides(languageOverrides);
+        }
+
+        // Look for region-specific properties
+        Properties regionOverrides = readProperties(resourceName + "_" + locale.toString()
+            + "." + PROPERTIES_EXTENSION);
+        if (regionOverrides != null) {
+            applyOverrides(regionOverrides);
+        }
+
+        if (properties == null) {
+            throw new MissingResourceException("Can't find resources for \"" + baseName + "\".",
+                null, null);
+        }
+    }
+
+    public Resources getParent() {
+        return parent;
+    }
+
+    public String getBaseName() {
+        return baseName;
+    }
+
+    public Locale getLocale() {
+        return locale;
+    }
+
+    public Charset getCharset() {
+        return charset;
+    }
+
+    @Override
+    public String get(Object key) {
+        return (properties.containsKey(key)) ?
+            (String)properties.get(key) : (parent == null) ? null : parent.get(key);
+    }
+
+    @Override
+    public boolean containsKey(Object key) {
+        return properties.containsKey(key);
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public Set<String> keySet() {
+        Set<?> keys = Collections.unmodifiableSet(properties.keySet());
+        return (Set<String>)keys;
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public Set<Map.Entry<String, String>> entrySet() {
+        Set<?> entries = Collections.unmodifiableSet(properties.entrySet());
+        return (Set<Map.Entry<String, String>>)entries;
+    }
+
+    private Properties readProperties(String resourceName) throws IOException {
+        Properties properties = null;
+
+        ClassLoader classLoader = getClass().getClassLoader();
+        InputStream inputStream = classLoader.getResourceAsStream(resourceName);
+
+        if (inputStream != null) {
+            Reader reader = new BufferedReader(new InputStreamReader(inputStream, charset), BUFFER_SIZE);
+
+            properties = new Properties();
+            try {
+                properties.load(reader);
+            } finally {
+                reader.close();
+            }
+        }
+
+        return properties;
+    }
+
+    private void applyOverrides(Properties overrides) {
+        if (properties == null) {
+            properties = overrides;
+        } else {
+            for (Object key : overrides.keySet()) {
+                if (properties.containsKey(key)) {
+                    properties.put(key, overrides.get(key));
+                }
+            }
+        }
+    }
+}

Added: pivot/branches/3.x/core/src/org/apache/pivot/util/Service.java
URL: http://svn.apache.org/viewvc/pivot/branches/3.x/core/src/org/apache/pivot/util/Service.java?rev=1057053&view=auto
==============================================================================
--- pivot/branches/3.x/core/src/org/apache/pivot/util/Service.java (added)
+++ pivot/branches/3.x/core/src/org/apache/pivot/util/Service.java Sun Jan  9 23:19:19 2011
@@ -0,0 +1,106 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you 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 org.apache.pivot.util;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+/**
+ * Utility class for locating and instantiating service providers.
+ */
+public class Service {
+    /**
+     * Attempts to load a service provider.
+     *
+     * @param providerName
+     * The name of the provider to load. The method first looks for a system
+     * property with this name. The value of the property is expected to be the
+     * name of a class that implements the expected provider interface.
+     * <p>
+     * If the system property does not exist, the method then attempts to load
+     * a resource with this name from the META-INF/services directory. The
+     * resource is expected to be a text file containing a single line that is
+     * the name of the provider class.
+     */
+    public static Object getProvider(String providerName) {
+        String providerClassName = null;
+
+        // First look for a system property
+        try {
+            providerClassName = System.getProperty(providerName);
+        } catch(SecurityException exception) {
+            // No-op
+        }
+
+        // Next look for a service descriptor on the classpath
+        if (providerClassName == null) {
+            String serviceName = "META-INF/services/" + providerName;
+
+            ClassLoader classLoader = Service.class.getClassLoader();
+            InputStream serviceInputStream = classLoader.getResourceAsStream(serviceName);
+
+            if (serviceInputStream != null) {
+                try {
+                    BufferedReader reader = null;
+                    try {
+                        reader = new BufferedReader(new InputStreamReader(serviceInputStream, "UTF-8"));
+                        String line = reader.readLine();
+                        while (line != null
+                            && (line.length() == 0
+                                || line.startsWith("#"))) {
+                            line = reader.readLine();
+                        }
+
+                        providerClassName = line;
+                    } finally {
+                        if (reader != null) {
+                            reader.close();
+                        }
+                    }
+                } catch(IOException exception) {
+                    // No-op
+                }
+            }
+        }
+
+        // Try to load the provider class
+        Class<?> providerClass = null;
+
+        if (providerClassName != null) {
+            try {
+                providerClass = Class.forName(providerClassName);
+            } catch(ClassNotFoundException exception) {
+                // The specified class could not be found
+            }
+        }
+
+        Object provider = null;
+        if (providerClass != null) {
+            try {
+                provider = providerClass.newInstance();
+            } catch(InstantiationException exception) {
+                // No-op
+            } catch(IllegalAccessException exception) {
+                // No-op
+            }
+        }
+
+        return provider;
+    }
+}

Added: pivot/branches/3.x/core/src/org/apache/pivot/util/Time.java
URL: http://svn.apache.org/viewvc/pivot/branches/3.x/core/src/org/apache/pivot/util/Time.java?rev=1057053&view=auto
==============================================================================
--- pivot/branches/3.x/core/src/org/apache/pivot/util/Time.java (added)
+++ pivot/branches/3.x/core/src/org/apache/pivot/util/Time.java Sun Jan  9 23:19:19 2011
@@ -0,0 +1,353 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you 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 org.apache.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;
+
+/**
+ * Class representing a time of day, independent of any particular time zone.
+ */
+public final class Time implements Comparable<Time>, Serializable {
+    private static final long serialVersionUID = 0;
+
+    /**
+     * Represents a range of times.
+     */
+    public static final class Range {
+        public final Time start;
+        public final Time end;
+
+        public Range(Time time) {
+            this(time, time);
+        }
+
+        public Range(Time start, Time end) {
+            this.start = start;
+            this.end = end;
+        }
+
+        public Range(String start, String end) {
+            this.start = Time.decode(start);
+            this.end = Time.decode(end);
+        }
+
+        public Range(Range range) {
+            if (range == null) {
+                throw new IllegalArgumentException("range is null.");
+            }
+
+            start = range.start;
+            end = range.end;
+        }
+
+        public int getLength() {
+            return Math.abs(start.subtract(end)) + 1;
+        }
+
+        public boolean contains(Range range) {
+            if (range == null) {
+                throw new IllegalArgumentException("range is null.");
+            }
+
+            Range normalizedRange = range.normalize();
+
+            boolean contains;
+            if (start.compareTo(end) < 0) {
+                contains = (start.compareTo(normalizedRange.start) <= 0
+                    && end.compareTo(normalizedRange.end) >= 0);
+            } else {
+                contains = (end.compareTo(normalizedRange.start) <= 0
+                    && start.compareTo(normalizedRange.end) >= 0);
+            }
+
+            return contains;
+        }
+
+        public boolean contains(Time time) {
+            if (time == null) {
+                throw new IllegalArgumentException("time is null.");
+            }
+
+            boolean contains;
+            if (start.compareTo(end) < 0) {
+                contains = (start.compareTo(time) <= 0
+                    && end.compareTo(time) >= 0);
+            } else {
+                contains = (end.compareTo(time) <= 0
+                    && start.compareTo(time) >= 0);
+            }
+
+            return contains;
+        }
+
+        public boolean intersects(Range range) {
+            if (range == null) {
+                throw new IllegalArgumentException("range is null.");
+            }
+
+            Range normalizedRange = range.normalize();
+
+            boolean intersects;
+            if (start.compareTo(end) < 0) {
+                intersects = (start.compareTo(normalizedRange.end) <= 0
+                    && end.compareTo(normalizedRange.start) >= 0);
+            } else {
+                intersects = (end.compareTo(normalizedRange.end) <= 0
+                    && start.compareTo(normalizedRange.start) >= 0);
+            }
+
+            return intersects;
+        }
+
+        public Range normalize() {
+            Time earlier = (start.compareTo(end) < 0 ? start : end);
+            Time later = (earlier == start ? end : start);
+            return new Range(earlier, later);
+        }
+    }
+
+    /**
+     * The hour value, in 24-hour format.
+     */
+    public final int hour;
+
+    /**
+     * The minute value.
+     */
+    public final int minute;
+
+    /**
+     * The second value.
+     */
+    public final int second;
+
+    /**
+     * The millisecond value.
+     */
+    public final int millisecond;
+
+    public static final int MILLISECONDS_PER_SECOND = 1000;
+    public static final int MILLISECONDS_PER_MINUTE = 60 * MILLISECONDS_PER_SECOND;
+    public static final int MILLISECONDS_PER_HOUR = 60 * MILLISECONDS_PER_MINUTE;
+    public static final int MILLISECONDS_PER_DAY = 24 * MILLISECONDS_PER_HOUR;
+
+    private static final Pattern PATTERN = Pattern.compile("^(\\d{2}):(\\d{2}):(\\d{2})(\\.(\\d{3}))?$");
+
+    public Time() {
+        this(new GregorianCalendar());
+    }
+
+    public Time(Calendar calendar) {
+        if (calendar == null) {
+            throw new IllegalArgumentException();
+        }
+
+        this.hour = calendar.get(Calendar.HOUR_OF_DAY);
+        this.minute = calendar.get(Calendar.MINUTE);
+        this.second = calendar.get(Calendar.SECOND);
+        this.millisecond = calendar.get(Calendar.MILLISECOND);
+    }
+
+    public Time(int hour, int minute, int second) {
+        this(hour, minute, second, 0);
+    }
+
+    public Time(int hour, int minute, int second, int millisecond) {
+        if (hour < 0 || hour > 23) {
+            throw new IllegalArgumentException("Invalid hour.");
+        }
+
+        if (minute < 0 || minute > 59) {
+            throw new IllegalArgumentException("Invalid minute.");
+        }
+
+        if (second < 0 || second > 59) {
+            throw new IllegalArgumentException("Invalid second.");
+        }
+
+        if (millisecond < 0 || millisecond > 999) {
+            throw new IllegalArgumentException("Invalid millisecond.");
+        }
+
+        this.hour = hour;
+        this.minute = minute;
+        this.second = second;
+        this.millisecond = millisecond;
+    }
+
+    public Time(int milliseconds) {
+        milliseconds %= MILLISECONDS_PER_DAY;
+        milliseconds = (milliseconds + MILLISECONDS_PER_DAY) % MILLISECONDS_PER_DAY;
+
+        hour = milliseconds / MILLISECONDS_PER_HOUR;
+        milliseconds %= MILLISECONDS_PER_HOUR;
+
+        minute = milliseconds / MILLISECONDS_PER_MINUTE;
+        milliseconds %= MILLISECONDS_PER_MINUTE;
+
+        second = milliseconds / MILLISECONDS_PER_SECOND;
+        milliseconds %= MILLISECONDS_PER_SECOND;
+
+        millisecond = milliseconds;
+    }
+
+    /**
+     * Adds the specified milliseconds of days to this time and returns the
+     * resulting time. The number of milliseconds may be negative, in which
+     * case the result will be a time prior to this time.
+     *
+     * @param milliseconds
+     * The number of milliseconds to add to this time.
+     *
+     * @return
+     * The resulting time.
+     */
+    public Time add(int milliseconds) {
+        return new Time(toMilliseconds() + milliseconds);
+    }
+
+    /**
+     * Gets the number of milliseconds in between this time and the specified
+     * time. If this time represents a time later than the specified time, the
+     * difference will be positive. If this time represents a time before the
+     * specified time, the difference will be negative. If the two times represent
+     * the same time, the difference will be zero.
+     *
+     * @param time
+     * The time to subtract from this time.
+     *
+     * @return
+     * The number of milliseconds in between this time and <tt>time</tt>.
+     */
+    public int subtract(Time time) {
+        if (time == null) {
+            throw new IllegalArgumentException();
+        }
+
+        return toMilliseconds() - time.toMilliseconds();
+    }
+
+    /**
+     * Returns the number of milliseconds since midnight represented by
+     * this time.
+     *
+     * @return
+     * The number of milliseconds since midnight represented by this time.
+     */
+    public int toMilliseconds() {
+        return hour * MILLISECONDS_PER_HOUR
+            + minute * MILLISECONDS_PER_MINUTE
+            + second * MILLISECONDS_PER_SECOND
+            + millisecond;
+    }
+
+    @Override
+    public int compareTo(Time time) {
+        int result = hour - time.hour;
+
+        if (result == 0) {
+            result = minute - time.minute;
+
+            if (result == 0) {
+                result = second - time.second;
+
+                if (result == 0) {
+                    result = millisecond - time.millisecond;
+                }
+            }
+        }
+
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        return (o instanceof Time
+            && ((Time)o).hour == hour
+            && ((Time)o).minute == minute
+            && ((Time)o).second == second
+            && ((Time)o).millisecond == millisecond);
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + hour;
+        result = prime * result + minute;
+        result = prime * result + second;
+        result = prime * result + millisecond;
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder buf = new StringBuilder();
+
+        NumberFormat format = NumberFormat.getIntegerInstance();
+        format.setGroupingUsed(false);
+        format.setMinimumIntegerDigits(2);
+
+        buf.append(format.format(hour));
+        buf.append(":");
+        buf.append(format.format(minute));
+        buf.append(":");
+        buf.append(format.format(second));
+
+        if (millisecond > 0) {
+            buf.append(".");
+
+            format.setMinimumIntegerDigits(3);
+            buf.append(format.format(millisecond));
+        }
+
+        return buf.toString();
+    }
+
+    /**
+     * Creates a new time representing the specified time string. The time
+     * string must be in the full <tt>ISO 8601</tt> extended "time" format,
+     * which  is <tt>[hh]:[mm]:[ss]</tt>. An optional millisecond suffix of
+     * the form <tt>.[nnn]</tt> is also supported.
+     *
+     * @param value
+     * A string in the form of <tt>[hh]:[mm]:[ss]</tt> or
+     * <tt>[hh]:[mm]:[ss].[nnn]</tt> (e.g. 17:19:20 or 17:19:20.412).
+     */
+    public static Time decode(String value) {
+        Matcher matcher = PATTERN.matcher(value);
+
+        if (!matcher.matches()) {
+            throw new IllegalArgumentException("Invalid time format: " + value);
+        }
+
+        int hour = Integer.parseInt(matcher.group(1));
+        int minute = Integer.parseInt(matcher.group(2));
+        int second = Integer.parseInt(matcher.group(3));
+
+        String millisecondSequence = matcher.group(4);
+        int millisecond = (millisecondSequence == null) ?
+            0 : Integer.parseInt(millisecondSequence.substring(1));
+
+        return new Time(hour, minute, second, millisecond);
+    }
+}

Added: pivot/branches/3.x/core/src/org/apache/pivot/util/TypeLiteral.java
URL: http://svn.apache.org/viewvc/pivot/branches/3.x/core/src/org/apache/pivot/util/TypeLiteral.java?rev=1057053&view=auto
==============================================================================
--- pivot/branches/3.x/core/src/org/apache/pivot/util/TypeLiteral.java (added)
+++ pivot/branches/3.x/core/src/org/apache/pivot/util/TypeLiteral.java Sun Jan  9 23:19:19 2011
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you 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 org.apache.pivot.util;
+
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+
+/**
+ * Represents a generic type {@code T}. Java doesn't yet provide a way to
+ * represent generic types, so this class does. Clients create a subclass
+ * of this class, which enables retrieval the type information even at runtime.
+ * <p>
+ * For example, to get a reference to a generic type {@code List<String>}, you
+ * create an empty anonymous inner class, like so:
+ * <p>
+ * {@code Type genericType = (new TypeLiteral<List<String>>() {}).getType();}
+ * <p>
+ * This class is a drastically reduced derivation from
+ * <a href="http://code.google.com/p/google-guice/">Google Guice</a>'s
+ * {@code TypeLiteral} class, written by Bob Lee and Jesse Wilson.
+ */
+public class TypeLiteral<T> {
+    private final Type type;
+
+    /**
+     * Constructs a new type literal. Derives represented class from type
+     * parameter.
+     * <p>
+     * Clients create an empty anonymous subclass. Doing so embeds the type
+     * parameter in the anonymous class's type hierarchy so we can reconstitute it
+     * at runtime despite erasure.
+     */
+    protected TypeLiteral() {
+        Type genericSuperclass = getClass().getGenericSuperclass();
+        if (!(genericSuperclass instanceof ParameterizedType)) {
+            throw new RuntimeException("Missing type parameter.");
+        }
+
+        ParameterizedType parameterizedType = (ParameterizedType)genericSuperclass;
+        this.type = parameterizedType.getActualTypeArguments()[0];
+    }
+
+    /**
+     * Gets underlying {@code Type} instance.
+     */
+    public final Type getType() {
+        return type;
+    }
+}

Added: pivot/branches/3.x/core/src/org/apache/pivot/util/Version.java
URL: http://svn.apache.org/viewvc/pivot/branches/3.x/core/src/org/apache/pivot/util/Version.java?rev=1057053&view=auto
==============================================================================
--- pivot/branches/3.x/core/src/org/apache/pivot/util/Version.java (added)
+++ pivot/branches/3.x/core/src/org/apache/pivot/util/Version.java Sun Jan  9 23:19:19 2011
@@ -0,0 +1,171 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you 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 org.apache.pivot.util;
+
+import java.io.Serializable;
+
+/**
+ * Represents a version number. Version numbers are defined as:
+ * <p>
+ * <i>major</i>.<i>minor</i>.<i>maintenance</i>_<i>update</i>
+ * <p>
+ * for example, "JDK 1.6.0_10".
+ */
+public class Version implements Comparable<Version>, Serializable {
+    private static final long serialVersionUID = -3677773163272115116L;
+
+    private byte majorRevision = 0;
+    private byte minorRevision = 0;
+    private byte maintenanceRevision = 0;
+    private byte updateRevision = 0;
+    private String build = null;
+
+    public Version(int majorRevision, int minorRevision, int maintenanceRevision,
+        int updateRevision) {
+        this(majorRevision, minorRevision, maintenanceRevision, updateRevision, null);
+    }
+
+    public Version(int majorRevision, int minorRevision, int maintenanceRevision,
+        int updateRevision, String build) {
+        if (majorRevision > 0x7f) {
+            throw new IllegalArgumentException("majorRevision must be less than "
+                + 0x7f + ".");
+        }
+
+        if (minorRevision > 0xff) {
+            throw new IllegalArgumentException("minorRevision must be less than "
+                + 0xff + ".");
+        }
+
+        if (maintenanceRevision > 0xff) {
+            throw new IllegalArgumentException("maintenanceRevision must be less than "
+                + 0xff + ".");
+        }
+
+        if (updateRevision > 0xff) {
+            throw new IllegalArgumentException("updateRevision must be less than "
+                + 0xff + ".");
+        }
+
+        this.majorRevision = (byte)majorRevision;
+        this.minorRevision = (byte)minorRevision;
+        this.maintenanceRevision = (byte)maintenanceRevision;
+        this.updateRevision = (byte)updateRevision;
+        this.build = build;
+    }
+
+    public byte getMajorRevision() {
+        return majorRevision;
+    }
+
+    public byte getMinorRevision() {
+        return minorRevision;
+    }
+
+    public byte getMaintenanceRevision() {
+        return maintenanceRevision;
+    }
+
+    public byte getUpdateRevision() {
+        return updateRevision;
+    }
+
+    public int getNumber() {
+        int number = ((majorRevision) & 0xff) << (8 * 3)
+            | ((minorRevision) & 0xff) << (8 * 2)
+            | ((maintenanceRevision) & 0xff) << (8 * 1)
+            | ((updateRevision) & 0xff) << (8 * 0);
+
+        return number;
+    }
+
+    @Override
+    public int compareTo(Version version) {
+        return (getNumber() - version.getNumber());
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        return (object instanceof Version
+            && compareTo((Version)object) == 0);
+    }
+
+    @Override
+    public int hashCode() {
+        return getNumber();
+    }
+
+    @Override
+    public String toString() {
+        String string = majorRevision
+            + "." + minorRevision
+            + "." + maintenanceRevision
+            + "_" + String.format("%02d", updateRevision);
+
+        if (build != null) {
+            string += "-" + build;
+        }
+
+        return string;
+    }
+
+    public static Version decode(String string) {
+        Version version = null;
+
+        byte majorRevision = 0;
+        byte minorRevision = 0;
+        byte maintenanceRevision = 0;
+        byte updateRevision = 0;
+        String build = null;
+
+        String revision;
+        int i = string.indexOf("-");
+        if (i == -1) {
+            revision = string;
+        } else {
+            revision = string.substring(0, i);
+            build = string.substring(i + 1);
+        }
+
+        String[] revisionNumbers = revision.split("\\.");
+
+        if (revisionNumbers.length > 0) {
+            majorRevision = Byte.parseByte(revisionNumbers[0]);
+
+            if (revisionNumbers.length > 1) {
+                minorRevision = Byte.parseByte(revisionNumbers[1]);
+
+                if (revisionNumbers.length > 2) {
+                    String[] maintenanceRevisionNumbers = revisionNumbers[2].split("_");
+
+                    if (maintenanceRevisionNumbers.length > 0) {
+                        maintenanceRevision = Byte.parseByte(maintenanceRevisionNumbers[0]);
+
+                        if (maintenanceRevisionNumbers.length > 1) {
+                            updateRevision = Byte.parseByte(maintenanceRevisionNumbers[1]);
+                        }
+                    }
+                }
+            }
+
+            version = new Version(majorRevision, minorRevision, maintenanceRevision,
+                updateRevision, build);
+        }
+
+        return version;
+    }
+}

Added: pivot/branches/3.x/core/src/org/apache/pivot/util/concurrent/AbortException.java
URL: http://svn.apache.org/viewvc/pivot/branches/3.x/core/src/org/apache/pivot/util/concurrent/AbortException.java?rev=1057053&view=auto
==============================================================================
--- pivot/branches/3.x/core/src/org/apache/pivot/util/concurrent/AbortException.java (added)
+++ pivot/branches/3.x/core/src/org/apache/pivot/util/concurrent/AbortException.java Sun Jan  9 23:19:19 2011
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you 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 org.apache.pivot.util.concurrent;
+
+/**
+ * Thrown when a task is aborted.
+ */
+public class AbortException extends RuntimeException {
+    private static final long serialVersionUID = 0;
+
+    public AbortException() {
+        super();
+    }
+
+    public AbortException(String message) {
+        super(message);
+    }
+}

Added: pivot/branches/3.x/core/src/org/apache/pivot/util/concurrent/ParallelTaskGroup.java
URL: http://svn.apache.org/viewvc/pivot/branches/3.x/core/src/org/apache/pivot/util/concurrent/ParallelTaskGroup.java?rev=1057053&view=auto
==============================================================================
--- pivot/branches/3.x/core/src/org/apache/pivot/util/concurrent/ParallelTaskGroup.java (added)
+++ pivot/branches/3.x/core/src/org/apache/pivot/util/concurrent/ParallelTaskGroup.java Sun Jan  9 23:19:19 2011
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you 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 org.apache.pivot.util.concurrent;
+
+/**
+ * Class that runs a group of tasks in parallel and notifies listeners
+ * when all tasks are complete.
+ */
+public class ParallelTaskGroup extends TaskGroup {
+    @Override
+    @SuppressWarnings("unchecked")
+    public synchronized Void execute() throws TaskExecutionException {
+        TaskListener<Object> taskListener = new TaskListener<Object>() {
+            @Override
+            public void taskExecuted(Task<Object> task) {
+                synchronized (ParallelTaskGroup.this) {
+                    complete++;
+                    ParallelTaskGroup.this.notify();
+                }
+            }
+        };
+
+        complete = 0;
+        for (Task<?> task : tasks) {
+            ((Task<Object>)task).execute(taskListener);
+        }
+
+        while (complete < tasks.size()) {
+            try {
+                wait();
+            } catch (InterruptedException exception) {
+                throw new TaskExecutionException(exception);
+            }
+        }
+
+        return null;
+    }
+}

Added: pivot/branches/3.x/core/src/org/apache/pivot/util/concurrent/SerialTaskGroup.java
URL: http://svn.apache.org/viewvc/pivot/branches/3.x/core/src/org/apache/pivot/util/concurrent/SerialTaskGroup.java?rev=1057053&view=auto
==============================================================================
--- pivot/branches/3.x/core/src/org/apache/pivot/util/concurrent/SerialTaskGroup.java (added)
+++ pivot/branches/3.x/core/src/org/apache/pivot/util/concurrent/SerialTaskGroup.java Sun Jan  9 23:19:19 2011
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you 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 org.apache.pivot.util.concurrent;
+
+/**
+ * Class that runs a sequence of tasks in series and notifies listeners
+ * when all tasks are complete.
+ */
+public class SerialTaskGroup extends TaskGroup {
+    @Override
+    @SuppressWarnings("unchecked")
+    public synchronized Void execute() throws TaskExecutionException {
+        TaskListener<Object> taskListener = new TaskListener<Object>() {
+            @Override
+            public void taskExecuted(Task<Object> task) {
+                synchronized (SerialTaskGroup.this) {
+                    complete++;
+                    SerialTaskGroup.this.notify();
+                }
+            }
+        };
+
+        complete = 0;
+        for (Task<?> task : tasks) {
+            if (abort) {
+                throw new AbortException();
+            }
+
+            ((Task<Object>)task).execute(taskListener);
+
+            try {
+                wait();
+            } catch (InterruptedException exception) {
+                throw new TaskExecutionException(exception);
+            }
+        }
+
+        return null;
+    }
+}