001// ***************************************************************************************************************************
002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
003// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
005// * with the License.  You may obtain a copy of the License at                                                              *
006// *                                                                                                                         *
007// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
008// *                                                                                                                         *
009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
011// * specific language governing permissions and limitations under the License.                                              *
012// ***************************************************************************************************************************
013package org.apache.juneau;
014
015import static org.apache.juneau.collections.JsonMap.*;
016import static org.apache.juneau.common.internal.StringUtils.*;
017import static java.util.Collections.*;
018
019import java.text.*;
020import java.util.*;
021import java.util.function.*;
022
023import org.apache.juneau.collections.*;
024import org.apache.juneau.internal.*;
025
026/**
027 * A one-time-use non-thread-safe object that's meant to be used once and then thrown away.
028 *
029 * <h5 class='section'>Notes:</h5><ul>
030 *    <li class='warn'>This class is not typically thread safe.
031 * </ul>
032 *
033 * <h5 class='section'>See Also:</h5><ul>
034 * </ul>
035 */
036public abstract class ContextSession {
037
038   //-----------------------------------------------------------------------------------------------------------------
039   // Builder
040   //-----------------------------------------------------------------------------------------------------------------
041
042   /**
043    * Builder class.
044    */
045   @FluentSetters
046   public static abstract class Builder {
047      Context ctx;
048      JsonMap properties;
049      boolean unmodifiable;
050      Boolean debug;
051
052      /**
053       * Constructor.
054       *
055       * @param ctx The context creating this session.
056       */
057      protected Builder(Context ctx) {
058         this.ctx = ctx;
059         debug = ctx.debug;
060      }
061
062      /**
063       * Build the object.
064       *
065       * @return The built object.
066       */
067      public abstract ContextSession build();
068
069      /**
070       * Debug mode.
071       *
072       * <p>
073       * Enables the following additional information during parsing:
074       * <ul>
075       *    <li> When bean setters throws exceptions, the exception includes the object stack information in order to determine how that method was invoked.
076       * </ul>
077       *
078       * <p>
079       * If not specified, defaults to {@link Context.Builder#debug()}.
080       *
081       * <h5 class='section'>See Also:</h5><ul>
082       *    <li class='ja'>{@link org.apache.juneau.annotation.BeanConfig#debug()}
083       *    <li class='jm'>{@link org.apache.juneau.Context.Builder#debug()}
084       *
085       * @param value
086       *    The new value for this property.
087       *    <br>Can be <jk>null</jk>.  Value will be ignored.
088       * @return This object.
089       */
090      @FluentSetter
091      public Builder debug(Boolean value) {
092         if (value != null)
093            debug = value;
094         return this;
095      }
096
097      /**
098       * Create an unmodifiable session.
099       *
100       * <p>
101       * The created ContextSession object will be unmodifiable which makes it suitable for caching and reuse.
102       *
103       * @return This object.
104       */
105      @FluentSetter
106      public Builder unmodifiable() {
107         unmodifiable = true;
108         return this;
109      }
110
111      /**
112       * Session properties.
113       *
114       * <p>
115       * Session properties are generic key-value pairs that can be passed through the session and made
116       * available to any customized serializers/parsers or swaps.
117       *
118       * @param value
119       *    The new value for this property.
120       *    <br>Can be <jk>null</jk>.
121       * @return This object.
122       */
123      @FluentSetter
124      public Builder properties(Map<String,Object> value) {
125         properties = JsonMap.of(value);
126         return this;
127      }
128
129      /**
130       * Adds a property to this session.
131       *
132       * @param key The property key.
133       * @param value The property value.
134       * @return This object.
135       */
136      @FluentSetter
137      public Builder property(String key, Object value) {
138         if (properties == null)
139            properties = JsonMap.create();
140         if (value == null) {
141            properties.remove(key);
142         } else {
143            properties.put(key, value);
144         }
145         return this;
146      }
147
148      /**
149       * Applies a consumer to this builder if it's the specified type.
150       *
151       * @param <T> The expected type.
152       * @param type The expected type.
153       * @param apply   The consumer to apply.
154       * @return This object.
155       */
156      @FluentSetter
157      public <T> Builder apply(Class<T> type, Consumer<T> apply) {
158         if (type.isInstance(this))
159            apply.accept(type.cast(this));
160         return this;
161      }
162
163      // <FluentSetters>
164
165      // </FluentSetters>
166   }
167
168   //-----------------------------------------------------------------------------------------------------------------
169   // Instance
170   //-----------------------------------------------------------------------------------------------------------------
171
172   private final JsonMap properties;
173   private List<String> warnings;   // Any warnings encountered.
174
175   private final Context ctx;
176   private final boolean debug;
177   private final boolean unmodifiable;
178
179   /**
180    * Default constructor.
181    *
182    * @param builder The builder for this object
183    */
184   protected ContextSession(Builder builder) {
185      ctx = builder.ctx;
186      unmodifiable = builder.unmodifiable;
187      JsonMap sp = builder.properties == null ? JsonMap.EMPTY_MAP : builder.properties;
188      if (unmodifiable)
189         sp = sp.unmodifiable();
190      properties = sp;
191      debug = builder.debug;
192   }
193
194   /**
195    * Returns the session properties on this session.
196    *
197    * @return The session properties on this session.  Never <jk>null</jk>.
198    */
199   public final JsonMap getSessionProperties() {
200      return properties;
201   }
202
203   /**
204    * Returns the context that created this session.
205    *
206    * @return The context that created this session.
207    */
208   public Context getContext() {
209      return ctx;
210   }
211
212   /**
213    * Logs a warning message.
214    *
215    * @param msg The warning message.
216    * @param args Optional {@link MessageFormat}-style arguments.
217    */
218   public void addWarning(String msg, Object... args) {
219      if (unmodifiable)
220         return;
221      if (warnings == null)
222         warnings = new LinkedList<>();
223      warnings.add((warnings.size() + 1) + ": " + format(msg, args));
224   }
225
226   /**
227    * Returns the warnings that occurred in this session.
228    *
229    * @return The warnings that occurred in this session, or <jk>null</jk> if no warnings occurred.
230    */
231   public final List<String> getWarnings() {
232      return warnings == null ? emptyList() : warnings;
233   }
234
235   /**
236    * Throws a {@link BeanRuntimeException} if any warnings occurred in this session and debug is enabled.
237    */
238   public void checkForWarnings() {
239      if (debug && ! getWarnings().isEmpty())
240         throw new BeanRuntimeException("Warnings occurred in session: \n" + join(getWarnings(), "\n"));
241   }
242
243   //-----------------------------------------------------------------------------------------------------------------
244   // Configuration properties
245   //-----------------------------------------------------------------------------------------------------------------
246
247   /**
248    * Debug mode enabled.
249    *
250    * @see Context.Builder#debug()
251    * @return
252    *    <jk>true</jk> if debug mode is enabled.
253    */
254   public boolean isDebug() {
255      return debug;
256   }
257
258   //-----------------------------------------------------------------------------------------------------------------
259   // Other methods
260   //-----------------------------------------------------------------------------------------------------------------
261
262   /**
263    * Returns the properties on this bean as a map for debugging.
264    *
265    * @return The properties on this bean as a map for debugging.
266    */
267   protected JsonMap properties() {
268      return filteredMap("debug", debug);
269   }
270
271   @Override /* Object */
272   public String toString() {
273      return ObjectUtils.toPropertyMap(this).asReadableString();
274   }
275}