From 41b01cc2899bb186984cecd737d938e952968902 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Wed, 5 Jan 2022 10:12:08 +0000 Subject: [PATCH] Only write parent entries when entry is new Previously, when writing a repackaged jar, an attempt to write all of an entry's parent directories would always be made, irrespective of whether or not the entry itself had already been written. This was inefficient as, due to the way that the jar is written, once an entry itself has been written, we know that all of its parent directories will also have been written. This commit updates the jar writer so that no attempt is made to write parent directory entries if the entry itself has already been written. Fixes gh-29175 --- .../boot/loader/tools/AbstractJarWriter.java | 4 ++-- .../boot/loader/tools/RepackagerTests.java | 15 ++++++++++++++- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/AbstractJarWriter.java b/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/AbstractJarWriter.java index 6e53cb09fc..0e713989ca 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/AbstractJarWriter.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/AbstractJarWriter.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -265,8 +265,8 @@ public abstract class AbstractJarWriter implements LoaderClassesWriter { private void writeEntry(JarArchiveEntry entry, Library library, EntryWriter entryWriter, UnpackHandler unpackHandler) throws IOException { String name = entry.getName(); - writeParentDirectoryEntries(name); if (this.writtenEntries.add(name)) { + writeParentDirectoryEntries(name); entry.setUnixMode(name.endsWith("/") ? UNIX_DIR_MODE : UNIX_FILE_MODE); entry.getGeneralPurposeBit().useUTF8ForNames(true); if (!entry.isDirectory() && entry.getSize() == -1) { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/RepackagerTests.java b/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/RepackagerTests.java index 1367231dcb..9f8f186b3c 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/RepackagerTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/RepackagerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -38,6 +38,7 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.loader.tools.sample.ClassWithMainMethod; import org.springframework.util.FileCopyUtils; +import org.springframework.util.StopWatch; import org.springframework.util.StringUtils; import static org.assertj.core.api.Assertions.assertThat; @@ -198,6 +199,18 @@ class RepackagerTests extends AbstractPackagerTests { } } + @Test + void repackagingDeeplyNestedPackageIsNotProhibitivelySlow() throws IOException { + StopWatch stopWatch = new StopWatch(); + stopWatch.start(); + this.testJarFile.addClass("a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/Some.class", + ClassWithMainMethod.class); + Repackager repackager = createRepackager(this.testJarFile.getFile(), true); + repackager.repackage(this.destination, NO_LIBRARIES, null, null); + stopWatch.stop(); + assertThat(stopWatch.getTotalTimeMillis()).isLessThan(5000); + } + private boolean hasLauncherClasses(File file) throws IOException { return hasEntry(file, "org/springframework/boot/") && hasEntry(file, "org/springframework/boot/loader/JarLauncher.class");