Skip to content

Commit

Permalink
Handle folders consistently.
Browse files Browse the repository at this point in the history
  • Loading branch information
raphw committed Dec 19, 2024
1 parent 7333299 commit d692bfe
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 47 deletions.
105 changes: 72 additions & 33 deletions byte-buddy-dep/src/main/java/net/bytebuddy/build/Plugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import net.bytebuddy.pool.TypePool;
import net.bytebuddy.utility.CompoundList;
import net.bytebuddy.utility.FileSystem;
import net.bytebuddy.utility.QueueFactory;
import net.bytebuddy.utility.StreamDrainer;
import net.bytebuddy.utility.nullability.AlwaysNull;
import net.bytebuddy.utility.nullability.MaybeNull;
Expand Down Expand Up @@ -2518,14 +2519,16 @@ public void remove() {
interface Element {

/**
* Returns the element's relative path and name.
* Returns the element's relative path and name. If the name ends with a {@code /}, it represents
* a folder.
*
* @return The element's path and name.
*/
String getName();

/**
* Returns an input stream to read this element's binary information.
* Returns an input stream to read this element's binary information. Must not be invoked for
* folders.
*
* @return An input stream that represents this element's binary information.
* @throws IOException If an I/O error occurs.
Expand Down Expand Up @@ -2865,15 +2868,15 @@ protected static class CompoundIterator implements Iterator<Element> {
/**
* A backlog of iterables to still consider.
*/
private final List<? extends Iterable<? extends Element>> backlog;
private final Queue<? extends Iterable<? extends Element>> backlog;

/**
* Creates a compound iterator.
*
* @param iterables The iterables to consider.
*/
protected CompoundIterator(List<? extends Iterable<? extends Element>> iterables) {
backlog = iterables;
backlog = QueueFactory.make(iterables);
forward();
}

Expand Down Expand Up @@ -2904,7 +2907,7 @@ public Element next() {
*/
private void forward() {
while ((current == null || !current.hasNext()) && !backlog.isEmpty()) {
current = backlog.remove(0).iterator();
current = backlog.remove().iterator();
}
}

Expand Down Expand Up @@ -3180,23 +3183,23 @@ protected class FolderIterator implements Iterator<Element> {
/**
* A list of files and folders to process with the next processed file at the end of the list.
*/
private final List<File> files;
private final Queue<File> files;

/**
* Creates a new iterator representation for all files within a folder.
*
* @param folder The root folder.
*/
protected FolderIterator(File folder) {
files = new ArrayList<File>(Collections.singleton(folder));
File candidate;
do {
candidate = files.remove(files.size() - 1);
File[] file = candidate.listFiles();
if (file != null) {
files.addAll(Arrays.asList(file));
files = QueueFactory.make();
File[] file = folder.listFiles();
if (file != null) {
for (File candidate : file) {
if (!candidate.equals(new File(folder, JarFile.MANIFEST_NAME))) {
files.add(candidate);
}
}
} while (!files.isEmpty() && (files.get(files.size() - 1).isDirectory() || files.get(files.size() - 1).equals(new File(folder, JarFile.MANIFEST_NAME))));
}
}

/**
Expand All @@ -3211,17 +3214,18 @@ public boolean hasNext() {
*/
@SuppressFBWarnings(value = "IT_NO_SUCH_ELEMENT", justification = "Exception is thrown by invoking removeFirst on an empty list.")
public Element next() {
try {
return new Element.ForFile(folder, files.remove(files.size() - 1));
} finally {
while (!files.isEmpty() && (files.get(files.size() - 1).isDirectory() || files.get(files.size() - 1).equals(new File(folder, JarFile.MANIFEST_NAME)))) {
File folder = files.remove(files.size() - 1);
File[] file = folder.listFiles();
if (file != null) {
files.addAll(Arrays.asList(file));
File next = files.remove();
if (next.isDirectory()) {
File[] file = next.listFiles();
if (file != null) {
for (File candidate : file) {
if (!candidate.equals(new File(folder, JarFile.MANIFEST_NAME))) {
files.add(candidate);
}
}
}
}
return new Element.ForFile(folder, next);
}

/**
Expand Down Expand Up @@ -3313,7 +3317,17 @@ public Filtering(Source delegate, ElementMatcher<Element> matcher, boolean manif
* @return A source that applies an appropriate filter.
*/
public static Source dropMultiReleaseClassFilesAbove(Source delegate, ClassFileVersion classFileVersion) {
return new Filtering(delegate, new MultiReleaseVersionMatcher(classFileVersion), true);
return new Filtering(delegate, new MultiReleaseVersionMatcher(classFileVersion));
}

/**
* Wraps a source to exclude elements that represent folders.
*
* @param delegate The delegate source.
* @return A source that drops folders and delegates to the original source.
*/
public static Source dropFolders(Source delegate) {
return new Filtering(delegate, NoFolderMatcher.INSTANCE);
}

/**
Expand Down Expand Up @@ -3368,6 +3382,25 @@ public boolean matches(@MaybeNull Element target) {
return true;
}
}

/**
* A matcher that removes folders from the iteration.
*/
@HashCodeAndEqualsPlugin.Enhance
protected enum NoFolderMatcher implements ElementMatcher<Element> {

/**
* The singleton instance.
*/
INSTANCE;

/**
* {@inheritDoc}
*/
public boolean matches(@MaybeNull Element target) {
return target == null || target.getName().endsWith("/");
}
}
}
}

Expand Down Expand Up @@ -3466,18 +3499,21 @@ public void store(ClassFileVersion classFileVersion, Map<TypeDescription, byte[]
*/
public void retain(Source.Element element) throws IOException {
JarEntry entry = element.resolveAs(JarEntry.class);
String name = element.getName();
outputStream.putNextEntry(entry == null
? new JarEntry(element.getName())
? new JarEntry(name)
: entry);
InputStream inputStream = element.getInputStream();
try {
byte[] buffer = new byte[1024];
int length;
while ((length = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, length);
if (entry != null || !name.endsWith("/")) {
InputStream inputStream = element.getInputStream();
try {
byte[] buffer = new byte[1024];
int length;
while ((length = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, length);
}
} finally {
inputStream.close();
}
} finally {
inputStream.close();
}
outputStream.closeEntry();
}
Expand Down Expand Up @@ -3798,8 +3834,9 @@ public void store(ClassFileVersion classFileVersion, Map<TypeDescription, byte[]
*/
public void retain(Source.Element element) throws IOException {
String name = element.getName();
File target = new File(folder, name);
if (!name.endsWith("/")) {
File target = new File(folder, name), resolved = element.resolveAs(File.class);
File resolved = element.resolveAs(File.class);
if (!target.getCanonicalPath().startsWith(folder.getCanonicalPath() + File.separatorChar)) {
throw new IllegalArgumentException(target + " is not a subdirectory of " + folder);
} else if (!target.getParentFile().isDirectory() && !target.getParentFile().mkdirs()) {
Expand Down Expand Up @@ -3827,6 +3864,8 @@ public void retain(Source.Element element) throws IOException {
inputStream.close();
}
}
} else if (!target.isDirectory() && !target.mkdirs()) {
throw new IllegalStateException("Cannot create requested directory: " + target);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,15 @@
import org.junit.Test;
import org.junit.rules.TemporaryFolder;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.*;
import java.util.Iterator;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;

import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.fail;

public class PluginEngineSourceForFolderTest {

Expand Down Expand Up @@ -94,6 +92,17 @@ public void testFileInSubFolder() throws Exception {
assertThat(origin.toClassFileLocator(null).locate("Bar").isResolved(), is(false));
Iterator<Plugin.Engine.Source.Element> iterator = origin.iterator();
assertThat(iterator.hasNext(), is(true));
Plugin.Engine.Source.Element folder = iterator.next();
assertThat(folder.getName(), is("bar/"));
assertThat(folder.resolveAs(Object.class), nullValue(Object.class));
assertThat(folder.resolveAs(File.class), is(file.getParentFile()));
try {
folder.getInputStream();
fail("Did not expect input stream to allow resolution from folder");
} catch (IOException ignored) {
/* expected */
}
assertThat(iterator.hasNext(), is(true));
Plugin.Engine.Source.Element element = iterator.next();
assertThat(element.getName(), is("bar/Foo.class"));
assertThat(element.resolveAs(Object.class), nullValue(Object.class));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.*;

public class PluginEngineTargetForFolderTest {
Expand Down Expand Up @@ -119,6 +120,48 @@ public void testWriteResourceFromFile() throws Exception {
}
}

@Test
public void testWriteFolder() throws Exception {
Plugin.Engine.Target target = new Plugin.Engine.Target.ForFolder(folder);
Plugin.Engine.Source.Element element = mock(Plugin.Engine.Source.Element.class);
when(element.getName()).thenReturn(FOO + "/" + BAR + "/");
when(element.getInputStream()).thenThrow(new AssertionError());
File original = temporaryFolder.newFile();
try {
Plugin.Engine.Target.Sink sink = target.write(null);
sink.retain(element);
assertThat(new File(folder, FOO + "/" + BAR).isDirectory(), is(true));
assertThat(new File(folder, FOO + "/" + BAR).delete(), is(true));
assertThat(new File(folder, FOO).isDirectory(), is(true));
assertThat(new File(folder, FOO).delete(), is(true));
} finally {
assertThat(original.delete(), is(true));
}
}

@Test
public void testWriteFolderCannotReplaceFile() throws Exception {
Plugin.Engine.Target target = new Plugin.Engine.Target.ForFolder(folder);
assertThat(new File(folder, FOO).createNewFile(), is(true));
Plugin.Engine.Source.Element element = mock(Plugin.Engine.Source.Element.class);
when(element.getName()).thenReturn(FOO + "/");
when(element.getInputStream()).thenThrow(new AssertionError());
File original = temporaryFolder.newFile();
try {
Plugin.Engine.Target.Sink sink = target.write(null);
try {
sink.retain(element);
fail("Expected error on overwritten file");
} catch (IllegalStateException exception) {
assertThat(exception.getMessage(), is("Cannot create requested directory: " + new File(folder, FOO)));
}
assertThat(new File(folder, FOO).isFile(), is(true));
assertThat(new File(folder, FOO).delete(), is(true));
} finally {
assertThat(original.delete(), is(true));
}
}

@Test
public void testManifest() throws Exception {
Manifest manifest = new Manifest();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -441,21 +441,23 @@ public void retain(Plugin.Engine.Source.Element element) throws IOException {
if (entry != null && entry.isDirectory()) {
return;
}
String name = element.getName();
try {
outputStream.putNextEntry(new JarEntry(element.getName()));
InputStream inputStream = element.getInputStream();
try {
byte[] buffer = new byte[1024];
int length;
while ((length = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, length);
outputStream.putNextEntry(new JarEntry(name));
if (!name.endsWith("/")) {
InputStream inputStream = element.getInputStream();
try {
byte[] buffer = new byte[1024];
int length;
while ((length = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, length);
}
} finally {
inputStream.close();
}
} finally {
inputStream.close();
}
outputStream.closeEntry();
} catch (ZipException exception) {
String name = element.getName();
if (!name.startsWith("META-INF") && !name.endsWith("-info.class") && name.endsWith(".class")) {
throw exception;
}
Expand Down

0 comments on commit d692bfe

Please sign in to comment.