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