diff --git a/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/main/java/org/springframework/boot/jarmode/layertools/IndexedLayers.java b/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/main/java/org/springframework/boot/jarmode/layertools/IndexedLayers.java index f09e0d3f22..a76f43e0f0 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/main/java/org/springframework/boot/jarmode/layertools/IndexedLayers.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/main/java/org/springframework/boot/jarmode/layertools/IndexedLayers.java @@ -20,59 +20,52 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.NoSuchFileException; -import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.jar.JarFile; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.stream.Collectors; import java.util.zip.ZipEntry; import org.springframework.util.Assert; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; import org.springframework.util.StreamUtils; /** * {@link Layers} implementation backed by a {@code BOOT-INF/layers.idx} file. * * @author Phillip Webb + * @author Madhura Bhave */ class IndexedLayers implements Layers { - private static final String APPLICATION_LAYER = "application"; - - private static final String SPRING_BOOT_APPLICATION_LAYER = "springbootapplication"; - - private static final Pattern LAYER_PATTERN = Pattern.compile("^BOOT-INF\\/layers\\/([a-zA-Z0-9-]+)\\/.*$"); - - private List layers; + private MultiValueMap layers = new LinkedMultiValueMap<>(); IndexedLayers(String indexFile) { String[] lines = indexFile.split("\n"); - this.layers = Arrays.stream(lines).map(String::trim).filter((line) -> !line.isEmpty()) - .collect(Collectors.toCollection(ArrayList::new)); + Arrays.stream(lines).map(String::trim).filter((line) -> !line.isEmpty()).forEach((line) -> { + String[] content = line.split(" "); + Assert.state(content.length == 2, "Layer index file is malformed"); + this.layers.add(content[0], content[1]); + }); Assert.state(!this.layers.isEmpty(), "Empty layer index file loaded"); - if (!this.layers.contains(APPLICATION_LAYER)) { - this.layers.add(0, SPRING_BOOT_APPLICATION_LAYER); - } } @Override public Iterator iterator() { - return this.layers.iterator(); + return this.layers.keySet().iterator(); } @Override public String getLayer(ZipEntry entry) { String name = entry.getName(); - Matcher matcher = LAYER_PATTERN.matcher(name); - if (matcher.matches()) { - String layer = matcher.group(1); - Assert.state(this.layers.contains(layer), () -> "Unexpected layer '" + layer + "'"); - return layer; + for (Map.Entry> indexEntry : this.layers.entrySet()) { + if (indexEntry.getValue().contains(name)) { + return indexEntry.getKey(); + } } - return this.layers.contains(APPLICATION_LAYER) ? APPLICATION_LAYER : SPRING_BOOT_APPLICATION_LAYER; + throw new IllegalStateException("No layer defined in index for file '" + name + "'"); } /** diff --git a/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/test/java/org/springframework/boot/jarmode/layertools/HelpCommandTests.java b/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/test/java/org/springframework/boot/jarmode/layertools/HelpCommandTests.java index 33ed674572..2ada64661c 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/test/java/org/springframework/boot/jarmode/layertools/HelpCommandTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/test/java/org/springframework/boot/jarmode/layertools/HelpCommandTests.java @@ -77,10 +77,10 @@ class HelpCommandTests { JarEntry indexEntry = new JarEntry("BOOT-INF/layers.idx"); jarOutputStream.putNextEntry(indexEntry); Writer writer = new OutputStreamWriter(jarOutputStream, StandardCharsets.UTF_8); - writer.write("a\n"); - writer.write("b\n"); - writer.write("c\n"); - writer.write("d\n"); + writer.write("0001 BOOT-INF/lib/a.jar\n"); + writer.write("0001 BOOT-INF/lib/b.jar\n"); + writer.write("0002 BOOT-INF/lib/c.jar\n"); + writer.write("0003 BOOT-INF/lib/d.jar\n"); writer.flush(); } return file; diff --git a/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/test/java/org/springframework/boot/jarmode/layertools/IndexedLayersTests.java b/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/test/java/org/springframework/boot/jarmode/layertools/IndexedLayersTests.java index ac4d668ec7..4e5bb883ec 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/test/java/org/springframework/boot/jarmode/layertools/IndexedLayersTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/test/java/org/springframework/boot/jarmode/layertools/IndexedLayersTests.java @@ -16,10 +16,14 @@ package org.springframework.boot.jarmode.layertools; +import java.io.InputStreamReader; import java.util.zip.ZipEntry; import org.junit.jupiter.api.Test; +import org.springframework.core.io.ClassPathResource; +import org.springframework.util.FileCopyUtils; + import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; import static org.mockito.BDDMockito.given; @@ -29,6 +33,7 @@ import static org.mockito.Mockito.mock; * Tests for {@link IndexedLayers}. * * @author Phillip Webb + * @author Madhura Bhave */ class IndexedLayersTests { @@ -39,41 +44,35 @@ class IndexedLayersTests { } @Test - void createWhenIndexFileHasNoApplicationLayerAddSpringBootApplication() { - IndexedLayers layers = new IndexedLayers("test"); - assertThat(layers).contains("springbootapplication"); + void createWhenIndexFileIsMalformedThrowsException() throws Exception { + assertThatIllegalStateException().isThrownBy(() -> new IndexedLayers("test")) + .withMessage("Layer index file is malformed"); } @Test - void iteratorReturnsLayers() { - IndexedLayers layers = new IndexedLayers("test\napplication"); + void iteratorReturnsLayers() throws Exception { + IndexedLayers layers = new IndexedLayers(getIndex()); assertThat(layers).containsExactly("test", "application"); } @Test - void getLayerWhenMatchesLayerPatterReturnsLayer() { - IndexedLayers layers = new IndexedLayers("test"); - assertThat(layers.getLayer(mockEntry("BOOT-INF/layers/test/lib/file.jar"))).isEqualTo("test"); + void getLayerWhenMatchesNameReturnsLayer() throws Exception { + IndexedLayers layers = new IndexedLayers(getIndex()); + assertThat(layers.getLayer(mockEntry("BOOT-INF/lib/a.jar"))).isEqualTo("test"); + assertThat(layers.getLayer(mockEntry("BOOT-INF/classes/Demo.class"))).isEqualTo("application"); } @Test - void getLayerWhenMatchesLayerPatterForMissingLayerThrowsException() { - IndexedLayers layers = new IndexedLayers("test"); - assertThatIllegalStateException() - .isThrownBy(() -> layers.getLayer(mockEntry("BOOT-INF/layers/missing/lib/file.jar"))) - .withMessage("Unexpected layer 'missing'"); + void getLayerWhenMatchesNameForMissingLayerThrowsException() throws Exception { + IndexedLayers layers = new IndexedLayers(getIndex()); + assertThatIllegalStateException().isThrownBy(() -> layers.getLayer(mockEntry("file.jar"))) + .withMessage("No layer defined in index for file " + "'file.jar'"); } - @Test - void getLayerWhenDoesNotMatchLayerPatternReturnsApplication() { - IndexedLayers layers = new IndexedLayers("test\napplication"); - assertThat(layers.getLayer(mockEntry("META-INF/MANIFEST.MF"))).isEqualTo("application"); - } - - @Test - void getLayerWhenDoesNotMatchLayerPatternAndHasNoApplicationLayerReturnsSpringApplication() { - IndexedLayers layers = new IndexedLayers("test"); - assertThat(layers.getLayer(mockEntry("META-INF/MANIFEST.MF"))).isEqualTo("springbootapplication"); + private String getIndex() throws Exception { + ClassPathResource resource = new ClassPathResource("test-layers.idx", getClass()); + InputStreamReader reader = new InputStreamReader(resource.getInputStream()); + return FileCopyUtils.copyToString(reader); } private ZipEntry mockEntry(String name) { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/test/java/org/springframework/boot/jarmode/layertools/LayerToolsJarModeTests.java b/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/test/java/org/springframework/boot/jarmode/layertools/LayerToolsJarModeTests.java index a556bc5e94..a1ad8154ff 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/test/java/org/springframework/boot/jarmode/layertools/LayerToolsJarModeTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/test/java/org/springframework/boot/jarmode/layertools/LayerToolsJarModeTests.java @@ -85,10 +85,10 @@ class LayerToolsJarModeTests { JarEntry indexEntry = new JarEntry("BOOT-INF/layers.idx"); jarOutputStream.putNextEntry(indexEntry); Writer writer = new OutputStreamWriter(jarOutputStream, StandardCharsets.UTF_8); - writer.write("a\n"); - writer.write("b\n"); - writer.write("c\n"); - writer.write("d\n"); + writer.write("0001 BOOT-INF/lib/a.jar\n"); + writer.write("0001 BOOT-INF/lib/b.jar\n"); + writer.write("0002 BOOT-INF/lib/c.jar\n"); + writer.write("0003 BOOT-INF/lib/d.jar\n"); writer.flush(); } return file; diff --git a/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/test/java/org/springframework/boot/jarmode/layertools/ListCommandTests.java b/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/test/java/org/springframework/boot/jarmode/layertools/ListCommandTests.java index 2d9e793922..720f79eca2 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/test/java/org/springframework/boot/jarmode/layertools/ListCommandTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/test/java/org/springframework/boot/jarmode/layertools/ListCommandTests.java @@ -39,6 +39,7 @@ import static org.mockito.BDDMockito.given; * Tests for {@link ListCommand}. * * @author Phillip Webb + * @author Madhura Bhave */ class ListCommandTests { @@ -74,7 +75,7 @@ class ListCommandTests { File file = new File(this.temp, name); try (ZipOutputStream jarOutputStream = new ZipOutputStream(new FileOutputStream(file))) { writeLayersIndex(jarOutputStream); - String entryPrefix = "BOOT-INF/layers/"; + String entryPrefix = "BOOT-INF/lib/"; jarOutputStream.putNextEntry(new ZipEntry(entryPrefix + "a/")); jarOutputStream.closeEntry(); jarOutputStream.putNextEntry(new ZipEntry(entryPrefix + "a/a.jar")); @@ -97,10 +98,10 @@ class ListCommandTests { JarEntry indexEntry = new JarEntry("BOOT-INF/layers.idx"); out.putNextEntry(indexEntry); Writer writer = new OutputStreamWriter(out, StandardCharsets.UTF_8); - writer.write("a\n"); - writer.write("b\n"); - writer.write("c\n"); - writer.write("d\n"); + writer.write("0001 BOOT-INF/lib/a.jar\n"); + writer.write("0001 BOOT-INF/lib/b.jar\n"); + writer.write("0002 BOOT-INF/lib/c.jar\n"); + writer.write("0003 BOOT-INF/lib/d.jar\n"); writer.flush(); } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/test/resources/org/springframework/boot/jarmode/layertools/list-output.txt b/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/test/resources/org/springframework/boot/jarmode/layertools/list-output.txt index f843c42935..81f6325ca6 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/test/resources/org/springframework/boot/jarmode/layertools/list-output.txt +++ b/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/test/resources/org/springframework/boot/jarmode/layertools/list-output.txt @@ -1,5 +1,3 @@ -springbootapplication -a -b -c -d +0001 +0002 +0003 diff --git a/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/test/resources/org/springframework/boot/jarmode/layertools/test-layers.idx b/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/test/resources/org/springframework/boot/jarmode/layertools/test-layers.idx new file mode 100644 index 0000000000..2d5367a066 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/test/resources/org/springframework/boot/jarmode/layertools/test-layers.idx @@ -0,0 +1,3 @@ +test BOOT-INF/lib/a.jar +test BOOT-INF/lib/b.jar +application BOOT-INF/classes/Demo.class