com.ideaaedi.commonds.jar.JarUtil Maven / Gradle / Ivy
The newest version!
package com.ideaaedi.commonds.jar;
import com.ideaaedi.commonds.constants.StrConstant;
import com.ideaaedi.commonds.constants.SymbolConstant;
import com.ideaaedi.commonds.file.FileOrderSupport;
import com.ideaaedi.commonds.io.IOUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.tuple.Pair;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.attribute.FileTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
/**
* jar/war操作工具类
*
* @author JustryDeng
* @since 1.0.0
*/
@Slf4j
public final class JarUtil {
/** 打包时需要忽略的(可能由操作系统产生的)文件 */
private static final String[] IGNORE_FILE_SUFFIX = {".DS_Store", "Thumbs.db"};
/** /classes/ */
private static final String CLASSES_DIR = "/classes/";
/** WEB-INF */
private static final String WEB_INF = "WEB-INF";
/** jar协议 */
private static final String JAR_PROTOCOL = "jar:";
/** war协议 */
private static final String WAR_PROTOCOL = "war:";
/** file协议 */
private static final String FILE_PROTOCOL = "file:";
/**
* @see JarUtil#doJarWar(String, String, FileOrderSupport)
*/
public static String doJarWar(String srcDir, String targetJarOrWar) {
return doJarWar(srcDir, targetJarOrWar, null);
}
/**
* 把目录压缩成jar(or war)
*
* @param srcDir
* 需要打包的目录(如 /tmp/demo-1.0.0/)
* @param targetJarOrWar
* 打包出的jar/war文件路径(如 /tmp/abc.jar)
* @param fileOrderSupport
* 文件排序序号提供者
注:部分jar包会对jar包内的条目有顺序要求(如:spring-boot的jar包的lib目录下,lib包的顺序要和pom.xml中的声明顺序保持一致),
* 此时就可以使用此字段来实现排序了。
*
* @return 打包出的jar/war文件路径
*/
public static String doJarWar(String srcDir, String targetJarOrWar, FileOrderSupport fileOrderSupport) {
File jarDirFile = new File(srcDir);
// 枚举jarDir下的所有文件以及目录
List files = IOUtil.listSubFile(jarDirFile, 0);
if (fileOrderSupport != null) {
files = files.stream().sorted(Comparator.comparing(fileOrderSupport::obtainFileOrder)).collect(Collectors.toList());
}
ZipOutputStream zos = null;
OutputStream out = null;
try {
File generatedJar = new File(targetJarOrWar);
// 如果原来的jar已存在,则先删除原来的jar
if (generatedJar.exists()) {
IOUtil.delete(generatedJar);
}
// jar包里面的文件的起始"root"位置
int rootStartIndex = jarDirFile.getAbsolutePath().length() + 1;
out = new FileOutputStream(generatedJar);
zos = new ZipOutputStream(out);
for (File file : files) {
if (isIgnore(file)) {
continue;
}
String fileName = file.getAbsolutePath().substring(rootStartIndex);
fileName = fileName.replace(File.separator, SymbolConstant.SLASH);
// 目录,添加一个目录entry
if (file.isDirectory()) {
ZipEntry zipEntry = new ZipEntry(fileName + SymbolConstant.SLASH);
zipEntry.setTime(file.lastModified());
zipEntry.setLastModifiedTime(FileTime.fromMillis(file.lastModified()));
zos.putNextEntry(zipEntry);
}
// jar文件, 需要写CRC32信息
else if (fileName.endsWith(StrConstant.JAR_SUFFIX)) {
byte[] bytes = IOUtil.toBytes(file);
ZipEntry ze = new ZipEntry(fileName);
ze.setMethod(ZipEntry.STORED);
ze.setSize(bytes.length);
ze.setCrc(IOUtil.computeCrc32(bytes));
ze.setTime(file.lastModified());
ze.setLastModifiedTime(FileTime.fromMillis(file.lastModified()));
zos.putNextEntry(ze);
zos.write(bytes);
}
// 其它文件直接写入
else {
ZipEntry zipEntry = new ZipEntry(fileName);
zipEntry.setTime(file.lastModified());
zipEntry.setLastModifiedTime(FileTime.fromMillis(file.lastModified()));
zos.putNextEntry(zipEntry);
byte[] bytes = IOUtil.toBytes(file);
zos.write(bytes);
}
zos.closeEntry();
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
IOUtil.close(zos, out);
}
return targetJarOrWar;
}
/**
* 解压jar(or war)至指定的目录
*
* @see JarUtil#unJarWar(String, String, boolean, Collection)
*/
public static > List unJarWar(String jarWarPath, String targetDir) {
return unJarWar(jarWarPath, targetDir, true, null);
}
/**
* 解压jar(or war)至指定的目录
*
* @param jarWarPath
* 待解压的jar(or war)文件
* @param targetDir
* 解压后文件放置的文件夹
* @param delOldTargetDirIfAlreadyExist
* 若targetDir已存在,是否先将原来的targetDir进行删除
* @param entryNamePrefixes
* 只有当entryName为指定的前缀时,才对该entry进行解压(若为null或者长度为0, 则解压所有文件) 如: ["BOOT-INF/classes/",
* "BOOT-INF/classes/com/example/ssm/author/JustryDeng.class"]
注:当entry对应jar或者war中的目录时,那么其值形如
* BOOT-INF/classes/
注:当entry对应jar或者war中的文件时,那么其值形如
* BOOT-INF/classes/com/example/ssm/author/JustryDeng.class
*
* @return (按压缩文件中条目的顺序)解压出来的(有序的)文件(包含目录)的完整路径
*/
public static > List unJarWar(String jarWarPath, String targetDir,
boolean delOldTargetDirIfAlreadyExist,
T entryNamePrefixes) {
List list = new ArrayList<>();
File target = new File(targetDir);
if (delOldTargetDirIfAlreadyExist) {
IOUtil.delete(target);
}
guarantyDirExist(target);
ZipFile zipFile = null;
// 此集合中的file会保留上一次修改时间,不会被当前解压操作覆盖
List storeLastModifiedTimeList = new ArrayList<>();
Map filePthAndLastModifiedTimeMap = new HashMap<>(64);
try {
zipFile = new ZipFile(new File(jarWarPath));
ZipEntry entry;
File targetFile;
Enumeration extends ZipEntry> entries = zipFile.entries();
while (entries.hasMoreElements()) {
entry = entries.nextElement();
String entryName = entry.getName();
// 若entryNamePrefixes不为空,则不解压前缀不匹配的文件或文件夹
if (entryNamePrefixes != null && entryNamePrefixes.size() > 0
&& entryNamePrefixes.stream().noneMatch(entryName::startsWith)) {
continue;
}
if (entry.isDirectory()) {
targetFile = new File(target, entryName);
guarantyDirExist(targetFile);
String absolutePath = targetFile.getAbsolutePath();
storeLastModifiedTimeList.add(absolutePath);
filePthAndLastModifiedTimeMap.put(absolutePath, entry.getLastModifiedTime().toMillis());
} else {
// 有时遍历时,文件先于文件夹出来,所以也得保证目录存在
int lastSeparatorIndex = entryName.lastIndexOf(SymbolConstant.SLASH);
if (lastSeparatorIndex > 0) {
guarantyDirExist(new File(target, entryName.substring(0, lastSeparatorIndex)));
}
// 解压文件
targetFile = new File(target, entryName);
byte[] bytes = IOUtil.toBytes(zipFile.getInputStream(entry));
IOUtil.toFile(bytes, targetFile, true);
String absolutePath = targetFile.getAbsolutePath();
list.add(absolutePath);
storeLastModifiedTimeList.add(absolutePath);
filePthAndLastModifiedTimeMap.put(absolutePath, entry.getLastModifiedTime().toMillis());
}
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
IOUtil.close(zipFile);
}
for (String filePath : storeLastModifiedTimeList) {
Long lastModifiedTime = filePthAndLastModifiedTimeMap.get(filePath);
//noinspection ResultOfMethodCallIgnored
new File(filePath).setLastModified(lastModifiedTime == null ? System.currentTimeMillis() :
lastModifiedTime);
}
return list;
}
/**
* 保证目录存在
*
* @param dir
* 目录
*/
public static void guarantyDirExist(File dir) {
if (!dir.exists()) {
//noinspection ResultOfMethodCallIgnored
dir.mkdirs();
}
}
/**
* 从(zip/jar)压缩文件中获取文件
*
* 注:jar其实也是zip。
*
*
* 示例
*
* String path = JarUtil.getFileFromJar("/tmp/demo.jar", "META-INF/services/com.niantou.filter.HttpExtFilter", new
* File("/root/HttpExtFilter.txt"));
*
*
*
* @param zip
* 压缩文件
* @param fileName
* 压缩文件的(相对压缩文件的root的)相对路径文件名
* @param targetFile
* 获取出来的目标文件
*
* @return 获取出来的目标文件的绝对路径
*/
public static String getFileFromZip(File zip, String fileName, File targetFile) {
byte[] bytes = getFileFromZip(zip, fileName);
IOUtil.toFile(bytes, targetFile, true);
return targetFile.getAbsolutePath();
}
/**
* 从(zip/jar/war)压缩文件中获取一个文件的字节
*
* 注:jar其实也是zip。 war虽然不是zip,但是也是可以使用压缩/解压zip的方式来进行压缩解压的。
*
*
* 示例
*
* byte[] bytes = JarUtil.getFileFromJar("/tmp/demo.jar", "META-INF/services/com.niantou.filter.HttpExtFilter");
*
*
*
* @param zip
* 压缩文件
* @param fileName
* 压缩文件的(相对压缩文件的root的)相对路径文件名
*
* @return 文件字节(可能为null)
*/
public static byte[] getFileFromZip(File zip, String fileName) {
ZipFile zipFile = null;
InputStream is = null;
try {
if (!zip.exists()) {
return null;
}
zipFile = new ZipFile(zip);
ZipEntry zipEntry = zipFile.getEntry(fileName);
if (zipEntry == null) {
return null;
}
is = zipFile.getInputStream(zipEntry);
return IOUtil.toBytes(is);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
IOUtil.close(is, zipFile);
}
}
/**
* 修改zip文件(.java、.war文件)中的条目
*
* @param zipFile
* 要修改的zip文件(.java、.war文件)
* @param replacerMap
* 替换器(k-ZipFile中,要被替换的ZipEntry的相对路径,如:BOOT-INF/classes/application.yml; V-要替换成的内容)
* @return 被替换了的ZipEntry的相对路径及重写前后的内容信息
* k - ZipEntry的相对路径,如:BOOT-INF/classes/application.yml
* v - 左:重写前的内容,右:重写后的内容
*/
public static Map> rewriteZipEntry(ZipFile zipFile, Map replacerMap) throws IOException {
Map> map = new HashMap<>(8);
if (replacerMap == null || replacerMap.size() == 0) {
return map;
}
if (zipFile == null) {
log.warn("zipFile is null.");
return map;
}
String zipFilePath = zipFile.getName();
if (!new File(zipFilePath).exists()) {
log.warn("zipFile [" + zipFilePath + "] non-exist.");
return map;
}
List zipEntryList = new LinkedList<>();
Enumeration extends ZipEntry> entries = zipFile.entries();
while (entries.hasMoreElements()) {
ZipEntry zipEntry = entries.nextElement();
zipEntryList.add(zipEntry);
}
Map originBytesMap = new HashMap<>(64);
for (ZipEntry zipEntry : zipEntryList) {
String zipEntryName = zipEntry.getName();
byte[] originBytes = IOUtil.toBytes(zipFile.getInputStream(zipEntry));
originBytesMap.put(zipEntryName, originBytes);
}
// zipFile.getName()形如: /abc/my-project.jar /abc/my-project.war /abc/my-projectzip
try (FileOutputStream fos = new FileOutputStream(zipFilePath, false);
ZipOutputStream zos = new ZipOutputStream(fos)) {
for (ZipEntry zipEntry : zipEntryList) {
String zipEntryName = zipEntry.getName();
byte[] originBytes = originBytesMap.get(zipEntryName);
byte[] replaceBytes = replacerMap.get(zipEntryName);
if (replacerMap.containsKey(zipEntryName) && replaceBytes != null) {
// 覆盖
ZipEntry ze = new ZipEntry(zipEntryName);
ze.setMethod(ZipEntry.STORED);
ze.setSize(replaceBytes.length);
ze.setCrc(IOUtil.computeCrc32(replaceBytes));
ze.setTime(zipEntry.getTime());
ze.setLastModifiedTime(zipEntry.getLastModifiedTime());
zos.putNextEntry(ze);
zos.write(replaceBytes);
map.put(zipEntryName, Pair.of(originBytes, replaceBytes));
} else {
// 其它的不动
zos.putNextEntry(new ZipEntry(zipEntry));
zos.write(originBytes);
}
}
}
return map;
}
/**
* 判断originJarOrWar是jar文件还是war文件
*
* @param originJarOrWar
* jar或者war的文件名(或全路径文件名)
*
* @return true-jar包; false-war包
* @throws IllegalArgumentException
* 当originJarOrWar既不是jar文件又不是war文件时抛出
*/
public static boolean isJarOrWar(String originJarOrWar) throws IllegalArgumentException {
if (originJarOrWar.endsWith(StrConstant.JAR_SUFFIX)) {
return true;
}
if (originJarOrWar.endsWith(StrConstant.WAR_SUFFIX)) {
return false;
}
throw new IllegalArgumentException("suffix-file [" + originJarOrWar + "] is not support.");
}
/**
* 是否忽略这个文件
*
* @param file
* 文件
*
* @return 是否忽略这个文件
*/
private static boolean isIgnore(File file) {
for (String suffix : IGNORE_FILE_SUFFIX) {
if (file.getAbsolutePath().endsWith(suffix)) {
return true;
}
}
return false;
}
/**
* 获取当前类对应的其所在的classes目录全路径名(或其所在jar包/war包文件全路径名)
*
* @param projectPath
* 当前类对应的项目路径
*
* @return 当前类对应的其所在的classes目录全路径名(或其所在jar包/war包文件全路径名)
*/
public static String getRootPath(String projectPath) {
Objects.requireNonNull(projectPath, "projectPath cannot be null.");
try {
projectPath = URLDecoder.decode(projectPath, StandardCharsets.UTF_8.name());
} catch (UnsupportedEncodingException e) {
// ignore
}
if (projectPath.startsWith(JAR_PROTOCOL) || projectPath.startsWith(WAR_PROTOCOL)) {
// jar协议/war协议的协议声明长度为4
projectPath = projectPath.substring(4);
}
if (projectPath.startsWith(FILE_PROTOCOL)) {
// file协议的协议声明长度为5
projectPath = projectPath.substring(5);
}
// 没解压的war包
if (projectPath.contains("*")) {
return projectPath.substring(0, projectPath.indexOf("*"));
}
// war包解压后的WEB-INF目录
else if (projectPath.contains(WEB_INF)) {
return projectPath.substring(0, projectPath.indexOf(WEB_INF));
}
// jar(jar包中文件URL有专用的格式jar:!/{entry}, 所以如果包含!,说明也是jar包)
else if (projectPath.contains(SymbolConstant.EXCLAMATION_MARK)) {
return projectPath.substring(0, projectPath.indexOf(SymbolConstant.EXCLAMATION_MARK));
}
// 普通jar/war
else if (projectPath.endsWith(StrConstant.JAR_SUFFIX) || projectPath.endsWith(StrConstant.WAR_SUFFIX)) {
return projectPath;
}
// no (项目还未打包时,存放于/classes/下)
else if (projectPath.contains(CLASSES_DIR)) {
return projectPath.substring(0, projectPath.indexOf(CLASSES_DIR) + CLASSES_DIR.length());
}
throw new RuntimeException("cannot identify projectPath [" + projectPath + "].");
}
}