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.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 }