001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.collections4.map;
018
019import java.io.IOException;
020import java.io.ObjectInputStream;
021import java.io.ObjectOutputStream;
022import java.io.Serializable;
023import java.util.Map;
024
025import org.apache.commons.collections4.Predicate;
026
027/**
028 * Decorates another {@code Map} to validate that additions
029 * match a specified predicate.
030 * <p>
031 * This map exists to provide validation for the decorated map.
032 * It is normally created to decorate an empty map.
033 * If an object cannot be added to the map, an IllegalArgumentException is thrown.
034 * </p>
035 * <p>
036 * One usage would be to ensure that no null keys are added to the map.
037 * </p>
038 * <pre>Map map = PredicatedSet.decorate(new HashMap(), NotNullPredicate.INSTANCE, null);</pre>
039 * <p>
040 * <strong>Note that PredicatedMap is not synchronized and is not thread-safe.</strong>
041 * If you wish to use this map from multiple threads concurrently, you must use
042 * appropriate synchronization. The simplest approach is to wrap this map
043 * using {@link java.util.Collections#synchronizedMap(Map)}. This class may throw
044 * exceptions when accessed by concurrent threads without synchronization.
045 * </p>
046 * <p>
047 * This class is Serializable from Commons Collections 3.1.
048 * </p>
049 *
050 * @param <K> the type of the keys in this map
051 * @param <V> the type of the values in this map
052 * @since 3.0
053 */
054public class PredicatedMap<K, V>
055        extends AbstractInputCheckedMapDecorator<K, V>
056        implements Serializable {
057
058    /** Serialization version */
059    private static final long serialVersionUID = 7412622456128415156L;
060
061    /**
062     * Factory method to create a predicated (validating) map.
063     * <p>
064     * If there are any elements already in the list being decorated, they
065     * are validated.
066     * </p>
067     *
068     * @param <K>  the key type
069     * @param <V>  the value type
070     * @param map  the map to decorate, must not be null
071     * @param keyPredicate  the predicate to validate the keys, null means no check
072     * @param valuePredicate  the predicate to validate to values, null means no check
073     * @return a new predicated map
074     * @throws NullPointerException if the map is null
075     * @since 4.0
076     */
077    public static <K, V> PredicatedMap<K, V> predicatedMap(final Map<K, V> map,
078                                                           final Predicate<? super K> keyPredicate,
079                                                           final Predicate<? super V> valuePredicate) {
080        return new PredicatedMap<>(map, keyPredicate, valuePredicate);
081    }
082
083    /** The key predicate to use */
084    protected final Predicate<? super K> keyPredicate;
085
086    /** The value predicate to use */
087    protected final Predicate<? super V> valuePredicate;
088
089    /**
090     * Constructor that wraps (not copies).
091     *
092     * @param map  the map to decorate, must not be null
093     * @param keyPredicate  the predicate to validate the keys, null means no check
094     * @param valuePredicate  the predicate to validate to values, null means no check
095     * @throws NullPointerException if the map is null
096     */
097    protected PredicatedMap(final Map<K, V> map, final Predicate<? super K> keyPredicate,
098                            final Predicate<? super V> valuePredicate) {
099        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}