All Downloads are FREE. Search and download functionalities are using the official Maven repository.

co.cask.cdap.common.lang.jar.JarResources Maven / Gradle / Ivy

There is a newer version: 5.1.2
Show newest version
/*
 * Copyright © 2014 Cask Data, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package co.cask.cdap.common.lang.jar;

import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.common.io.ByteStreams;
import org.apache.twill.filesystem.Location;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarInputStream;
import java.util.jar.Manifest;

/**
 * JarResources: JarResources maps all resources included in a
 * Zip or Jar file. Additionaly, it provides a method to extract one
 * as a blob.
 */
public final class JarResources {
  private static final Logger LOG = LoggerFactory.getLogger(JarResources.class);

  // archive resource mapping tables
  private final Map classContents = Maps.newHashMap();

  // Set of all non-directory entry names
  private final Set fileEntries = Sets.newHashSet();
  private final Manifest manifest;
  private final Location jarLocation;

  /**
   * Creates a JarResources using a {@link Location}. It extracts all resources from
   * a Jar into a internal map, keyed by resource names.
   *
   * @param jar location of JAR file.
   * @throws IOException
   */
  public JarResources(Location jar) throws IOException {
    this.jarLocation = jar;
    manifest = init(jar);
  }

  /**
   * Returns the {@link java.util.jar.Manifest} object if it presents in the archive file, or {@code null} otherwise.
   *
   * @see JarFile#getManifest()
   */
  public Manifest getManifest() {
    return manifest;
  }


  /**
   * Checks if an entry exists with the given name.
   * @param name Name of the entry to check
   * @return {@code true} if the entry exists, {@code false} otherwise.
   */
  public boolean contains(String name) {
    return fileEntries.contains(name);
  }

  /**
   * Extracts a archive resource as a blob.
   *
   * @param name a resource name.
   * @return A byte array containing content of the given name or {@code null} if not such entry exists.
   */
  public byte[] getResource(String name) {
    if (classContents.containsKey(name)) {
      return classContents.get(name);
    }

    // Read fully from resource stream and return the bytes.
    try {
      InputStream input = getResourceAsStream(name);
      if (input == null) {
        return null;
      }
      try {
        return ByteStreams.toByteArray(input);
      } finally {
        input.close();
      }

    } catch (IOException e) {
      return null;
    }
  }

  /**
   * Returns an {@link InputStream} for resource with the given name.
   * @param name Name of the resource.
   * @return An opened {@link InputStream} or {@code null} if no such resource exists. Caller is responsible for
   *         closing the stream.
   * @throws IOException
   */
  public InputStream getResourceAsStream(String name) throws IOException {
    if (classContents.containsKey(name)) {
      return new ByteArrayInputStream(classContents.get(name));
    }

    if (!fileEntries.contains(name)) {
      return null;
    }

    // Find the entry that match the given name
    JarInputStream jarInput = new JarInputStream(new BufferedInputStream(jarLocation.getInputStream()));
    JarEntry entry = jarInput.getNextJarEntry();
    while (entry != null && (entry.isDirectory() || !entry.getName().equals(name))) {
      entry = jarInput.getNextJarEntry();
    }

    // The while should found the entry, as the check on fileEntries guarantee it.
    return jarInput;
  }

  /**
   * initializes internal hash tables with Jar file resources.
   */
  private Manifest init(Location jarLocation) throws IOException {
    try (JarInputStream jarInput = new JarInputStream(new BufferedInputStream(jarLocation.getInputStream()))) {
      Manifest manifest = jarInput.getManifest();
      JarEntry ze;
      // For each ".class" entry in the jar file, read the bytes and stores it in the classContents map.
      while ((ze = jarInput.getNextJarEntry()) != null) {
        if (ze.isDirectory()) {
          continue;
        }

        fileEntries.add(ze.getName());

        // The JarInputStream only tries to read the MANIFEST file if it is the first entry in the jar
        // Otherwise, we'll see the manifest file here, hence need to construct it from the current entry.
        if (ze.getName().equals(JarFile.MANIFEST_NAME)) {
          manifest = new Manifest(jarInput);
          continue;
        }

        // Store only the .class files content
        if (!ze.getName().endsWith(".class")) {
          continue;
        }

        // ".class" file would be read in memory, and it shouldn't be too big.
        if (ze.getSize() > Integer.MAX_VALUE) {
          throw new IOException("Jar entry is too big to fit in memory.");
        }

        byte[] bytes;
        if (ze.getSize() < 0) {
          bytes = ByteStreams.toByteArray(jarInput);
        } else {
          bytes = new byte[(int) ze.getSize()];
          ByteStreams.readFully(jarInput, bytes);
        }
        // add to internal resource hashtable
        classContents.put(ze.getName(), bytes);
        if (LOG.isTraceEnabled()) {
          LOG.trace(ze.getName() + "size=" + ze.getSize() + ",csize=" + ze.getCompressedSize());
        }
      }
      return manifest;

    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy