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.parser;
018
019import static org.apache.juneau.commons.utils.IoUtils.*;
020import static org.apache.juneau.commons.utils.Shorts.*;
021import static org.apache.juneau.commons.utils.StringUtils.*;
022
023import java.io.*;
024import java.nio.charset.*;
025
026import org.apache.juneau.commons.utils.*;
027import org.apache.juneau.marshall.*;
028
029/**
030 * A wrapper around an object that a parser reads its input from.
031 *
032 * <p>
033 * For character-based parsers, the input object can be any of the following:
034 * <ul>
035 *    <li>{@link Reader}
036 *    <li>{@link CharSequence}
037 *    <li>{@link InputStream}
038 *    <li><code><jk>byte</jk>[]</code>
039 *    <li><code><jk>null</jk></code>
040 * </ul>
041 *
042 * <p>
043 * For stream-based parsers, the input object can be any of the following:
044 * <ul>
045 *    <li>{@link InputStream}
046 *    <li><code><jk>byte</jk>[]</code>
047 *    <li>{@link String} - Hex-encoded bytes.  (not BASE-64!)
048 *    <li><code><jk>null</jk></code>
049 * </ul>
050 *
051 * <p>
052 * Note that Readers and InputStreams will NOT be automatically closed when {@link #close()} is called
053 * unless the {@code autoCloseStreams} setting is enabled.
054 *
055 * <h5 class='section'>See Also:</h5><ul>
056 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/SerializersAndParsers">Serializers and Parsers</a>
057 * </ul>
058 */
059@SuppressWarnings({
060   "resource" // ParserPipe manages Closeable resources
061})
062public class ParserPipe implements Closeable {
063
064   private final Object input;
065   final boolean debug;
066   final boolean strict;
067   final boolean autoCloseStreams;
068   final boolean unbuffered;
069   private final Charset charset;
070
071   private String inputString;
072   private InputStream inputStream;
073   private Reader reader;
074   private boolean doClose;
075   private final BinaryFormat binaryFormat;
076   private Positionable positionable;
077
078   /**
079    * Shortcut constructor, typically for straight string input.
080    *
081    * <p>
082    * Equivalent to calling <code><jk>new</jk> ParserPipe(input, <jk>false</jk>, <jk>false</jk>, <jk>false</jk>, <jk>false</jk>, <jk>null</jk>);</code>
083    *
084    * @param input The input object.
085    *    <br>Can be <jk>null</jk> (the pipe wraps no data; {@link #getReader()}/{@link #getInputStream()} return <jk>null</jk>).
086    */
087   public ParserPipe(Object input) {
088      this(input, false, false, false, false, null);
089   }
090
091   /**
092    * Constructor for stream-based parsers.
093    *
094    * @param input The parser input object.
095    *    <br>Can be <jk>null</jk> (the pipe wraps no data; {@link #getReader()}/{@link #getInputStream()} return <jk>null</jk>).
096    * @param debug
097    *    If <jk>true</jk>, the input contents will be copied locally and accessible via the {@link #getInputAsString()}
098    *    method.
099    *    This allows the contents of the pipe to be accessed when a problem occurs.
100    * @param autoCloseStreams
101    *    Automatically close {@link InputStream InputStreams} and {@link Reader Readers} when passed in as input.
102    * @param unbuffered
103    *    If <jk>true</jk>, we read one character at a time from underlying readers when the readers are expected to be parsed
104    *    multiple times.
105    *    <br>Otherwise, we read character data into a reusable buffer.
106    * @param binaryFormat The binary format of input strings when converted to bytes.
107    *    <br>Can be <jk>null</jk> (string input converts to an empty byte array).
108    */
109   public ParserPipe(Object input, boolean debug, boolean autoCloseStreams, boolean unbuffered, BinaryFormat binaryFormat) {
110      this.input = input;
111      this.debug = debug;
112      this.strict = false;
113      this.autoCloseStreams = autoCloseStreams;
114      this.unbuffered = unbuffered;
115      this.charset = null;
116      if (input instanceof CharSequence input2)
117         this.inputString = input2.toString();
118      this.binaryFormat = binaryFormat;
119   }
120
121   /**
122    * Constructor for reader-based parsers.
123    *
124    * @param input The parser input object.
125    *    <br>Can be <jk>null</jk> (the pipe wraps no data; {@link #getReader()}/{@link #getInputStream()} return <jk>null</jk>).
126    * @param debug
127    *    If <jk>true</jk>, the input contents will be copied locally and accessible via the {@link #getInputAsString()}
128    *    method.
129    *    This allows the contents of the pipe to be accessed when a problem occurs.
130    * @param strict
131    *    If <jk>true</jk>, sets {@link CodingErrorAction#REPORT} on {@link CharsetDecoder#onMalformedInput(CodingErrorAction)}
132    *    and {@link CharsetDecoder#onUnmappableCharacter(CodingErrorAction)}.
133    *    Otherwise, sets them to {@link CodingErrorAction#REPLACE}.
134    * @param autoCloseStreams
135    *    Automatically close {@link InputStream InputStreams} and {@link Reader Readers} when passed in as input.
136    * @param unbuffered
137    *    If <jk>true</jk>, we read one character at a time from underlying readers when the readers are expected to be parsed
138    *    multiple times.
139    *    <br>Otherwise, we read character data into a reusable buffer.
140    * @param streamCharset
141    *    The charset to expect when reading from {@link InputStream InputStreams}.
142    *    <br>Can be <jk>null</jk> (defaults to UTF-8).
143    */
144   public ParserPipe(Object input, boolean debug, boolean strict, boolean autoCloseStreams, boolean unbuffered, Charset streamCharset) {
145      this.input = input;
146      this.debug = debug;
147      this.strict = strict;
148      this.autoCloseStreams = autoCloseStreams;
149      this.unbuffered = unbuffered;
150      this.charset = streamCharset != null ? streamCharset : UTF8;
151      if (input instanceof CharSequence cs2)
152         this.inputString = cs2.toString();
153      this.binaryFormat = null;
154   }
155
156   /**
157    * Returns the contents of this pipe as a string.
158    *
159    * @return The contents of this pipe as a string.
160    * @throws IOException If thrown from inner reader.
161    */
162   public String asString() throws IOException {
163      if (inputString == null)
164         inputString = read(getReader());
165      return inputString;
166   }
167
168   @Override /* Overridden from Closeable */
169   public void close() {
170      try {
171         if (doClose)
172            IoUtils.close(reader, inputStream);
173      } catch (IOException e) {
174         throw brex(e);
175      }
176   }
177
178   /**
179    * Returns the contents of this pipe as a buffered reader.
180    *
181    * <p>
182    * If the reader passed into this pipe is already a buffered reader, that reader will be returned.
183    *
184    * @return The contents of this pipe as a buffered reader.
185    * @throws IOException Thrown by underlying stream.
186    */
187   public Reader getBufferedReader() throws IOException { return toBufferedReader(getReader()); }
188
189   /**
190    * Returns the input to this parser as a plain string.
191    *
192    * <p>
193    * This method only returns a value if {@link org.apache.juneau.marshall.Context.Builder#debug() Context.Builder#debug()} is enabled.
194    *
195    * @return The input as a string, or <jk>null</jk> if debug mode not enabled.
196    */
197   public String getInputAsString() { return inputString; }
198
199   /**
200    * Wraps the specified input object inside an input stream.
201    *
202    * <p>
203    * Subclasses can override this method to implement their own input streams.
204    *
205    * @return The input object wrapped in an input stream, or <jk>null</jk> if the object is null.
206    * @throws IOException If object could not be converted to an input stream.
207    */
208   public InputStream getInputStream() throws IOException {
209      if (input == null)
210         return null;
211
212      if (input instanceof InputStream input2) {
213         if (debug) {
214            var b = readBytes(input2);
215            inputString = toHex(b);
216            inputStream = new ByteArrayInputStream(b);
217         } else {
218            inputStream = input2;
219            doClose = autoCloseStreams;
220         }
221      } else if (input instanceof byte[] input2) {
222         if (debug)
223            inputString = toHex(input2);
224         inputStream = new ByteArrayInputStream(input2);
225         doClose = false;
226      } else if (input instanceof String input2) {
227         inputString = input2;
228         inputStream = new ByteArrayInputStream(convertFromString(input2));
229         doClose = false;
230      } else {
231         throw ioex("Cannot convert object of type %s to an InputStream.", cn(input));
232      }
233
234      return inputStream;
235   }
236
237   /**
238    * Converts this pipe into a {@link ParserReader}.
239    *
240    * @return The converted pipe, or <jk>null</jk> if the input object is <jk>null</jk>.
241    * @throws IOException Thrown by underlying stream.
242    */
243   public ParserReader getParserReader() throws IOException {
244      if (input == null)
245         return null;
246      return input instanceof ParserReader input2 ? input2 : new ParserReader(this);
247   }
248
249   /**
250    * Wraps the specified input object inside a reader.
251    *
252    * <p>
253    * Subclasses can override this method to implement their own readers.
254    *
255    * @return The input object wrapped in a Reader, or <jk>null</jk> if the object is null.
256    * @throws IOException If object could not be converted to a reader.
257    */
258   @SuppressWarnings({
259      "java:S3776" // Cognitive complexity acceptable for reader initialization logic
260   })
261   public Reader getReader() throws IOException {
262      if (input == null)
263         return null;
264
265      if (input instanceof Reader input2) {
266         if (debug) {
267            inputString = read(input2);
268            reader = new StringReader(inputString);
269         } else {
270            reader = input2;
271            doClose = autoCloseStreams;
272         }
273      } else if (input instanceof CharSequence input2) {
274         inputString = input2.toString();
275         reader = new ParserReader(this);
276         doClose = false;
277      } else if (input instanceof InputStream || input instanceof byte[]) {
278         doClose = input instanceof InputStream && autoCloseStreams;
279         InputStream is = (input instanceof InputStream input2 ? input2 : new ByteArrayInputStream((byte[])input));
280         CharsetDecoder cd = charset.newDecoder();
281         if (strict) {
282            cd.onMalformedInput(CodingErrorAction.REPORT);
283            cd.onUnmappableCharacter(CodingErrorAction.REPORT);
284         } else {
285            cd.onMalformedInput(CodingErrorAction.REPLACE);
286            cd.onUnmappableCharacter(CodingErrorAction.REPLACE);
287         }
288         reader = new InputStreamReader(is, cd);
289         if (debug) {
290            inputString = read(reader);
291            reader = new StringReader(inputString);
292         }
293      } else {
294         throw ioex("Cannot convert object of type %s to an InputStream.", cn(input));
295      }
296
297      return reader;
298   }
299
300   /**
301    * Returns <jk>true</jk> if the contents passed into this pipe was a {@link CharSequence}.
302    *
303    * @return <jk>true</jk> if the contents passed into this pipe was a {@link CharSequence}.
304    */
305   public boolean isString() { return nn(inputString); }
306
307   /**
308    * Sets the ParserReader/ParserInputStream/XmlReader constructed from this pipe.
309    *
310    * <p>
311    * Used for gathering the failure position when {@link ParseException} is thrown.
312    *
313    * @param positionable The ParserReader/ParserInputStream/XmlReader constructed from this pipe.
314    *    <br>Can be <jk>null</jk> (the failure position reported on {@link ParseException} is {@link Position#UNKNOWN}).
315    */
316   public void setPositionable(Positionable positionable) { this.positionable = positionable; }
317
318   private byte[] convertFromString(String in) {
319      return binaryFormat == null ? new byte[0] : binaryFormat.parse(in);
320   }
321
322   Position getPosition() {
323      if (positionable == null)
324         return Position.UNKNOWN;
325      Position p = positionable.getPosition();
326      if (p == null)
327         return Position.UNKNOWN;
328      return p;
329   }
330}