View Javadoc
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       * Implementation of 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(this.decorated().iterator(), parent);
69          }
70  
71          @Override
72          @SuppressWarnings("unchecked")
73          public Object[] toArray() {
74              final Object[] array = this.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 = this.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      * Implementation of 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      * Implementation of 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 }