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.plaintext;
018
019import static org.apache.juneau.commons.utils.Shorts.*;
020
021import java.io.*;
022import java.util.*;
023import java.util.concurrent.*;
024
025import org.apache.juneau.commons.bean.*;
026import org.apache.juneau.commons.collections.*;
027import org.apache.juneau.marshall.*;
028import org.apache.juneau.marshall.parser.*;
029import org.apache.juneau.marshall.stream.*;
030import org.apache.juneau.marshall.swap.spi.*;
031
032/**
033 * Parsers HTTP plain text request bodies into Group 5 POJOs.
034 *
035 * <p>
036 * See <a class="doclink" href="https://juneau.apache.org/docs/topics/PojoCategories">POJO Categories</a>.
037 *
038 * <h5 class='topic'>Media types</h5>
039 *
040 * Handles <c>Accept</c> types:  <bc>text/plain</bc>
041 * <p>
042 * Produces <c>Content-Type</c> types:  <bc>text/plain</bc>
043 *
044 * <h5 class='topic'>Description</h5>
045 *
046 * Essentially just converts plain text to POJOs via static <c>fromString()</c> or <c>valueOf()</c>, or
047 * through constructors that take a single string argument.
048 *
049 * <p>
050 * Also parses objects using a transform if the object class has an {@link ObjectSwap ObjectSwap&lt;?,String&gt;} transform
051 * defined on it.
052 *
053 * <h5 class='section'>Notes:</h5><ul>
054 *    <li class='note'>This class is thread safe and reusable.
055 * </ul>
056 *
057 */
058@SuppressWarnings({
059   "java:S1192", // Duplicated literals (argument/property names) read more clearly inline than as constants
060   "java:S9149", // Per-format static factories intentionally shadow the parent's.
061   "resource" // Closeable resources are owned by the caller's parser session; Eclipse JDT @Owning warning is by design.
062})
063public class PlainTextParser extends ReaderParser implements PlainTextMetaProvider, RecordReadable {
064
065   /**
066    * Builder class.
067    */
068   public static class Builder extends ReaderParser.Builder<Builder> {
069
070      private static final Cache<HashKey,PlainTextParser> CACHE = Cache.of(HashKey.class, PlainTextParser.class).build();
071
072      /**
073       * Constructor, default settings.
074       */
075      protected Builder() {
076         consumes("text/plain");
077      }
078
079      /**
080       * Copy constructor.
081       *
082       * @param copyFrom The builder to copy from.
083       *    <br>Cannot be <jk>null</jk>.
084       */
085      protected Builder(Builder copyFrom) {
086         super(reqnn("copyFrom", copyFrom));
087      }
088
089      /**
090       * Copy constructor.
091       *
092       * @param copyFrom The bean to copy from.
093       *    <br>Cannot be <jk>null</jk>.
094       */
095      protected Builder(PlainTextParser copyFrom) {
096         super(reqnn("copyFrom", copyFrom));
097      }
098
099      @Override /* Overridden from Context.Builder<?> */
100      public PlainTextParser build() {
101         return cache(CACHE).build(PlainTextParser.class);
102      }
103
104      @Override /* Overridden from Context.Builder<?> */
105      public Builder copy() {
106         return new Builder(this);
107      }
108
109
110   }
111
112   /** Default parser, all default settings.*/
113   public static final PlainTextParser DEFAULT = new PlainTextParser(create());
114
115   /**
116    * Creates a new builder for this object.
117    *
118    * @return A new builder.
119    */
120   public static Builder create() {
121      return new Builder();
122   }
123
124   private final Map<ClassMeta<?>,PlainTextClassMeta> plainTextClassMetas = new ConcurrentHashMap<>();
125   private final Map<BeanPropertyMeta,PlainTextBeanPropertyMeta> plainTextBeanPropertyMetas = new ConcurrentHashMap<>();
126
127   /**
128    * Constructor.
129    *
130    * @param builder The builder for this object.
131    */
132   public PlainTextParser(Builder builder) {
133      super(builder);
134   }
135
136   @Override /* Overridden from Context */
137   public Builder copy() {
138      return new Builder(this);
139   }
140
141   @Override /* Overridden from Context */
142   public PlainTextParserSession.Builder createSession() {
143      return PlainTextParserSession.create(this);
144   }
145
146   @Override /* Overridden from PlainTextMetaProvider */
147   public PlainTextBeanPropertyMeta getPlainTextBeanPropertyMeta(BeanPropertyMeta bpm) {
148      if (bpm == null)
149         return PlainTextBeanPropertyMeta.DEFAULT;
150      return plainTextBeanPropertyMetas.computeIfAbsent(bpm, k -> new PlainTextBeanPropertyMeta(k.getDelegateFor(), this));
151   }
152
153   @Override /* Overridden from PlainTextMetaProvider */
154   public PlainTextClassMeta getPlainTextClassMeta(ClassMeta<?> cm) {
155      return plainTextClassMetas.computeIfAbsent(cm, k -> new PlainTextClassMeta(k, this));
156   }
157
158   @Override /* Overridden from Context */
159   public PlainTextParserSession getSession() { return createSession().build(); }
160
161   /**
162    * Convenience delegator that opens a {@link RecordReader} over the input using
163    * <b>default session arguments</b> (mirrors {@link #read(Object, Class)}).
164    *
165    * <p>
166    * The real implementation lives on {@link PlainTextParserSession#readRecords(Object)}.  Callers
167    * that need request-derived configuration (locale, timezone, schema, swaps) should call
168    * {@link #createSession()} and invoke {@link PlainTextParserSession#readRecords(Object)} on the
169    * built session instead.
170    *
171    * @param input The input.
172    * @return A new {@link RecordReader} cursor.
173    * @throws IOException If a problem occurred opening the underlying input.
174    */
175   @Override /* RecordReadable */
176   public RecordReader readRecords(Object input) throws IOException {
177      return getSession().readRecords(input);
178   }
179
180   @Override /* RecordReadable */
181   public boolean isRecordStreaming() {
182      return false;
183   }
184}