PredicatedMap.java

  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. import java.io.IOException;
  19. import java.io.ObjectInputStream;
  20. import java.io.ObjectOutputStream;
  21. import java.io.Serializable;
  22. import java.util.Map;

  23. import org.apache.commons.collections4.Predicate;

  24. /**
  25.  * Decorates another {@code Map} to validate that additions
  26.  * match a specified predicate.
  27.  * <p>
  28.  * This map exists to provide validation for the decorated map.
  29.  * It is normally created to decorate an empty map.
  30.  * If an object cannot be added to the map, an IllegalArgumentException is thrown.
  31.  * </p>
  32.  * <p>
  33.  * One usage would be to ensure that no null keys are added to the map.
  34.  * </p>
  35.  * <pre>Map map = PredicatedSet.decorate(new HashMap(), NotNullPredicate.INSTANCE, null);</pre>
  36.  * <p>
  37.  * <strong>Note that PredicatedMap is not synchronized and is not thread-safe.</strong>
  38.  * If you wish to use this map from multiple threads concurrently, you must use
  39.  * appropriate synchronization. The simplest approach is to wrap this map
  40.  * using {@link java.util.Collections#synchronizedMap(Map)}. This class may throw
  41.  * exceptions when accessed by concurrent threads without synchronization.
  42.  * </p>
  43.  * <p>
  44.  * This class is Serializable from Commons Collections 3.1.
  45.  * </p>
  46.  *
  47.  * @param <K> the type of the keys in this map
  48.  * @param <V> the type of the values in this map
  49.  * @since 3.0
  50.  */
  51. public class PredicatedMap<K, V>
  52.         extends AbstractInputCheckedMapDecorator<K, V>
  53.         implements Serializable {

  54.     /** Serialization version */
  55.     private static final long serialVersionUID = 7412622456128415156L;

  56.     /**
  57.      * Factory method to create a predicated (validating) map.
  58.      * <p>
  59.      * If there are any elements already in the list being decorated, they
  60.      * are validated.
  61.      * </p>
  62.      *
  63.      * @param <K>  the key type
  64.      * @param <V>  the value type
  65.      * @param map  the map to decorate, must not be null
  66.      * @param keyPredicate  the predicate to validate the keys, null means no check
  67.      * @param valuePredicate  the predicate to validate to values, null means no check
  68.      * @return a new predicated map
  69.      * @throws NullPointerException if the map is null
  70.      * @since 4.0
  71.      */
  72.     public static <K, V> PredicatedMap<K, V> predicatedMap(final Map<K, V> map,
  73.                                                            final Predicate<? super K> keyPredicate,
  74.                                                            final Predicate<? super V> valuePredicate) {
  75.         return new PredicatedMap<>(map, keyPredicate, valuePredicate);
  76.     }

  77.     /** The key predicate to use */
  78.     protected final Predicate<? super K> keyPredicate;

  79.     /** The value predicate to use */
  80.     protected final Predicate<? super V> valuePredicate;

  81.     /**
  82.      * Constructor that wraps (not copies).
  83.      *
  84.      * @param map  the map to decorate, must not be null
  85.      * @param keyPredicate  the predicate to validate the keys, null means no check
  86.      * @param valuePredicate  the predicate to validate to values, null means no check
  87.      * @throws NullPointerException if the map is null
  88.      */
  89.     protected PredicatedMap(final Map<K, V> map, final Predicate<? super K> keyPredicate,
  90.                             final Predicate<? super V> valuePredicate) {
  91.         super(map);
  92.         this.keyPredicate = keyPredicate;
  93.         this.valuePredicate = valuePredicate;
  94.         map.forEach(this::validate);
  95.     }

  96.     /**
  97.      * Override to validate an object set into the map via {@code setValue}.
  98.      *
  99.      * @param value  the value to validate
  100.      * @return the value itself
  101.      * @throws IllegalArgumentException if invalid
  102.      * @since 3.1
  103.      */
  104.     @Override
  105.     protected V checkSetValue(final V value) {
  106.         if (!valuePredicate.test(value)) {
  107.             throw new IllegalArgumentException("Cannot set value - Predicate rejected it");
  108.         }
  109.         return value;
  110.     }

  111.     /**
  112.      * Override to only return true when there is a value transformer.
  113.      *
  114.      * @return true if a value predicate is in use
  115.      * @since 3.1
  116.      */
  117.     @Override
  118.     protected boolean isSetValueChecking() {
  119.         return valuePredicate != null;
  120.     }

  121.     @Override
  122.     public V put(final K key, final V value) {
  123.         validate(key, value);
  124.         return map.put(key, value);
  125.     }

  126.     @Override
  127.     public void putAll(final Map<? extends K, ? extends V> mapToCopy) {
  128.         for (final Map.Entry<? extends K, ? extends V> entry : mapToCopy.entrySet()) {
  129.             validate(entry.getKey(), entry.getValue());
  130.         }
  131.         super.putAll(mapToCopy);
  132.     }

  133.     /**
  134.      * Deserializes the map in using a custom routine.
  135.      *
  136.      * @param in  the input stream
  137.      * @throws IOException if an error occurs while reading from the stream
  138.      * @throws ClassNotFoundException if an object read from the stream cannot be loaded
  139.      * @since 3.1
  140.      */
  141.     @SuppressWarnings("unchecked") // (1) should only fail if input stream is incorrect
  142.     private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
  143.         in.defaultReadObject();
  144.         map = (Map<K, V>) in.readObject(); // (1)
  145.     }

  146.     /**
  147.      * Validates a key value pair.
  148.      *
  149.      * @param key  the key to validate
  150.      * @param value  the value to validate
  151.      * @throws IllegalArgumentException if invalid
  152.      */
  153.     protected void validate(final K key, final V value) {
  154.         if (keyPredicate != null && !keyPredicate.test(key)) {
  155.             throw new IllegalArgumentException("Cannot add key - Predicate rejected it");
  156.         }
  157.         if (valuePredicate != null && !valuePredicate.test(value)) {
  158.             throw new IllegalArgumentException("Cannot add value - Predicate rejected it");
  159.         }
  160.     }

  161.     /**
  162.      * Serializes this object to an ObjectOutputStream.
  163.      *
  164.      * @param out the target ObjectOutputStream.
  165.      * @throws IOException thrown when an I/O errors occur writing to the target stream.
  166.      * @since 3.1
  167.      */
  168.     private void writeObject(final ObjectOutputStream out) throws IOException {
  169.         out.defaultWriteObject();
  170.         out.writeObject(map);
  171.     }

  172. }