
org.hsqldb.util.preprocessor.Document Maven / Gradle / Ivy
/* Copyright (c) 2001-2022, The HSQL Development Group
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of the HSQL Development Group nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
* OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.hsqldb.util.preprocessor;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
/*
* $Id: Document.java 6550 2022-06-08 11:09:08Z fredt $
*/
/**
* Simple line-oriented text document ADT.
*
* @author Campbell Burnet (campbell-burnet@users dot sourceforge.net)
* @version 2.7.0
* @since 1.8.1
*/
@SuppressWarnings("ClassWithoutLogger")
public class Document {
/**
* for the given character set name.
*
* @param charsetName to test; {@code null} returns {@code false}.
* @return {@code true} if supported, else {@code false}.
*/
public static boolean isSupportedCharset(final String charsetName) {
return charsetName != null
&& !charsetName.trim().isEmpty()
&& Charset.isSupported(charsetName);
}
private final List lines;
/**
* constructs a new, empty instance.
*/
public Document() {
this.lines = new ArrayList<>(16);
}
/**
* Constructs a new instance that is effectively a copy of {@code source}.
*
* @param source to copy; may be {@code null}, which is treated like an
* {@code empty} instance.
*/
public Document(final Document source) {
this();
if (source != null) {
final Document target = this;
target.appendDocument(source);
}
}
/**
* to this instance.
*
* @param line to add; must not be {@code null}.
* @return this instance.
* @throws IllegalArgumentException if {@code line} is {@code null}.
*/
public Document addSouceLine(final String line) {
if (line == null) {
throw new IllegalArgumentException("line: null");
}
this.lines.add(line);
return this;
}
/**
* to this instance.
*
* @param source to append; may be {@code null}, which is treated like an
* {@code empty} instance.
* @return this instance
*/
@SuppressWarnings("AccessingNonPublicFieldOfAnotherObject")
public Document appendDocument(final Document source) {
if (source != null && source.size() > 0) {
this.lines.addAll(source.lines);
}
return this;
}
/**
* this instance, making it {@link #isEmpty() empty}.
*
* @return this instance.
*/
public Document clear() {
this.lines.clear();
return this;
}
/**
* if at least one line in this instance contains the given sequence.
*
* Note: each line is tested separately; no test is performed to see
* if the sequence spans more than one line.
*
*
* @param sequence to test.
* @return {@code true} if the condition holds, else {@code false}.
*/
public boolean contains(final CharSequence sequence) {
if (sequence == null) {
return false;
}
final String toFind = sequence.toString();
final List list = this.lines;
final int size = list.size();
for (int i = 0; i < size; i++) {
if (-1 < list.get(i).indexOf(toFind, 0)) {
return true;
}
}
return false;
}
/**
* from this instance at the given index.
*
* @param index at which to remove a line.
* @return this instance.
* @throws IndexOutOfBoundsException if the index is out of range
* ({@code index < 0 || index >= size())}
*/
public Document deleteSourceLine(final int index) {
this.lines.remove(index);
return this;
}
/**
* if the given document has effectively the same content as this instance.
*
* @param document to test; may be {@code null}, which is treated as
* {@link #isEmpty() empty}.
* @return {@code true} if the content of the given {@code document} is
* equal to the content of this instance, as per
* {@link List#equals(Object)}.
*/
@SuppressWarnings("AccessingNonPublicFieldOfAnotherObject")
public boolean contentEquals(final Document document) {
return (document == this)
? true
: document == null
? this.isEmpty()
: this.lines.equals(document.lines);
}
/**
* at the given index.
*
* @param index at which to retrieve the line.
* @return the line at the given index.
* @throws IndexOutOfBoundsException if the index is out of range
* ({@code index < 0 || index >= size()})
*/
public String getSourceLine(final int index) {
return this.lines.get(index);
}
/**
* if this instance contains no lines.
*
* @return {@code true} if {@link #size()} {@code == 0}, else {@code false}.
*/
public boolean isEmpty() {
return this.lines.isEmpty();
}
/**
* at the specified index.
*
* @param index at which the specified line is to be inserted
* @param line to be inserted
* @return this instance
* @throws NullPointerException if the specified line is {@code null}.
* @throws IndexOutOfBoundsException if the index is out of range
* ({@code index < 0 || index >= size()})
*/
public Document insertSourceLine(final int index, final String line) {
if (line == null) {
throw new IllegalArgumentException("line: null");
}
this.lines.add(index, line);
return this;
}
/**
* as if by invoking {@code this.clear().appendDocument(source)}.
*
* @param source content used to replace the existing content of this
* instance; may be {@code null}, which is treated as
* {@link #isEmpty() empty}.
* @return this instance.
*/
public Document replaceWith(final Document source) {
return this.clear().appendDocument(source);
}
/**
* at the specified index in this instance.
*
* @param index index of the line to replace
* @param line to be stored at the specified index
* @return this instance.
* @throws NullPointerException if the specified line is {@code null}.
* @throws IndexOutOfBoundsException if the index is out of range
* ({@code index < 0 || index >= size()})
*/
public Document setSourceLine(final int index, final String line) {
if (line == null) {
throw new IllegalArgumentException("null");
}
this.lines.set(index, line);
return this;
}
/**
* of this instance, as the number of lines.
*
* @return the number of lines in this instance.
*/
public int size() {
return this.lines.size();
}
// ------------------------ I/O convenience methods ----------------------------
/**
* this instance from the given input stream using the given character
* encoding.
*
* Note: it is the responsibility of the caller to close the input
* stream.
*
*
* @param inputStream from which to load; must not be {@code null}.
* @param encoding {@code null} for the system default.
* @return this object.
* @throws IOException if an I/O error occurs.
* @throws IllegalArgumentException if the encoding is not supported.
* @throws NullPointerException if the inputStream is {@code null}.
*/
public Document load(final InputStream inputStream, final String encoding) throws IOException,
IllegalArgumentException, NullPointerException {
return load((Object) inputStream, encoding);
}
/**
* this instance from the given reader.
*
* Note: it is the responsibility of the caller to close the reader.
*
*
* @param reader from which to load; must not be {@code null}.
* @return this object.
* @throws IOException in an I/O error occurs.
* @throws NullPointerException if the reader is {@code null}.
*/
public Document load(final Reader reader) throws IOException,
NullPointerException {
return load(reader, null);
}
/**
* this instance from the given file using the given character encoding.
*
* Note: the underlying file input stream is closed automatically.
*
*
* @param file from which to load; {@code null} is treated the empty
* path.
* @param encoding {@code null} for the system default.
* @return this object.
* @throws IOException in an I/O error occurs.
* @throws IllegalArgumentException if the encoding is not supported.
*/
public Document load(final File file, final String encoding) throws IOException,
IllegalArgumentException {
return load((Object) (file == null ? new File("") : file), encoding);
}
/**
* this instance from the given abstract file path using the given character
* encoding.
*
* Note: the underlying file input stream is closed automatically.
*
*
* @param path from which to load; {@code null} is treated the empty
* path.
* @param encoding {@code null} for the system default.
* @return this object.
* @throws IOException in an I/O error occurs.
* @throws IllegalArgumentException if the encoding is not supported.
*/
public Document load(final String path, final String encoding) throws IOException,
IllegalArgumentException {
return load((Object) (path == null ? "" : path), encoding);
}
/**
* this instance from the given source.
*
* Note: if the source is an InputStream or Reader, it is the
* responsibility of the caller to close; otherwise, the underlying file
* input stream is closed automatically.
*
*
* @param source required; by default, must be an instance of an
* InputStream, Reader, File, or String denoting an
* abstract file path.
* @param encoding optional; ignored if source is a Writer. {@code null} is
* taken to be {@link Charset#defaultCharset()}.
* @return this instance.
* @throws IOException if an I/O error occurs.
* @throws NullPointerException if source is {@code null}.
* @throws IllegalArgumentException if the encoding is not supported.
*/
@SuppressWarnings("NestedAssignment")
protected Document load(final Object source, final String encoding) throws IOException,
NullPointerException,
IllegalArgumentException {
if (source == null) {
throw new NullPointerException("source must not be null.");
}
Charset charset = (source instanceof Reader || encoding == null)
? Charset.defaultCharset() : null;
if (charset == null && isSupportedCharset(encoding)) {
charset = Charset.forName(encoding);
} else {
throw new IllegalArgumentException("encoding: " + encoding);
}
BufferedReader reader = null;
boolean close = false;
if (source instanceof InputStream) {
final InputStream is = (InputStream) source;
final InputStreamReader isr = new InputStreamReader(is, charset);
reader = new BufferedReader(isr);
} else if (source instanceof File) {
final InputStream fis = new FileInputStream((File) source);
final InputStreamReader isr = new InputStreamReader(fis, charset);
close = true;
reader = new BufferedReader(isr);
} else if (source instanceof String) {
final InputStream fis = new FileInputStream((String) source);
final InputStreamReader isr = new InputStreamReader(fis, charset);
close = true;
reader = new BufferedReader(isr);
} else if (source instanceof BufferedReader) {
reader = (BufferedReader) source;
} else if (source instanceof Reader) {
reader = new BufferedReader((Reader) source);
} else {
throw new IOException("unhandled load source: " + source); // NOI18N
}
clear();
String line;
final List list = this.lines;
try {
while (null != (line = reader.readLine())) {
list.add(line);
}
} finally {
if (close) {
try {
reader.close();
} catch (IOException ex) {
}
}
}
return this;
}
/**
* to the given output stream using the given character encoding.
*
* Note: it is the responsibility of the caller to close the output
* stream.
*
*
* @param outputStream to which to save; must not be {@code null}.
* @param encoding {@code null} for the system default.
* @return this object.
* @throws IOException if an I/O error occurs.
* @throws IllegalArgumentException if the encoding is not supported.
* @throws NullPointerException if the output stream is {@code null}.
*/
public Document save(final OutputStream outputStream, final String encoding) throws IOException {
return save((Object) outputStream, encoding);
}
/**
* to the given file using the given character encoding.
*
* @param file to which to save; {@code null} is treated as having an
* empty path
* @param encoding {@code null} for the system default.
* @return this object.
* @throws IOException if an I/O error occurs.
* @throws IllegalArgumentException if the encoding is not supported.
*/
public Document save(final File file, final String encoding) throws IOException,
IllegalArgumentException {
return save((Object) (file == null ? new File("") : file), encoding);
}
/**
* to the given abstract file path using the given character encoding.
*
* @param path to which to save; {@code null} is treated as the empty
* path;
* @param encoding {@code null} for the system default.
* @return this object.
* @throws IOException if an I/O error occurs.
* @throws IllegalArgumentException if the encoding is not supported.
*/
public Document save(final String path, final String encoding) throws IOException,
IllegalArgumentException {
return save((Object) (path == null ? "" : path), encoding);
}
/**
* to the given writer.
*
* Note: it is the responsibility of the caller to close the writer.
*
*
* @param writer to which to save; must not be {@code null}.
* @return this object.
* @throws IOException if an I/O error occurs.
* @throws NullPointerException if the writer is {@code null}.
*/
public Document save(final Writer writer) throws IOException {
return save(writer, null);
}
/**
* this instance to the given target.
*
* Note: if the target is an OutputStream or Writer, it is the
* responsibility of the caller to close; otherwise, the underlying file
* output stream is closed automatically.
*
*
* @param target required; by default, must be an instance of an
* OutputStream, Writer, File, or String denoting an
* abstract file path.
* @param encoding optional; does not apply to Writer. {@code null} is taken
* to be {@link Charset#defaultCharset()}.
* @return this instance.
* @throws IOException if an I/O error occurs.
* @throws NullPointerException if target is {@code null}.
*/
protected Document save(final Object target, final String encoding) throws IOException {
if (target == null) {
throw new NullPointerException("target must not be null.");
}
Charset charset = (target instanceof Writer || encoding == null)
? Charset.defaultCharset() : null;
if (charset == null && isSupportedCharset(encoding)) {
charset = Charset.forName(encoding);
} else {
throw new IllegalArgumentException("encoding: " + encoding);
}
BufferedWriter writer = null;
boolean close = false;
if (target instanceof OutputStream) {
final OutputStream os = (OutputStream) target;
final OutputStreamWriter osw = new OutputStreamWriter(os, charset);
writer = new BufferedWriter(osw);
} else if (target instanceof File) {
final OutputStream fos = new FileOutputStream((File) target);
final OutputStreamWriter osw = new OutputStreamWriter(fos, charset);
close = true;
writer = new BufferedWriter(osw);
} else if (target instanceof String) {
OutputStream fos = new FileOutputStream((String) target);
final OutputStreamWriter osw = new OutputStreamWriter(fos, charset);
close = true;
writer = new BufferedWriter(osw);
} else if (target instanceof BufferedWriter) {
writer = (BufferedWriter) target;
} else if (target instanceof Writer) {
writer = new BufferedWriter(writer);
} else {
throw new IOException("unhandled save target: " + target);
}
final List list = this.lines;
final int count = list.size();
try {
for (int i = 0; i < count; i++) {
writer.write(list.get(i));
writer.newLine();
}
writer.flush();
} finally {
if (close) {
try {
writer.close();
} catch (IOException ignored) {
}
}
}
return this;
}
}