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.parser; 014 015import static org.apache.juneau.internal.IOUtils.*; 016import static org.apache.juneau.internal.StringUtils.*; 017 018import java.io.*; 019import java.nio.charset.*; 020 021import org.apache.juneau.*; 022import org.apache.juneau.internal.*; 023 024/** 025 * A wrapper around an object that a parser reads its input from. 026 * 027 * <p> 028 * For character-based parsers, the input object can be any of the following: 029 * <ul> 030 * <li>{@link Reader} 031 * <li>{@link CharSequence} 032 * <li>{@link InputStream} 033 * <li><code><jk>byte</jk>[]</code> 034 * <li>{@link File} 035 * <li><code><jk>null</jk></code> 036 * </ul> 037 * 038 * <p> 039 * For stream-based parsers, the input object can be any of the following: 040 * <ul> 041 * <li>{@link InputStream} 042 * <li><code><jk>byte</jk>[]</code> 043 * <li>{@link File} 044 * <li>{@link String} - Hex-encoded bytes. (not BASE-64!) 045 * <li><code><jk>null</jk></code> 046 * </ul> 047 * 048 * <p> 049 * Note that Readers and InputStreams will NOT be automatically closed when {@link #close()} is called, but 050 * streams and readers created from other types (e.g. Files) WILL be automatically closed. 051 */ 052public final class ParserPipe implements Closeable { 053 054 private final Object input; 055 final boolean debug, strict, autoCloseStreams, unbuffered; 056 private final String fileCharset, inputStreamCharset; 057 058 private String inputString; 059 private InputStream inputStream; 060 private Reader reader; 061 private ParserReader parserReader; 062 private boolean doClose; 063 064 /** 065 * Constructor. 066 * 067 * @param input The parser input object. 068 * @param debug 069 * If <jk>true</jk>, the input contents will be copied locally and accessible via the {@link #getInputAsString()} 070 * method. 071 * This allows the contents of the pipe to be accessed when a problem occurs. 072 * @param strict 073 * If <jk>true</jk>, sets {@link CodingErrorAction#REPORT} on {@link CharsetDecoder#onMalformedInput(CodingErrorAction)} 074 * and {@link CharsetDecoder#onUnmappableCharacter(CodingErrorAction)}. 075 * Otherwise, sets them to {@link CodingErrorAction#REPLACE}. 076 * @param autoCloseStreams 077 * Automatically close {@link InputStream InputStreams} and {@link Reader Readers} when passed in as input. 078 * @param unbuffered 079 * If <jk>true</jk>, we read one character at a time from underlying readers when the readers are expected to be parsed 080 * multiple times. 081 * <br>Otherwise, we read character data into a reusable buffer. 082 * @param fileCharset 083 * The charset to expect when reading from {@link File Files}. 084 * Use <js>"default"</js> to specify {@link Charset#defaultCharset()}. 085 * @param inputStreamCharset 086 * The charset to expect when reading from {@link InputStream InputStreams}. 087 * Use <js>"default"</js> to specify {@link Charset#defaultCharset()}. 088 */ 089 public ParserPipe(Object input, boolean debug, boolean strict, boolean autoCloseStreams, boolean unbuffered, String fileCharset, String inputStreamCharset) { 090 this.input = input; 091 this.debug = debug; 092 this.strict = strict; 093 this.autoCloseStreams = autoCloseStreams; 094 this.unbuffered = unbuffered; 095 this.fileCharset = fileCharset; 096 this.inputStreamCharset = inputStreamCharset; 097 if (input instanceof CharSequence) 098 this.inputString = input.toString(); 099 } 100 101 /** 102 * Shortcut constructor, typically for straight string input. 103 * 104 * <p> 105 * Equivalent to calling <code><jk>new</jk> ParserPipe(input, <jk>false</jk>, <jk>false</jk>, <jk>null</jk>, <jk>null</jk>);</code> 106 * 107 * @param input The input object. 108 */ 109 public ParserPipe(Object input) { 110 this(input, false, false, false, false, null, null); 111 } 112 113 /** 114 * Wraps the specified input object inside an input stream. 115 * 116 * <p> 117 * Subclasses can override this method to implement their own input streams. 118 * 119 * @return The input object wrapped in an input stream, or <jk>null</jk> if the object is null. 120 * @throws IOException If object could not be converted to an input stream. 121 */ 122 public InputStream getInputStream() throws IOException { 123 if (input == null) 124 return null; 125 126 if (input instanceof InputStream) { 127 if (debug) { 128 byte[] b = readBytes((InputStream)input, 1024); 129 inputString = toHex(b); 130 inputStream = new ByteArrayInputStream(b); 131 } else { 132 inputStream = (InputStream)input; 133 doClose = autoCloseStreams; 134 } 135 } else if (input instanceof byte[]) { 136 if (debug) 137 inputString = toHex((byte[])input); 138 inputStream = new ByteArrayInputStream((byte[])input); 139 doClose = false; 140 } else if (input instanceof String) { 141 inputString = (String)input; 142 inputStream = new ByteArrayInputStream(fromHex((String)input)); 143 doClose = false; 144 } else if (input instanceof File) { 145 if (debug) { 146 byte[] b = readBytes((File)input); 147 inputString = toHex(b); 148 inputStream = new ByteArrayInputStream(b); 149 } else { 150 inputStream = new FileInputStream((File)input); 151 doClose = true; 152 } 153 } else { 154 throw new IOException("Cannot convert object of type "+input.getClass().getName()+" to an InputStream."); 155 } 156 157 return inputStream; 158 } 159 160 161 /** 162 * Wraps the specified input object inside a reader. 163 * 164 * <p> 165 * Subclasses can override this method to implement their own readers. 166 * 167 * @return The input object wrapped in a Reader, or <jk>null</jk> if the object is null. 168 * @throws IOException If object could not be converted to a reader. 169 */ 170 public Reader getReader() throws IOException { 171 if (input == null) 172 return null; 173 174 if (input instanceof Reader) { 175 if (debug) { 176 inputString = read((Reader)input); 177 reader = new StringReader(inputString); 178 } else { 179 reader = (Reader)input; 180 doClose = autoCloseStreams; 181 } 182 } else if (input instanceof CharSequence) { 183 inputString = input.toString(); 184 reader = new ParserReader(this); 185 doClose = false; 186 } else if (input instanceof InputStream || input instanceof byte[]) { 187 doClose = input instanceof InputStream && autoCloseStreams; 188 InputStream is = ( 189 input instanceof InputStream 190 ? (InputStream)input 191 : new ByteArrayInputStream((byte[])input) 192 ); 193 CharsetDecoder cd = ( 194 "default".equalsIgnoreCase(inputStreamCharset) 195 ? Charset.defaultCharset() 196 : Charset.forName(inputStreamCharset) 197 ).newDecoder(); 198 if (strict) { 199 cd.onMalformedInput(CodingErrorAction.REPORT); 200 cd.onUnmappableCharacter(CodingErrorAction.REPORT); 201 } else { 202 cd.onMalformedInput(CodingErrorAction.REPLACE); 203 cd.onUnmappableCharacter(CodingErrorAction.REPLACE); 204 } 205 reader = new InputStreamReader(is, cd); 206 if (debug) { 207 inputString = read(reader); 208 reader = new StringReader(inputString); 209 } 210 } else if (input instanceof File) { 211 CharsetDecoder cd = ( 212 "DEFAULT".equalsIgnoreCase(fileCharset) 213 ? Charset.defaultCharset() 214 : Charset.forName(fileCharset) 215 ).newDecoder(); 216 if (strict) { 217 cd.onMalformedInput(CodingErrorAction.REPORT); 218 cd.onUnmappableCharacter(CodingErrorAction.REPORT); 219 } else { 220 cd.onMalformedInput(CodingErrorAction.REPLACE); 221 cd.onUnmappableCharacter(CodingErrorAction.REPLACE); 222 } 223 reader = new InputStreamReader(new FileInputStream((File)input), cd); 224 if (debug) { 225 inputString = read(reader); 226 reader = new StringReader(inputString); 227 } 228 doClose = true; 229 } else { 230 throw new IOException("Cannot convert object of type "+input.getClass().getName()+" to a Reader."); 231 } 232 233 return reader; 234 } 235 236 /** 237 * Returns the contents of this pipe as a buffered reader. 238 * 239 * <p> 240 * If the reader passed into this pipe is already a buffered reader, that reader will be returned. 241 * 242 * @return The contents of this pipe as a buffered reader. 243 * @throws Exception 244 */ 245 public Reader getBufferedReader() throws Exception { 246 return IOUtils.getBufferedReader(getReader()); 247 } 248 249 /** 250 * Returns the input to this parser as a plain string. 251 * 252 * <p> 253 * This method only returns a value if {@link BeanContext#BEAN_debug} is enabled. 254 * 255 * @return The input as a string, or <jk>null</jk> if debug mode not enabled. 256 */ 257 public String getInputAsString() { 258 return inputString; 259 } 260 261 /** 262 * Converts this pipe into a {@link ParserReader}. 263 * 264 * @return The converted pipe. 265 * @throws Exception 266 */ 267 public ParserReader getParserReader() throws Exception { 268 if (input == null) 269 return null; 270 if (input instanceof ParserReader) 271 parserReader = (ParserReader)input; 272 else 273 parserReader = new ParserReader(this); 274 return parserReader; 275 } 276 277 /** 278 * Returns <jk>true</jk> if the contents passed into this pipe was a {@link CharSequence}. 279 * 280 * @return <jk>true</jk> if the contents passed into this pipe was a {@link CharSequence}. 281 */ 282 public boolean isString() { 283 return inputString != null; 284 } 285 286 @Override /* Closeable */ 287 public void close() { 288 try { 289 if (doClose) 290 IOUtils.close(reader, inputStream); 291 } catch (IOException e) { 292 throw new BeanRuntimeException(e); 293 } 294 } 295}