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.*;
018import org.apache.juneau.collections.*;
019
020/**
021 * Represents a wrapped {@link BeanMap} where property values can be overridden, removed, or reordered without
022 * affecting the underlying bean.
023 *
024 * <p>
025 * Provides the {@link #filterKeys(List)} method for specifying the keys to keep in the bean map and in what order
026 * they should appear.
027 *
028 * @param <T> The class type of the wrapped bean.
029 */
030public class DelegateBeanMap<T> extends BeanMap<T> {
031
032   private Set<String> keys = new LinkedHashSet<>();
033   private OMap overrideValues = new OMap();
034
035   /**
036    * Constructor.
037    *
038    * @param bean The bean being wrapped.
039    * @param session The bean session that created this bean map.
040    */
041   @SuppressWarnings("unchecked")
042   public
043   DelegateBeanMap(T bean, BeanSession session) {
044      super(session, bean, session.getBeanMeta((Class<T>)bean.getClass()));
045   }
046
047   /**
048    * Add a key in the next position.
049    *
050    * @param key The key to add.
051    */
052   public void addKey(String key) {
053      this.keys.add(key);
054   }
055
056   @Override /* Map */
057   public Object put(String key, Object val) {
058      this.overrideValues.put(key, val);
059      this.keys.add(key);
060      return null;
061   }
062
063   @Override /* Map */
064   public Object get(Object key) {
065      if (overrideValues.containsKey(key))
066         return overrideValues.get(key);
067      return super.get(key);
068   }
069
070   @Override /* Map */
071   public Set<String> keySet() {
072      return keys;
073   }
074
075   /**
076    * Remove all but the specified properties from this bean map.
077    *
078    * <p>
079    * This does not affect the underlying bean.
080    *
081    * @param keys The remaining keys in the bean map (in the specified order).
082    * @return This object (for method chaining).
083    */
084   public DelegateBeanMap<T> filterKeys(List<String> keys) {
085      this.keys.clear();
086      this.keys.addAll(keys);
087      return this;
088   }
089
090   @Override /* Map */
091   public Object remove(Object key) {
092      keys.remove(key);
093      return null;
094   }
095
096   @Override /* BeanMap */
097   public BeanMeta<T> getMeta() {
098      return new BeanMetaFiltered<>(super.getMeta(), keys);
099   }
100
101   @Override /* Map */
102   public Set<Entry<String,Object>> entrySet() {
103      Set<Entry<String,Object>> s = Collections.newSetFromMap(new LinkedHashMap<Map.Entry<String,Object>,Boolean>());
104      for (final String key : keys) {
105         BeanMapEntry bme;
106         if (overrideValues.containsKey(key))
107            bme = new BeanMapEntryOverride(this, this.getPropertyMeta(key), overrideValues.get(key));
108         else
109            bme = this.getProperty(key);
110         if (bme == null)
111            throw new BeanRuntimeException(super.getClassMeta().getInnerClass(), "Property ''{0}'' not found on class.", key);
112         s.add(bme);
113      }
114      return s;
115   }
116
117   @Override /* BeanMap */
118   public Collection<BeanPropertyMeta> getProperties() {
119      List<BeanPropertyMeta> l = new ArrayList<>(keys.size());
120      for (final String key : keys) {
121         BeanPropertyMeta p = this.getPropertyMeta(key);
122         if (overrideValues.containsKey(key))
123            p = BeanPropertyMeta.builder(this.meta, key).overrideValue(overrideValues.get(key)).delegateFor(p).build();
124         if (p == null)
125            p = BeanPropertyMeta.builder(this.meta, key).overrideValue(null).delegateFor(p).build();
126         l.add(p);
127      }
128      return l;
129   }
130
131   final class BeanMapEntryOverride extends BeanMapEntry {
132      Object value;
133
134      BeanMapEntryOverride(BeanMap<?> bm, BeanPropertyMeta bpm, Object value) {
135         super(bm, bpm, bpm.getName());
136         this.value = value;
137      }
138
139      @Override /* Map.Entry */
140      public Object getValue() {
141         return value;
142      }
143   }
144}