add compressors class for discovering compression formats
diff --git a/src/main/java/org/apache/commons/compress2/FormatDiscoverer.java b/src/main/java/org/apache/commons/compress2/FormatDiscoverer.java
new file mode 100644
index 0000000..a838012
--- /dev/null
+++ b/src/main/java/org/apache/commons/compress2/FormatDiscoverer.java
@@ -0,0 +1,117 @@
+/*
+ * 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.compress2;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.Spliterator;
+import java.util.Spliterators;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.ServiceConfigurationError;
+import java.util.ServiceLoader;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
+import org.apache.commons.compress2.Format;
+
+/**
+ * Loads formats defined as "services" and provides access to them.
+ *
+ * <p>Uses {@link java.util.ServiceLoader} under the covers but iterates over all formats found eagerly inside the
+ * constructor so errors are reported early.</p>
+ */
+public abstract class FormatDiscoverer<F extends Format> implements Iterable<F> {
+ private final ServiceLoader<F> formatLoader;
+ private Map<String, F> formats;
+
+ /**
+ * Loads services using the given class loader.
+ * @throws ServiceConfigurationError if an error occurs reading a service file or instantiating a format
+ */
+ protected FormatDiscoverer(ClassLoader cl, Class<F> clazz) throws ServiceConfigurationError {
+ this(ServiceLoader.load(clazz, cl));
+ }
+
+ private FormatDiscoverer(ServiceLoader<F> loader) {
+ formatLoader = loader;
+ fillMap();
+ }
+
+ /**
+ * Clears the cached formats and rebuilds it.
+ *
+ * @see ServiceLoader#reload
+ */
+ public void reload() {
+ formatLoader.reload();
+ fillMap();
+ }
+
+ /**
+ * Iterator over all known formats.
+ */
+ public Iterator<F> iterator() {
+ return formats.values().iterator();
+ }
+
+ /**
+ * Iterates over all known formats that support writing.
+ */
+ public Iterable<F> getFormatsWithWriteSupport() {
+ return filter(Format::supportsWriting);
+ }
+
+ /**
+ * Gets a format by its name.
+ * @param name the {@link Format#getName name} of the format.
+ * @return the Format instance if one is known by that name
+ */
+ public Optional<F> getFormatByName(String name) {
+ return Optional.ofNullable(formats.get(name));
+ }
+
+ private void fillMap() throws ServiceConfigurationError {
+ Set<F> ts = new TreeSet<F>(Format.AUTO_DETECTION_ORDER);
+ ts.addAll(asList(formatLoader));
+ formats = Collections.unmodifiableMap(ts.stream()
+ .collect(Collectors.toMap(Format::getName, Function.identity())));
+ }
+
+ protected Iterable<F> filter(final Predicate<F> p) {
+ return () -> StreamSupport.stream(Spliterators.spliterator(formats.values(),
+ Spliterator.NONNULL),
+ false)
+ .filter(p)
+ .iterator();
+ }
+
+ private static <T> List<T> asList(Iterable<T> i) {
+ List<T> l = new ArrayList<T>();
+ for (T t : i) {
+ l.add(t);
+ }
+ return l;
+ }
+}
diff --git a/src/main/java/org/apache/commons/compress2/archivers/Archivers.java b/src/main/java/org/apache/commons/compress2/archivers/Archivers.java
index 26f527c..014c2de 100644
--- a/src/main/java/org/apache/commons/compress2/archivers/Archivers.java
+++ b/src/main/java/org/apache/commons/compress2/archivers/Archivers.java
@@ -18,22 +18,9 @@
*/
package org.apache.commons.compress2.archivers;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.Spliterator;
-import java.util.Spliterators;
-import java.util.List;
-import java.util.Map;
+import java.util.Optional;
import java.util.ServiceConfigurationError;
-import java.util.ServiceLoader;
-import java.util.Set;
-import java.util.TreeSet;
-import java.util.function.Function;
-import java.util.function.Predicate;
-import java.util.stream.Collectors;
-import java.util.stream.StreamSupport;
-import org.apache.commons.compress2.Format;
+import org.apache.commons.compress2.FormatDiscoverer;
/**
* Loads ArchiveFormats defined as "services" from {@code
@@ -42,9 +29,7 @@
* <p>Uses {@link java.util.ServiceLoader} under the covers but iterates over all formats found eagerly inside the
* constructor so errors are reported early.</p>
*/
-public class Archivers implements Iterable<ArchiveFormat<? extends ArchiveEntry>> {
- private final ServiceLoader<ArchiveFormat> formatLoader;
- private Map<String, ArchiveFormat<? extends ArchiveEntry>> archivers;
+public class Archivers extends FormatDiscoverer<ArchiveFormat> {
/**
* Loads services using the current thread's context class loader.
@@ -59,90 +44,37 @@
* @throws ServiceConfigurationError if an error occurs reading a service file or instantiating a format
*/
public Archivers(ClassLoader cl) throws ServiceConfigurationError {
- this(ServiceLoader.load(ArchiveFormat.class, cl));
- }
-
- private Archivers(ServiceLoader<ArchiveFormat> loader) {
- formatLoader = loader;
- fillMap();
- }
-
- /**
- * Clears the cached formats and rebuilds it.
- *
- * @see ServiceLoader#reload
- */
- public void reload() {
- formatLoader.reload();
- fillMap();
- }
-
- /**
- * Iterator over all known formats.
- */
- public Iterator<ArchiveFormat<? extends ArchiveEntry>> iterator() {
- return archivers.values().iterator();
- }
-
- /**
- * Iterates over all known formats that can write archives.
- */
- public Iterable<ArchiveFormat<? extends ArchiveEntry>> getFormatsWithWriteSupport() {
- return filter(ArchiveFormat::supportsWriting);
+ super(cl, ArchiveFormat.class);
}
/**
* Iterates over all known formats that can write archives to channels.
*/
- public Iterable<ArchiveFormat<? extends ArchiveEntry>> getFormatsWithWriteSupportForNonSeekableChannels() {
+ public Iterable<ArchiveFormat> getFormatsWithWriteSupportForNonSeekableChannels() {
return filter(ArchiveFormat::supportsWritingToNonSeekableChannels);
}
/**
* Iterates over all known formats that can read archives from channels.
*/
- public Iterable<ArchiveFormat<? extends ArchiveEntry>> getFormatsWithReadSupportForNonSeekableChannels() {
+ public Iterable<ArchiveFormat> getFormatsWithReadSupportForNonSeekableChannels() {
return filter(ArchiveFormat::supportsReadingFromNonSeekableChannels);
}
/**
* Iterates over all known formats that provide random access input.
*/
- public Iterable<ArchiveFormat<? extends ArchiveEntry>> getFormatsWithRandomAccessInput() {
+ public Iterable<ArchiveFormat> getFormatsWithRandomAccessInput() {
return filter(ArchiveFormat::supportsRandomAccessInput);
}
/**
* Gets a format by its name.
* @param name the {@link ArchiveFormat#getName name} of the format.
- * @return the ArchiveFormat instance or null if not format is known by that name
+ * @return the Format instance if one is known by that name
*/
- public ArchiveFormat getArchiveFormatByName(String name) {
- return archivers.get(name);
+ public Optional<ArchiveFormat> getArchiveFormatByName(String name) {
+ return getFormatByName(name);
}
- private void fillMap() throws ServiceConfigurationError {
- Set<ArchiveFormat<? extends ArchiveEntry>> ts =
- new TreeSet<ArchiveFormat<? extends ArchiveEntry>>(Format.AUTO_DETECTION_ORDER);
- ts.addAll(asList(formatLoader));
- archivers = Collections.unmodifiableMap(ts.stream()
- .collect(Collectors.toMap(ArchiveFormat::getName, Function.identity())));
- }
-
- private Iterable<ArchiveFormat<? extends ArchiveEntry>>
- filter(final Predicate<ArchiveFormat<? extends ArchiveEntry>> p) {
- return () -> StreamSupport.stream(Spliterators.spliterator(archivers.values(),
- Spliterator.NONNULL),
- false)
- .filter(p)
- .iterator();
- }
-
- private static <T> List<T> asList(Iterable<T> i) {
- List<T> l = new ArrayList<T>();
- for (T t : i) {
- l.add(t);
- }
- return l;
- }
}
diff --git a/src/main/java/org/apache/commons/compress2/compressors/Compressors.java b/src/main/java/org/apache/commons/compress2/compressors/Compressors.java
new file mode 100644
index 0000000..b3a0672
--- /dev/null
+++ b/src/main/java/org/apache/commons/compress2/compressors/Compressors.java
@@ -0,0 +1,58 @@
+/*
+ * 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.compress2.compressors;
+
+import java.util.Optional;
+import java.util.ServiceConfigurationError;
+import org.apache.commons.compress2.FormatDiscoverer;
+
+/**
+ * Loads CompressionFormats defined as "services" from {@code
+ * META-INF/services/org.apache.commons.compress2.compressors.CompressionFormat} and provides access to them.
+ *
+ * <p>Uses {@link java.util.ServiceLoader} under the covers but iterates over all formats found eagerly inside the
+ * constructor so errors are reported early.</p>
+ */
+public class Compressors extends FormatDiscoverer<CompressionFormat> {
+
+ /**
+ * Loads services using the current thread's context class loader.
+ * @throws ServiceConfigurationError if an error occurs reading a service file or instantiating a format
+ */
+ public Compressors() throws ServiceConfigurationError {
+ this(Thread.currentThread().getContextClassLoader());
+ }
+
+ /**
+ * Loads services using the given class loader.
+ * @throws ServiceConfigurationError if an error occurs reading a service file or instantiating a format
+ */
+ public Compressors(ClassLoader cl) throws ServiceConfigurationError {
+ super(cl, CompressionFormat.class);
+ }
+
+ /**
+ * Gets a format by its name.
+ * @param name the {@link CompressionFormat#getName name} of the format.
+ * @return the Format instance if one is known by that name
+ */
+ public Optional<CompressionFormat> getCompressionFormatByName(String name) {
+ return getFormatByName(name);
+ }
+}
diff --git a/src/main/resources/META-INF/services/org.apache.commons.compress2.compressors.CompressionFormat b/src/main/resources/META-INF/services/org.apache.commons.compress2.compressors.CompressionFormat
new file mode 100644
index 0000000..421c8fe
--- /dev/null
+++ b/src/main/resources/META-INF/services/org.apache.commons.compress2.compressors.CompressionFormat
@@ -0,0 +1 @@
+org.apache.commons.compress2.formats.deflate.DeflateCompressionFormat
diff --git a/src/test/java/org/apache/commons/compress2/archivers/ArchiversTest.java b/src/test/java/org/apache/commons/compress2/archivers/ArchiversTest.java
index 0846244..397c10e 100644
--- a/src/test/java/org/apache/commons/compress2/archivers/ArchiversTest.java
+++ b/src/test/java/org/apache/commons/compress2/archivers/ArchiversTest.java
@@ -28,8 +28,9 @@
@Test
public void shouldFindArArchiveFormatByName() {
- ArchiveFormat<? extends ArchiveEntry> arFormat =
- new Archivers().getArchiveFormatByName(ArArchiveFormat.AR_FORMAT_NAME);
+ ArchiveFormat arFormat =
+ new Archivers().getArchiveFormatByName(ArArchiveFormat.AR_FORMAT_NAME)
+ .orElse(null);
Assert.assertNotNull(arFormat);
Assert.assertEquals(ArArchiveFormat.class, arFormat.getClass());
}
@@ -59,8 +60,8 @@
shouldNotFind(ArArchiveFormat.class, new Archivers().getFormatsWithRandomAccessInput());
}
- private void shouldFind(Class<?> archiveFormat, Iterable<ArchiveFormat<? extends ArchiveEntry>> i) {
- for (ArchiveFormat<? extends ArchiveEntry> a : i) {
+ private void shouldFind(Class<?> archiveFormat, Iterable<ArchiveFormat> i) {
+ for (ArchiveFormat a : i) {
if (archiveFormat.equals(a.getClass())) {
return;
}
@@ -68,8 +69,8 @@
Assert.fail("Expected to find " + archiveFormat);
}
- private void shouldNotFind(Class<?> archiveFormat, Iterable<ArchiveFormat<? extends ArchiveEntry>> i) {
- for (ArchiveFormat<? extends ArchiveEntry> a : i) {
+ private void shouldNotFind(Class<?> archiveFormat, Iterable<ArchiveFormat> i) {
+ for (ArchiveFormat a : i) {
if (archiveFormat.equals(a.getClass())) {
Assert.fail("Didn't expect to find " + archiveFormat);
}
diff --git a/src/test/java/org/apache/commons/compress2/compressors/CompressorsTest.java b/src/test/java/org/apache/commons/compress2/compressors/CompressorsTest.java
new file mode 100644
index 0000000..723abb4
--- /dev/null
+++ b/src/test/java/org/apache/commons/compress2/compressors/CompressorsTest.java
@@ -0,0 +1,64 @@
+/*
+ * 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.compress2.compressors;
+
+
+import org.apache.commons.compress2.formats.deflate.DeflateCompressionFormat;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class CompressorsTest {
+
+ @Test
+ public void shouldFindDeflateCompressionFormatByName() {
+ CompressionFormat deflateFormat =
+ new Compressors().getCompressionFormatByName(DeflateCompressionFormat.DEFLATE_FORMAT_NAME)
+ .orElse(null);
+ Assert.assertNotNull(deflateFormat);
+ Assert.assertEquals(DeflateCompressionFormat.class, deflateFormat.getClass());
+ }
+
+ @Test
+ public void shouldFindDeflateCompressionFormatWhenIterating() {
+ shouldFind(DeflateCompressionFormat.class, new Compressors());
+ }
+
+ @Test
+ public void shouldFindDeflateCompressionFormatAsWritableFormat() {
+ shouldFind(DeflateCompressionFormat.class, new Compressors().getFormatsWithWriteSupport());
+ }
+
+ private void shouldFind(Class<?> compressionFormat, Iterable<CompressionFormat> i) {
+ for (CompressionFormat a : i) {
+ if (compressionFormat.equals(a.getClass())) {
+ return;
+ }
+ }
+ Assert.fail("Expected to find " + compressionFormat);
+ }
+
+ private void shouldNotFind(Class<?> compressionFormat, Iterable<CompressionFormat> i) {
+ for (CompressionFormat a : i) {
+ if (compressionFormat.equals(a.getClass())) {
+ Assert.fail("Didn't expect to find " + compressionFormat);
+ }
+ }
+ }
+}