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