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   *   https://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.zstandard;
20  
21  import org.apache.commons.compress.utils.OsgiUtils;
22  
23  /**
24   * Utility code for the Zstandard compression format.
25   *
26   * <p>
27   * This class avoids making the underlying {@code zstd} classes part of the public or protected API. The underlying implementation is provided through the
28   * <a href="https://github.com/luben/zstd-jni/">Zstandard JNI</a> library which is based on <a href="https://github.com/facebook/zstd/">zstd</a>.
29   * </p>
30   *
31   * @ThreadSafe
32   * @see <a href="https://github.com/luben/zstd-jni/">Zstandard JNI</a>
33   * @see <a href="https://github.com/facebook/zstd/">zstd</a>
34   * @since 1.16
35   */
36  public class ZstdUtils {
37  
38      enum CachedAvailability {
39          DONT_CACHE, CACHED_AVAILABLE, CACHED_UNAVAILABLE
40      }
41  
42      /**
43       * Zstandard Frame Magic Bytes.
44       */
45      private static final byte[] ZSTANDARD_FRAME_MAGIC = { (byte) 0x28, (byte) 0xB5, (byte) 0x2F, (byte) 0xFD };
46  
47      /**
48       * Skippable Frame Magic Bytes - the three common bytes.
49       */
50      private static final byte[] SKIPPABLE_FRAME_MAGIC = { (byte) 0x2A, (byte) 0x4D, (byte) 0x18 };
51  
52      private static volatile CachedAvailability cachedZstdAvailability;
53  
54      static {
55          cachedZstdAvailability = CachedAvailability.DONT_CACHE;
56          setCacheZstdAvailablity(!OsgiUtils.isRunningInOsgiEnvironment());
57      }
58  
59      // only exists to support unit tests
60      static CachedAvailability getCachedZstdAvailability() {
61          return cachedZstdAvailability;
62      }
63  
64      private static boolean internalIsZstdCompressionAvailable() {
65          try {
66              Class.forName("com.github.luben.zstd.ZstdInputStream");
67              return true;
68          } catch (final NoClassDefFoundError | Exception error) { // NOSONAR
69              return false;
70          }
71      }
72  
73      /**
74       * Are the classes required to support Zstandard compression available?
75       *
76       * @return true if the classes required to support Zstandard compression are available
77       */
78      public static boolean isZstdCompressionAvailable() {
79          final CachedAvailability cachedResult = cachedZstdAvailability;
80          if (cachedResult != CachedAvailability.DONT_CACHE) {
81              return cachedResult == CachedAvailability.CACHED_AVAILABLE;
82          }
83          return internalIsZstdCompressionAvailable();
84      }
85  
86      /**
87       * Checks if the signature matches what is expected for a Zstandard file.
88       *
89       * @param signature the bytes to check
90       * @param length    the number of bytes to check
91       * @return true if signature matches the Ztstandard or skippable frame magic bytes, false otherwise
92       */
93      public static boolean matches(final byte[] signature, final int length) {
94          if (length < ZSTANDARD_FRAME_MAGIC.length) {
95              return false;
96          }
97          boolean isZstandard = true;
98          for (int i = 0; i < ZSTANDARD_FRAME_MAGIC.length; ++i) {
99              if (signature[i] != ZSTANDARD_FRAME_MAGIC[i]) {
100                 isZstandard = false;
101                 break;
102             }
103         }
104         if (isZstandard) {
105             return true;
106         }
107         if (0x50 == (signature[0] & 0xF0)) {
108             // skippable frame
109             for (int i = 0; i < SKIPPABLE_FRAME_MAGIC.length; ++i) {
110                 if (signature[i + 1] != SKIPPABLE_FRAME_MAGIC[i]) {
111                     return false;
112                 }
113             }
114             return true;
115         }
116         return false;
117     }
118 
119     /**
120      * Sets whether to cache the result of the Zstandard for Java check.
121      *
122      * <p>
123      * This defaults to {@code false} in an OSGi environment and {@code true} otherwise.
124      * </p>
125      *
126      * @param doCache whether to cache the result
127      */
128     public static void setCacheZstdAvailablity(final boolean doCache) {
129         if (!doCache) {
130             cachedZstdAvailability = CachedAvailability.DONT_CACHE;
131         } else if (cachedZstdAvailability == CachedAvailability.DONT_CACHE) {
132             final boolean hasZstd = internalIsZstdCompressionAvailable();
133             cachedZstdAvailability = hasZstd ? CachedAvailability.CACHED_AVAILABLE : CachedAvailability.CACHED_UNAVAILABLE;
134         }
135     }
136 
137     /** Private constructor to prevent instantiation of this utility class. */
138     private ZstdUtils() {
139     }
140 }