JarUtil.java
/*
** Module : JarUtil.java
** Abstract : Utility methods for jar files.
**
** Copyright (c) 2024, Golden Code Development Corporation.
**
** -#- -I- --Date-- --------------------------------Description----------------------------------
** 001 GBB 20240401 Initial version. Methods moved here from MultiClassLoader and Utils for reusability.
** resolveClasspathJars() to return List instead of Set.
** 002 ICP 20240705 Fixed method of obtaining the class loader.
*/
/*
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Affero General Public License as
** published by the Free Software Foundation, either version 3 of the
** License, or (at your option) any later version.
**
** This program 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 Affero General Public License for more details.
**
** You may find a copy of the GNU Affero GPL version 3 at the following
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
**
** Additional terms under GNU Affero GPL version 3 section 7:
**
** Under Section 7 of the GNU Affero GPL version 3, the following additional
** terms apply to the works covered under the License. These additional terms
** are non-permissive additional terms allowed under Section 7 of the GNU
** Affero GPL version 3 and may not be removed by you.
**
** 0. Attribution Requirement.
**
** You must preserve all legal notices or author attributions in the covered
** work or Appropriate Legal Notices displayed by works containing the covered
** work. You may not remove from the covered work any author or developer
** credit already included within the covered work.
**
** 1. No License To Use Trademarks.
**
** This license does not grant any license or rights to use the trademarks
** Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
** of Golden Code Development Corporation. You are not authorized to use the
** name Golden Code, FWD, or the names of any author or contributor, for
** publicity purposes without written authorization.
**
** 2. No Misrepresentation of Affiliation.
**
** You may not represent yourself as Golden Code Development Corporation or FWD.
**
** You may not represent yourself for publicity purposes as associated with
** Golden Code Development Corporation, FWD, or any author or contributor to
** the covered work, without written authorization.
**
** 3. No Misrepresentation of Source or Origin.
**
** You may not represent the covered work as solely your work. All modified
** versions of the covered work must be marked in a reasonable way to make it
** clear that the modified work is not originating from Golden Code Development
** Corporation or FWD. All modified versions must contain the notices of
** attribution required in this license.
*/
package com.goldencode.p2j.util;
import com.goldencode.p2j.classloader.*;
import com.goldencode.p2j.util.logging.*;
import java.io.*;
import java.net.*;
import java.util.*;
import java.util.function.*;
import java.util.logging.*;
/** Utility methods for jar files. */
public class JarUtil
{
/**
* Search the resource jar files for the resource with the given filename.
* <p>
* The algorithm first attempts to find an exact match for the filename. If not found, it
* applies the PROPATH to the filename, first prepending the package root to the name.
*
* @param filename
* Filename of the resource, optionally including a path.
* @param searchFn
* The search function for the resource with the given filename
* @param propathSearchFn
* The propath search function for the resource with the given filename found in PROPATH
* @param propathSupplier
* The propath supplier that list PROPATH entries.
*
* @return A {@code JarResult} object, if the search succeeds, else {@code null}.
*/
public static JarResource searchResourceJars(
CentralLogger logger,
String filename,
Function<JarClassLoader, Function<String, String>> searchFn,
Function<JarClassLoader, Function<String, String>> propathSearchFn,
Supplier<List<String>> propathSupplier)
{
final char WIN_SEP = '\\';
final String JAR_SEP_STR = "/";
final char JAR_SEP = '/';
// the first thing to do working with jar base filename is replace all Windows separators
filename = filename.replace(WIN_SEP, JAR_SEP);
// resource name as found in jar file
String resName = null;
// class loader for jar file in which resource is found
JarClassLoader jarClassLoader = null;
// get class loader for application jar
ClassLoader cl = MultiClassLoader.getClassLoader();
if (cl != null)
{
// first clean up initial string
// possible current directory symbol
int len = filename.length();
if (len > 0 && filename.charAt(0) == '.')
{
filename = filename.substring(1);
}
// try to consider filenames starting with / as current from PROPATH entries
if (len > 0 && filename.charAt(0) == JAR_SEP)
{
filename = filename.substring(1);
}
// walk jars marked as resource jars in the directory
for (String path : propathSupplier.get())
{
String jarFullName = resolveJarName(logger, path, true);
// have the valid jar name
if (jarFullName != null)
{
// get jar file loader from cache
jarClassLoader = JarClassLoader.getOrCreateCachedJarClassLoader(jarFullName, cl.getParent());
// not possible to get jar file, may be the wrong name?
if (jarClassLoader == null)
{
continue;
}
resName = Utils.getRefinedPathName(filename, JAR_SEP_STR);
// first try the search with no PROPATH modifications
resName = searchFn.apply(jarClassLoader).apply(resName);
if (resName == null && propathSearchFn != null)
{
resName = propathSearchFn.apply(jarClassLoader).apply(filename);
}
if (resName != null)
{
return new JarResource(jarClassLoader, resName);
}
}
}
}
return null;
}
/**
* Resolve the jar name and return it as an unique name. If the file does
* not exist, it is a directory or its canonical path can not be determined,
* then this method returns <code>null</code>.
*
* @param jarName
* The jar name.
* @param fullPath
* <code>true</code> if the <code>jarName</code> contains also
* the jar's path. When <code>false</code>, it is assumed that
* the path is the one specified in the "customer_libs" system
* property.
*
* @return See above.
*/
public static String resolveJarName(CentralLogger logger, String jarName, boolean fullPath)
{
String absoluteJarPath;
if (!fullPath)
{
String managedDir = MultiClassLoader.managedCustomerLibs();
if (managedDir == null)
{
if (logger.isLoggable(Level.SEVERE))
{
logger.log(Level.SEVERE, "Managed customer libraries directory is not specified");
}
return null;
}
absoluteJarPath = managedDir + jarName;
}
else
{
absoluteJarPath = jarName;
}
try
{
return resolveJarName(absoluteJarPath);
}
catch (IOException e)
{
logger.log(Level.SEVERE, e.getMessage());
return null;
}
}
/**
* Returns a Set of resolved paths to the jars in the java class path. CentralLogger should be
* <code>null</code> if called from ClassLoader, otherwise causes recursion since the loader is still
* not initialized.
*
* @param logger
* <code>null</code> if called from ClassLoader, CentralLogger instance otherwise.
*
* @return See above.
*/
public static List<String> resolveClasspathJars(CentralLogger logger)
{
List<String> classpathJars = new ArrayList<>();
String cp = System.getProperty("java.class.path");
for (String path : cp.split(File.pathSeparator))
{
if (!path.endsWith(".jar"))
{
continue;
}
try
{
String pathToJar = JarUtil.resolveJarName(path);
if (classpathJars.contains(pathToJar))
{
continue;
}
classpathJars.add(pathToJar);
}
catch (IOException e)
{
if (logger == null)
{
e.printStackTrace();
}
else
{
logger.log(Level.FINE, "Couldn't resolve jar " + path, e);
}
}
}
return classpathJars;
}
/**
* Get the actual jar name from the given path.
*
* @param path
* The jar name, with full path.
*
* @return See above.
*/
public static String prettyName(String path)
{
if (path.contains(File.separator))
{
path = path.substring(path.lastIndexOf(File.separator) + 1);
}
return path;
}
/**
* Extract the jar name from the given URL.
*
* @param logger
* The logger.
* @param url
* The jar URL.
*
* @return See above.
*/
public static String extractJarName(CentralLogger logger, URL url)
{
String name = resolveJarName(logger, url.getFile(), true);
return name == null ? null : prettyName(name);
}
/**
* Returns the string of this abstract pathname to the found jar file.
*
* @param absolutePathToJar
* The absolute path to the jar file.
*
* @return See above.
*
* @throws IOException
* If the path doesn't exist or is not a file.
*/
private static String resolveJarName(String absolutePathToJar) throws IOException
{
File f = new File(absolutePathToJar);
if (!f.exists() || f.isDirectory())
{
throw new IOException("Could not find file for jar " + absolutePathToJar
+ ": file does not exist or is not an ordinary file");
}
return f.getCanonicalPath();
}
/**
* Object returned by a search for a resource in a jar file.
*/
public static class JarResource
{
/** Class loader for the jar */
private final JarClassLoader loader;
/** Fully qualified name of the resource in the jar file */
private final String name;
/**
* Constructor.
*
* @param loader
* Class loader for the jar.
* @param name
* Fully qualified name of the resource in the jar file.
*/
public JarResource(JarClassLoader loader, String name)
{
this.loader = loader;
this.name = name;
}
/**
* Get the jar class loader.
*
* @return Jar class loader.
*/
public JarClassLoader getLoader()
{
return loader;
}
/**
* Get the fully qualified name of the resource in the jar file.
*
* @return Resource name.
*/
public String getName()
{
return name;
}
}
}