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;
018
019import static java.util.Collections.*;
020import static org.apache.juneau.commons.utils.AssertionUtils.*;
021import static org.apache.juneau.commons.utils.CollectionUtils.*;
022import static org.apache.juneau.commons.utils.StringUtils.*;
023import static org.apache.juneau.commons.utils.ThrowableUtils.*;
024import static org.apache.juneau.commons.utils.Utils.*;
025
026import java.text.*;
027import java.util.*;
028import java.util.function.*;
029
030import org.apache.juneau.commons.collections.*;
031import org.apache.juneau.commons.function.*;
032import org.apache.juneau.commons.reflect.*;
033
034/**
035 * A one-time-use non-thread-safe object that's meant to be used once and then thrown away.
036 *
037 * <h5 class='section'>Notes:</h5><ul>
038 *    <li class='warn'>This class is not typically thread safe.
039 * </ul>
040 *
041 */
042public abstract class ContextSession {
043
044   /**
045    * Builder class.
046    */
047   public static abstract class Builder {
048      private Boolean debug;
049      private boolean unmodifiable;
050      private Context ctx;
051      private ResettableSupplier<LinkedHashMap<String,Object>> properties;
052
053      /**
054       * Constructor.
055       *
056       * @param ctx The context creating this session.
057       *    <br>Cannot be <jk>null</jk>.
058       */
059      protected Builder(Context ctx) {
060         this.ctx = assertArgNotNull("ctx", ctx);
061         this.properties = memr(LinkedHashMap::new);
062      }
063
064      /**
065       * Applies a consumer to this builder if it's the specified type.
066       *
067       * @param <T> The expected type.
068       * @param type The expected type.
069       *    <br>Cannot be <jk>null</jk>.
070       * @param apply   The consumer to apply.
071       *    <br>Cannot be <jk>null</jk>.
072       * @return This object.
073       */
074      public <T> Builder apply(Class<T> type, Consumer<T> apply) {
075         if (assertArgNotNull("type", type).isInstance(this))
076            assertArgNotNull("apply", apply).accept(type.cast(this));
077         return this;
078      }
079
080      /**
081       * Build the object.
082       *
083       * @return The built object.
084       */
085      public abstract ContextSession build();
086
087      /**
088       * Debug mode.
089       *
090       * <p>
091       * Enables the following additional information during parsing:
092       * <ul>
093       *    <li> When bean setters throws exceptions, the exception includes the object stack information in order to determine how that method was invoked.
094       * </ul>
095       *
096       * <p>
097       * If not specified, defaults to {@link Context.Builder#debug()}.
098       *
099       * <h5 class='section'>See Also:</h5><ul>
100       *    <li class='ja'>{@link org.apache.juneau.annotation.BeanConfig#debug()}
101       *    <li class='jm'>{@link org.apache.juneau.Context.Builder#debug()}
102       *
103       * @param value
104       *    The new value for this property.
105       *    <br>If <jk>null</jk>, defaults to {@link Context#isDebug()}.
106       * @return This object.
107       */
108      public Builder debug(Boolean value) {
109         debug = value;
110         return this;
111      }
112
113      /**
114       * Session properties.
115       *
116       * <p>
117       * Session properties are generic key-value pairs that can be passed through the session and made
118       * available to any customized serializers/parsers or swaps.
119       *
120       * @param value
121       *    The new value for this property.
122       *    <br>Cannot be <jk>null</jk>.
123       * @return This object.
124       */
125      public Builder properties(Map<String,Object> value) {
126         assertArgNotNull("value", value);
127         properties.reset();
128         properties.get().putAll(value);
129         return this;
130      }
131
132      /**
133       * Adds a property to this session.
134       *
135       * @param key The property key.
136       *    <br>Cannot be <jk>null</jk>.
137       * @param value The property value.
138       *    <br>Can be <jk>null</jk> (removes the property).
139       * @return This object.
140       */
141      public Builder property(String key, Object value) {
142         assertArgNotNull("key", key);
143         var map = properties.get();
144         if (value == null) {
145            map.remove(key);
146         } else {
147            map.put(key, value);
148         }
149         return this;
150      }
151
152      /**
153       * Create an unmodifiable session.
154       *
155       * <p>
156       * The created ContextSession object will be unmodifiable which makes it suitable for caching and reuse.
157       *
158       * @return This object.
159       */
160      public Builder unmodifiable() {
161         unmodifiable = true;
162         return this;
163      }
164   }
165
166   private final boolean debug;
167   private final boolean unmodifiable;
168   private final Context ctx;
169   private final Map<String,Object> properties;
170   private List<String> warnings;   // Any warnings encountered.
171
172   /**
173    * Default constructor.
174    *
175    * @param builder The builder for this object.
176    *    <br>Cannot be <jk>null</jk>.
177    */
178   protected ContextSession(Builder builder) {
179      assertArgNotNull("builder", builder);
180      ctx = builder.ctx;
181      debug = opt(builder.debug).orElse(ctx.isDebug());
182      unmodifiable = builder.unmodifiable;
183      var sp = builder.properties.get();
184      if (unmodifiable) {
185         properties = sp.isEmpty() ? Collections.emptyMap() : u(sp);
186      } else {
187         properties = sp;
188      }
189   }
190
191   /**
192    * Logs a warning message.
193    *
194    * @param msg The warning message.
195    *    <br>Cannot be <jk>null</jk>.
196    * @param args Optional {@link MessageFormat}-style arguments.
197    *    <br>Cannot contain <jk>null</jk> values.
198    */
199   public void addWarning(String msg, Object...args) {
200      assertArgsNotNull("msg", msg, "args", args);
201      if (unmodifiable)
202         return;
203      if (warnings == null)
204         warnings = new LinkedList<>();
205      warnings.add((warnings.size() + 1) + ": " + f(msg, args));
206   }
207
208   /**
209    * Throws a {@link BeanRuntimeException} if any warnings occurred in this session and debug is enabled.
210    */
211   public void checkForWarnings() {
212      if (debug && ! getWarnings().isEmpty())
213         throw bex("Warnings occurred in session: \n" + join(getWarnings(), "\n"));
214   }
215
216   /**
217    * Returns the context that created this session.
218    *
219    * @return The context that created this session.
220    */
221   public Context getContext() { return ctx; }
222
223   /**
224    * Returns the session properties on this session.
225    *
226    * @return The session properties on this session.  Never <jk>null</jk>.
227    */
228   public final Map<String,Object> getSessionProperties() { return properties; }
229
230   /**
231    * Returns the warnings that occurred in this session.
232    *
233    * @return The warnings that occurred in this session, or <jk>null</jk> if no warnings occurred.
234    */
235   public final List<String> getWarnings() { return warnings == null ? emptyList() : warnings; }
236
237   /**
238    * Debug mode enabled.
239    *
240    * @see Context.Builder#debug()
241    * @return
242    *    <jk>true</jk> if debug mode is enabled.
243    */
244   public boolean isDebug() { return debug; }
245
246   @Override /* Overridden from Object */
247   public String toString() {
248      return r(properties());
249   }
250
251   /**
252    * Returns the properties on this bean as a map for debugging.
253    *
254    * @return The properties on this bean as a map for debugging.
255    */
256   protected FluentMap<String,Object> properties() {
257      return filteredBeanPropertyMap()
258         .a("debug", debug);
259   }
260}