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 = new LinkedHashSet<>();
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    * @return This object (for method chaining).
082    */
083   public DelegateBeanMap<T> filterKeys(List<String> keys) {
084      this.keys.clear();
085      this.keys.addAll(keys);
086      return this;
087   }
088
089   @Override /* Map */
090   public Object remove(Object key) {
091      keys.remove(key);
092      return null;
093   }
094
095   @Override /* BeanMap */
096   public BeanMeta<T> getMeta() {
097      return new BeanMetaFiltered<>(super.getMeta(), keys);
098   }
099
100   @Override /* Map */
101   public Set<Entry<String,Object>> entrySet() {
102      Set<Entry<String,Object>> s = Collections.newSetFromMap(new LinkedHashMap<Map.Entry<String,Object>,Boolean>());
103      for (final String key : keys) {
104         BeanMapEntry bme;
105         if (overrideValues.containsKey(key))
106            bme = new BeanMapEntryOverride(this, this.getPropertyMeta(key), overrideValues.get(key));
107         else
108            bme = this.getProperty(key);
109         if (bme == null)
110            throw new BeanRuntimeException(super.getClassMeta().getInnerClass(), "Property ''{0}'' not found on class.", key);
111         s.add(bme);
112      }
113      return s;
114   }
115
116   @Override /* BeanMap */
117   public Collection<BeanPropertyMeta> getProperties() {
118      List<BeanPropertyMeta> l = new ArrayList<>(keys.size());
119      for (final String key : keys) {
120         BeanPropertyMeta p = this.getPropertyMeta(key);
121         if (overrideValues.containsKey(key))
122            p = BeanPropertyMeta.builder(this.meta, key).overrideValue(overrideValues.get(key)).delegateFor(p).build();
123         if (p == null)
124            p = BeanPropertyMeta.builder(this.meta, key).overrideValue(null).delegateFor(p).build();
125         l.add(p);
126      }
127      return l;
128   }
129
130   final class BeanMapEntryOverride extends BeanMapEntry {
131      Object value;
132
133      BeanMapEntryOverride(BeanMap<?> bm, BeanPropertyMeta bpm, Object value) {
134         super(bm, bpm, bpm.getName());
135         this.value = value;
136      }
137
138      @Override /* Map.Entry */
139      public Object getValue() {
140         return value;
141      }
142   }
143}