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
pull/29333/head
Andy Wilkinson 3 years ago
parent 9a6f35cd8e
commit 41b01cc289

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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, private void writeEntry(JarArchiveEntry entry, Library library, EntryWriter entryWriter,
UnpackHandler unpackHandler) throws IOException { UnpackHandler unpackHandler) throws IOException {
String name = entry.getName(); String name = entry.getName();
writeParentDirectoryEntries(name);
if (this.writtenEntries.add(name)) { if (this.writtenEntries.add(name)) {
writeParentDirectoryEntries(name);
entry.setUnixMode(name.endsWith("/") ? UNIX_DIR_MODE : UNIX_FILE_MODE); entry.setUnixMode(name.endsWith("/") ? UNIX_DIR_MODE : UNIX_FILE_MODE);
entry.getGeneralPurposeBit().useUTF8ForNames(true); entry.getGeneralPurposeBit().useUTF8ForNames(true);
if (!entry.isDirectory() && entry.getSize() == -1) { if (!entry.isDirectory() && entry.getSize() == -1) {

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.boot.loader.tools.sample.ClassWithMainMethod;
import org.springframework.util.FileCopyUtils; import org.springframework.util.FileCopyUtils;
import org.springframework.util.StopWatch;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -198,6 +199,18 @@ class RepackagerTests extends AbstractPackagerTests<Repackager> {
} }
} }
@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 { private boolean hasLauncherClasses(File file) throws IOException {
return hasEntry(file, "org/springframework/boot/") return hasEntry(file, "org/springframework/boot/")
&& hasEntry(file, "org/springframework/boot/loader/JarLauncher.class"); && hasEntry(file, "org/springframework/boot/loader/JarLauncher.class");

Loading…
Cancel
Save