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, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.mapreduce;
019
020import java.io.BufferedOutputStream;
021import java.io.File;
022import java.io.FileInputStream;
023import java.io.FileOutputStream;
024import java.io.IOException;
025import java.io.InputStream;
026import java.net.URL;
027import java.net.URLDecoder;
028import java.text.MessageFormat;
029import java.util.Enumeration;
030import java.util.jar.JarFile;
031import java.util.jar.JarOutputStream;
032import java.util.jar.Manifest;
033import java.util.zip.ZipEntry;
034import java.util.zip.ZipOutputStream;
035import org.apache.yetus.audience.InterfaceAudience;
036
037import org.apache.hbase.thirdparty.com.google.common.base.Preconditions;
038
039/**
040 * Finds the Jar for a class. If the class is in a directory in the classpath, it creates a Jar on
041 * the fly with the contents of the directory and returns the path to that Jar. If a Jar is created,
042 * it is created in the system temporary directory. This file was forked from
043 * hadoop/common/branches/branch-2@1377176.
044 */
045@InterfaceAudience.Private
046public final class JarFinder {
047
048  private static void copyToZipStream(File file, ZipEntry entry, ZipOutputStream zos)
049    throws IOException {
050    InputStream is = new FileInputStream(file);
051    try {
052      zos.putNextEntry(entry);
053      byte[] arr = new byte[4096];
054      int read = is.read(arr);
055      while (read > -1) {
056        zos.write(arr, 0, read);
057        read = is.read(arr);
058      }
059    } finally {
060      try {
061        is.close();
062      } finally {
063        zos.closeEntry();
064      }
065    }
066  }
067
068  public static void jarDir(File dir, String relativePath, ZipOutputStream zos) throws IOException {
069    Preconditions.checkNotNull(relativePath, "relativePath");
070    Preconditions.checkNotNull(zos, "zos");
071
072    // by JAR spec, if there is a manifest, it must be the first entry in the
073    // ZIP.
074    File manifestFile = new File(dir, JarFile.MANIFEST_NAME);
075    ZipEntry manifestEntry = new ZipEntry(JarFile.MANIFEST_NAME);
076    if (!manifestFile.exists()) {
077      zos.putNextEntry(manifestEntry);
078      new Manifest().write(new BufferedOutputStream(zos));
079      zos.closeEntry();
080    } else {
081      copyToZipStream(manifestFile, manifestEntry, zos);
082    }
083    zos.closeEntry();
084    zipDir(dir, relativePath, zos, true);
085    zos.close();
086  }
087
088  private static void zipDir(File dir, String relativePath, ZipOutputStream zos, boolean start)
089    throws IOException {
090    String[] dirList = dir.list();
091    if (dirList == null) {
092      return;
093    }
094    for (String aDirList : dirList) {
095      File f = new File(dir, aDirList);
096      if (!f.isHidden()) {
097        if (f.isDirectory()) {
098          if (!start) {
099            ZipEntry dirEntry = new ZipEntry(relativePath + f.getName() + "/");
100            zos.putNextEntry(dirEntry);
101            zos.closeEntry();
102          }
103          String filePath = f.getPath();
104          File file = new File(filePath);
105          zipDir(file, relativePath + f.getName() + "/", zos, false);
106        } else {
107          String path = relativePath + f.getName();
108          if (!path.equals(JarFile.MANIFEST_NAME)) {
109            ZipEntry anEntry = new ZipEntry(path);
110            copyToZipStream(f, anEntry, zos);
111          }
112        }
113      }
114    }
115  }
116
117  private static void createJar(File dir, File jarFile) throws IOException {
118    Preconditions.checkNotNull(dir, "dir");
119    Preconditions.checkNotNull(jarFile, "jarFile");
120    File jarDir = jarFile.getParentFile();
121    if (!jarDir.exists()) {
122      if (!jarDir.mkdirs()) {
123        throw new IOException(MessageFormat.format("could not create dir [{0}]", jarDir));
124      }
125    }
126    try (FileOutputStream fos = new FileOutputStream(jarFile);
127      JarOutputStream jos = new JarOutputStream(fos)) {
128      jarDir(dir, "", jos);
129    }
130  }
131
132  /**
133   * Returns the full path to the Jar containing the class. It always return a JAR.
134   * @param klass class.
135   * @return path to the Jar containing the class.
136   */
137  public static String getJar(Class klass) {
138    Preconditions.checkNotNull(klass, "klass");
139    ClassLoader loader = klass.getClassLoader();
140    if (loader != null) {
141      String class_file = klass.getName().replaceAll("\\.", "/") + ".class";
142      try {
143        for (Enumeration itr = loader.getResources(class_file); itr.hasMoreElements();) {
144          URL url = (URL) itr.nextElement();
145          String path = url.getPath();
146          if (path.startsWith("file:")) {
147            path = path.substring("file:".length());
148          }
149          path = URLDecoder.decode(path, "UTF-8");
150          if ("jar".equals(url.getProtocol())) {
151            path = URLDecoder.decode(path, "UTF-8");
152            return path.replaceAll("!.*$", "");
153          } else if ("file".equals(url.getProtocol())) {
154            String klassName = klass.getName();
155            klassName = klassName.replace(".", "/") + ".class";
156            path = path.substring(0, path.length() - klassName.length());
157            File baseDir = new File(path);
158            File testDir = new File(System.getProperty("test.build.dir", "target/test-dir"));
159            testDir = testDir.getAbsoluteFile();
160            if (!testDir.exists()) {
161              testDir.mkdirs();
162            }
163            File tempJar = File.createTempFile("hadoop-", "", testDir);
164            tempJar = new File(tempJar.getAbsolutePath() + ".jar");
165            tempJar.deleteOnExit();
166            createJar(baseDir, tempJar);
167            return tempJar.getAbsolutePath();
168          }
169        }
170      } catch (IOException e) {
171        throw new RuntimeException(e);
172      }
173    }
174    return null;
175  }
176
177  private JarFinder() {
178  }
179}