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