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}