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}