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