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     *
067     * @param <K>  the key type
068     * @param <V>  the value type
069     * @param map  the map to decorate, must not be null
070     * @param keyPredicate  the predicate to validate the keys, null means no check
071     * @param valuePredicate  the predicate to validate to values, null means no check
072     * @return a new predicated map
073     * @throws NullPointerException if the map is null
074     * @since 4.0
075     */
076    public static <K, V> PredicatedMap<K, V> predicatedMap(final Map<K, V> map,
077                                                           final Predicate<? super K> keyPredicate,
078                                                           final Predicate<? super V> valuePredicate) {
079        return new PredicatedMap<>(map, keyPredicate, valuePredicate);
080    }
081
082    /** The key predicate to use */
083    protected final Predicate<? super K> keyPredicate;
084
085    /** The value predicate to use */
086    protected final Predicate<? super V> valuePredicate;
087
088    /**
089     * Constructor that wraps (not copies).
090     *
091     * @param map  the map to decorate, must not be null
092     * @param keyPredicate  the predicate to validate the keys, null means no check
093     * @param valuePredicate  the predicate to validate to values, null means no check
094     * @throws NullPointerException if the map is null
095     */
096    protected PredicatedMap(final Map<K, V> map, final Predicate<? super K> keyPredicate,
097                            final Predicate<? super V> valuePredicate) {
098        super(map);
099        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}