工具类就是为了提高开发效率而出现的,很多实现逻辑方式是固定的,那么就可以作为工具类使用,不需要重复实现,简化代码。
StringUtils是我们开发过程中常用的工具类,其中的的方法也比较熟悉,比如
//比较两个字符串是否相同,即使a或者b有为空的情况,那么也能比较不会出现空指针现象
String a = "A";
String b = "B";
if (StringUtils.equals(a,b)){
System.out.println("a和b相同");
}else {
System.out.println("a和b不相同");
}
//超类Object有相同功能的方法equals,但是如a为空,那么就会出现空指针现象
String b = "B";
if (a.equals(b)){
System.out.println("a和b相同");
}else {
System.out.println("a和b不相同");
}
//判断字符串是否为空StringUtils工具类中有多个方法,可以根据注释了解每种方法的具体使用方式
StringUtils.isAllBlank(a); StringUtils.isBlank(a); StringUtils.isAnyBlank(a);
StringUtils.isAllEmpty(a); StringUtils.isEmpty(a); StringUtils.isAnyEmpty(a);
//其中isBlank和isEmpty的区别在于如果字符串为空格字符串如:" ",那么isBlank会认为空返回true,而isEmpty不认为是空,返回false,其他方法类似
//当然StringuUtils也有对应判断字符串不为空的方法,即在以上方法中名中加一个Not,如:isNotBlank
//判断某个字符串是否包含另外一个字符串,StringUtils中提供了过个方法,其实通过方法名就能猜到每个方法的使用方式
StringUtils.contains(a,b);
StringUtils.containsAny(a,b);
StringUtils.containsNone(a,b);
StringUtils.containsOnly(a,b);
其实StringUtils中很多方法与String中的方法相同,但是可以避免空指针异常,比如:
String中有方法startWith() 和 endWith() 若一个字符串为空的时候调用方法就会报空指针异常,但是使用StringUtils就可以避免
//若a为字符串,但是可能为空,那么就可能出现空指针
boolean rs = a.endWith("b");
//若使用工具类StringUtils,那么即使a为空也不会出现空指针
boolean rs = StringUtils.endWith(a,"b");
StringUtils工具类还有其他的一些String用到的方法
StringUtils.replace() 将字符串中某些内容替换
StringUtils.indexOf() 判断某个字符在字符串中的第一次出现的下标位置
StringUtils.lastIndexOf() 判断某个字符在字符串中最后一次出现的位置
。。。。
DateUtils工具类我们通过类名就能明白这个类是对时间操作的工具类。
如果我们想在当前时间推后一天,若按照以前的方式我们可能需要判断目前的日期是的“日”是哪一天,如果是“30日”还需要判断,月份是哪个月,有没有31日,如果是28日,或者29日,还需要判断是不是2月份,年份是闰年还是平年,如果是31日,还得判断是哪个月,如果是12月份,那么月份就不能直接加一,而是赋值为一,等等一系列判断,非常繁琐。那么使用DateUtils只需要一行代码就能解决。
//今天是2019年8月31日
String day = “2019年8月31日”;
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy年MM月dd日");
Date today = dateFormat.parse(day);
Date tomorrow= DateUtils.addDays(today, 1);
String rs = dateFormat.format(tomorrow);
System.out.println(rs)
结果:2019年09月01日
注意:如果今天的时间写错了,比如:2019年8月32日,那么系统会自动将今天的时间变成2019年9月1日,明天的时间则为2019年09月02日
除了添加一天的方法,也有添加一小时,一周的方法等
DateUtils.addYears(); //添加年
DateUtils.addMonths(); //添加月
DateUtils.addWeeks(); //添加周
DateUtils.addDays(); //添加天
DateUtils.addHours(); //添加小时
DateUtils.addMinutes(); //添加分钟
DateUtils.addSeconds() //添加秒
。。。。。
日期比较也是我们常用到的时间处理方式,比较两个日期是否是同一天,那么可能需要先比较年,再比较月,最后比较日,整个代码比较繁琐,使用DateUtils工具类也是只需要一行代码就能解决。
//判断两个日期是否相同
boolean sameDay = DateUtils.isSameDay(date2, date1);
DateUtils工具类还提供了将日期Date转换成日历的方法
Calendar calendar = DateUtils.toCalendar(date);
CollectionUtils工具类是对集合的一些操作。可能我们平常最多用到的方法是判断集合是否为空
//CollectionUtils不仅可以判断Collection的子集合,也能判断Map是否为空
boolean empty = CollectionUtils.isEmpty(list);
判断两个集合是否有相同的元素
//判断两个集合如果有相同的元素返回true,反之返回false
boolean b = CollectionUtils.containsAny(list, list2);
将数组转换成list集合
//平常在代码中将数组转换成list可能常用的java.util包 Arrays.asList()
List list = Arrays.asList(arr);
//CollectionUtils工具类也提供了将诉数组转换成list,返回的list是没有泛型的
List list = CollectionUtils.arrayToList(arr);
获取集合list 或者 set 的最后一个元素
//我们如果想要获取list的最后一个元素的话可以使用下标,因为list的有序的
String last = list.get(list.size() - 1);
//但是如果要获取set集合的最后一个元素就比较麻烦,set的无序的没有下标,但是可以使用CollectionUtils工具类
String last = CollectionUtils.lastElement(set);
FileUtils工具类从名称也能看出来是对文件的一些操作。
复制文件或者文件夹,使用常规的方法,就会先从某个文件目录中读取文件或者文件夹,然后再写出到另外一个文件目录中,但是如果使用工具FileUtils就可以跟方便的实现,如下:
//将文件夹original中的文件全部拷贝到文件夹objective
File srcDir = new File("G:\\original");
File destDir = new File("G:\\objective");
try {
FileUtils.copyDirectory(srcDir,destDir);
} catch (IOException e) {
e.printStackTrace();
}
//拷贝文件
File srcFile = new File("G:\\original\\src\\test.txt");
File destFile = new File("G:\\objective\\desc\\copy.txt");
try {
FileUtils.copyFile(srcFile,destFile);
} catch (IOException e) {
e.printStackTrace();
}
上面的方法是将某个目录下的文件拷贝到另外的目录下,或者将某个文件拷贝到另外一个文件中,下面还有其他拷贝方法
//本方法是将目录original,整体拷贝到目录objective中,即在目录objective下面有个子目录original
File srcDir = new File("G:\\original");
File destDir = new File("G:\\objective");
try {
FileUtils.copyDirectoryToDirectory(srcDir,destDir);
} catch (IOException e) {
e.printStackTrace();
}
//将text.txt文件拷贝到objective目录下,文件名保持原来的
File srcFile = new File("G:\\original\\src\\test.txt");
File file = new File("G:\\objective");
try {
FileUtils.copyFileToDirectory();
} catch (IOException e) {
e.printStackTrace();
}
FileUtils提供了删除文件或目录的方法,比如要清空某个文件目录下的所有内容,如下:
//清空目录objective下的所有内容,但是不删除objective目录
File file = new File("G:\\objective");
try {
FileUtils.cleanDirectory(new File("G:\\objective"));
} catch (IOException e) {
e.printStackTrace();
}
与cleanDirectory()方法不同的还有一个删除目录的方法
//删除目录objective以及子目录子文件,即G盘下没有了objective目录
File file = new File("G:\\objective");
try {
FileUtils.deleteDirectory(file);
} catch (IOException e) {
e.printStackTrace();
}
FileUtils工具类中提供了一些读取文件的方法
//读取文件返回读取的内容字符串,另一个重载的方法可以设置编码
String context = FileUtils.readFileToString(file);
String context = FileUtils.readFileToString(file,encoding);
//读取文件返回字节数组
byte[] bytes = FileUtils.readFileToByteArray(file);
//读取文件返回流
FileInputStream fileInputStream = FileUtils.openInputStream(file);
FileOutputStream fileOutputStream = FileUtils.openOutputStream(file);
//既然有读取文件内容的方法那么必然也有写的方法
FileUtils.writeStringToFile(file,data);
FileUtils.writeStringToFile(file,data,encoding);
IOUtils是对流的操作,我们常常操作文件的时候就使用io流,所以上面FileUtils提到的文件复制也可以使用IOUtils实现,只是IOUtils是对文件流进行操作。
//IOUtils有多个重载的copy()方法,返回复制的字节数,示例如下:
File srcFile = new File("G:\\original\\src\\test - 副本.txt");
File destFile = new File("G:\\objective\\desc\\copy.txt");
try {
FileInputStream fileInputStream = FileUtils.openInputStream(srcFile);
FileOutputStream fileOutputStream = FileUtils.openOutputStream(destFile);
int copy = IOUtils.copy(fileInputStream, fileOutputStream);
System.out.println(copy);
} catch (IOException e) {
e.printStackTrace();
}
//copy方法
copy(InputStream input, OutputStream output)
copy(InputStream input, Writer output)
copy(InputStream input, Writer output, String encoding)
copy(Reader input, Writer output)
copy(Reader input, OutputStream output)
copy(Reader input, OutputStream output, String encoding)
IOUtils还提供了两个重载的方法,用于判断两个流的内容是否相同,比如读取两个文件返回了流,那么想要判断两个文件的内容是否相同,那么可以使用IOUtils提供的方法
FileInputStream fileInputStream1 = FileUtils.openInputStream(destFile);
FileInputStream fileInputStream2 = FileUtils.openInputStream(destFile1);
boolean b = IOUtils.contentEquals(fileInputStream1, fileInputStream);
System.out.println(b);
//重载的方法
contentEquals(Reader input1, Reader input2)
HttpClient是 Apache Jakarta Common 下的子项目,用来提供高效的、最新的、功能丰富的支持 HTTP 协议的客户端编程工具包,并且它支持 HTTP 协议最新的版本和建议。实现了所有 HTTP 的方法(GET,POST,PUT,HEAD 等)。以下是一个简单的Http请求使用GET方法
//客户端
public static void main(String[] args) {
//创建httpClient实例
HttpClient httpClient = new HttpClient();
//创建一个GET方法
GetMethod getMethod = new GetMethod("http://ip:port/MySpringBootDemo/api/test");
try {
//发起get请求
int code = httpClient.executeMethod(getMethod);
if (code != HttpStatus.SC_OK){
System.out.println("请求异常");
}
byte[] responseBody = getMethod.getResponseBody();
System.out.println(new String(responseBody,"utf-8"));
} catch (IOException e) {
e.printStackTrace();
}finally {
//释放连接
getMethod.releaseConnection();
}
}
............................................................................................................
//服务端
@RestController
@RequestMapping("/api")
public class ConfigurationApplication {
@RequestMapping("test")
@ResponseBody
public void getNpws(HttpServletResponse response){
File file = new File("G:\\objective\\desc\\copy.txt");
getFile(response,file);
}
private void getFile(HttpServletResponse response,File file) {
try(
OutputStream output = response.getOutputStream();
FileInputStream input = new FileInputStream(file)
) {
byte[] bts = new byte[1024];
int len = -1;
while((len=input.read(bts))!=-1){
output.write(bts,0,len);
}
}catch (Exception e){
e.printStackTrace();
}
}
}
HttpClient可以设置一些参数,使用HttpClientParams
//HttpClient设置参数
httpClient.setParams(httpClientParams);
HttpClientParams httpClientParams = new HttpClientParams();
//设置连接超时时间,单位是毫秒
httpClientParams.setConnectionManagerTimeout(long timeout);
//设置是否先尝试身份验证
setAuthenticationPreemptive(boolean value)
//设置字符集
setContentCharset(String charset)
上面的示例中用到了GetMethod,从名称上能猜到这个类是发起GET请求的,那么它可能会提供一些get请求的配置
//设置请求头,这个方法会覆盖原来的请求头
setRequestHeader(Header header)
//添加请求头,这个方法只是添加不会重写原来的请求头
addRequestHeader(Header header)
//请求头的创建可以使用无参构造函数,然后通过Header的两个方法设置参数,请求头是键值对key,value形式的,如:
Hearder header = new Header();
header.setName("appToken");
header.setValue("adfdf");
//也可以通过有参构造函数直接创建
Header header = new Header("appToken","adfdf");
请求类似IO流不能一直保持着,当请求完毕后要释放连接
//当响应处理完毕后要断开连接
releaseConnection();
http请求一般使用最多的就是get和post请求,下面我们看看PostMethod,其实和GetMethod类似
public static void main(String[] args) {
//创建HttpClient实例
HttpClient httpClient = new HttpClient();
String url = "http://192.168.0.115:8899/MySpringBootDemo/api/npws";
PostMethod postMethod = new PostMethod(url);
//添加请求参数
postMethod.setParameter("ajbh","123");
Header header = new Header();
header.setName("token");
header.setValue("adfg");
//设置请求头
postMethod.setRequestHeader(header);
try {
int code = httpClient.executeMethod(postMethod);
if (code != HttpStatus.SC_OK){
System.out.println("请求异常");
}
byte[] responseBody = postMethod.getResponseBody();
System.out.println(new String(responseBody));
} catch (IOException e) {
e.printStackTrace();
}finally{
postMethod.releaseConnection();
}
}
}
一般我们使用post请求消息类型会选择json格式的,PostMethod通过RequestEntity可以设置
//RequestEntity是一个接口,它有多个实现类分别是:
//StringRequestEntity; ByteArrayRequestEntity; FileRequestEntity; InputStreamRequestEntity; MultipartRequestEntity
JSONObject jsonObject = new JSONObject();
RequestEntity requestEntity = new StringRequestEntity(jsonObject.toString(),"application/json", "UTF-8");
postMethod.setRequestEntity(requestEntity);
Apache commons 包中有一个工具类HttpURLConnection,它是java.net.HttpURLConnection的子类,Apache commons中的HttpURLConnection的方法都是不可用的,其实我们一般使用的都使用java.net.HttpURLConnection,所以下面的示例也是java.net.HttpURLConnection
//要请求的ip和端口
private static String ruleManagerServerUrl = "http://ip:端口";
public static void main(String[] args) {
try {
//获取当前服务器地址
String localAddr = InetAddress.getLocalHost().getHostAddress();
JSONObject obj = new JSONObject();
obj.put("ip", localAddr);
obj.put("port", "8080");
URL url = new URL(ruleManagerServerUrl + "/api/addHost");
HttpURLConnection connection = (HttpURLConnection)url.openConnection();
connection.setConnectTimeout(3000);
connection.setRequestMethod("POST");
connection.setDoOutput(true);
connection.setDoInput(true);
connection.setRequestProperty("Content-Type", "application/json");
connection.setRequestProperty("Connection", "Keep-Alive");
connection.setRequestProperty("Charset", "UTF-8");
connection.setRequestProperty("accept", "application/json");
String s = obj.toJSONString();
byte[] writebytes = s.getBytes("UTF-8");
connection.setRequestProperty("Content-Length", String.valueOf(writebytes.length));
OutputStream out = connection.getOutputStream();
out.write(writebytes);
out.flush();
out.close();
if (200 == connection.getResponseCode()) {
String message = connection.getResponseMessage();
if (StringUtils.equals("添加服务器名称失败", message)) {
logger.warn("添加服务器名称失败");
}
}
} catch (Exception var9) {
logger.error("请求失败", var9);
}
}
平常开发中总会遇到加密的情况,比如登录的用户名和密码会通过加密后存储到数据库中,其实就是将实际的用户名和密码通过另外一种算法进行编码,隐藏了真正的内容,还有就是文本存储到数据库中的时候文本内容太大,那么就可以对文本内容进行编码比如Base64后存储到数据库中。
public static void main(String[] args) {
String str = "程序员";
Base64 base64 = new Base64();
String base64Str ;
try {
//Base64编码
base64Str = base64.encodeToString(str.getBytes("UTF-8"));
System.out.println(base64Str);
}catch (Exception e){
logger.error("文本Base64编码异常",e);
return;
}
结果:56iL5bqP5ZGY
............................................................................................................
//Base64解码
byte[] decode = base64.decode(base64Str);
try {
String rs = new String(decode,"utf-8");
System.out.println(rs);
}catch (Exception e){
logger.error("文本解码异常",e);
}
}
结果:程序员
当然在java 中不止只有这一种Base64编码方法,java.Util包中也有Base64
public static void main(String[] args) {
String str = "程序员";
String base64Str;
try {
//Base64编码
byte[] encode = Base64.getEncoder().encode(str.getBytes("utf-8"));
base64Str = new String(encode,"utf-8");
System.out.println(base64Str);
}catch (Exception e){
logger.error("Base64编码异常",e);
return;
}
//Base64解码
byte[] decode = Base64.getDecoder().decode(base64Str);
try {
String rs = new String(decode,"utf-8");
System.out.println(rs);
}catch (Exception e){
logger.error("Base64解码异常",e);
}
}
DigestUtils是一个加密工具类,有各种加密的方法,但是我们常用到的是md5加密。当我们不知道这个工具类的时候想要对一段文字加密的时候会自己写md5加密算法,如下:
package test;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class MD5Util {
//第一种实现
public static void main(String[] args) {
String string = "程序员";
char[] hexDigits={'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
try {
byte[] btInput = string.getBytes();
// 获得MD5摘要算法的 MessageDigest 对象
MessageDigest mdInst = MessageDigest.getInstance("MD5");
// 使用指定的字节更新摘要
mdInst.update(btInput);
// 获得密文
byte[] md = mdInst.digest();
// 把密文转换成十六进制的字符串形式
int j = md.length;
char[] str = new char[j * 2];
int k = 0;
for (int i = 0; i < j; i++) {
byte byte0 = md[i];
str[k++] = hexDigits[byte0 >>> 4 & 0xf];
str[k++] = hexDigits[byte0 & 0xf];
}
System.out.println(str);
} catch (Exception e) {
e.printStackTrace();
}
}
结果:72D9ADF4944F23E5EFDE37F6364C126F
............................................................................................................
//第二种实现
public static void main(String[] args) {
String string = "程序员";
try {
MessageDigest md5 = MessageDigest.getInstance("MD5");
md5.update(string.getBytes());
byte[] b = md5.digest();
StringBuffer sb = new StringBuffer();
for (int n = 0; n < b.length; n++) {
int i = b[n];
if (i < 0){
i += 256;
}
if (i < 16) {
sb.append("0");
}
sb.append(Integer.toHexString(i));
}
System.out.println(sb.toString());
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
}
结果:72d9adf4944f23e5efde37f6364c126f
以上不论哪一种方法都需要大段的代码,如果项目中多处使用,还需要封装成一个工具类,其实Apache commons中已经有封装好的工具类,使用很方便,一行代码解决问题
String s = DigestUtils.md5Hex(string);
System.out.println(s);
结果:72d9adf4944f23e5efde37f6364c126f
//当然工具类DigestUtils还提供了其他的md5加密方法
//加密字节数组
DigestUtils.md5Hex(byte[]);
//加密流的
DigestUtils.md5Hex(inputStream);
平常我们一般都是通过工具压缩文件,比如360压缩,好压压缩,那么现在我们学习一下使用java代码压缩文件,一下是zip普通方法压缩文件夹
方法1:压缩文件夹
public class ZipClass {
private static final Logger logger = LoggerFactory.getLogger(ZipClass.class);
private static final int BUFFER_SIZE = 2 * 1024;
//主函数
public static void main(String[] args) {
try {
//压缩文件名称
File file = new File("G:\\original.zip");
//创建文件输出流
FileOutputStream fileOutputStream = FileUtils.openOutputStream(file);
//压缩文件
toZip("G:\\original",fileOutputStream,true);
} catch (IOException e) {
logger.error("压缩文件异常",e);
}
}
/**
* 压缩成ZIP
* @param srcDir 压缩文件夹路径
* @param out 压缩文件输出流
* @param keepDirStructure 是否保留原来的目录结构(true:保留目录结构;false:所有文件跑到压缩包根目录下)
* (注意:不保留目录结构可能会出现同名文件,会压缩失败)
* @throws RuntimeException 压缩失败会抛出运行时异常
*/
private static void toZip(String srcDir, OutputStream out, boolean keepDirStructure)
throws RuntimeException{
//记录压缩开始时间
long start = System.currentTimeMillis();
//创建zip输出流
try (ZipOutputStream zos = new ZipOutputStream(out)){
File sourceFile = new File(srcDir);
compress(sourceFile,zos,sourceFile.getName(),keepDirStructure);
//记录压缩结束时间
long end = System.currentTimeMillis();
System.out.println("压缩完成,耗时:" + (end - start) +" ms");
} catch (Exception e) {
logger.error("压缩文件{}异常",srcDir,e);
}
}
/**
* 递归压缩方法
* @param sourceFile 源文件
* @param zos zip输出流
* @param name 压缩后的名称
* @param keepDirStructure 是否保留原来的目录结构(true:保留目录结构;false:所有文件跑到压缩包根目录下)
* (注意:不保留目录结构可能会出现同名文件,会压缩失败)
* @throws Exception 异常
*/
private static void compress(File sourceFile, ZipOutputStream zos, String name,
boolean keepDirStructure) throws Exception{
byte[] buf = new byte[BUFFER_SIZE];
if(sourceFile.isFile()){
// 向zip输出流中添加一个zip实体,构造器中name为zip实体的文件的名字
zos.putNextEntry(new ZipEntry(name));
// copy文件到zip输出流中
int len;
FileInputStream in = new FileInputStream(sourceFile);
while ((len = in.read(buf)) != -1){
zos.write(buf, 0, len);
}
zos.closeEntry();
in.close();
} else {
File[] listFiles = sourceFile.listFiles();
if(listFiles == null || listFiles.length == 0){
// 需要保留原来的文件结构时,需要对空文件夹进行处理
if(keepDirStructure){
// 空文件夹的处理
zos.putNextEntry(new ZipEntry(name + "/"));
// 没有文件,不需要文件的copy
zos.closeEntry();
}
}else {
for (File file : listFiles) {
// 判断是否需要保留原来的文件结构
if (keepDirStructure) {
// 注意:file.getName()前面需要带上父文件夹的名字加一斜杠,
// 不然最后压缩包中就不能保留原来的文件结构,即:所有文件都跑到压缩包根目录下了
compress(file, zos, name + "/" + file.getName(),keepDirStructure);
} else {
compress(file, zos, file.getName(),keepDirStructure);
}
}
}
}
}
}
方法2:压缩多个文件
/**
* 压缩成ZIP 压缩多个文件
* @param srcFiles 需要压缩的文件列表
* @param out 压缩文件输出流
* @throws RuntimeException 压缩失败会抛出运行时异常
*/
private static void toZip(List srcFiles , OutputStream out)throws RuntimeException {
long start = System.currentTimeMillis();
try (ZipOutputStream zos = new ZipOutputStream(out)){
for (File srcFile : srcFiles) {
byte[] buf = new byte[BUFFER_SIZE];
zos.putNextEntry(new ZipEntry(srcFile.getName()));
int len;
FileInputStream in = new FileInputStream(srcFile);
while ((len = in.read(buf)) != -1){
zos.write(buf, 0, len);
}
zos.closeEntry();
in.close();
}
long end = System.currentTimeMillis();
System.out.println("压缩完成,耗时:" + (end - start) +" ms");
} catch (Exception e) {
throw new RuntimeException("zip error from ZipUtils",e);
}
}
Apache commons也提供了压缩zip文件的方法:
private static void toZip() {
ZipArchiveEntry zipArchiveEntry = new ZipArchiveEntry("压缩zip");
File file = new File("G:\\test1.txt");
try (FileInputStream inputStream = new FileInputStream(file)) {
File zipFile = new File("G:\\test1.zip");
ZipArchiveOutputStream zipArchiveOutputStream = new ZipArchiveOutputStream(zipFile);
zipArchiveOutputStream.putArchiveEntry(zipArchiveEntry);
byte[] bytes = new byte[1024];
int len;
while ((len = inputStream.read(bytes)) != -1){
zipArchiveOutputStream.write(len);
}
zipArchiveOutputStream.closeArchiveEntry();
zipArchiveOutputStream.close();
}catch (Exception e){
e.printStackTrace();
}
}
既然有压缩文件,那么必然需要解压
/**
* 解压Zip文件
*
* @param zipFile
* 需要解压的zip文件位置
* @param destDir
* 解压的目标位置
*/
public static void unzip(String zipFile, String destDir) {
File f;
try (ArchiveInputStream i = new ArchiveStreamFactory().createArchiveInputStream(ArchiveStreamFactory.ZIP,
Files.newInputStream(Paths.get(zipFile)))) {
ArchiveEntry entry ;
while ((entry = i.getNextEntry()) != null) {
if (!i.canReadEntryData(entry)) {
continue;
}
f = new File(destDir, entry.getName());
if (entry.isDirectory()) {
if (!f.isDirectory() && !f.mkdirs()) {
throw new IOException("failed to create directory " + f);
}
} else {
File parent = f.getParentFile();
if (!parent.isDirectory() && !parent.mkdirs()) {
throw new IOException("failed to create directory " + parent);
}
try (OutputStream o = Files.newOutputStream(f.toPath())) {
IOUtils.copy(i, o);
}
}
}
} catch (IOException | ArchiveException e) {
e.printStackTrace();
}