    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.xz;
    020
    021import java.util.HashMap;
    022import java.util.Map;
    023import org.apache.commons.compress.compressors.FileNameUtil;
    024
    025/**
    026 * Utility code for the xz compression format.
    027 * @ThreadSafe
    028 * @since 1.4
    029 */
    030public class XZUtils {
    031
    032    private static final FileNameUtil fileNameUtil;
    033
    034    /**
    035     * XZ Header Magic Bytes begin a XZ file.
    036     *
    037     * <p>This is a copy of {@code org.tukaani.xz.XZ.HEADER_MAGIC} in
    038     * XZ for Java version 1.5.</p>
    039     */
    040    private static final byte[] HEADER_MAGIC = {
    041        (byte) 0xFD, '7', 'z', 'X', 'Z', '\0'
    042    };
    043
    044    static enum CachedAvailability {
    045        DONT_CACHE, CACHED_AVAILABLE, CACHED_UNAVAILABLE
    046    }
    047
    048    private static volatile CachedAvailability cachedXZAvailability;
    049
    050    static {
    051        final Map<String, String> uncompressSuffix = new HashMap<>();
    052        uncompressSuffix.put(".txz", ".tar");
    053        uncompressSuffix.put(".xz", "");
    054        uncompressSuffix.put("-xz", "");
    055        fileNameUtil = new FileNameUtil(uncompressSuffix, ".xz");
    056        cachedXZAvailability = CachedAvailability.DONT_CACHE;
    057        try {
    058            Class.forName("org.osgi.framework.BundleEvent");
    059        } catch (final Exception ex) {
    060            setCacheXZAvailablity(true);
    061        }
    062    }
    063
    064    /** Private constructor to prevent instantiation of this utility class. */
    065    private XZUtils() {
    066    }
    067
    068    /**
    069     * Checks if the signature matches what is expected for a .xz file.
    070     *
    071     * <p>This is more or less a copy of the version found in {@link
    072     * XZCompressorInputStream} but doesn't depend on the presence of
    073     * XZ for Java.</p>
    074     *
    075     * @param   signature     the bytes to check
    076     * @param   length        the number of bytes to check
    077     * @return  true if signature matches the .xz magic bytes, false otherwise
    078     * @since 1.9
    079     */
    080    public static boolean matches(final byte[] signature, final int length) {
    081        if (length < HEADER_MAGIC.length) {
    082            return false;
    083        }
    084
    085        for (int i = 0; i < HEADER_MAGIC.length; ++i) {
    086            if (signature[i] != HEADER_MAGIC[i]) {
    087                return false;
    088            }
    089        }
    090
    091        return true;
    092    }
    093
    094    /**
    095     * Are the classes required to support XZ compression available?
    096     * @since 1.5
    097     * @return true if the classes required to support XZ compression are available
    098     */
    099    public static boolean isXZCompressionAvailable() {
    100        final CachedAvailability cachedResult = cachedXZAvailability;
    101        if (cachedResult != CachedAvailability.DONT_CACHE) {
    102            return cachedResult == CachedAvailability.CACHED_AVAILABLE;
    103        }
    104        return internalIsXZCompressionAvailable();
    105    }
    106
    107    private static boolean internalIsXZCompressionAvailable() {
    108        try {
    109            XZCompressorInputStream.matches(null, 0);
    110            return true;
    111        } catch (final NoClassDefFoundError error) {
    112            return false;
    113        }
    114    }
    115
    116    /**
    117     * Detects common xz suffixes in the given filename.
    118     *
    119     * @param filename name of a file
    120     * @return {@code true} if the filename has a common xz suffix,
    121     *         {@code false} otherwise
    122     */
    123    public static boolean isCompressedFilename(final String filename) {
    124        return fileNameUtil.isCompressedFilename(filename);
    125    }
    126
    127    /**
    128     * Maps the given name of a xz-compressed file to the name that the
    129     * file should have after uncompression. Commonly used file type specific
    130     * suffixes like ".txz" are automatically detected and
    131     * correctly mapped. For example the name "package.txz" is mapped to
    132     * "package.tar". And any filenames with the generic ".xz" suffix
    133     * (or any other generic xz suffix) is mapped to a name without that
    134     * suffix. If no xz suffix is detected, then the filename is returned
    135     * unmapped.
    136     *
    137     * @param filename name of a file
    138     * @return name of the corresponding uncompressed file
    139     */
    140    public static String getUncompressedFilename(final String filename) {
    141        return fileNameUtil.getUncompressedFilename(filename);
    142    }
    143
    144    /**
    145     * Maps the given filename to the name that the file should have after
    146     * compression with xz. Common file types with custom suffixes for
    147     * compressed versions are automatically detected and correctly mapped.
    148     * For example the name "package.tar" is mapped to "package.txz". If no
    149     * custom mapping is applicable, then the default ".xz" suffix is appended
    150     * to the filename.
    151     *
    152     * @param filename name of a file
    153     * @return name of the corresponding compressed file
    154     */
    155    public static String getCompressedFilename(final String filename) {
    156        return fileNameUtil.getCompressedFilename(filename);
    157    }
    158
    159    /**
    160     * Whether to cache the result of the XZ for Java check.
    161     *
    162     * <p>This defaults to {@code false} in an OSGi environment and {@code true} otherwise.</p>
    163     * @param doCache whether to cache the result
    164     * @since 1.9
    165     */
    166    public static void setCacheXZAvailablity(final boolean doCache) {
    167        if (!doCache) {
    168            cachedXZAvailability = CachedAvailability.DONT_CACHE;
    169        } else if (cachedXZAvailability == CachedAvailability.DONT_CACHE) {
    170            final boolean hasXz = internalIsXZCompressionAvailable();
    171            cachedXZAvailability = hasXz ? CachedAvailability.CACHED_AVAILABLE
    172                : CachedAvailability.CACHED_UNAVAILABLE;
    173        }
    174    }
    175
    176    // only exists to support unit tests
    177    static CachedAvailability getCachedXZAvailability() {
    178        return cachedXZAvailability;
    179    }
    180}