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       * </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 }