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.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       *
67       * @param <K>  the key type
68       * @param <V>  the value type
69       * @param map  the map to decorate, must not be null
70       * @param keyPredicate  the predicate to validate the keys, null means no check
71       * @param valuePredicate  the predicate to validate to values, null means no check
72       * @return a new predicated map
73       * @throws NullPointerException if the map is null
74       * @since 4.0
75       */
76      public static <K, V> PredicatedMap<K, V> predicatedMap(final Map<K, V> map,
77                                                             final Predicate<? super K> keyPredicate,
78                                                             final Predicate<? super V> valuePredicate) {
79          return new PredicatedMap<>(map, keyPredicate, valuePredicate);
80      }
81  
82      /** The key predicate to use */
83      protected final Predicate<? super K> keyPredicate;
84  
85      /** The value predicate to use */
86      protected final Predicate<? super V> valuePredicate;
87  
88      /**
89       * Constructor that wraps (not copies).
90       *
91       * @param map  the map to decorate, must not be null
92       * @param keyPredicate  the predicate to validate the keys, null means no check
93       * @param valuePredicate  the predicate to validate to values, null means no check
94       * @throws NullPointerException if the map is null
95       */
96      protected PredicatedMap(final Map<K, V> map, final Predicate<? super K> keyPredicate,
97                              final Predicate<? super V> valuePredicate) {
98          super(map);
99          this.keyPredicate = keyPredicate;
100         this.valuePredicate = valuePredicate;
101         map.forEach(this::validate);
102     }
103 
104     /**
105      * Override to validate an object set into the map via {@code setValue}.
106      *
107      * @param value  the value to validate
108      * @return the value itself
109      * @throws IllegalArgumentException if invalid
110      * @since 3.1
111      */
112     @Override
113     protected V checkSetValue(final V value) {
114         if (!valuePredicate.evaluate(value)) {
115             throw new IllegalArgumentException("Cannot set value - Predicate rejected it");
116         }
117         return value;
118     }
119 
120     /**
121      * Override to only return true when there is a value transformer.
122      *
123      * @return true if a value predicate is in use
124      * @since 3.1
125      */
126     @Override
127     protected boolean isSetValueChecking() {
128         return valuePredicate != null;
129     }
130 
131     @Override
132     public V put(final K key, final V value) {
133         validate(key, value);
134         return map.put(key, value);
135     }
136 
137     @Override
138     public void putAll(final Map<? extends K, ? extends V> mapToCopy) {
139         for (final Map.Entry<? extends K, ? extends V> entry : mapToCopy.entrySet()) {
140             validate(entry.getKey(), entry.getValue());
141         }
142         super.putAll(mapToCopy);
143     }
144 
145     /**
146      * Read the map in using a custom routine.
147      *
148      * @param in  the input stream
149      * @throws IOException if an error occurs while reading from the stream
150      * @throws ClassNotFoundException if an object read from the stream can not be loaded
151      * @since 3.1
152      */
153     @SuppressWarnings("unchecked") // (1) should only fail if input stream is incorrect
154     private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
155         in.defaultReadObject();
156         map = (Map<K, V>) in.readObject(); // (1)
157     }
158 
159     /**
160      * Validates a key value pair.
161      *
162      * @param key  the key to validate
163      * @param value  the value to validate
164      * @throws IllegalArgumentException if invalid
165      */
166     protected void validate(final K key, final V value) {
167         if (keyPredicate != null && !keyPredicate.evaluate(key)) {
168             throw new IllegalArgumentException("Cannot add key - Predicate rejected it");
169         }
170         if (valuePredicate != null && !valuePredicate.evaluate(value)) {
171             throw new IllegalArgumentException("Cannot add value - Predicate rejected it");
172         }
173     }
174 
175     /**
176      * Write the map out using a custom routine.
177      *
178      * @param out  the output stream
179      * @throws IOException if an error occurs while writing to the stream
180      * @since 3.1
181      */
182     private void writeObject(final ObjectOutputStream out) throws IOException {
183         out.defaultWriteObject();
184         out.writeObject(map);
185     }
186 
187 }