1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.commons.collections.map;
18
19 import java.lang.reflect.Array;
20 import java.util.Iterator;
21 import java.util.Map;
22 import java.util.Set;
23
24 import org.apache.commons.collections.iterators.AbstractIteratorDecorator;
25 import org.apache.commons.collections.keyvalue.AbstractMapEntryDecorator;
26 import org.apache.commons.collections.set.AbstractSetDecorator;
27
28 /**
29 * An abstract base class that simplifies the task of creating map decorators.
30 * <p>
31 * The Map API is very difficult to decorate correctly, and involves implementing
32 * lots of different classes. This class exists to provide a simpler API.
33 * <p>
34 * Special hook methods are provided that are called when objects are added to
35 * the map. By overriding these methods, the input can be validated or manipulated.
36 * In addition to the main map methods, the entrySet is also affected, which is
37 * the hardest part of writing map implementations.
38 * <p>
39 * This class is package-scoped, and may be withdrawn or replaced in future
40 * versions of Commons Collections.
41 *
42 * @since 3.1
43 * @version $Id: AbstractInputCheckedMapDecorator.java 1435824 2013-01-20 11:41:17Z tn $
44 */
45 abstract class AbstractInputCheckedMapDecorator<K, V>
46 extends AbstractMapDecorator<K, V> {
47
48 /**
49 * Constructor only used in deserialization, do not use otherwise.
50 */
51 protected AbstractInputCheckedMapDecorator() {
52 super();
53 }
54
55 /**
56 * Constructor that wraps (not copies).
57 *
58 * @param map the map to decorate, must not be null
59 * @throws IllegalArgumentException if map is null
60 */
61 protected AbstractInputCheckedMapDecorator(final Map<K, V> map) {
62 super(map);
63 }
64
65 //-----------------------------------------------------------------------
66 /**
67 * Hook method called when a value is being set using <code>setValue</code>.
68 * <p>
69 * An implementation may validate the value and throw an exception
70 * or it may transform the value into another object.
71 * <p>
72 * This implementation returns the input value.
73 *
74 * @param value the value to check
75 * @throws UnsupportedOperationException if the map may not be changed by setValue
76 * @throws IllegalArgumentException if the specified value is invalid
77 * @throws ClassCastException if the class of the specified value is invalid
78 * @throws NullPointerException if the specified value is null and nulls are invalid
79 */
80 protected abstract V checkSetValue(V value);
81
82 /**
83 * Hook method called to determine if <code>checkSetValue</code> has any effect.
84 * <p>
85 * An implementation should return false if the <code>checkSetValue</code> method
86 * has no effect as this optimises the implementation.
87 * <p>
88 * This implementation returns <code>true</code>.
89 *
90 * @return true always
91 */
92 protected boolean isSetValueChecking() {
93 return true;
94 }
95
96 //-----------------------------------------------------------------------
97 @Override
98 public Set<Map.Entry<K, V>> entrySet() {
99 if (isSetValueChecking()) {
100 return new EntrySet(map.entrySet(), this);
101 }
102 return map.entrySet();
103 }
104
105 //-----------------------------------------------------------------------
106 /**
107 * Implementation of an entry set that checks additions via setValue.
108 */
109 @SuppressWarnings("serial")
110 private class EntrySet extends AbstractSetDecorator<Map.Entry<K, V>> {
111
112 /** The parent map */
113 private final AbstractInputCheckedMapDecorator<K, V> parent;
114
115 protected EntrySet(final Set<Map.Entry<K, V>> set, final AbstractInputCheckedMapDecorator<K, V> parent) {
116 super(set);
117 this.parent = parent;
118 }
119
120 @Override
121 public Iterator<Map.Entry<K, V>> iterator() {
122 return new EntrySetIterator(collection.iterator(), parent);
123 }
124
125 @Override
126 @SuppressWarnings("unchecked")
127 public Object[] toArray() {
128 final Object[] array = collection.toArray();
129 for (int i = 0; i < array.length; i++) {
130 array[i] = new MapEntry((Map.Entry<K, V>) array[i], parent);
131 }
132 return array;
133 }
134
135 @Override
136 @SuppressWarnings("unchecked")
137 public <T> T[] toArray(final T[] array) {
138 Object[] result = array;
139 if (array.length > 0) {
140 // we must create a new array to handle multi-threaded situations
141 // where another thread could access data before we decorate it
142 result = (Object[]) Array.newInstance(array.getClass().getComponentType(), 0);
143 }
144 result = collection.toArray(result);
145 for (int i = 0; i < result.length; i++) {
146 result[i] = new MapEntry((Map.Entry<K, V>) result[i], parent);
147 }
148
149 // check to see if result should be returned straight
150 if (result.length > array.length) {
151 return (T[]) result;
152 }
153
154 // copy back into input array to fulfil the method contract
155 System.arraycopy(result, 0, array, 0, result.length);
156 if (array.length > result.length) {
157 array[result.length] = null;
158 }
159 return array;
160 }
161 }
162
163 /**
164 * Implementation of an entry set iterator that checks additions via setValue.
165 */
166 private class EntrySetIterator extends AbstractIteratorDecorator<Map.Entry<K, V>> {
167
168 /** The parent map */
169 private final AbstractInputCheckedMapDecorator<K, V> parent;
170
171 protected EntrySetIterator(final Iterator<Map.Entry<K, V>> iterator,
172 final AbstractInputCheckedMapDecorator<K, V> parent) {
173 super(iterator);
174 this.parent = parent;
175 }
176
177 @Override
178 public Map.Entry<K, V> next() {
179 final Map.Entry<K, V> entry = iterator.next();
180 return new MapEntry(entry, parent);
181 }
182 }
183
184 /**
185 * Implementation of a map entry that checks additions via setValue.
186 */
187 private class MapEntry extends AbstractMapEntryDecorator<K, V> {
188
189 /** The parent map */
190 private final AbstractInputCheckedMapDecorator<K, V> parent;
191
192 protected MapEntry(final Map.Entry<K, V> entry, final AbstractInputCheckedMapDecorator<K, V> parent) {
193 super(entry);
194 this.parent = parent;
195 }
196
197 @Override
198 public V setValue(V value) {
199 value = parent.checkSetValue(value);
200 return entry.setValue(value);
201 }
202 }
203
204 }