View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   * http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.commons.compress.compressors.xz;
20  
21  import java.util.HashMap;
22  import java.util.Map;
23  
24  import org.apache.commons.compress.compressors.FileNameUtil;
25  import org.apache.commons.compress.utils.OsgiUtils;
26  
27  /**
28   * Utility code for the XZ compression format.
29   *
30   * @ThreadSafe
31   * @since 1.4
32   */
33  public class XZUtils {
34  
35      enum CachedAvailability {
36          DONT_CACHE, CACHED_AVAILABLE, CACHED_UNAVAILABLE
37      }
38  
39      private static final FileNameUtil fileNameUtil;
40  
41      /**
42       * XZ Header Magic Bytes begin a XZ file.
43       *
44       * <p>
45       * This is a copy of {@code org.tukaani.xz.XZ.HEADER_MAGIC} in XZ for Java version 1.5.
46       * </p>
47       */
48      private static final byte[] HEADER_MAGIC = { (byte) 0xFD, '7', 'z', 'X', 'Z', '\0' };
49  
50      private static volatile CachedAvailability cachedXZAvailability;
51  
52      static {
53          final Map<String, String> uncompressSuffix = new HashMap<>();
54          uncompressSuffix.put(".txz", ".tar");
55          uncompressSuffix.put(".xz", "");
56          uncompressSuffix.put("-xz", "");
57          fileNameUtil = new FileNameUtil(uncompressSuffix, ".xz");
58          cachedXZAvailability = CachedAvailability.DONT_CACHE;
59          setCacheXZAvailablity(!OsgiUtils.isRunningInOsgiEnvironment());
60      }
61  
62      // only exists to support unit tests
63      static CachedAvailability getCachedXZAvailability() {
64          return cachedXZAvailability;
65      }
66  
67      /**
68       * Maps the given file name to the name that the file should have after compression with xz. Common file types with custom suffixes for compressed versions
69       * are automatically detected and correctly mapped. For example the name "package.tar" is mapped to "package.txz". If no custom mapping is applicable, then
70       * the default ".xz" suffix is appended to the file name.
71       *
72       * @param fileName name of a file
73       * @return name of the corresponding compressed file
74       * @deprecated Use {@link #getCompressedFileName(String)}.
75       */
76      @Deprecated
77      public static String getCompressedFilename(final String fileName) {
78          return fileNameUtil.getCompressedFileName(fileName);
79      }
80  
81      /**
82       * Maps the given file name to the name that the file should have after compression with xz. Common file types with custom suffixes for compressed versions
83       * are automatically detected and correctly mapped. For example the name "package.tar" is mapped to "package.txz". If no custom mapping is applicable, then
84       * the default ".xz" suffix is appended to the file name.
85       *
86       * @param fileName name of a file
87       * @return name of the corresponding compressed file
88       * @since 1.25.0
89       */
90      public static String getCompressedFileName(final String fileName) {
91          return fileNameUtil.getCompressedFileName(fileName);
92      }
93  
94      /**
95       * Maps the given name of a xz-compressed file to the name that the file should have after uncompression. Commonly used file type specific suffixes like
96       * ".txz" are automatically detected and correctly mapped. For example the name "package.txz" is mapped to "package.tar". And any file names with the
97       * generic ".xz" suffix (or any other generic xz suffix) is mapped to a name without that suffix. If no xz suffix is detected, then the file name is
98       * returned unmapped.
99       *
100      * @param fileName name of a file
101      * @return name of the corresponding uncompressed file
102      * @deprecated Use {@link #getUncompressedFileName(String)}.
103      */
104     @Deprecated
105     public static String getUncompressedFilename(final String fileName) {
106         return fileNameUtil.getUncompressedFileName(fileName);
107     }
108 
109     /**
110      * Maps the given name of a xz-compressed file to the name that the file should have after uncompression. Commonly used file type specific suffixes like
111      * ".txz" are automatically detected and correctly mapped. For example the name "package.txz" is mapped to "package.tar". And any file names with the
112      * generic ".xz" suffix (or any other generic xz suffix) is mapped to a name without that suffix. If no xz suffix is detected, then the file name is
113      * returned unmapped.
114      *
115      * @param fileName name of a file
116      * @return name of the corresponding uncompressed file
117      * @since 1.25.0
118      */
119     public static String getUncompressedFileName(final String fileName) {
120         return fileNameUtil.getUncompressedFileName(fileName);
121     }
122 
123     private static boolean internalIsXZCompressionAvailable() {
124         try {
125             XZCompressorInputStream.matches(null, 0);
126             return true;
127         } catch (final NoClassDefFoundError error) { // NOSONAR
128             return false;
129         }
130     }
131 
132     /**
133      * Detects common xz suffixes in the given file name.
134      *
135      * @param fileName name of a file
136      * @return {@code true} if the file name has a common xz suffix, {@code false} otherwise
137      * @deprecated Use {@link #isCompressedFileName(String)}.
138      */
139     @Deprecated
140     public static boolean isCompressedFilename(final String fileName) {
141         return fileNameUtil.isCompressedFileName(fileName);
142     }
143 
144     /**
145      * Detects common xz suffixes in the given file name.
146      *
147      * @param fileName name of a file
148      * @return {@code true} if the file name has a common xz suffix, {@code false} otherwise
149      * @since 1.25.0
150      */
151     public static boolean isCompressedFileName(final String fileName) {
152         return fileNameUtil.isCompressedFileName(fileName);
153     }
154 
155     /**
156      * Are the classes required to support XZ compression available?
157      *
158      * @since 1.5
159      * @return true if the classes required to support XZ compression are available
160      */
161     public static boolean isXZCompressionAvailable() {
162         final CachedAvailability cachedResult = cachedXZAvailability;
163         if (cachedResult != CachedAvailability.DONT_CACHE) {
164             return cachedResult == CachedAvailability.CACHED_AVAILABLE;
165         }
166         return internalIsXZCompressionAvailable();
167     }
168 
169     /**
170      * Checks if the signature matches what is expected for a .xz file.
171      *
172      * <p>
173      * This is more or less a copy of the version found in {@link XZCompressorInputStream} but doesn't depend on the presence of XZ for Java.
174      * </p>
175      *
176      * @param signature the bytes to check
177      * @param length    the number of bytes to check
178      * @return true if signature matches the .xz magic bytes, false otherwise
179      * @since 1.9
180      */
181     public static boolean matches(final byte[] signature, final int length) {
182         if (length < HEADER_MAGIC.length) {
183             return false;
184         }
185 
186         for (int i = 0; i < HEADER_MAGIC.length; ++i) {
187             if (signature[i] != HEADER_MAGIC[i]) {
188                 return false;
189             }
190         }
191 
192         return true;
193     }
194 
195     /**
196      * Whether to cache the result of the XZ for Java check.
197      *
198      * <p>
199      * This defaults to {@code false} in an OSGi environment and {@code true} otherwise.
200      * </p>
201      *
202      * @param doCache whether to cache the result
203      * @since 1.9
204      */
205     public static void setCacheXZAvailablity(final boolean doCache) {
206         if (!doCache) {
207             cachedXZAvailability = CachedAvailability.DONT_CACHE;
208         } else if (cachedXZAvailability == CachedAvailability.DONT_CACHE) {
209             final boolean hasXz = internalIsXZCompressionAvailable();
210             cachedXZAvailability = hasXz ? CachedAvailability.CACHED_AVAILABLE // NOSONAR
211                     : CachedAvailability.CACHED_UNAVAILABLE;
212         }
213     }
214 
215     /** Private constructor to prevent instantiation of this utility class. */
216     private XZUtils() {
217     }
218 }