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