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