001: /*
002: * Copyright 2004-2005 The Apache Software Foundation
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: package org.apache.commons.collections.map;
017:
018: import java.lang.reflect.Array;
019: import java.util.Iterator;
020: import java.util.Map;
021: import java.util.Set;
022:
023: import org.apache.commons.collections.iterators.AbstractIteratorDecorator;
024: import org.apache.commons.collections.keyvalue.AbstractMapEntryDecorator;
025: import org.apache.commons.collections.set.AbstractSetDecorator;
026:
027: /**
028: * An abstract base class that simplifies the task of creating map decorators.
029: * <p>
030: * The Map API is very difficult to decorate correctly, and involves implementing
031: * lots of different classes. This class exists to provide a simpler API.
032: * <p>
033: * Special hook methods are provided that are called when objects are added to
034: * the map. By overriding these methods, the input can be validated or manipulated.
035: * In addition to the main map methods, the entrySet is also affected, which is
036: * the hardest part of writing map implementations.
037: * <p>
038: * This class is package-scoped, and may be withdrawn or replaced in future
039: * versions of Commons Collections.
040: *
041: * @since Commons Collections 3.1
042: * @version $Revision: 155406 $ $Date: 2005-02-26 12:55:26 +0000 (Sat, 26 Feb 2005) $
043: *
044: * @author Stephen Colebourne
045: */
046: abstract class AbstractInputCheckedMapDecorator extends
047: AbstractMapDecorator {
048:
049: /**
050: * Constructor only used in deserialization, do not use otherwise.
051: */
052: protected AbstractInputCheckedMapDecorator() {
053: super ();
054: }
055:
056: /**
057: * Constructor that wraps (not copies).
058: *
059: * @param map the map to decorate, must not be null
060: * @throws IllegalArgumentException if map is null
061: */
062: protected AbstractInputCheckedMapDecorator(Map map) {
063: super (map);
064: }
065:
066: //-----------------------------------------------------------------------
067: /**
068: * Hook method called when a value is being set using <code>setValue</code>.
069: * <p>
070: * An implementation may validate the value and throw an exception
071: * or it may transform the value into another object.
072: * <p>
073: * This implementation returns the input value.
074: *
075: * @param value the value to check
076: * @throws UnsupportedOperationException if the map may not be changed by setValue
077: * @throws IllegalArgumentException if the specified value is invalid
078: * @throws ClassCastException if the class of the specified value is invalid
079: * @throws NullPointerException if the specified value is null and nulls are invalid
080: */
081: protected abstract Object checkSetValue(Object value);
082:
083: /**
084: * Hook method called to determine if <code>checkSetValue</code> has any effect.
085: * <p>
086: * An implementation should return false if the <code>checkSetValue</code> method
087: * has no effect as this optimises the implementation.
088: * <p>
089: * This implementation returns <code>true</code>.
090: *
091: * @return true always
092: */
093: protected boolean isSetValueChecking() {
094: return true;
095: }
096:
097: //-----------------------------------------------------------------------
098: public Set entrySet() {
099: if (isSetValueChecking()) {
100: return new EntrySet(map.entrySet(), this );
101: } else {
102: return map.entrySet();
103: }
104: }
105:
106: //-----------------------------------------------------------------------
107: /**
108: * Implementation of an entry set that checks additions via setValue.
109: */
110: static class EntrySet extends AbstractSetDecorator {
111:
112: /** The parent map */
113: private final AbstractInputCheckedMapDecorator parent;
114:
115: protected EntrySet(Set set,
116: AbstractInputCheckedMapDecorator parent) {
117: super (set);
118: this .parent = parent;
119: }
120:
121: public Iterator iterator() {
122: return new EntrySetIterator(collection.iterator(), parent);
123: }
124:
125: public Object[] toArray() {
126: Object[] array = collection.toArray();
127: for (int i = 0; i < array.length; i++) {
128: array[i] = new MapEntry((Map.Entry) array[i], parent);
129: }
130: return array;
131: }
132:
133: public Object[] toArray(Object array[]) {
134: Object[] result = array;
135: if (array.length > 0) {
136: // we must create a new array to handle multi-threaded situations
137: // where another thread could access data before we decorate it
138: result = (Object[]) Array.newInstance(array.getClass()
139: .getComponentType(), 0);
140: }
141: result = collection.toArray(result);
142: for (int i = 0; i < result.length; i++) {
143: result[i] = new MapEntry((Map.Entry) result[i], parent);
144: }
145:
146: // check to see if result should be returned straight
147: if (result.length > array.length) {
148: return result;
149: }
150:
151: // copy back into input array to fulfil the method contract
152: System.arraycopy(result, 0, array, 0, result.length);
153: if (array.length > result.length) {
154: array[result.length] = null;
155: }
156: return array;
157: }
158: }
159:
160: /**
161: * Implementation of an entry set iterator that checks additions via setValue.
162: */
163: static class EntrySetIterator extends AbstractIteratorDecorator {
164:
165: /** The parent map */
166: private final AbstractInputCheckedMapDecorator parent;
167:
168: protected EntrySetIterator(Iterator iterator,
169: AbstractInputCheckedMapDecorator parent) {
170: super (iterator);
171: this .parent = parent;
172: }
173:
174: public Object next() {
175: Map.Entry entry = (Map.Entry) iterator.next();
176: return new MapEntry(entry, parent);
177: }
178: }
179:
180: /**
181: * Implementation of a map entry that checks additions via setValue.
182: */
183: static class MapEntry extends AbstractMapEntryDecorator {
184:
185: /** The parent map */
186: private final AbstractInputCheckedMapDecorator parent;
187:
188: protected MapEntry(Map.Entry entry,
189: AbstractInputCheckedMapDecorator parent) {
190: super (entry);
191: this .parent = parent;
192: }
193:
194: public Object setValue(Object value) {
195: value = parent.checkSetValue(value);
196: return entry.setValue(value);
197: }
198: }
199:
200: }
|