You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by bo...@apache.org on 2018/04/29 17:02:03 UTC

commons-compress git commit: COMPRESS-118 provide a more fluent archiving interface

Repository: commons-compress
Updated Branches:
  refs/heads/master eee4d197d -> f62c52315


COMPRESS-118 provide a more fluent archiving interface


Project: http://git-wip-us.apache.org/repos/asf/commons-compress/repo
Commit: http://git-wip-us.apache.org/repos/asf/commons-compress/commit/f62c5231
Tree: http://git-wip-us.apache.org/repos/asf/commons-compress/tree/f62c5231
Diff: http://git-wip-us.apache.org/repos/asf/commons-compress/diff/f62c5231

Branch: refs/heads/master
Commit: f62c523154dfedcf49a87a865db545bb8c55e795
Parents: eee4d19
Author: Stefan Bodewig <bo...@apache.org>
Authored: Sun Apr 29 19:01:25 2018 +0200
Committer: Stefan Bodewig <bo...@apache.org>
Committed: Sun Apr 29 19:01:25 2018 +0200

----------------------------------------------------------------------
 .../compress/archivers/examples/Archive.java    | 128 +++++++++++++++++++
 .../compress/archivers/examples/ArchiveCli.java |  44 +++++++
 .../compress/archivers/examples/Chain.java      |  42 ++++++
 .../archivers/examples/ChainDefinition.java     |  70 ++++++++++
 .../archivers/examples/ChainPayload.java        |  68 ++++++++++
 .../compress/archivers/examples/ChainStep.java  |  40 ++++++
 .../examples/DirectoryBasedSupplier.java        | 107 ++++++++++++++++
 .../archivers/examples/FileFilterAdapter.java   |  37 ++++++
 .../archivers/examples/FileToArchiveSink.java   | 123 ++++++++++++++++++
 .../compress/archivers/examples/Filter.java     |  44 +++++++
 .../examples/SevenZOutputFileSink.java          |  78 +++++++++++
 .../compress/archivers/examples/Sink.java       |  47 +++++++
 .../compress/archivers/examples/Supplier.java   |  30 +++++
 .../archivers/examples/ThrowingIterator.java    |  30 +++++
 14 files changed, 888 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/commons-compress/blob/f62c5231/src/main/java/org/apache/commons/compress/archivers/examples/Archive.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/commons/compress/archivers/examples/Archive.java b/src/main/java/org/apache/commons/compress/archivers/examples/Archive.java
new file mode 100644
index 0000000..c451170
--- /dev/null
+++ b/src/main/java/org/apache/commons/compress/archivers/examples/Archive.java
@@ -0,0 +1,128 @@
+/*
+ * 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.commons.compress.archivers.examples;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.io.IOException;
+import org.apache.commons.compress.archivers.ArchiveException;
+
+/**
+ * Consumes files and passes them to a sink, usually used to create an archive of them.
+ * @since 1.17
+ */
+public class Archive {
+    /**
+     * Sets up a chain of operations and consumes the files from a supplier of files.
+     * @since 1.17
+     */
+    public interface ChainBuilder {
+        /**
+         * Adds a filter to the chain.
+         */
+        ChainBuilder filter(Filter<File> filter);
+        /**
+         * Adds a filter to the chain.
+         */
+        ChainBuilder filter(FileFilter filter);
+        /**
+         * Adds a filter to the chain that filters out entries that cannot be read.
+         */
+        ChainBuilder skipUnreadable();
+        /**
+         * Adds a filter to the chain that filters out everything that is not a file.
+         */
+        ChainBuilder skipNonFiles();
+        /**
+         * Actually consumes all the files supplied.
+         */
+        void to(Sink<File> sink) throws IOException, ArchiveException;
+    }
+
+    /**
+     * Sets the source of files to be a directory.
+     */
+    public static ChainBuilder directory(File f) {
+        return source(new DirectoryBasedSupplier(f));
+    }
+
+    /**
+     * Sets the source of files to process.
+     */
+    public static ChainBuilder source(Supplier<ThrowingIterator<ChainPayload<File>>> supplier) {
+        return new Builder(supplier);
+    }
+
+    private static class Builder implements ChainBuilder {
+        private final Supplier<ThrowingIterator<ChainPayload<File>>> supplier;
+        private ChainDefinition<File> chainDef = new ChainDefinition<>();
+
+        Builder(Supplier<ThrowingIterator<ChainPayload<File>>> supplier) {
+            this.supplier = supplier;
+        }
+
+        public ChainBuilder filter(Filter<File> filter) {
+            chainDef.add(filter);
+            return this;
+        }
+        public ChainBuilder filter(FileFilter filter) {
+            return filter(new FileFilterAdapter(filter));
+        }
+        public ChainBuilder skipUnreadable() {
+            return filter(new FileFilter() {
+                @Override
+                public boolean accept(File f) {
+                    return f.canRead();
+                }
+            });
+        }
+        public ChainBuilder skipNonFiles() {
+            return filter(new FileFilter() {
+                @Override
+                public boolean accept(File f) {
+                    return f.isFile();
+                }
+            });
+        }
+        public void to(Sink<File> sink) throws IOException, ArchiveException {
+            chainDef.add(sink);
+            chainDef.freeze();
+            new Archive(supplier, chainDef, sink).run();
+        }
+    }
+
+    private final Supplier<ThrowingIterator<ChainPayload<File>>> supplier;
+    private final ChainDefinition<File> chainDef;
+    private final Sink<File> sink;
+
+    private Archive(Supplier<ThrowingIterator<ChainPayload<File>>> supplier, ChainDefinition<File> chainDef,
+        Sink<File> sink) {
+        this.supplier = supplier;
+        this.chainDef = chainDef;
+        this.sink = sink;
+    }
+
+    private void run() throws IOException, ArchiveException {
+        ThrowingIterator<ChainPayload<File>> iter = supplier.get();
+        while (iter.hasNext()) {
+            chainDef.chain().next(iter.next());
+        }
+        sink.finish();
+    }
+}

http://git-wip-us.apache.org/repos/asf/commons-compress/blob/f62c5231/src/main/java/org/apache/commons/compress/archivers/examples/ArchiveCli.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/commons/compress/archivers/examples/ArchiveCli.java b/src/main/java/org/apache/commons/compress/archivers/examples/ArchiveCli.java
new file mode 100644
index 0000000..7df4a82
--- /dev/null
+++ b/src/main/java/org/apache/commons/compress/archivers/examples/ArchiveCli.java
@@ -0,0 +1,44 @@
+/*
+ * 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.commons.compress.archivers.examples;
+
+import java.io.File;
+import java.io.IOException;
+import org.apache.commons.compress.archivers.ArchiveException;
+
+/**
+ * Simple command line tool that creates an archive from the contents of a directory.
+ *
+ * <p>Usage: <code>ArchiveCli dir format archive</code></p>
+ * @since 1.17
+ */
+public class ArchiveCli {
+
+    public static void main(String[] args) throws IOException, ArchiveException {
+        if (args.length != 3) {
+            System.err.println("Usage: ArchiveCli dir format target");
+            System.exit(1);
+        }
+        try (Sink<File> sink = FileToArchiveSink.forFile(args[1], new File(args[2]))) {
+            Archive.directory(new File(args[0]))
+                .to(sink);
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/commons-compress/blob/f62c5231/src/main/java/org/apache/commons/compress/archivers/examples/Chain.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/commons/compress/archivers/examples/Chain.java b/src/main/java/org/apache/commons/compress/archivers/examples/Chain.java
new file mode 100644
index 0000000..9989395
--- /dev/null
+++ b/src/main/java/org/apache/commons/compress/archivers/examples/Chain.java
@@ -0,0 +1,42 @@
+/*
+ * 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.commons.compress.archivers.examples;
+
+import java.io.IOException;
+import java.util.Iterator;
+import org.apache.commons.compress.archivers.ArchiveException;
+
+/**
+ * Encapsulates the execution flow of a chain of operations.
+ * @since 1.17
+ */
+public class Chain<T> {
+
+    private final Iterator<ChainStep<T>> chain;
+
+    public Chain(Iterator<ChainStep<T>> chain) {
+        this.chain = chain;
+    }
+
+    public void next(ChainPayload<T> payload) throws IOException, ArchiveException {
+        if (chain.hasNext()) {
+            chain.next().process(payload, this);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/commons-compress/blob/f62c5231/src/main/java/org/apache/commons/compress/archivers/examples/ChainDefinition.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/commons/compress/archivers/examples/ChainDefinition.java b/src/main/java/org/apache/commons/compress/archivers/examples/ChainDefinition.java
new file mode 100644
index 0000000..5136b1d
--- /dev/null
+++ b/src/main/java/org/apache/commons/compress/archivers/examples/ChainDefinition.java
@@ -0,0 +1,70 @@
+/*
+ * 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.commons.compress.archivers.examples;
+
+import java.util.Deque;
+import java.util.LinkedList;
+
+/**
+ * The recipe for building a {@link Chain}.
+ * @since 1.17
+ */
+public class ChainDefinition<T> {
+    private final Deque<ChainStep<T>> steps = new LinkedList<>();
+    private volatile boolean frozen = false;
+
+    /**
+     * Adds a step.
+     * @throws IllegalStateException if the definition is already frozen.
+     */
+    public void add(ChainStep<T> step) {
+        if (frozen) {
+            throw new IllegalStateException("the definition is already frozen");
+        }
+        steps.addLast(step);
+    }
+
+    /**
+     * Freezes the definition.
+     *
+     * <p>Once this method has been invoked {@link #add} can no longer be invoked.</p>
+     *
+     * @throws IllegalStateException if the last step of the definition is not a sink.
+     */
+    public void freeze() {
+        if (!frozen) {
+            frozen = true;
+            if (!(steps.getLast() instanceof Sink)) {
+                throw new IllegalStateException("this definition doesn't end in a sink");
+            }
+        }
+    }
+
+    /**
+     * Returns a chain for this definition.
+     *
+     * @throws IllegalStateException if the definition is not frozen.
+     */
+    public Chain<T> chain() {
+        if (!frozen) {
+            throw new IllegalStateException("the definition hasn't been frozen, yet");
+        }
+        return new Chain(steps.iterator());
+    }
+}

http://git-wip-us.apache.org/repos/asf/commons-compress/blob/f62c5231/src/main/java/org/apache/commons/compress/archivers/examples/ChainPayload.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/commons/compress/archivers/examples/ChainPayload.java b/src/main/java/org/apache/commons/compress/archivers/examples/ChainPayload.java
new file mode 100644
index 0000000..82d96fb
--- /dev/null
+++ b/src/main/java/org/apache/commons/compress/archivers/examples/ChainPayload.java
@@ -0,0 +1,68 @@
+/*
+ * 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.commons.compress.archivers.examples;
+
+import java.io.InputStream;
+
+/**
+ * The data that is pushed through a chain.
+ * @since 1.17
+ */
+public class ChainPayload<T> {
+    private final T entry;
+    private final String entryName;
+    private final Supplier<InputStream> input;
+    /**
+     * Constructs the payload.
+     * @param T entry the actual payload
+     * @param entryName the local name of the entry. This may - for
+     * example - be the file name relative to a directory.
+     * @param input supplies an input stream to the entry's
+     * content. Is not expected to be called more than once.
+     */
+    public ChainPayload(T entry, String entryName, Supplier<InputStream> input) {
+        this.entry = entry;
+        this.entryName = entryName;
+        this.input = input;
+    }
+    /**
+     * Provides the real payload.
+     */
+    public T getEntry() {
+        return entry;
+    }
+    /**
+     * Provides the local name of the entry.
+     *
+     * <p>This may - for example - be the file name relative to a
+     * directory.</p>
+     */
+    public String getEntryName() {
+        return entryName;
+    }
+    /**
+     * Returns a {@link Supplier} that can be used to read the entry's content.
+     *
+     * <p>The supplier is not required to be callable more than
+     * once.</p>
+     */
+    public Supplier<InputStream> getInput() {
+        return input;
+    }
+}

http://git-wip-us.apache.org/repos/asf/commons-compress/blob/f62c5231/src/main/java/org/apache/commons/compress/archivers/examples/ChainStep.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/commons/compress/archivers/examples/ChainStep.java b/src/main/java/org/apache/commons/compress/archivers/examples/ChainStep.java
new file mode 100644
index 0000000..9edb5aa
--- /dev/null
+++ b/src/main/java/org/apache/commons/compress/archivers/examples/ChainStep.java
@@ -0,0 +1,40 @@
+/*
+ * 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.commons.compress.archivers.examples;
+
+import java.io.IOException;
+import org.apache.commons.compress.archivers.ArchiveException;
+
+/**
+ * A step inside of a {@link Chain}.
+ * @since 1.17
+ */
+public interface ChainStep<T> {
+    /**
+     * Process the chain's payload.
+     *
+     * <p>Any non-terminal step that invokes the {@link Supplier} of
+     * the payload is responsible for providing a fresh supplier if
+     * the chain is to be continued.</p>
+     *
+     * @param payload the payload.
+     * @param chain chain to return control to once processing is done.
+     */
+    void process(ChainPayload<T> payload, Chain<T> chain) throws IOException, ArchiveException;
+}

http://git-wip-us.apache.org/repos/asf/commons-compress/blob/f62c5231/src/main/java/org/apache/commons/compress/archivers/examples/DirectoryBasedSupplier.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/commons/compress/archivers/examples/DirectoryBasedSupplier.java b/src/main/java/org/apache/commons/compress/archivers/examples/DirectoryBasedSupplier.java
new file mode 100644
index 0000000..7bfe58b
--- /dev/null
+++ b/src/main/java/org/apache/commons/compress/archivers/examples/DirectoryBasedSupplier.java
@@ -0,0 +1,107 @@
+/*
+ * 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.commons.compress.archivers.examples;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ * Recursively returns all files and directories contained inside of a base directory.
+ * @since 1.17
+ */
+public class DirectoryBasedSupplier
+    implements Supplier<ThrowingIterator<ChainPayload<File>>> {
+
+    private final File dir;
+
+    /**
+     * @param dir the directory to provide entries from.
+     */
+    public DirectoryBasedSupplier(File dir) {
+        if (!dir.isDirectory()) {
+            throw new IllegalArgumentException("dir is not a readable directory");
+        }
+        this.dir = dir;
+    }
+
+    @Override
+    public ThrowingIterator<ChainPayload<File>> get() throws IOException {
+        return new DirectoryIterator("", dir);
+    }
+
+    private class DirectoryIterator implements ThrowingIterator<ChainPayload<File>> {
+        private final Iterator<File> files;
+        private final String namePrefix;
+        private DirectoryIterator nestedIterator;
+        DirectoryIterator(String namePrefix, File dir) throws IOException {
+            this.namePrefix = namePrefix;
+            File[] fs = dir.listFiles();
+            files = fs == null
+                ? new Iterator<File>() {
+                    @Override
+                    public boolean hasNext() {
+                        return false;
+                    }
+                    @Override
+                    public File next() {
+                        throw new NoSuchElementException();
+                    }
+                }
+            : Arrays.asList(fs).iterator();
+        }
+
+        @Override
+        public boolean hasNext() throws IOException {
+            if (nestedIterator != null && nestedIterator.hasNext()) {
+                return true;
+            }
+            if (nestedIterator != null) {
+                nestedIterator = null;
+            }
+            return files.hasNext();
+        }
+
+        @Override
+        public ChainPayload<File> next() throws IOException {
+            if (!hasNext()) {
+                throw new NoSuchElementException();
+            }
+            if (nestedIterator != null) {
+                return nestedIterator.next();
+            }
+            final File f = files.next();
+            String entryName = namePrefix + f.getName();
+            if (f.isDirectory()) {
+                entryName += "/";
+                nestedIterator = new DirectoryIterator(entryName, f);
+            }
+            return new ChainPayload(f, entryName, new Supplier<InputStream>() {
+                    @Override
+                    public InputStream get() throws IOException {
+                        return new FileInputStream(f);
+                    }
+                });
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/commons-compress/blob/f62c5231/src/main/java/org/apache/commons/compress/archivers/examples/FileFilterAdapter.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/commons/compress/archivers/examples/FileFilterAdapter.java b/src/main/java/org/apache/commons/compress/archivers/examples/FileFilterAdapter.java
new file mode 100644
index 0000000..9f5a846
--- /dev/null
+++ b/src/main/java/org/apache/commons/compress/archivers/examples/FileFilterAdapter.java
@@ -0,0 +1,37 @@
+/*
+ * 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.commons.compress.archivers.examples;
+
+import java.io.File;
+import java.io.FileFilter;
+
+/**
+ * @since 1.17
+ */
+public class FileFilterAdapter extends Filter<File> {
+    private final FileFilter filter;
+    public FileFilterAdapter(FileFilter f) {
+        filter = f;
+    }
+
+    @Override
+    public boolean accept(String entryName, File entry) {
+        return filter.accept(entry);
+    }
+}

http://git-wip-us.apache.org/repos/asf/commons-compress/blob/f62c5231/src/main/java/org/apache/commons/compress/archivers/examples/FileToArchiveSink.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/commons/compress/archivers/examples/FileToArchiveSink.java b/src/main/java/org/apache/commons/compress/archivers/examples/FileToArchiveSink.java
new file mode 100644
index 0000000..5afdb75
--- /dev/null
+++ b/src/main/java/org/apache/commons/compress/archivers/examples/FileToArchiveSink.java
@@ -0,0 +1,123 @@
+/*
+ * 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.commons.compress.archivers.examples;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.FileOutputStream;
+import java.nio.channels.Channels;
+import java.nio.channels.FileChannel;
+import java.nio.channels.SeekableByteChannel;
+import java.nio.file.StandardOpenOption;
+import org.apache.commons.compress.archivers.ArchiveEntry;
+import org.apache.commons.compress.archivers.ArchiveException;
+import org.apache.commons.compress.archivers.ArchiveOutputStream;
+import org.apache.commons.compress.archivers.ArchiveStreamFactory;
+import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
+import org.apache.commons.compress.utils.IOUtils;
+
+/**
+ * Sink that creates an archive from files.
+ * @since 1.17
+ */
+public class FileToArchiveSink extends Sink<File> {
+    private final ArchiveOutputStream os;
+
+    /**
+     * Wraps an ArchiveOutputStream.
+     */
+    public FileToArchiveSink(ArchiveOutputStream os) {
+        this.os = os;
+    }
+
+    @Override
+    public void consume(ChainPayload<File> payload) throws IOException, ArchiveException {
+        ArchiveEntry e = os.createArchiveEntry(payload.getEntry(), payload.getEntryName());
+        os.putArchiveEntry(e);
+        if (!payload.getEntry().isDirectory()) {
+            try (InputStream in = new BufferedInputStream(payload.getInput().get())) {
+                IOUtils.copy(in, os);
+            }
+        }
+        os.closeArchiveEntry();
+    }
+
+    @Override
+    public void finish() throws IOException {
+        os.finish();
+    }
+
+    @Override
+    public void close() throws IOException {
+        os.close();
+    }
+
+    /**
+     * Wraps an ArchiveOutputStream.
+     */
+    public static Sink<File> forStream(ArchiveOutputStream os) {
+        return new FileToArchiveSink(os);
+    }
+
+    /**
+     * Uses {@link ArchiveFactory#createArchiveOutputStream}.
+     *
+     * <p>Will not support 7z.</p>
+     */
+    public static Sink<File> forStream(String format, OutputStream os) throws IOException, ArchiveException {
+        return new FileToArchiveSink(new ArchiveStreamFactory().createArchiveOutputStream(format, os));
+    }
+
+    /**
+     * Uses {@link ArchiveFactory#createArchiveOutputStream} unless
+     * special handling for ZIP or 7z is required.
+     */
+    public static Sink<File> forFile(String format, File target) throws IOException, ArchiveException {
+        if (prefersSeekableByteChannel(format)) {
+            return forChannel(format, FileChannel.open(target.toPath(), StandardOpenOption.WRITE,
+                StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING));
+        }
+        return new FileToArchiveSink(new ArchiveStreamFactory()
+            .createArchiveOutputStream(format, new FileOutputStream(target)));
+    }
+
+    /**
+     * Uses {@link ArchiveFactory#createArchiveOutputStream} unless
+     * special handling for ZIP or 7z is required.
+     */
+    public static Sink<File> forChannel(String format, SeekableByteChannel c) throws IOException, ArchiveException {
+        if (!prefersSeekableByteChannel(format)) {
+            return forStream(format, Channels.newOutputStream(c));
+        } else if (ArchiveStreamFactory.ZIP.equalsIgnoreCase(format)) {
+            return forStream(new ZipArchiveOutputStream(c));
+        } else if (ArchiveStreamFactory.SEVEN_Z.equalsIgnoreCase(format)) {
+            return new SevenZOutputFileSink(c);
+        } else {
+            throw new ArchiveException("don't know how to handle format " + format);
+        }
+    }
+
+    private static boolean prefersSeekableByteChannel(String format) {
+        return ArchiveStreamFactory.ZIP.equalsIgnoreCase(format) || ArchiveStreamFactory.SEVEN_Z.equalsIgnoreCase(format);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/commons-compress/blob/f62c5231/src/main/java/org/apache/commons/compress/archivers/examples/Filter.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/commons/compress/archivers/examples/Filter.java b/src/main/java/org/apache/commons/compress/archivers/examples/Filter.java
new file mode 100644
index 0000000..924864c
--- /dev/null
+++ b/src/main/java/org/apache/commons/compress/archivers/examples/Filter.java
@@ -0,0 +1,44 @@
+/*
+ * 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.commons.compress.archivers.examples;
+
+import java.io.IOException;
+import org.apache.commons.compress.archivers.ArchiveException;
+
+/**
+ * Filtering stage of a {@link Expand} or {@link Archive} chain.
+ * @since 1.17
+ */
+public abstract class Filter<T> implements ChainStep<T> {
+    /**
+     * Decides whether to process an entry or not.
+     *
+     * @param name of the entry
+     * @param entry the entry
+     * @return true if the entry shall be processed.
+     */
+    public abstract boolean accept(String entryName, T entry);
+
+    @Override
+    public void process(ChainPayload<T> payload, Chain<T> chain) throws IOException, ArchiveException {
+        if (accept(payload.getEntryName(), payload.getEntry())) {
+            chain.next(payload);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/commons-compress/blob/f62c5231/src/main/java/org/apache/commons/compress/archivers/examples/SevenZOutputFileSink.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/commons/compress/archivers/examples/SevenZOutputFileSink.java b/src/main/java/org/apache/commons/compress/archivers/examples/SevenZOutputFileSink.java
new file mode 100644
index 0000000..f9a1e14
--- /dev/null
+++ b/src/main/java/org/apache/commons/compress/archivers/examples/SevenZOutputFileSink.java
@@ -0,0 +1,78 @@
+/*
+ * 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.commons.compress.archivers.examples;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.channels.SeekableByteChannel;
+import org.apache.commons.compress.archivers.ArchiveEntry;
+import org.apache.commons.compress.archivers.ArchiveException;
+import org.apache.commons.compress.archivers.sevenz.SevenZOutputFile;
+import org.apache.commons.compress.utils.IOUtils;
+
+/**
+ * Sink that creates a 7z archive from files.
+ * @since 1.17
+ */
+public class SevenZOutputFileSink extends Sink<File> {
+
+    private final SevenZOutputFile outFile;
+
+    public SevenZOutputFileSink(File f) throws IOException {
+        this(new SevenZOutputFile(f));
+    }
+
+    public SevenZOutputFileSink(SeekableByteChannel c) throws IOException {
+        this(new SevenZOutputFile(c));
+    }
+
+    public SevenZOutputFileSink(SevenZOutputFile outFile) {
+        this.outFile = outFile;
+    }
+
+    @Override
+    public void consume(ChainPayload<File> payload) throws IOException, ArchiveException {
+        ArchiveEntry e = outFile.createArchiveEntry(payload.getEntry(), payload.getEntryName());
+        outFile.putArchiveEntry(e);
+        if (!payload.getEntry().isDirectory()) {
+            final byte[] buffer = new byte[8024];
+            int n = 0;
+            long count = 0;
+            try (InputStream in = new BufferedInputStream(payload.getInput().get())) {
+                while (-1 != (n = in.read(buffer))) {
+                    outFile.write(buffer, 0, n);
+                    count += n;
+                }
+            }
+        }
+        outFile.closeArchiveEntry();
+    }
+
+    @Override
+    public void finish() throws IOException {
+        outFile.finish();
+    }
+
+    @Override
+    public void close() throws IOException {
+        outFile.close();
+    }
+}

http://git-wip-us.apache.org/repos/asf/commons-compress/blob/f62c5231/src/main/java/org/apache/commons/compress/archivers/examples/Sink.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/commons/compress/archivers/examples/Sink.java b/src/main/java/org/apache/commons/compress/archivers/examples/Sink.java
new file mode 100644
index 0000000..eb81c1c
--- /dev/null
+++ b/src/main/java/org/apache/commons/compress/archivers/examples/Sink.java
@@ -0,0 +1,47 @@
+/*
+ * 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.commons.compress.archivers.examples;
+
+import java.io.Closeable;
+import java.io.IOException;
+import org.apache.commons.compress.archivers.ArchiveException;
+
+/**
+ * Final stage of a {@link Expand} or {@link Archive} chain.
+ * @since 1.17
+ */
+public abstract class Sink<T> implements ChainStep<T>, Closeable {
+    /**
+     * Consume a single entry.
+     */
+    public abstract void consume(ChainPayload<T> payload) throws IOException, ArchiveException;
+
+    /**
+     * Is invoked once all entries have been processed.
+     *
+     * <p>This implementation is empty.
+     */
+    public void finish() throws IOException, ArchiveException {
+    }
+
+    @Override
+    public void process(ChainPayload<T> payload, Chain<T> chain) throws IOException, ArchiveException {
+        consume(payload);
+    }
+}

http://git-wip-us.apache.org/repos/asf/commons-compress/blob/f62c5231/src/main/java/org/apache/commons/compress/archivers/examples/Supplier.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/commons/compress/archivers/examples/Supplier.java b/src/main/java/org/apache/commons/compress/archivers/examples/Supplier.java
new file mode 100644
index 0000000..6c73f91
--- /dev/null
+++ b/src/main/java/org/apache/commons/compress/archivers/examples/Supplier.java
@@ -0,0 +1,30 @@
+/*
+ * 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.commons.compress.archivers.examples;
+
+import java.io.IOException;
+import org.apache.commons.compress.archivers.ArchiveException;
+
+/**
+ * Used inside of {@link ChainPayload} as well as {@link Archive} and {@link Expand}.
+ * @since 1.12
+ */
+public interface Supplier<T> {
+    T get() throws IOException, ArchiveException;
+}

http://git-wip-us.apache.org/repos/asf/commons-compress/blob/f62c5231/src/main/java/org/apache/commons/compress/archivers/examples/ThrowingIterator.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/commons/compress/archivers/examples/ThrowingIterator.java b/src/main/java/org/apache/commons/compress/archivers/examples/ThrowingIterator.java
new file mode 100644
index 0000000..4a7bad8
--- /dev/null
+++ b/src/main/java/org/apache/commons/compress/archivers/examples/ThrowingIterator.java
@@ -0,0 +1,30 @@
+/*
+ * 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.commons.compress.archivers.examples;
+
+import java.io.IOException;
+import org.apache.commons.compress.archivers.ArchiveException;
+
+/**
+ * Specialized iterator that is allowed to throw Exceptions.
+ */
+public interface ThrowingIterator<T> {
+    boolean hasNext() throws IOException, ArchiveException;
+    T next() throws IOException, ArchiveException;
+}