2929
3030import java .io .InputStream ;
3131import java .io .OutputStream ;
32- import java .util .Collections ;
33- import java .util .HashMap ;
3432import java .util .Locale ;
3533import java .util .Map ;
3634import java .util .Set ;
3735import java .util .concurrent .ConcurrentHashMap ;
3836import java .util .concurrent .atomic .AtomicReference ;
3937import java .util .stream .Collectors ;
4038
39+ import org .apache .commons .compress .compressors .CompressorException ;
4140import org .apache .commons .compress .compressors .CompressorStreamFactory ;
4241import org .apache .commons .compress .compressors .deflate .DeflateCompressorInputStream ;
4342import org .apache .commons .compress .compressors .deflate .DeflateParameters ;
6261 * <p>
6362 * This class is thread-safe and uses {@link AtomicReference} to cache the available input and output stream providers.
6463 * </p>
64+ *
65+ * @since 5.5
6566 */
6667public class CompressorFactory {
6768
@@ -76,22 +77,13 @@ public class CompressorFactory {
7677 private final AtomicReference <Set <String >> outputProvidersCache = new AtomicReference <>();
7778 private final Map <String , String > formattedNameCache = new ConcurrentHashMap <>();
7879
79- private static final Map <String , String > COMPRESSION_ALIASES ;
80- static {
81- final Map <String , String > aliases = new HashMap <>();
82- aliases .put ("gzip" , "gz" );
83- aliases .put ("x-gzip" , "gz" );
84- aliases .put ("compress" , "z" );
85- COMPRESSION_ALIASES = Collections .unmodifiableMap (aliases );
86- }
87-
8880 /**
8981 * Returns a set of available input stream compression providers.
9082 *
9183 * @return a set of available input stream compression providers in lowercase.
9284 */
9385 public Set <String > getAvailableInputProviders () {
94- return getAvailableProviders ( inputProvidersCache , false );
86+ return inputProvidersCache . updateAndGet ( existing -> existing != null ? existing : fetchAvailableInputProviders () );
9587 }
9688
9789 /**
@@ -100,7 +92,7 @@ public Set<String> getAvailableInputProviders() {
10092 * @return a set of available output stream compression providers in lowercase.
10193 */
10294 public Set <String > getAvailableOutputProviders () {
103- return getAvailableProviders ( outputProvidersCache , true );
95+ return outputProvidersCache . updateAndGet ( existing -> existing != null ? existing : fetchAvailableOutputProviders () );
10496 }
10597
10698 /**
@@ -119,29 +111,35 @@ public String getFormattedName(final String name) {
119111 return null ;
120112 }
121113 final String lowerCaseName = name .toLowerCase (Locale .ROOT );
122- return formattedNameCache .computeIfAbsent (lowerCaseName , key -> COMPRESSION_ALIASES .getOrDefault (key , key ));
114+ return formattedNameCache .computeIfAbsent (lowerCaseName , key -> {
115+ if ("gzip" .equals (key ) || "x-gzip" .equals (key )) {
116+ return "gz" ;
117+ } else if ("compress" .equals (key )) {
118+ return "z" ;
119+ }
120+ return key ;
121+ });
123122 }
124123
125-
126124 /**
127125 * Creates an input stream for the specified compression format and decompresses the provided input stream.
128126 * <p>
129- * This method uses the specified compression name to decompress the input stream and supports the "nowrap " option
127+ * This method uses the specified compression name to decompress the input stream and supports the "noWrap " option
130128 * for deflate streams.
131129 * </p>
132130 *
133131 * @param name the compression format.
134132 * @param inputStream the input stream to decompress.
135- * @param nowrap if true, disables the zlib header and trailer for deflate streams.
133+ * @param noWrap if true, disables the zlib header and trailer for deflate streams.
136134 * @return the decompressed input stream, or the original input stream if the format is not supported.
137135 */
138- public InputStream getCompressorInputStream (final String name , final InputStream inputStream , final boolean nowrap ) {
136+ public InputStream getCompressorInputStream (final String name , final InputStream inputStream , final boolean noWrap ) throws CompressorException {
139137 Args .notNull (inputStream , "InputStream" );
140138 Args .notNull (name , "name" );
141139
142140 final String formattedName = getFormattedName (name );
143141 return isSupported (formattedName , false )
144- ? createCompressorInputStream (formattedName , inputStream , nowrap )
142+ ? createCompressorInputStream (formattedName , inputStream , noWrap )
145143 : inputStream ;
146144 }
147145
@@ -152,30 +150,15 @@ public InputStream getCompressorInputStream(final String name, final InputStream
152150 * @param outputStream the output stream to compress.
153151 * @return the compressed output stream, or the original output stream if the format is not supported.
154152 */
155- public OutputStream getCompressorOutputStream (final String name , final OutputStream outputStream ) {
153+ public OutputStream getCompressorOutputStream (final String name , final OutputStream outputStream ) throws CompressorException {
156154 final String formattedName = getFormattedName (name );
157155 return isSupported (formattedName , true )
158156 ? createCompressorOutputStream (formattedName , outputStream )
159157 : outputStream ;
160- }
161158
162- /**
163- * Compresses the provided HTTP entity using the specified compression format.
164- *
165- * @param entity the HTTP entity to compress.
166- * @param contentEncoding the compression format.
167- * @return a compressed {@link HttpEntity}, or {@code null} if the compression format is unsupported.
168- */
169- public HttpEntity compressEntity (final HttpEntity entity , final String contentEncoding ) {
170- Args .notNull (entity , "Entity" );
171- Args .notNull (contentEncoding , "Content Encoding" );
172- if (!isSupported (contentEncoding , true )) {
173- LOG .warn ("Unsupported compression type: {}" , contentEncoding );
174- return null ;
175- }
176- return new CompressingEntity (entity , contentEncoding );
177159 }
178160
161+
179162 /**
180163 * Decompresses the provided HTTP entity using the specified compression format.
181164 *
@@ -192,103 +175,108 @@ public HttpEntity decompressEntity(final HttpEntity entity, final String content
192175 *
193176 * @param entity the HTTP entity to decompress.
194177 * @param contentEncoding the compression format.
195- * @param nowrap if true, disables the zlib header and trailer for deflate streams.
178+ * @param noWrap if true, disables the zlib header and trailer for deflate streams.
196179 * @return a decompressed {@link HttpEntity}, or {@code null} if the compression format is unsupported.
197180 */
198- public HttpEntity decompressEntity (final HttpEntity entity , final String contentEncoding , final boolean nowrap ) {
181+ public HttpEntity decompressEntity (final HttpEntity entity , final String contentEncoding , final boolean noWrap ) {
199182 Args .notNull (entity , "Entity" );
200183 Args .notNull (contentEncoding , "Content Encoding" );
201184 if (!isSupported (contentEncoding , false )) {
202185 LOG .warn ("Unsupported decompression type: {}" , contentEncoding );
203186 return null ;
204187 }
205- return new DecompressEntity (entity , contentEncoding , nowrap );
188+ return new DecompressEntity (entity , contentEncoding , noWrap );
206189 }
207190
208191 /**
209- * Creates a compressor input stream for the given compression format and input stream.
210- * <p>
211- * This method handles the special case for deflate compression where the zlib header can be skipped.
212- * </p>
192+ * Compresses the provided HTTP entity using the specified compression format.
213193 *
214- * @param name the compression format.
215- * @param inputStream the input stream to decompress.
216- * @param nowrap if true, disables the zlib header and trailer for deflate streams.
217- * @return a decompressed input stream, or null if an error occurs.
194+ * @param entity the HTTP entity to compress.
195+ * @param contentEncoding the compression format.
196+ * @return a compressed {@link HttpEntity}, or {@code null} if the compression format is unsupported.
218197 */
219- private InputStream createCompressorInputStream (final String name , final InputStream inputStream , final boolean nowrap ) {
220- try {
221- if ("deflate" .equalsIgnoreCase (name )) {
222- final DeflateParameters parameters = new DeflateParameters ();
223- parameters .setWithZlibHeader (nowrap );
224- return new DeflateCompressorInputStream (inputStream , parameters );
225- }
226- return compressorStreamFactory .createCompressorInputStream (name , inputStream , true );
227- } catch (final Exception ex ) {
228- LOG .warn ("Could not create compressor {} input stream" , name , ex );
198+ public HttpEntity compressEntity (final HttpEntity entity , final String contentEncoding ) {
199+ Args .notNull (entity , "Entity" );
200+ Args .notNull (contentEncoding , "Content Encoding" );
201+ if (!isSupported (contentEncoding , true )) {
202+ LOG .warn ("Unsupported compression type: {}" , contentEncoding );
229203 return null ;
230204 }
205+ return new CompressingEntity (entity , contentEncoding );
231206 }
232207
233208 /**
234- * Determines if the specified compression format is supported for either input or output streams .
209+ * Fetches the available input stream compression providers from Commons Compress .
235210 *
236- * @param name the compression format.
237- * @param isOutput if true, checks if the format is supported for output; otherwise, checks for input support.
238- * @return true if the format is supported, false otherwise.
211+ * @return a set of available input stream compression providers in lowercase.
239212 */
240- private boolean isSupported ( final String name , final boolean isOutput ) {
241- final String formattedName = getFormattedName ( name );
242- return isOutput
243- ? getAvailableOutputProviders (). contains ( formattedName )
244- : getAvailableInputProviders (). contains ( formattedName );
213+ private Set < String > fetchAvailableInputProviders ( ) {
214+ final Set < String > inputNames = compressorStreamFactory . getInputStreamCompressorNames ( );
215+ return inputNames . stream ()
216+ . map ( String :: toLowerCase )
217+ . collect ( Collectors . toSet () );
245218 }
246219
247220 /**
248- * Creates a compressor output stream for the given compression format and output stream .
221+ * Fetches the available output stream compression providers from Commons Compress .
249222 *
250- * @param name the compression format.
251- * @param outputStream the output stream to compress.
252- * @return a compressed output stream, or null if an error occurs.
223+ * @return a set of available output stream compression providers in lowercase.
253224 */
254- private OutputStream createCompressorOutputStream (final String name , final OutputStream outputStream ) {
255- try {
256- return compressorStreamFactory .createCompressorOutputStream (name , outputStream );
257- } catch (final Exception ex ) {
258- LOG .warn ("Could not create compressor {} output stream" , name , ex );
259-
260- return null ;
261- }
225+ private Set <String > fetchAvailableOutputProviders () {
226+ final Set <String > outputNames = compressorStreamFactory .getOutputStreamCompressorNames ();
227+ return outputNames .stream ()
228+ .map (String ::toLowerCase )
229+ .collect (Collectors .toSet ());
262230 }
263231
264232 /**
265- * Retrieves the available compression providers for input or output streams .
233+ * Creates a compressor input stream for the given compression format and input stream .
266234 * <p>
267- * This method uses a cache to avoid redundant lookups and ensures the providers are formatted in lowercase.
235+ * This method handles the special case for deflate compression where the zlib header can be optionally included.
236+ * The noWrap parameter directly controls the behavior of the zlib header:
237+ * - If noWrap is {@code true}, the deflate stream is processed without zlib headers (raw Deflate).
238+ * - If noWrap is {@code false}, the deflate stream includes the zlib header.
268239 * </p>
269240 *
270- * @param cache the cache that stores the available providers.
271- * @param isOutput if true, retrieves available providers for output streams; otherwise, for input streams.
272- * @return a set of available compression providers in lowercase.
241+ * @param name the compression format (e.g., "gzip", "deflate").
242+ * @param inputStream the input stream to decompress; must not be {@code null}.
243+ * @param noWrap if {@code true}, disables the zlib header and trailer for deflate streams (raw Deflate).
244+ * @return a decompressed input stream, or {@code null} if an error occurs during stream creation.
245+ * @throws CompressorException if an error occurs while creating the compressor input stream or if the compression format is unsupported.
273246 */
274- private Set <String > getAvailableProviders (final AtomicReference <Set <String >> cache , final boolean isOutput ) {
275- return cache .updateAndGet (existing -> existing != null ? existing : fetchAvailableProviders (isOutput ));
247+ private InputStream createCompressorInputStream (final String name , final InputStream inputStream , final boolean noWrap ) throws CompressorException {
248+ if ("deflate" .equalsIgnoreCase (name )) {
249+ final DeflateParameters parameters = new DeflateParameters ();
250+ parameters .setWithZlibHeader (noWrap );
251+ return new DeflateCompressorInputStream (inputStream , parameters );
252+ }
253+ return compressorStreamFactory .createCompressorInputStream (name , inputStream , true );
276254 }
277255
278256 /**
279- * Fetches the available compression providers by querying the {@link CompressorStreamFactory} .
257+ * Creates a compressor output stream for the given compression format and output stream .
280258 *
281- * @param isOutput if true, fetches available providers for output streams; otherwise, for input streams.
282- * @return a set of available compression providers in lowercase.
259+ * @param name the compression format.
260+ * @param outputStream the output stream to compress.
261+ * @return a compressed output stream, or null if an error occurs.
262+ * @throws CompressorException if an error occurs while creating the compressor output stream.
283263 */
284- private Set <String > fetchAvailableProviders (final boolean isOutput ) {
285- return (isOutput
286- ? CompressorStreamFactory .findAvailableCompressorOutputStreamProviders ()
287- : CompressorStreamFactory .findAvailableCompressorInputStreamProviders ())
288- .keySet ().stream ()
289- .map (String ::toLowerCase )
290- .collect (Collectors .collectingAndThen (Collectors .toSet (), Collections ::unmodifiableSet ));
264+ private OutputStream createCompressorOutputStream (final String name , final OutputStream outputStream ) throws CompressorException {
265+ return compressorStreamFactory .createCompressorOutputStream (name , outputStream );
266+ }
267+
268+ /**
269+ * Determines if the specified compression format is supported for either input or output streams.
270+ *
271+ * @param name the compression format.
272+ * @param isOutput if true, checks if the format is supported for output; otherwise, checks for input support.
273+ * @return true if the format is supported, false otherwise.
274+ */
275+ private boolean isSupported (final String name , final boolean isOutput ) {
276+ final Set <String > availableProviders = isOutput ? getAvailableOutputProviders () : getAvailableInputProviders ();
277+ return availableProviders .contains (name );
291278 }
292279}
293280
294281
282+
0 commit comments