/*
* JBoss, Home of Professional Open Source
* Copyright 2012, Red Hat Middleware LLC, and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* Licensed 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.jboss.shrinkwrap.impl.base.exporter;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Collection;
import java.util.Iterator;
import org.jboss.shrinkwrap.api.Archive;
import org.jboss.shrinkwrap.api.ArchivePath;
import org.jboss.shrinkwrap.api.Node;
import org.jboss.shrinkwrap.api.asset.Asset;
import org.jboss.shrinkwrap.api.exporter.ArchiveExportException;
import org.jboss.shrinkwrap.impl.base.io.IOUtil;
import org.jboss.shrinkwrap.impl.base.path.PathUtil;
Base for on-demand input streams. Encodes data on the fly, when read method is executed.
Author: Michal Matloka
/**
* Base for on-demand input streams. Encodes data on the fly, when read method is executed.
*
* @author <a href="mailto:mmatloka@gmail.com">Michal Matloka</a>
*/
public abstract class AbstractOnDemandInputStream<T extends OutputStream> extends InputStream {
Number of bytes kept in buffer.
/**
* Number of bytes kept in buffer.
*/
private static final int BUFFER_LENGTH = 4096;
Iterator over nodes contained in base archive.
/**
* Iterator over nodes contained in base archive.
*/
private final Iterator<Node> nodesIterator;
Created by abstract method.
/**
* Created by abstract method.
*/
protected T outputStream;
Base for outputStream.
/**
* Base for outputStream.
*/
private final ByteArrayOutputStream bufferedOutputStream = new ByteArrayOutputStream();
Stream of currently processed Node.
/**
* Stream of currently processed Node.
*/
private InputStream currentNodeStream;
Stream to the buffer.
/**
* Stream to the buffer.
*/
private ByteArrayInputStream bufferInputStream;
If output stream was closed - we should finish.
/**
* If output stream was closed - we should finish.
*/
private boolean outputStreamClosed = false;
Currently processed archive path - for displaying exception.
/**
* Currently processed archive path - for displaying exception.
*/
private ArchivePath currentPath = null;
Creates stream directly from archive.
Params: - archive –
/**
* Creates stream directly from archive.
*
* @param archive
*/
public AbstractOnDemandInputStream(final Archive<?> archive) {
final Collection<Node> nodes = archive.getContent().values();
this.nodesIterator = nodes.iterator();
}
@Override
public int read() throws IOException {
if (outputStream == null && !outputStreamClosed) {
// first run
outputStream = createOutputStream(bufferedOutputStream);
}
int value = bufferInputStream != null ? bufferInputStream.read() : -1;
if (value == -1) {
if (currentNodeStream != null) {
// current node was not processed completely
try {
doCopy();
bufferInputStream = new ByteArrayInputStream(bufferedOutputStream.toByteArray());
bufferedOutputStream.reset();
return this.read();
} catch (final Throwable t) {
throw new ArchiveExportException("Failed to write asset to output: " + currentPath.get(), t);
}
} else if (nodesIterator.hasNext()) {
// current node was processed completely, process next one
final Node currentNode = nodesIterator.next();
currentPath = currentNode.getPath();
final String pathName = PathUtil.optionallyRemovePrecedingSlash(currentPath.get());
final boolean isDirectory = currentNode.getAsset() == null;
String resolvedPath = pathName;
if (isDirectory) {
resolvedPath = PathUtil.optionallyAppendSlash(resolvedPath);
startAsset(resolvedPath, currentNode.getAsset());
endAsset();
} else {
startAsset(resolvedPath, currentNode.getAsset());
try {
currentNodeStream = currentNode.getAsset().openStream();
doCopy();
} catch (final Throwable t) {
throw new ArchiveExportException("Failed to write asset to output: " + currentPath.get(), t);
}
bufferInputStream = new ByteArrayInputStream(bufferedOutputStream.toByteArray());
bufferedOutputStream.reset();
}
} else {
// each node was processed
if (!outputStreamClosed) {
outputStream.close();
outputStreamClosed = true;
// output closed, now process what was saved on close
bufferInputStream = new ByteArrayInputStream(bufferedOutputStream.toByteArray());
bufferedOutputStream.close();
currentNodeStream = null;
outputStream = null;
return this.read();
}
// everything was read, end
return -1;
}
// chosen new node or new data in buffer - read again
return this.read();
}
return value;
}
Performs copy operation between currentNodeStream and outputStream using buffer length.
Throws: - IOException –
/**
* Performs copy operation between currentNodeStream and outputStream using buffer length.
*
* @throws IOException
*/
private void doCopy() throws IOException {
int copied = IOUtil.copy(currentNodeStream, outputStream, BUFFER_LENGTH);
if (copied == -1) {
currentNodeStream.close();
currentNodeStream = null;
endAsset();
}
}
Start entry in stream.
Params: - path –
Throws:
/**
* Start entry in stream.
*
* @param path
* @throws IOException
*/
private void startAsset(final String path, final Asset asset) throws IOException {
putNextEntry(outputStream, path, asset);
}
Close entry in stream.
Throws: - IOException –
/**
* Close entry in stream.
*
* @throws IOException
*/
private void endAsset() throws IOException {
closeEntry(outputStream);
}
Creates the real OutputStream
to which we'll write, wrapping the provided target. Params: - outputStream –
Throws: - IOException –
If an error occurred in creating the stream
Returns:
/**
* Creates the real {@link OutputStream} to which we'll write, wrapping the provided target.
*
* @param outputStream
* @return
* @throws IOException
* If an error occurred in creating the stream
*/
protected abstract T createOutputStream(final OutputStream outputStream) throws IOException;
Writes the next entry (demarcates a new file/folder is to be written).
Params: - outputStream –
- context –
Throws: - IOException –
If an error occurred writing the entry
/**
* Writes the next entry (demarcates a new file/folder is to be written).
*
* @param outputStream
* @param context
* @throws IOException
* If an error occurred writing the entry
*/
protected abstract void putNextEntry(final T outputStream, final String context, final Asset asset) throws IOException;
Closes the current entry context for the specified OutputStream
. Params: - outputStream –
/**
* Closes the current entry context for the specified {@link OutputStream}.
*
* @param outputStream
*/
protected abstract void closeEntry(final T outputStream) throws IOException;
}