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.io.IOException;
20 import java.io.ObjectInputStream;
21 import java.io.ObjectOutputStream;
22 import java.io.Serializable;
23 import java.util.Iterator;
24 import java.util.Map;
25
26 import org.apache.commons.collections.Predicate;
27
28 /**
29 * Decorates another <code>Map</code> to validate that additions
30 * match a specified predicate.
31 * <p>
32 * This map exists to provide validation for the decorated map.
33 * It is normally created to decorate an empty map.
34 * If an object cannot be added to the map, an IllegalArgumentException is thrown.
35 * <p>
36 * One usage would be to ensure that no null keys are added to the map.
37 * <pre>Map map = PredicatedSet.decorate(new HashMap(), NotNullPredicate.INSTANCE, null);</pre>
38 * <p>
39 * <strong>Note that PredicatedMap is not synchronized and is not thread-safe.</strong>
40 * If you wish to use this map from multiple threads concurrently, you must use
41 * appropriate synchronization. The simplest approach is to wrap this map
42 * using {@link java.util.Collections#synchronizedMap(Map)}. This class may throw
43 * exceptions when accessed by concurrent threads without synchronization.
44 * <p>
45 * This class is Serializable from Commons Collections 3.1.
46 *
47 * @since 3.0
48 * @version $Id: PredicatedMap.java 1435824 2013-01-20 11:41:17Z tn $
49 */
50 public class PredicatedMap<K, V>
51 extends AbstractInputCheckedMapDecorator<K, V>
52 implements Serializable {
53
54 /** Serialization version */
55 private static final long serialVersionUID = 7412622456128415156L;
56
57 /** The key predicate to use */
58 protected final Predicate<? super K> keyPredicate;
59
60 /** The value predicate to use */
61 protected final Predicate<? super V> valuePredicate;
62
63 /**
64 * Factory method to create a predicated (validating) map.
65 * <p>
66 * If there are any elements already in the list being decorated, they
67 * are validated.
68 *
69 * @param <K> the key type
70 * @param <V> the value type
71 * @param map the map to decorate, must not be null
72 * @param keyPredicate the predicate to validate the keys, null means no check
73 * @param valuePredicate the predicate to validate to values, null means no check
74 * @return a new predicated map
75 * @throws IllegalArgumentException if the map is null
76 */
77 public static <K, V> PredicatedMap<K, V> predicatedMap(final Map<K, V> map,
78 final Predicate<? super K> keyPredicate,
79 final Predicate<? super V> valuePredicate) {
80 return new PredicatedMap<K, V>(map, keyPredicate, valuePredicate);
81 }
82
83 //-----------------------------------------------------------------------
84 /**
85 * Constructor that wraps (not copies).
86 *
87 * @param map the map to decorate, must not be null
88 * @param keyPredicate the predicate to validate the keys, null means no check
89 * @param valuePredicate the predicate to validate to values, null means no check
90 * @throws IllegalArgumentException if the map is null
91 */
92 protected PredicatedMap(final Map<K, V> map, final Predicate<? super K> keyPredicate,
93 final Predicate<? super V> valuePredicate) {
94 super(map);
95 this.keyPredicate = keyPredicate;
96 this.valuePredicate = valuePredicate;
97
98 final Iterator<Map.Entry<K, V>> it = map.entrySet().iterator();
99 while (it.hasNext()) {
100 final Map.Entry<K, V> entry = it.next();
101 validate(entry.getKey(), entry.getValue());
102 }
103 }
104
105 //-----------------------------------------------------------------------
106 /**
107 * Write the map out using a custom routine.
108 *
109 * @param out the output stream
110 * @throws IOException
111 * @since 3.1
112 */
113 private void writeObject(final ObjectOutputStream out) throws IOException {
114 out.defaultWriteObject();
115 out.writeObject(map);
116 }
117
118 /**
119 * Read the map in using a custom routine.
120 *
121 * @param in the input stream
122 * @throws IOException
123 * @throws ClassNotFoundException
124 * @since 3.1
125 */
126 @SuppressWarnings("unchecked") // (1) should only fail if input stream is incorrect
127 private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
128 in.defaultReadObject();
129 map = (Map<K, V>) in.readObject(); // (1)
130 }
131
132 //-----------------------------------------------------------------------
133 /**
134 * Validates a key value pair.
135 *
136 * @param key the key to validate
137 * @param value the value to validate
138 * @throws IllegalArgumentException if invalid
139 */
140 protected void validate(final K key, final V value) {
141 if (keyPredicate != null && keyPredicate.evaluate(key) == false) {
142 throw new IllegalArgumentException("Cannot add key - Predicate rejected it");
143 }
144 if (valuePredicate != null && valuePredicate.evaluate(value) == false) {
145 throw new IllegalArgumentException("Cannot add value - Predicate rejected it");
146 }
147 }
148
149 /**
150 * Override to validate an object set into the map via <code>setValue</code>.
151 *
152 * @param value the value to validate
153 * @return the value itself
154 * @throws IllegalArgumentException if invalid
155 * @since 3.1
156 */
157 @Override
158 protected V checkSetValue(final V value) {
159 if (valuePredicate.evaluate(value) == false) {
160 throw new IllegalArgumentException("Cannot set value - Predicate rejected it");
161 }
162 return value;
163 }
164
165 /**
166 * Override to only return true when there is a value transformer.
167 *
168 * @return true if a value predicate is in use
169 * @since 3.1
170 */
171 @Override
172 protected boolean isSetValueChecking() {
173 return valuePredicate != null;
174 }
175
176 //-----------------------------------------------------------------------
177 @Override
178 public V put(final K key, final V value) {
179 validate(key, value);
180 return map.put(key, value);
181 }
182
183 @Override
184 public void putAll(final Map<? extends K, ? extends V> mapToCopy) {
185 for (final Map.Entry<? extends K, ? extends V> entry : mapToCopy.entrySet()) {
186 validate(entry.getKey(), entry.getValue());
187 }
188 super.putAll(mapToCopy);
189 }
190
191 }