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; 018 019import static java.util.Collections.*; 020import static org.apache.juneau.commons.function.Suppliers.*; 021import static org.apache.juneau.commons.utils.CollectionUtils.*; 022import static org.apache.juneau.commons.utils.Shorts.*; 023import static org.apache.juneau.commons.utils.StringUtils.*; 024 025import java.util.*; 026import java.util.function.*; 027 028import org.apache.juneau.commons.collections.*; 029import org.apache.juneau.commons.conversion.*; 030import org.apache.juneau.commons.function.*; 031import org.apache.juneau.commons.reflect.*; 032 033/** 034 * A one-time-use non-thread-safe object that's meant to be used once and then thrown away. 035 * 036 * <h5 class='section'>Notes:</h5><ul> 037 * <li class='warn'>This class is not typically thread safe. 038 * </ul> 039 * 040 */ 041@SuppressWarnings({ 042 "java:S1192" // Duplicated literals (argument/property names) read more clearly inline than as constants 043}) 044public abstract class ContextSession { 045 046 /** 047 * Builder class. 048 */ 049 @SuppressWarnings({ 050 "java:S119" // 'SELF' (CRTP self-type) is intentional and clearer than a single-letter name. 051 }) 052 public abstract static class Builder<SELF extends Builder<SELF>> { 053 054 /** 055 * Returns this object cast to the self (CRTP) type. 056 * 057 * @return This object. 058 */ 059 @SuppressWarnings({ 060 "unchecked" // CRTP self-type cast is safe by construction. 061 }) 062 protected final SELF self() { 063 return (SELF)this; 064 } 065 066 private Boolean debug; 067 private boolean unmodifiable; 068 private final Context ctx; 069 private final Memoizer<LinkedHashMap<String,Object>> properties; 070 071 /** 072 * Convenience method for converting values using {@link BasicConverter}. 073 * 074 * @param <T> The target type. 075 * @param value The value to convert. 076 * <br>Can be <jk>null</jk> (returns <jk>null</jk>). 077 * @param type The target class. 078 * @return The converted value, or <jk>null</jk> if the input value was <jk>null</jk>. 079 */ 080 protected static <T> T cvt(Object value, Class<T> type) { 081 return BasicConverter.INSTANCE.to(value, null, null, type); 082 } 083 084 /** 085 * Constructor. 086 * 087 * @param ctx The context creating this session. 088 * <br>Cannot be <jk>null</jk>. 089 */ 090 protected Builder(Context ctx) { 091 this.ctx = reqnn("ctx", ctx); 092 this.properties = memoizer(LinkedHashMap::new); 093 } 094 095 /** 096 * Applies a consumer to this builder if it's the specified type. 097 * 098 * @param <T> The expected type. 099 * @param type The expected type. 100 * <br>Cannot be <jk>null</jk>. 101 * @param apply The consumer to apply. 102 * <br>Cannot be <jk>null</jk>. 103 * @return This object. 104 */ 105 public <T> SELF apply(Class<T> type, Consumer<T> apply) { 106 if (reqnn("type", type).isInstance(this)) 107 reqnn("apply", apply).accept(type.cast(this)); 108 return self(); 109 } 110 111 /** 112 * Build the object. 113 * 114 * @return The built object. 115 */ 116 public abstract ContextSession build(); 117 118 /** 119 * Debug mode. 120 * 121 * <p> 122 * Enables the following additional information during parsing: 123 * <ul> 124 * <li> When bean setters throws exceptions, the exception includes the object stack information in order to determine how that method was invoked. 125 * </ul> 126 * 127 * <p> 128 * If not specified, defaults to {@link Context.Builder#debug()}. 129 * 130 * <h5 class='section'>See Also:</h5><ul> 131 * <li class='ja'>{@link MarshalledConfig#debug()} 132 * <li class='jm'>{@link Context.Builder#debug()} 133 * 134 * @param value 135 * The new value for this property. 136 * <br>If <jk>null</jk>, defaults to {@link Context#isDebug()}. 137 * @return This object. 138 */ 139 public SELF debug(Boolean value) { 140 debug = value; 141 return self(); 142 } 143 144 /** 145 * Session properties. 146 * 147 * <p> 148 * Session properties are generic key-value pairs that can be passed through the session and made 149 * available to any customized serializers/parsers or swaps. 150 * 151 * @param value 152 * The new value for this property. 153 * <br>Cannot be <jk>null</jk>. 154 * @return This object. 155 */ 156 public SELF properties(Map<String,Object> value) { 157 reqnn("value", value); 158 properties.reset(); 159 value.forEach(this::property); 160 return self(); 161 } 162 163 /** 164 * Adds a property to this session. 165 * 166 * <p> 167 * If the key matches a known session property (e.g. <js>"debug"</js> or <js>"ContextSession.debug"</js>), 168 * the value is converted via {@link BasicConverter} and routed to the corresponding typed setter. 169 * Unknown keys are stored in the raw session properties map. 170 * 171 * @param key The property key. 172 * <br>Cannot be <jk>null</jk>. 173 * @param value The property value. 174 * <br>Can be <jk>null</jk> (resets typed properties to their default; removes raw properties). 175 * @return This object. 176 */ 177 public SELF property(String key, Object value) { 178 reqnn("key", key); 179 switch (key) { 180 case "debug", "ContextSession.debug": 181 return debug(cvt(value, Boolean.class)); 182 default: 183 var map = properties.get(); 184 if (value == null) { 185 map.remove(key); 186 } else { 187 map.put(key, value); 188 } 189 return self(); 190 } 191 } 192 193 /** 194 * Create an unmodifiable session. 195 * 196 * <p> 197 * The created ContextSession object will be unmodifiable which makes it suitable for caching and reuse. 198 * 199 * @return This object. 200 */ 201 public SELF unmodifiable() { 202 unmodifiable = true; 203 return self(); 204 } 205 } 206 207 private final boolean debug; 208 private final boolean unmodifiable; 209 private final Context ctx; 210 private final Map<String,Object> properties; 211 private List<String> warnings; // Any warnings encountered. 212 213 /** 214 * Default constructor. 215 * 216 * @param builder The builder for this object. 217 * <br>Cannot be <jk>null</jk>. 218 */ 219 protected ContextSession(Builder<?> builder) { 220 reqnn("builder", builder); 221 ctx = builder.ctx; 222 debug = o(builder.debug).orElse(ctx.isDebug()); 223 unmodifiable = builder.unmodifiable; 224 var sp = builder.properties.get(); 225 if (unmodifiable) { 226 properties = sp.isEmpty() ? Collections.emptyMap() : u(sp); 227 } else { 228 properties = sp; 229 } 230 } 231 232 /** 233 * Logs a warning message. 234 * 235 * @param msg The warning message. 236 * <br>Cannot be <jk>null</jk>. 237 * @param args Optional {@link String#format(String, Object...) String.format}-style arguments. 238 * <br>Cannot be <jk>null</jk> (individual values may be <jk>null</jk>). 239 */ 240 public void addWarning(String msg, Object...args) { 241 reqnn("msg", msg, "args", args); 242 if (unmodifiable) 243 return; 244 if (warnings == null) 245 warnings = ll(); 246 warnings.add((warnings.size() + 1) + ": " + f(msg, args)); 247 } 248 249 /** 250 * Throws a {@link BeanRuntimeException} if any warnings occurred in this session and debug is enabled. 251 */ 252 public void checkForWarnings() { 253 if (debug && ! getWarnings().isEmpty()) 254 throw brex("Warnings occurred in session: \n" + join(getWarnings(), "\n")); 255 } 256 257 /** 258 * Returns the context that created this session. 259 * 260 * @return The context that created this session. 261 */ 262 public Context getContext() { return ctx; } 263 264 /** 265 * Returns the session properties on this session. 266 * 267 * @return The session properties on this session. Never <jk>null</jk>. 268 */ 269 public final Map<String,Object> getSessionProperties() { return properties; } 270 271 /** 272 * Returns the warnings that occurred in this session. 273 * 274 * @return The warnings that occurred in this session, or an empty list if no warnings occurred. 275 */ 276 public final List<String> getWarnings() { return warnings == null ? emptyList() : warnings; } 277 278 /** 279 * Debug mode enabled. 280 * 281 * @see Context.Builder#debug() 282 * @return 283 * <jk>true</jk> if debug mode is enabled. 284 */ 285 public boolean isDebug() { return debug; } 286 287 @Override /* Overridden from Object */ 288 public String toString() { 289 return r(properties()); 290 } 291 292 /** 293 * Returns the properties on this bean as a map for debugging. 294 * 295 * @return The properties on this bean as a map for debugging. 296 */ 297 protected FluentMap<String,Object> properties() { 298 return filteredBeanPropertyMap() 299 .a("debug", debug); 300 } 301}