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.collections4.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.collections4.iterators.AbstractIteratorDecorator;
25 import org.apache.commons.collections4.keyvalue.AbstractMapEntryDecorator;
26 import org.apache.commons.collections4.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 * <p>
35 * Special hook methods are provided that are called when objects are added to
36 * the map. By overriding these methods, the input can be validated or manipulated.
37 * In addition to the main map methods, the entrySet is also affected, which is
38 * the hardest part of writing map implementations.
39 * </p>
40 * <p>
41 * This class is package-scoped, and may be withdrawn or replaced in future
42 * versions of Commons Collections.
43 * </p>
44 *
45 * @since 3.1
46 */
47 abstract class AbstractInputCheckedMapDecorator<K, V>
48 extends AbstractMapDecorator<K, V> {
49
50 /**
51 * Implements an entry set that checks additions via setValue.
52 */
53 private final class EntrySet extends AbstractSetDecorator<Map.Entry<K, V>> {
54
55 /** Generated serial version ID. */
56 private static final long serialVersionUID = 4354731610923110264L;
57
58 /** The parent map */
59 private final AbstractInputCheckedMapDecorator<K, V> parent;
60
61 protected EntrySet(final Set<Map.Entry<K, V>> set, final AbstractInputCheckedMapDecorator<K, V> parent) {
62 super(set);
63 this.parent = parent;
64 }
65
66 @Override
67 public Iterator<Map.Entry<K, V>> iterator() {
68 return new EntrySetIterator(decorated().iterator(), parent);
69 }
70
71 @Override
72 @SuppressWarnings("unchecked")
73 public Object[] toArray() {
74 final Object[] array = decorated().toArray();
75 for (int i = 0; i < array.length; i++) {
76 array[i] = new MapEntry((Map.Entry<K, V>) array[i], parent);
77 }
78 return array;
79 }
80
81 @Override
82 @SuppressWarnings("unchecked")
83 public <T> T[] toArray(final T[] array) {
84 Object[] result = array;
85 if (array.length > 0) {
86 // we must create a new array to handle multithreaded situations
87 // where another thread could access data before we decorate it
88 result = (Object[]) Array.newInstance(array.getClass().getComponentType(), 0);
89 }
90 result = decorated().toArray(result);
91 for (int i = 0; i < result.length; i++) {
92 result[i] = new MapEntry((Map.Entry<K, V>) result[i], parent);
93 }
94
95 // check to see if result should be returned straight
96 if (result.length > array.length) {
97 return (T[]) result;
98 }
99
100 // copy back into input array to fulfil the method contract
101 System.arraycopy(result, 0, array, 0, result.length);
102 if (array.length > result.length) {
103 array[result.length] = null;
104 }
105 return array;
106 }
107 }
108
109 /**
110 * Implements an entry set iterator that checks additions via setValue.
111 */
112 private final class EntrySetIterator extends AbstractIteratorDecorator<Map.Entry<K, V>> {
113
114 /** The parent map */
115 private final AbstractInputCheckedMapDecorator<K, V> parent;
116
117 protected EntrySetIterator(final Iterator<Map.Entry<K, V>> iterator,
118 final AbstractInputCheckedMapDecorator<K, V> parent) {
119 super(iterator);
120 this.parent = parent;
121 }
122
123 @Override
124 public Map.Entry<K, V> next() {
125 final Map.Entry<K, V> entry = getIterator().next();
126 return new MapEntry(entry, parent);
127 }
128 }
129
130 /**
131 * Implements a map entry that checks additions via setValue.
132 */
133 private final class MapEntry extends AbstractMapEntryDecorator<K, V> {
134
135 /** The parent map */
136 private final AbstractInputCheckedMapDecorator<K, V> parent;
137
138 protected MapEntry(final Map.Entry<K, V> entry, final AbstractInputCheckedMapDecorator<K, V> parent) {
139 super(entry);
140 this.parent = parent;
141 }
142
143 @Override
144 public V setValue(V value) {
145 value = parent.checkSetValue(value);
146 return getMapEntry().setValue(value);
147 }
148 }
149
150 /**
151 * Constructor only used in deserialization, do not use otherwise.
152 */
153 protected AbstractInputCheckedMapDecorator() {
154 }
155
156 /**
157 * Constructor that wraps (not copies).
158 *
159 * @param map the map to decorate, must not be null
160 * @throws NullPointerException if map is null
161 */
162 protected AbstractInputCheckedMapDecorator(final Map<K, V> map) {
163 super(map);
164 }
165
166 /**
167 * Hook method called when a value is being set using {@code setValue}.
168 * <p>
169 * An implementation may validate the value and throw an exception
170 * or it may transform the value into another object.
171 * </p>
172 * <p>
173 * This implementation returns the input value.
174 * </p>
175 *
176 * @param value the value to check
177 * @return the input value
178 * @throws UnsupportedOperationException if the map may not be changed by setValue
179 * @throws IllegalArgumentException if the specified value is invalid
180 * @throws ClassCastException if the class of the specified value is invalid
181 * @throws NullPointerException if the specified value is null and nulls are invalid
182 */
183 protected abstract V checkSetValue(V value);
184
185 @Override
186 public Set<Map.Entry<K, V>> entrySet() {
187 if (isSetValueChecking()) {
188 return new EntrySet(map.entrySet(), this);
189 }
190 return map.entrySet();
191 }
192
193 /**
194 * Hook method called to determine if {@code checkSetValue} has any effect.
195 * <p>
196 * An implementation should return false if the {@code checkSetValue} method
197 * has no effect as this optimizes the implementation.
198 * <p>
199 * This implementation returns {@code true}.
200 *
201 * @return true always
202 */
203 protected boolean isSetValueChecking() {
204 return true;
205 }
206
207 }