001// ***************************************************************************************************************************
002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
003// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
005// * with the License.  You may obtain a copy of the License at                                                              *
006// *                                                                                                                         *
007// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
008// *                                                                                                                         *
009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
011// * specific language governing permissions and limitations under the License.                                              *
012// ***************************************************************************************************************************
013package org.apache.juneau.internal;
014
015import java.util.*;
016
017import org.apache.juneau.*;
018
019/**
020 * Represents a wrapped {@link BeanMap} where property values can be overridden, removed, or reordered without
021 * affecting the underlying bean.
022 *
023 * <p>
024 * Provides the {@link #filterKeys(List)} method for specifying the keys to keep in the bean map and in what order
025 * they should appear.
026 *
027 * @param <T> The class type of the wrapped bean.
028 */
029public class DelegateBeanMap<T> extends BeanMap<T> {
030
031   private Set<String> keys = Collections.newSetFromMap(new LinkedHashMap<String,Boolean>());
032   private ObjectMap overrideValues = new ObjectMap();
033
034   /**
035    * Constructor.
036    *
037    * @param bean The bean being wrapped.
038    * @param session The bean session that created this bean map.
039    */
040   @SuppressWarnings("unchecked")
041   public
042   DelegateBeanMap(T bean, BeanSession session) {
043      super(session, bean, session.getBeanMeta((Class<T>)bean.getClass()));
044   }
045
046   /**
047    * Add a key in the next position.
048    *
049    * @param key The key to add.
050    */
051   public void addKey(String key) {
052      this.keys.add(key);
053   }
054
055   @Override /* Map */
056   public Object put(String key, Object val) {
057      this.overrideValues.put(key, val);
058      this.keys.add(key);
059      return null;
060   }
061
062   @Override /* Map */
063   public Object get(Object key) {
064      if (overrideValues.containsKey(key))
065         return overrideValues.get(key);
066      return super.get(key);
067   }
068
069   @Override /* Map */
070   public Set<String> keySet() {
071      return keys;
072   }
073
074   /**
075    * Remove all but the specified properties from this bean map.
076    *
077    * <p>
078    * This does not affect the underlying bean.
079    *
080    * @param keys The remaining keys in the bean map (in the specified order).
081    */
082   public void filterKeys(List<String> keys) {
083      this.keys.clear();
084      this.keys.addAll(keys);
085   }
086
087   @Override /* Map */
088   public Object remove(Object key) {
089      keys.remove(key);
090      return null;
091   }
092
093   @Override /* BeanMap */
094   public BeanMeta<T> getMeta() {
095      return new BeanMetaFiltered<>(super.getMeta(), keys);
096   }
097
098   @Override /* Map */
099   public Set<Entry<String,Object>> entrySet() {
100      Set<Entry<String,Object>> s = Collections.newSetFromMap(new LinkedHashMap<Map.Entry<String,Object>,Boolean>());
101      for (final String key : keys) {
102         BeanMapEntry bme;
103         if (overrideValues.containsKey(key))
104            bme = new BeanMapEntryOverride(this, this.getPropertyMeta(key), overrideValues.get(key));
105         else
106            bme = this.getProperty(key);
107         if (bme == null)
108            throw new BeanRuntimeException(super.getClassMeta().getInnerClass(), "Property ''{0}'' not found on class.", key);
109         s.add(bme);
110      }
111      return s;
112   }
113
114   @Override /* BeanMap */
115   public Collection<BeanPropertyMeta> getProperties() {
116      List<BeanPropertyMeta> l = new ArrayList<>(keys.size());
117      for (final String key : keys) {
118         BeanPropertyMeta p = this.getPropertyMeta(key);
119         if (overrideValues.containsKey(key))
120            p = BeanPropertyMeta.builder(this.meta, key).overrideValue(overrideValues.get(key)).delegateFor(p).build();
121         if (p == null)
122            throw new BeanRuntimeException(super.getClassMeta().getInnerClass(), "Property ''{0}'' not found on class.", key);
123         l.add(p);
124      }
125      return l;
126   }
127
128   final class BeanMapEntryOverride extends BeanMapEntry {
129      Object value;
130
131      BeanMapEntryOverride(BeanMap<?> bm, BeanPropertyMeta bpm, Object value) {
132         super(bm, bpm, bpm.getName());
133         this.value = value;
134      }
135
136      @Override /* Map.Entry */
137      public Object getValue() {
138         return value;
139      }
140   }
141}