Skip to content

Commit d692bfe

Browse files
committed
Handle folders consistently.
1 parent 7333299 commit d692bfe

File tree

4 files changed

+140
-47
lines changed

4 files changed

+140
-47
lines changed

byte-buddy-dep/src/main/java/net/bytebuddy/build/Plugin.java

Lines changed: 72 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import net.bytebuddy.pool.TypePool;
2929
import net.bytebuddy.utility.CompoundList;
3030
import net.bytebuddy.utility.FileSystem;
31+
import net.bytebuddy.utility.QueueFactory;
3132
import net.bytebuddy.utility.StreamDrainer;
3233
import net.bytebuddy.utility.nullability.AlwaysNull;
3334
import net.bytebuddy.utility.nullability.MaybeNull;
@@ -2518,14 +2519,16 @@ public void remove() {
25182519
interface Element {
25192520

25202521
/**
2521-
* Returns the element's relative path and name.
2522+
* Returns the element's relative path and name. If the name ends with a {@code /}, it represents
2523+
* a folder.
25222524
*
25232525
* @return The element's path and name.
25242526
*/
25252527
String getName();
25262528

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

28702873
/**
28712874
* Creates a compound iterator.
28722875
*
28732876
* @param iterables The iterables to consider.
28742877
*/
28752878
protected CompoundIterator(List<? extends Iterable<? extends Element>> iterables) {
2876-
backlog = iterables;
2879+
backlog = QueueFactory.make(iterables);
28772880
forward();
28782881
}
28792882

@@ -2904,7 +2907,7 @@ public Element next() {
29042907
*/
29052908
private void forward() {
29062909
while ((current == null || !current.hasNext()) && !backlog.isEmpty()) {
2907-
current = backlog.remove(0).iterator();
2910+
current = backlog.remove().iterator();
29082911
}
29092912
}
29102913

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

31853188
/**
31863189
* Creates a new iterator representation for all files within a folder.
31873190
*
31883191
* @param folder The root folder.
31893192
*/
31903193
protected FolderIterator(File folder) {
3191-
files = new ArrayList<File>(Collections.singleton(folder));
3192-
File candidate;
3193-
do {
3194-
candidate = files.remove(files.size() - 1);
3195-
File[] file = candidate.listFiles();
3196-
if (file != null) {
3197-
files.addAll(Arrays.asList(file));
3194+
files = QueueFactory.make();
3195+
File[] file = folder.listFiles();
3196+
if (file != null) {
3197+
for (File candidate : file) {
3198+
if (!candidate.equals(new File(folder, JarFile.MANIFEST_NAME))) {
3199+
files.add(candidate);
3200+
}
31983201
}
3199-
} while (!files.isEmpty() && (files.get(files.size() - 1).isDirectory() || files.get(files.size() - 1).equals(new File(folder, JarFile.MANIFEST_NAME))));
3202+
}
32003203
}
32013204

32023205
/**
@@ -3211,17 +3214,18 @@ public boolean hasNext() {
32113214
*/
32123215
@SuppressFBWarnings(value = "IT_NO_SUCH_ELEMENT", justification = "Exception is thrown by invoking removeFirst on an empty list.")
32133216
public Element next() {
3214-
try {
3215-
return new Element.ForFile(folder, files.remove(files.size() - 1));
3216-
} finally {
3217-
while (!files.isEmpty() && (files.get(files.size() - 1).isDirectory() || files.get(files.size() - 1).equals(new File(folder, JarFile.MANIFEST_NAME)))) {
3218-
File folder = files.remove(files.size() - 1);
3219-
File[] file = folder.listFiles();
3220-
if (file != null) {
3221-
files.addAll(Arrays.asList(file));
3217+
File next = files.remove();
3218+
if (next.isDirectory()) {
3219+
File[] file = next.listFiles();
3220+
if (file != null) {
3221+
for (File candidate : file) {
3222+
if (!candidate.equals(new File(folder, JarFile.MANIFEST_NAME))) {
3223+
files.add(candidate);
3224+
}
32223225
}
32233226
}
32243227
}
3228+
return new Element.ForFile(folder, next);
32253229
}
32263230

32273231
/**
@@ -3313,7 +3317,17 @@ public Filtering(Source delegate, ElementMatcher<Element> matcher, boolean manif
33133317
* @return A source that applies an appropriate filter.
33143318
*/
33153319
public static Source dropMultiReleaseClassFilesAbove(Source delegate, ClassFileVersion classFileVersion) {
3316-
return new Filtering(delegate, new MultiReleaseVersionMatcher(classFileVersion), true);
3320+
return new Filtering(delegate, new MultiReleaseVersionMatcher(classFileVersion));
3321+
}
3322+
3323+
/**
3324+
* Wraps a source to exclude elements that represent folders.
3325+
*
3326+
* @param delegate The delegate source.
3327+
* @return A source that drops folders and delegates to the original source.
3328+
*/
3329+
public static Source dropFolders(Source delegate) {
3330+
return new Filtering(delegate, NoFolderMatcher.INSTANCE);
33173331
}
33183332

33193333
/**
@@ -3368,6 +3382,25 @@ public boolean matches(@MaybeNull Element target) {
33683382
return true;
33693383
}
33703384
}
3385+
3386+
/**
3387+
* A matcher that removes folders from the iteration.
3388+
*/
3389+
@HashCodeAndEqualsPlugin.Enhance
3390+
protected enum NoFolderMatcher implements ElementMatcher<Element> {
3391+
3392+
/**
3393+
* The singleton instance.
3394+
*/
3395+
INSTANCE;
3396+
3397+
/**
3398+
* {@inheritDoc}
3399+
*/
3400+
public boolean matches(@MaybeNull Element target) {
3401+
return target == null || target.getName().endsWith("/");
3402+
}
3403+
}
33713404
}
33723405
}
33733406

@@ -3466,18 +3499,21 @@ public void store(ClassFileVersion classFileVersion, Map<TypeDescription, byte[]
34663499
*/
34673500
public void retain(Source.Element element) throws IOException {
34683501
JarEntry entry = element.resolveAs(JarEntry.class);
3502+
String name = element.getName();
34693503
outputStream.putNextEntry(entry == null
3470-
? new JarEntry(element.getName())
3504+
? new JarEntry(name)
34713505
: entry);
3472-
InputStream inputStream = element.getInputStream();
3473-
try {
3474-
byte[] buffer = new byte[1024];
3475-
int length;
3476-
while ((length = inputStream.read(buffer)) != -1) {
3477-
outputStream.write(buffer, 0, length);
3506+
if (entry != null || !name.endsWith("/")) {
3507+
InputStream inputStream = element.getInputStream();
3508+
try {
3509+
byte[] buffer = new byte[1024];
3510+
int length;
3511+
while ((length = inputStream.read(buffer)) != -1) {
3512+
outputStream.write(buffer, 0, length);
3513+
}
3514+
} finally {
3515+
inputStream.close();
34783516
}
3479-
} finally {
3480-
inputStream.close();
34813517
}
34823518
outputStream.closeEntry();
34833519
}
@@ -3798,8 +3834,9 @@ public void store(ClassFileVersion classFileVersion, Map<TypeDescription, byte[]
37983834
*/
37993835
public void retain(Source.Element element) throws IOException {
38003836
String name = element.getName();
3837+
File target = new File(folder, name);
38013838
if (!name.endsWith("/")) {
3802-
File target = new File(folder, name), resolved = element.resolveAs(File.class);
3839+
File resolved = element.resolveAs(File.class);
38033840
if (!target.getCanonicalPath().startsWith(folder.getCanonicalPath() + File.separatorChar)) {
38043841
throw new IllegalArgumentException(target + " is not a subdirectory of " + folder);
38053842
} else if (!target.getParentFile().isDirectory() && !target.getParentFile().mkdirs()) {
@@ -3827,6 +3864,8 @@ public void retain(Source.Element element) throws IOException {
38273864
inputStream.close();
38283865
}
38293866
}
3867+
} else if (!target.isDirectory() && !target.mkdirs()) {
3868+
throw new IllegalStateException("Cannot create requested directory: " + target);
38303869
}
38313870
}
38323871

byte-buddy-dep/src/test/java/net/bytebuddy/build/PluginEngineSourceForFolderTest.java

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,15 @@
66
import org.junit.Test;
77
import org.junit.rules.TemporaryFolder;
88

9-
import java.io.File;
10-
import java.io.FileOutputStream;
11-
import java.io.InputStream;
12-
import java.io.OutputStream;
9+
import java.io.*;
1310
import java.util.Iterator;
1411
import java.util.jar.Attributes;
1512
import java.util.jar.JarFile;
1613
import java.util.jar.Manifest;
1714

1815
import static org.hamcrest.CoreMatchers.*;
1916
import static org.hamcrest.MatcherAssert.assertThat;
17+
import static org.junit.Assert.fail;
2018

2119
public class PluginEngineSourceForFolderTest {
2220

@@ -94,6 +92,17 @@ public void testFileInSubFolder() throws Exception {
9492
assertThat(origin.toClassFileLocator(null).locate("Bar").isResolved(), is(false));
9593
Iterator<Plugin.Engine.Source.Element> iterator = origin.iterator();
9694
assertThat(iterator.hasNext(), is(true));
95+
Plugin.Engine.Source.Element folder = iterator.next();
96+
assertThat(folder.getName(), is("bar/"));
97+
assertThat(folder.resolveAs(Object.class), nullValue(Object.class));
98+
assertThat(folder.resolveAs(File.class), is(file.getParentFile()));
99+
try {
100+
folder.getInputStream();
101+
fail("Did not expect input stream to allow resolution from folder");
102+
} catch (IOException ignored) {
103+
/* expected */
104+
}
105+
assertThat(iterator.hasNext(), is(true));
97106
Plugin.Engine.Source.Element element = iterator.next();
98107
assertThat(element.getName(), is("bar/Foo.class"));
99108
assertThat(element.resolveAs(Object.class), nullValue(Object.class));

byte-buddy-dep/src/test/java/net/bytebuddy/build/PluginEngineTargetForFolderTest.java

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import static org.hamcrest.CoreMatchers.is;
2020
import static org.hamcrest.MatcherAssert.assertThat;
21+
import static org.junit.Assert.fail;
2122
import static org.mockito.Mockito.*;
2223

2324
public class PluginEngineTargetForFolderTest {
@@ -119,6 +120,48 @@ public void testWriteResourceFromFile() throws Exception {
119120
}
120121
}
121122

123+
@Test
124+
public void testWriteFolder() throws Exception {
125+
Plugin.Engine.Target target = new Plugin.Engine.Target.ForFolder(folder);
126+
Plugin.Engine.Source.Element element = mock(Plugin.Engine.Source.Element.class);
127+
when(element.getName()).thenReturn(FOO + "/" + BAR + "/");
128+
when(element.getInputStream()).thenThrow(new AssertionError());
129+
File original = temporaryFolder.newFile();
130+
try {
131+
Plugin.Engine.Target.Sink sink = target.write(null);
132+
sink.retain(element);
133+
assertThat(new File(folder, FOO + "/" + BAR).isDirectory(), is(true));
134+
assertThat(new File(folder, FOO + "/" + BAR).delete(), is(true));
135+
assertThat(new File(folder, FOO).isDirectory(), is(true));
136+
assertThat(new File(folder, FOO).delete(), is(true));
137+
} finally {
138+
assertThat(original.delete(), is(true));
139+
}
140+
}
141+
142+
@Test
143+
public void testWriteFolderCannotReplaceFile() throws Exception {
144+
Plugin.Engine.Target target = new Plugin.Engine.Target.ForFolder(folder);
145+
assertThat(new File(folder, FOO).createNewFile(), is(true));
146+
Plugin.Engine.Source.Element element = mock(Plugin.Engine.Source.Element.class);
147+
when(element.getName()).thenReturn(FOO + "/");
148+
when(element.getInputStream()).thenThrow(new AssertionError());
149+
File original = temporaryFolder.newFile();
150+
try {
151+
Plugin.Engine.Target.Sink sink = target.write(null);
152+
try {
153+
sink.retain(element);
154+
fail("Expected error on overwritten file");
155+
} catch (IllegalStateException exception) {
156+
assertThat(exception.getMessage(), is("Cannot create requested directory: " + new File(folder, FOO)));
157+
}
158+
assertThat(new File(folder, FOO).isFile(), is(true));
159+
assertThat(new File(folder, FOO).delete(), is(true));
160+
} finally {
161+
assertThat(original.delete(), is(true));
162+
}
163+
}
164+
122165
@Test
123166
public void testManifest() throws Exception {
124167
Manifest manifest = new Manifest();

byte-buddy-gradle-plugin/android-plugin/src/main/java/net/bytebuddy/build/gradle/android/ByteBuddyLocalClassesEnhancerTask.java

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -441,21 +441,23 @@ public void retain(Plugin.Engine.Source.Element element) throws IOException {
441441
if (entry != null && entry.isDirectory()) {
442442
return;
443443
}
444+
String name = element.getName();
444445
try {
445-
outputStream.putNextEntry(new JarEntry(element.getName()));
446-
InputStream inputStream = element.getInputStream();
447-
try {
448-
byte[] buffer = new byte[1024];
449-
int length;
450-
while ((length = inputStream.read(buffer)) != -1) {
451-
outputStream.write(buffer, 0, length);
446+
outputStream.putNextEntry(new JarEntry(name));
447+
if (!name.endsWith("/")) {
448+
InputStream inputStream = element.getInputStream();
449+
try {
450+
byte[] buffer = new byte[1024];
451+
int length;
452+
while ((length = inputStream.read(buffer)) != -1) {
453+
outputStream.write(buffer, 0, length);
454+
}
455+
} finally {
456+
inputStream.close();
452457
}
453-
} finally {
454-
inputStream.close();
455458
}
456459
outputStream.closeEntry();
457460
} catch (ZipException exception) {
458-
String name = element.getName();
459461
if (!name.startsWith("META-INF") && !name.endsWith("-info.class") && name.endsWith(".class")) {
460462
throw exception;
461463
}

0 commit comments

Comments
 (0)