001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.apache.commons.compress.compressors.lzma;
020
021import java.util.HashMap;
022import java.util.Map;
023
024import org.apache.commons.compress.compressors.FileNameUtil;
025import org.apache.commons.compress.utils.OsgiUtils;
026
027/**
028 * Utility code for the LZMA compression format.
029 *
030 * @ThreadSafe
031 * @since 1.10
032 */
033public class LZMAUtils {
034
035    enum CachedAvailability {
036        DONT_CACHE, CACHED_AVAILABLE, CACHED_UNAVAILABLE
037    }
038
039    private static final FileNameUtil fileNameUtil;
040
041    /**
042     * LZMA Header Magic Bytes begin a LZMA file.
043     */
044    private static final byte[] HEADER_MAGIC = { (byte) 0x5D, 0, 0 };
045
046    private static volatile CachedAvailability cachedLZMAAvailability;
047
048    static {
049        final Map<String, String> uncompressSuffix = new HashMap<>();
050        uncompressSuffix.put(".lzma", "");
051        uncompressSuffix.put("-lzma", "");
052        fileNameUtil = new FileNameUtil(uncompressSuffix, ".lzma");
053        cachedLZMAAvailability = CachedAvailability.DONT_CACHE;
054        setCacheLZMAAvailablity(!OsgiUtils.isRunningInOsgiEnvironment());
055    }
056
057    // only exists to support unit tests
058    static CachedAvailability getCachedLZMAAvailability() {
059        return cachedLZMAAvailability;
060    }
061
062    /**
063     * Maps the given file name to the name that the file should have after compression with LZMA.
064     *
065     * @param fileName name of a file
066     * @return name of the corresponding compressed file
067     * @deprecated Use {@link #getCompressedFileName(String)}.
068     */
069    @Deprecated
070    public static String getCompressedFilename(final String fileName) {
071        return fileNameUtil.getCompressedFileName(fileName);
072    }
073
074    /**
075     * Maps the given file name to the name that the file should have after compression with LZMA.
076     *
077     * @param fileName name of a file
078     * @return name of the corresponding compressed file
079     * @since 1.25.0
080     */
081    public static String getCompressedFileName(final String fileName) {
082        return fileNameUtil.getCompressedFileName(fileName);
083    }
084
085    /**
086     * Maps the given name of a LZMA-compressed file to the name that the file should have after uncompression. Any file names with the generic ".lzma" suffix
087     * (or any other generic LZMA suffix) is mapped to a name without that suffix. If no LZMA suffix is detected, then the file name is returned unmapped.
088     *
089     * @param fileName name of a file
090     * @return name of the corresponding uncompressed file
091     * @deprecated Use {@link #getUncompressedFileName(String)}.
092     */
093    @Deprecated
094    public static String getUncompressedFilename(final String fileName) {
095        return fileNameUtil.getUncompressedFileName(fileName);
096    }
097
098    /**
099     * Maps the given name of a LZMA-compressed file to the name that the file should have after uncompression. Any file names with the generic ".lzma" suffix
100     * (or any other generic LZMA suffix) is mapped to a name without that suffix. If no LZMA suffix is detected, then the file name is returned unmapped.
101     *
102     * @param fileName name of a file
103     * @return name of the corresponding uncompressed file
104     * @since 1.25.0
105     */
106    public static String getUncompressedFileName(final String fileName) {
107        return fileNameUtil.getUncompressedFileName(fileName);
108    }
109
110    private static boolean internalIsLZMACompressionAvailable() {
111        try {
112            LZMACompressorInputStream.matches(null, 0);
113            return true;
114        } catch (final NoClassDefFoundError error) { // NOSONAR
115            return false;
116        }
117    }
118
119    /**
120     * Detects common LZMA suffixes in the given file name.
121     *
122     * @param fileName name of a file
123     * @return {@code true} if the file name has a common LZMA suffix, {@code false} otherwise
124     * @deprecated Use {@link #isCompressedFileName(String)}.
125     */
126    @Deprecated
127    public static boolean isCompressedFilename(final String fileName) {
128        return fileNameUtil.isCompressedFileName(fileName);
129    }
130
131    /**
132     * Detects common LZMA suffixes in the given file name.
133     *
134     * @param fileName name of a file
135     * @return {@code true} if the file name has a common LZMA suffix, {@code false} otherwise
136     * @since 1.25.0
137     */
138    public static boolean isCompressedFileName(final String fileName) {
139        return fileNameUtil.isCompressedFileName(fileName);
140    }
141
142    /**
143     * Are the classes required to support LZMA compression available?
144     *
145     * @return true if the classes required to support LZMA compression are available
146     */
147    public static boolean isLZMACompressionAvailable() {
148        final CachedAvailability cachedResult = cachedLZMAAvailability;
149        if (cachedResult != CachedAvailability.DONT_CACHE) {
150            return cachedResult == CachedAvailability.CACHED_AVAILABLE;
151        }
152        return internalIsLZMACompressionAvailable();
153    }
154
155    /**
156     * Checks if the signature matches what is expected for a .lzma file.
157     *
158     * @param signature the bytes to check
159     * @param length    the number of bytes to check
160     * @return true if signature matches the .lzma magic bytes, false otherwise
161     */
162    public static boolean matches(final byte[] signature, final int length) {
163        if (length < HEADER_MAGIC.length) {
164            return false;
165        }
166
167        for (int i = 0; i < HEADER_MAGIC.length; ++i) {
168            if (signature[i] != HEADER_MAGIC[i]) {
169                return false;
170            }
171        }
172
173        return true;
174    }
175
176    /**
177     * Whether to cache the result of the LZMA check.
178     *
179     * <p>
180     * This defaults to {@code false} in an OSGi environment and {@code true} otherwise.
181     * </p>
182     *
183     * @param doCache whether to cache the result
184     */
185    public static void setCacheLZMAAvailablity(final boolean doCache) {
186        if (!doCache) {
187            cachedLZMAAvailability = CachedAvailability.DONT_CACHE;
188        } else if (cachedLZMAAvailability == CachedAvailability.DONT_CACHE) {
189            final boolean hasLzma = internalIsLZMACompressionAvailable();
190            cachedLZMAAvailability = hasLzma ? CachedAvailability.CACHED_AVAILABLE // NOSONAR
191                    : CachedAvailability.CACHED_UNAVAILABLE;
192        }
193    }
194
195    /** Private constructor to prevent instantiation of this utility class. */
196    private LZMAUtils() {
197    }
198}