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}