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.chain.web;
018
019import java.util.ArrayList;
020import java.util.Collection;
021import java.util.Enumeration;
022import java.util.HashSet;
023import java.util.Iterator;
024import java.util.List;
025import java.util.Map;
026import java.util.Objects;
027import java.util.Set;
028import java.util.function.Function;
029import java.util.function.Supplier;
030
031/**
032 * Implementation of {@code Map} for immutable parameters with a
033 * parameter-provider.
034 *
035 * @param <P> the type of the parameter-provider
036 * @param <T> the type of results supplied by this parameters
037 *
038 * @author Graff Stefan
039 * @since Chain 1.3
040 */
041public class ParameterMap<P, T> implements Map<String, T> {
042
043    /**
044     * The parameter-provider.
045     */
046    private final P parameter;
047
048    /**
049     * Function to return the value of a parameter as an
050     * {@link String}, or {@code null} if no parameter of the
051     * given name exists.
052     */
053    private final Function<String, T> valueFunction;
054
055    /**
056     * Supplier to return an {@link Enumeration} of {@link String}
057     * objects containing the names of the parameters contained
058     * in this object.
059     */
060    private final Supplier<Enumeration<String>> namesSupplier;
061
062    /**
063     * The constructor for an immutable parameter-map.
064     *
065     * @param parameter the parameter-provider
066     * @param valueFunction Function to return the value of a parameter
067     * @param namesSupplier Supplier to return all names of the parameter
068     */
069    public ParameterMap(final P parameter, final Function<String, T> valueFunction,
070            final Supplier<Enumeration<String>> namesSupplier) {
071
072        this.parameter = parameter;
073        this.valueFunction = valueFunction;
074        this.namesSupplier = namesSupplier;
075    }
076
077    /**
078     * Removes all of the mappings from this parameter-map.
079     * The parameter-map will be empty after this call returns.
080     *
081     * @throws UnsupportedOperationException because it is an
082     *         immutable parameter-map
083     */
084    @Override
085    public void clear() {
086        throw new UnsupportedOperationException();
087    }
088
089    /**
090     * Returns {@code true} if this parameter-map contains a mapping
091     * for the specified key.
092     *
093     * @param key The key whose presence in this parameter-map is to
094     *            be tested
095     *
096     * @return {@code true} if this parameter-map contains a mapping
097     *         for the specified key.
098     */
099    @Override
100    public boolean containsKey(Object key) {
101        return valueFunction.apply(key(key)) != null;
102    }
103
104    /**
105     * Returns {@code true} if this parameter-map maps one or more keys
106     * to the specified value.
107     *
108     * @param value value whose presence in this parameter-map is to be
109     *        tested
110     *
111     * @return {@code true} if this parameter-map maps one or more keys
112     *         to the specified value
113     */
114    @Override
115    public boolean containsValue(Object value) {
116        final Enumeration<String> keys = namesSupplier.get();
117        while (keys.hasMoreElements()) {
118            final T next = valueFunction.apply(keys.nextElement());
119            if (value.equals(next)) {
120                return true;
121            }
122        }
123        return false;
124    }
125
126    /**
127     * Returns a {@link Set} view of the mappings contained in this
128     * parameter-map. The set is not backed by the parameter-map, so
129     * changes to the parameter-map are not reflected in the set,
130     * and vice-versa.
131     *
132     * @return a set view of the mappings contained in this
133     *         parameter-map
134     */
135    @Override
136    public Set<Map.Entry<String, T>> entrySet() {
137        return entrySet(false);
138    }
139
140    /**
141     * Returns the value to which the specified key is mapped,
142     * or {@code null} if this parameter-map contains no mapping
143     * for the key.
144     *
145     * <p>A return value of {@code null} does not <i>necessarily</i>
146     * indicate that the parameter-map contains no mapping for the key;
147     * it's also possible that the parameter-map explicitly maps the key
148     * to {@code null}. The {@link #containsKey containsKey} operation
149     * may be used to distinguish these two cases.</p>
150     *
151     * @param key the key whose associated value is to be returned
152     *
153     * @return the value to which the specified key is mapped, or
154     *         {@code null} if this parameter-map contains no mapping for
155     *         the key
156     *
157     * @see #put(Object, Object)
158     */
159    @Override
160    public T get(Object key) {
161        return valueFunction.apply(key(key));
162    }
163
164    /**
165     * Returns {@code true} if this parameter-map contains no
166     * key-value mappings.
167     *
168     * @return {@code true} if this parameter-map contains no
169     *         key-value mappings
170     */
171    @Override
172    public boolean isEmpty() {
173        return !namesSupplier.get().hasMoreElements();
174    }
175
176    /**
177     * Returns a {@link Set} view of the keys contained in this
178     * parameter-map. The set is not backed by the parameter-map, so
179     * changes to the parameter-map are not reflected in the set, and
180     * vice-versa.
181     *
182     * @return a set view of the keys contained in this parameter-map
183     */
184    @Override
185    public Set<String> keySet() {
186        final Set<String> set = new HashSet<>();
187        final Enumeration<String> keys = namesSupplier.get();
188        while (keys.hasMoreElements()) {
189            set.add(keys.nextElement());
190        }
191        return set;
192    }
193
194    /**
195     * Associates the specified value with the specified key in this
196     * parameter-map. If the parameter-map previously contained a
197     * mapping for the key, the old value is replaced.
198     *
199     * @param key key with which the specified value is to be associated
200     * @param value value to be associated with the specified key
201     *
202     * @return the previous value associated with {@code key}, or
203     *         {@code null} if there was no mapping for {@code key}.
204     *         (A {@code null} return can also indicate that the
205     *         parameter-map previously associated {@code null} with
206     *         {@code key}.)
207     *
208     * @throws UnsupportedOperationException because it is an
209     *         immutable parameter-map
210     */
211    @Override
212    public T put(String key, T value) {
213        throw new UnsupportedOperationException();
214    }
215
216    /**
217     * Copies all of the mappings from the specified map to this
218     * parameter-map. These mappings will replace any mappings that
219     * this parameter-map had for any of the keys currently in the
220     * specified map.
221     *
222     * @param map mappings to be stored in this parameter-map
223     *
224     * @throws NullPointerException if the specified map is null
225     * @throws UnsupportedOperationException because it is an
226     *         immutable parameter-map
227     */
228    @Override
229    public void putAll(Map<? extends String, ? extends T> map) {
230        throw new UnsupportedOperationException();
231    }
232
233    /**
234     * Removes the mapping for the specified key from this
235     * parameter-map if present.
236     *
237     * @param key key whose mapping is to be removed from the
238     *        parameter-map
239     *
240     * @return the previous value associated with {@code key}, or
241     *         {@code null} if there was no mapping for {@code key}.
242     *         (A {@code null} return can also indicate that the
243     *         parameter-map previously associated {@code null} with
244     *         {@code key}.)
245     *
246     * @throws UnsupportedOperationException because it is an
247     *         immutable parameter-map
248     */
249    @Override
250    public T remove(Object key) {
251        throw new UnsupportedOperationException();
252    }
253
254    /**
255     * Returns the number of key-value mappings in this parameter-map.
256     *
257     * @return the number of key-value mappings in this parameter-map
258     */
259    @Override
260    public int size() {
261        int n = 0;
262        final Enumeration<String> keys = namesSupplier.get();
263        while (keys.hasMoreElements()) {
264            keys.nextElement();
265            n++;
266        }
267        return n;
268    }
269
270    /**
271     * Returns a {@link Collection} view of the values contained in
272     * this parameter-map. The collection is not backed by the
273     * parameter-map, so changes to the parameter-map are not
274     * reflected in the collection, and vice-versa.
275     *
276     * @return a view of the values contained in this parameter-map
277     */
278    @Override
279    public Collection<T> values() {
280        final List<T> list = new ArrayList<>();
281        final Enumeration<String> keys = namesSupplier.get();
282        while (keys.hasMoreElements()) {
283            list.add(valueFunction.apply(keys.nextElement()));
284        }
285        return list;
286    }
287
288    /**
289     * Returns the hash code value for this parameter-map. The
290     * hash code of a parameter-map is defined to be the sum of
291     * the hash codes of each entry in the parameter-map's
292     * {@code entrySet()} view. This ensures that {@code m1.equals(m2)}
293     * implies that {@code m1.hashCode()==m2.hashCode()} for any two
294     * parameter-maps {@code m1} and {@code m2}, as required by the
295     * general contract of {@link Object#hashCode}.
296     *
297     * @implSpec
298     * This implementation calls the {@code hashCode()} from the
299     * parameter-provider.
300     *
301     * @return the hash code value for this parameter-map
302     */
303    @Override
304    public int hashCode() {
305        return getParameter().hashCode();
306    }
307
308    /**
309     * Compares the specified object with this parameter-map for equality.
310     * Returns {@code true} if the given object is also a parameter-map
311     * and the two parameter-maps represent the same mappings. More formally,
312     * two parameter-maps {@code m1} and {@code m2} represent the same
313     * mappings if {@code m1.entrySet().equals(m2.entrySet())}.
314     *
315     * @implSpec
316     * This implementation first checks if the specified object is this
317     * parameter-map; if so it returns {@code true}. Then, it checks if
318     * the specified object is the identical class this parameter-map; if
319     * not, it returns {@code false}. If so, it calls the {@code equals()}
320     * from the parameter-provider and returns its return-code.
321     *
322     * @param obj object to be compared for equality with this
323     *        parameter-map
324     *
325     * @return {@code true} if the specified object is equal to this
326     *         parameter-map
327     */
328    @Override
329    public boolean equals(Object obj) {
330        if (this == obj) {
331            return true;
332        }
333        if (obj == null || getClass() != obj.getClass()) {
334            return false;
335        }
336        final ParameterMap<?, ?> other = (ParameterMap<?, ?>) obj;
337        return Objects.equals(getParameter(), other.getParameter());
338    }
339
340    /**
341     * Returns a string representation of this parameter-map.
342     * The string representation consists of a list of key-value
343     * mappings in the order returned by the parameter-map's
344     * {@code entrySet} view's iterator, enclosed in braces
345     * ({@code "{}"}). Adjacent mappings are separated by the
346     * characters {@code ", "} (comma and space). Each key-value
347     * mapping is rendered as the key followed by an equals sign
348     * ({@code "="}) followed by the associated value. Keys and
349     * values are converted to strings as by
350     * {@link String#valueOf(Object)}.
351     *
352     * @return a string representation of this parameter-map
353     */
354    public String toString() {
355        final Iterator<Entry<String, T>> entries = entrySet().iterator();
356        if (entries.hasNext()) {
357            return "{}";
358        }
359
360        final StringBuilder sb = new StringBuilder();
361        sb.append('{');
362        for (;;) {
363            final Entry<String, T> entrie = entries.next();
364            final String key = entrie.getKey();
365            final T value = entrie.getValue();
366            sb
367                .append(key)
368                .append('=')
369                .append(value);
370
371            if (entries.hasNext()) {
372                sb.append(',').append(' ');
373            } else {
374                return sb.append('}').toString();
375            }
376        }
377    }
378
379    /**
380     * Returns the parameter-class.
381     *
382     * @return the parameter-class
383     */
384    protected P getParameter() {
385        return parameter;
386    }
387
388    /**
389     * Returns the Function to return the value of a parameter as an
390     * {@link String}, or {@code null} if no parameter of the given
391     * name exists.
392     *
393     * @return the Function to return the value
394     */
395    protected  Function<String, T> getValueFunction() {
396        return valueFunction;
397    }
398
399    /**
400     * Returns the Supplier to return an {@link Enumeration} of
401     * {@link String} objects containing the names of the parameters
402     * contained in this object.
403     *
404     * @return Supplier to return an {@link Enumeration} with all
405     *         names of the parameters
406     */
407    protected Supplier<Enumeration<String>> getNamesSupplier() {
408        return namesSupplier;
409    }
410
411    /**
412     * Returns a {@link Set} view of the mappings contained
413     * in this map.
414     *
415     * @param modifiable Whether the entries should allow
416     *                   modification or not
417     *
418     * @return a set view of the mappings contained in this map
419     */
420    protected Set<Map.Entry<String, T>> entrySet(boolean modifiable) {
421        final Set<Map.Entry<String, T>> set = new HashSet<>();
422        final Enumeration<String> keys = namesSupplier.get();
423        while (keys.hasMoreElements()) {
424            final String key = keys.nextElement();
425            set.add(new MapEntry<>(key, valueFunction.apply(key), modifiable));
426        }
427        return set;
428    }
429
430    /**
431     * Converts the {@code key} to a {@link String}.
432     *
433     * @param key the key
434     *
435     * @return the key as {@link String}
436     */
437    protected static String key(Object key) {
438        if (key == null) {
439            throw new IllegalArgumentException();
440        } else if (key instanceof String) {
441            return (String) key;
442        } else {
443            return key.toString();
444        }
445    }
446}