/*
* This file is part of VIUtils.
*
* Copyright © 2012-2015 Visual Illusions Entertainment
*
* VIUtils is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License,
* or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this library.
* If not, see http://www.gnu.org/licenses/lgpl.html.
*/
package net.visualillusionsent.utils;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.security.CodeSource;
import java.security.cert.Certificate;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import static net.visualillusionsent.utils.Verify.notEmpty;
import static net.visualillusionsent.utils.Verify.notNull;
/**
* Jar File Utilities
*
* @author Jason (darkdiplomat)
* @version 1.2
* @since 1.1.0
*/
public final class JarUtils {
/* 1.2 @ VIUtils 1.4.0 */
private static final float classVersion = 1.2F;
/**
* Returns the path to the Jar File that the given class if from
*
* @param clazz
* the Class to check Jar path of
*
* @return path to the jar or {@code null} if path could not be determined
*
* @throws java.lang.NullPointerException
* if {@code clazz} is null
*/
public static String getJarPath(Class<?> clazz) {
notNull(clazz, "Class<?> clazz");
CodeSource codeSource = clazz.getProtectionDomain().getCodeSource();
try {
return codeSource.getLocation().toURI().getPath();
}
catch (URISyntaxException e) {
// IGNORED
}
return null;
}
/**
* Gets the manifest of the Jar that contains the specified Class
*
* @param clazz
* the class to check manifest for
*
* @return the Manifest if found
*
* @throws java.io.IOException
* if the Manifest is missing or the JarFile is not readable
* @throws java.lang.NullPointerException
* if {@code clazz} is null
*/
public static Manifest getManifest(Class<?> clazz) throws IOException {
notNull(clazz, "Class<?> clazz");
return getManifest(new JarFile(getJarPath(clazz)));
}
/**
* Gets the manifest of the Jar on the specified path
*
* @param path
* the path to the Jar file
*
* @return the Manifest if found
*
* @throws java.io.IOException
* if the Manifest is missing or the JarFile is not readable
* @throws java.lang.NullPointerException
* if {@code path} is null
* @throws java.lang.IllegalArgumentException
* if {@code path} is empty
*/
public static Manifest getManifest(String path) throws IOException {
notNull(path, "String path");
notEmpty(path, "String path");
return getManifest(new JarFile(path));
}
/**
* Gets the manifest of the {@link JarFile}
*
* @param jarFile
* the {@link JarFile} to get the manifest for
*
* @return the {@link Manifest} if found
*
* @throws java.io.IOException
* if the Manifest is missing or the JarFile is not readable
* @throws java.lang.NullPointerException
* if {@code jarFile} is null
*/
public static Manifest getManifest(JarFile jarFile) throws IOException {
notNull(jarFile, "JarFile jarFile");
return jarFile.getManifest();
}
/**
* Gets the {@link JarFile} for a given {@link Class}
*
* @param clazz
* the {@link Class} to get the {@link JarFile} for
*
* @return the {@link JarFile}
*
* @throws java.io.IOException
* if unable to get the JarFile
* @throws java.lang.NullPointerException
* if {@code clazz} is null
*/
public static JarFile getJarForClass(Class<?> clazz) throws IOException {
notNull(clazz, "Class<?> clazz");
return new JarFile(getJarPath(clazz));
}
/**
* Gets all {@link Class} files from a {@link JarFile}
*
* @param jarFile
* the {@link JarFile} to get classes from
*
* @return {@link Class} array
*
* @throws java.lang.ClassNotFoundException
* if the jar doesn't appear on the class-path and subsequently unable to load/find classes
* @throws java.lang.NullPointerException
* if {@code jarFile} is null
*/
public static Class[] getAllClasses(JarFile jarFile) throws ClassNotFoundException {
notNull(jarFile, "JarFile jarFile");
ArrayList<Class<?>> classes = new ArrayList<Class<?>>();
Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
String entName = entry.getName().replace("/", ".");
if (entName.endsWith(".class")) {
Class<?> cls = Class.forName(entName.substring(0, entName.length() - 6));
classes.add(cls);
}
}
return classes.toArray(new Class[classes.size()]);
}
/**
* Gets all {@link Class} files from a {@link JarFile} that extends a specific {@link Class}
*
* @param jarFile
* the {@link JarFile} to get classes from
* @param sCls
* the super {@link Class} to check extension of
*
* @return {@link Class} array
*
* @throws java.lang.ClassNotFoundException
* if the jar doesn't appear on the class-path and subsequently unable to load/find classes
* @throws java.lang.NullPointerException
* if {@code jarFile} or {@code sCls} is null
*/
@SuppressWarnings({"unchecked"})
public static <T> Class<? extends T>[] getAllClassesExtending(JarFile jarFile, Class<T> sCls) throws ClassNotFoundException {
notNull(jarFile, "JarFile jarFile");
notNull(sCls, "Class<T> sCls");
ArrayList<Class<? extends T>> classes = new ArrayList<Class<? extends T>>();
Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
String entName = entry.getName().replace("/", ".");
if (entName.endsWith(".class")) {
Class<?> cls = Class.forName(entName.substring(0, entName.length() - 6));
if (sCls.isAssignableFrom(cls)) {
classes.add(cls.asSubclass(sCls));
}
}
}
return classes.toArray(new Class[classes.size()]);
}
/**
* Gets all {@link Class} files from a {@link JarFile} in a specified package
*
* @param jarFile
* the {@link JarFile} to get classes from
* @param packageName
* the name of the package to get classes from
*
* @return {@link Class} array
*
* @throws java.lang.ClassNotFoundException
* if the jar doesn't appear on the class-path and subsequently unable to load/find classes
* @throws java.lang.NullPointerException
* if {@code jarFile} or {@code packageName} is null
* @throws java.lang.IllegalArgumentException
* if {@code packageName} is empty
*/
public static Class[] getClassesInPackage(JarFile jarFile, String packageName) throws ClassNotFoundException {
notNull(jarFile, "JarFile jarFile");
notNull(packageName, "String packageName");
notEmpty(packageName, "String packageName");
ArrayList<Class<?>> classes = new ArrayList<Class<?>>();
Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
String entName = entry.getName().replace("/", ".");
if (entName.startsWith(packageName) && entName.endsWith(".class")) {
Class<?> cls = Class.forName(entName.substring(0, entName.length() - 6));
classes.add(cls);
}
}
return classes.toArray(new Class[classes.size()]);
}
/**
* Gets all {@link Class} files from a {@link JarFile} in a specified package and extends a specific {@link Class}
*
* @param jarFile
* the {@link JarFile} to get classes from
* @param packageName
* the name of the package to get classes from
* @param sCls
* the super {@link Class} to check extension of
*
* @return {@link Class} array
*
* @throws java.lang.ClassNotFoundException
* if the jar doesn't appear on the class-path and subsequently unable to load/find classes
* @throws java.lang.NullPointerException
* if {@code jarFile} or {@code packageName} or {@code sCls} is null
* @throws java.lang.IllegalArgumentException
* if {@code packageName} is empty
*/
@SuppressWarnings({"unchecked"})
public static <T> Class<? extends T>[] getClassesInPackageExtending(JarFile jarFile, String packageName, Class<T> sCls) throws ClassNotFoundException {
notNull(jarFile, "JarFile jarFile");
notNull(packageName, "String packageName");
notEmpty(packageName, "String packageName");
notNull(sCls, "Class<T> sCls");
ArrayList<Class<? extends T>> classes = new ArrayList<Class<? extends T>>();
Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
String entName = entry.getName().replace("/", ".");
if (entName.startsWith(packageName) && entName.endsWith(".class")) {
Class<?> cls = Class.forName(entName.substring(0, entName.length() - 6));
if (sCls.isAssignableFrom(cls)) {
classes.add(cls.asSubclass(sCls));
}
}
}
return classes.toArray(new Class[classes.size()]);
}
/**
* Checks if a {@link JarFile} is properly signed
*
* @param jarPath
* the path to the {@link JarFile}
*
* @return {@code true} if properly signed; {@code false} otherwise
*
* @throws IOException
* if the JarFile is not readable or non-existent
* @throws java.lang.NullPointerException
* if {@code jarPath} is null
* @throws java.lang.IllegalArgumentException
* if {@code jarPath} is empty
*/
public static boolean isSigned(String jarPath) throws IOException {
notNull(jarPath, "String jarPath");
notEmpty(jarPath, "String jarPath");
return isSigned(new JarFile(jarPath, false)); // don't verify it twice...
}
/**
* Checks if a {@link JarFile} is properly signed
*
* @param jarFile
* the {@link JarFile} to check
*
* @return {@code true} if properly signed; {@code false} otherwise
*
* @throws java.lang.NullPointerException
* if {@code jarFile} is null
*/
public static boolean isSigned(JarFile jarFile) {
notNull(jarFile, "JarFile jarFile");
boolean passed = true;
InputStream is = null;
try {
Manifest man = jarFile.getManifest();
if (man == null) { // if no manifest, its not signed
return false;
}
byte[] buffer = new byte[4096]; // 4 Kilobyte buffer
Enumeration entries = jarFile.entries();
while (entries.hasMoreElements()) {
JarEntry je = (JarEntry) entries.nextElement();
// check entries' for correct signatures
is = jarFile.getInputStream(je);
// we just read. this will throw a SecurityException if a signature/digest check fails.
for (; ; ) { // intellij didn't like the empty while loop so i converted it to this
int r = is.read(buffer, 0, buffer.length);
if (r != -1) continue; // while its still reading bytes, continue
break; // everything read and no errors
}
is.close(); // Close each time
// Check if entry is signed
if (!je.isDirectory() && !je.getName().startsWith("META-INF")) { // Skip META-INF files
Certificate[] certs = je.getCertificates();
if (certs == null || certs.length == 0) { // If certs are null or empty, fail
passed = false;
break; // no need to continue checks
}
}
}
}
catch (Exception ex) {
passed = false;
}
finally {
if (is != null) {
try {
is.close(); // If we left this open, close it
jarFile.close(); // this is probably still open
}
catch (IOException ioex) {
UtilsLogger.warning("An IOException was raised while closing a JarFile...", ioex);
}
}
}
return passed;
}
/**
* Gets this class's version number
*
* @return the class version
*/
public static float getClassVersion() {
return classVersion;
}
}