blob: 6fddd42475e8d4ae3d7e93209dae8aa09de3a92d [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.freemarker.generator.base.document;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.LineIterator;
import org.apache.freemarker.generator.base.activation.ByteArrayDataSource;
import org.apache.freemarker.generator.base.activation.StringDataSource;
import org.apache.freemarker.generator.base.util.CloseableReaper;
import javax.activation.DataSource;
import javax.activation.FileDataSource;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.nio.charset.Charset;
import java.util.List;
import static java.nio.charset.Charset.forName;
import static java.util.Objects.requireNonNull;
import static org.apache.commons.io.IOUtils.lineIterator;
import static org.apache.freemarker.generator.base.FreeMarkerConstants.DOCUMENT_UNKNOWN_LENGTH;
/**
* Source document which encapsulates a data source. When accessing
* content it is loaded on demand on not kept in memory to allow
* processing of large volumes of data.
*/
public class Document implements Closeable {
/** Human-readable name of the document */
private final String name;
/** Charset for directly accessing text-based content */
private final Charset charset;
/** The data source */
private final DataSource dataSource;
/** The location of the content, e.g. file name */
private final String location;
/** Collect all closables handed out to the caller to be closed when the document is closed itself */
private final CloseableReaper closables;
public Document(String name, DataSource dataSource, String location, Charset charset) {
this.name = requireNonNull(name);
this.dataSource = requireNonNull(dataSource);
this.location = requireNonNull(location);
this.charset = requireNonNull(charset);
this.closables = new CloseableReaper();
}
public String getName() {
return name;
}
public String getBaseName() {
return FilenameUtils.getBaseName(name);
}
public String getExtension() {
return FilenameUtils.getExtension(name);
}
public Charset getCharset() {
return charset;
}
public String getLocation() {
return location;
}
/**
* Try to get the length lazily, efficient and without consuming the input stream.
*
* @return Length of document or UNKNOWN_LENGTH
*/
public long getLength() {
if (dataSource instanceof FileDataSource) {
return ((FileDataSource) dataSource).getFile().length();
} else if (dataSource instanceof StringDataSource) {
return ((StringDataSource) dataSource).length();
} else if (dataSource instanceof ByteArrayDataSource) {
return ((ByteArrayDataSource) dataSource).length();
} else {
return DOCUMENT_UNKNOWN_LENGTH;
}
}
/**
* Get an input stream which is closed together with this document.
*
* @return InputStream
* @throws IOException Operation failed
*/
public InputStream getInputStream() throws IOException {
return closables.add(getUnsafeInputStream());
}
/**
* Get an input stream which needs to be closed by the caller.
*
* @return InputStream
* @throws IOException Operation failed
*/
public InputStream getUnsafeInputStream() throws IOException {
return dataSource.getInputStream();
}
public String getText() throws IOException {
return getText(getCharset().name());
}
public String getText(String charsetName) throws IOException {
final StringWriter writer = new StringWriter();
try (InputStream is = getUnsafeInputStream()) {
IOUtils.copy(is, writer, forName(charsetName));
return writer.toString();
}
}
/**
* Gets the contents of an <code>InputStream</code> as a list of Strings,
* one entry per line, using the specified character encoding.
*
* @return the list of Strings, never null
* @throws IOException if an I/O error occurs
*/
public List<String> getLines() throws IOException {
return getLines(getCharset().name());
}
/**
* Gets the contents of an <code>InputStream</code> as a list of Strings,
* one entry per line, using the specified character encoding.
*
* @param charsetName The name of the requested charset
* @return the list of Strings, never null
* @throws IOException if an I/O error occurs
*/
public List<String> getLines(String charsetName) throws IOException {
try (InputStream inputStream = getUnsafeInputStream()) {
return IOUtils.readLines(inputStream, charsetName);
}
}
/**
* Returns an Iterator for the lines in an <code>InputStream</code>, using
* the default character encoding specified. The caller is responsible to close
* the line iterator.
*
* @return line iterator
* @throws IOException if an I/O error occurs
*/
public LineIterator getLineIterator() throws IOException {
return getLineIterator(getCharset().name());
}
/**
* Returns an Iterator for the lines in an <code>InputStream</code>, using
* the character encoding specified.
*
* @param charsetName The name of the requested charset
* @return line iterator
* @throws IOException if an I/O error occurs
*/
public LineIterator getLineIterator(String charsetName) throws IOException {
return closables.add(lineIterator(getUnsafeInputStream(), forName(charsetName)));
}
public byte[] getBytes() throws IOException {
try (InputStream inputStream = getUnsafeInputStream()) {
return IOUtils.toByteArray(inputStream);
}
}
/**
* Some tools create a {@link java.io.Closeable} which can bound to the
* lifecycle of the document. When the document is closed all the bound
* {@link java.io.Closeable} are closed as well.
*
* @param closeable Closable
* @param <T> Type of closable
* @return Closable
*/
public <T extends Closeable> T addClosable(T closeable) {
return closables.add(closeable);
}
@Override
public void close() {
closables.close();
}
@Override
public String toString() {
return "Document{" +
"name='" + name + '\'' +
", location=" + location +
", charset='" + charset + '\'' +
'}';
}
}