17782530: Annotations using retention policy source remain in .class
Change-Id: Ia62e0d6ef5024515567be6564197e723f0013ee0
This commit is contained in:
@@ -1,5 +1,3 @@
|
|||||||
<component name="CopyrightManager">
|
<component name="CopyrightManager">
|
||||||
<settings default="">
|
<settings default="" />
|
||||||
<module2copyright />
|
|
||||||
</settings>
|
|
||||||
</component>
|
</component>
|
||||||
2
tools/rmtypedefs/.idea/misc.xml
generated
2
tools/rmtypedefs/.idea/misc.xml
generated
@@ -3,7 +3,7 @@
|
|||||||
<component name="EntryPointsManager">
|
<component name="EntryPointsManager">
|
||||||
<entry_points version="2.0" />
|
<entry_points version="2.0" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_6" assert-keyword="true" jdk-15="true" project-jdk-name="1.6" project-jdk-type="JavaSDK">
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_6" assert-keyword="true" jdk-15="true" project-jdk-name="OpenJDK 1.7" project-jdk-type="JavaSDK">
|
||||||
<output url="file://$PROJECT_DIR$/out" />
|
<output url="file://$PROJECT_DIR$/out" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
|
|||||||
@@ -31,6 +31,26 @@
|
|||||||
</SOURCES>
|
</SOURCES>
|
||||||
</library>
|
</library>
|
||||||
</orderEntry>
|
</orderEntry>
|
||||||
|
<orderEntry type="module-library">
|
||||||
|
<library>
|
||||||
|
<CLASSES>
|
||||||
|
<root url="jar://$MODULE_DIR$/../../../prebuilts/tools/common/m2/repository/junit/junit/3.8.1/junit-3.8.1.jar!/" />
|
||||||
|
</CLASSES>
|
||||||
|
<JAVADOC />
|
||||||
|
<SOURCES>
|
||||||
|
<root url="jar://$MODULE_DIR$/../../../prebuilts/tools/common/m2/repository/junit/junit/3.8.1/junit-3.8.1-sources.jar!/" />
|
||||||
|
</SOURCES>
|
||||||
|
</library>
|
||||||
|
</orderEntry>
|
||||||
|
<orderEntry type="module-library">
|
||||||
|
<library>
|
||||||
|
<CLASSES>
|
||||||
|
<root url="jar://$MODULE_DIR$/../../../prebuilts/tools/common/m2/repository/org/eclipse/jdt/core/compiler/ecj/4.2.2/ecj-4.2.2.jar!/" />
|
||||||
|
</CLASSES>
|
||||||
|
<JAVADOC />
|
||||||
|
<SOURCES />
|
||||||
|
</library>
|
||||||
|
</orderEntry>
|
||||||
</component>
|
</component>
|
||||||
</module>
|
</module>
|
||||||
|
|
||||||
|
|||||||
@@ -16,17 +16,21 @@
|
|||||||
|
|
||||||
package com.android.tools.rmtypedefs;
|
package com.android.tools.rmtypedefs;
|
||||||
|
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import com.google.common.collect.Sets;
|
||||||
import com.google.common.io.Files;
|
import com.google.common.io.Files;
|
||||||
|
|
||||||
import org.objectweb.asm.AnnotationVisitor;
|
import org.objectweb.asm.AnnotationVisitor;
|
||||||
import org.objectweb.asm.ClassReader;
|
import org.objectweb.asm.ClassReader;
|
||||||
import org.objectweb.asm.ClassVisitor;
|
import org.objectweb.asm.ClassVisitor;
|
||||||
|
import org.objectweb.asm.ClassWriter;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.PrintStream;
|
import java.io.PrintStream;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import static org.objectweb.asm.Opcodes.ASM4;
|
import static org.objectweb.asm.Opcodes.ASM4;
|
||||||
|
|
||||||
@@ -50,6 +54,10 @@ public class RmTypeDefs {
|
|||||||
private boolean mHaveError;
|
private boolean mHaveError;
|
||||||
private boolean mDryRun;
|
private boolean mDryRun;
|
||||||
|
|
||||||
|
private Set<String> mAnnotationNames = Sets.newHashSet();
|
||||||
|
private List<File> mAnnotationClassFiles = Lists.newArrayList();
|
||||||
|
private Set<File> mAnnotationOuterClassFiles = Sets.newHashSet();
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
new RmTypeDefs().run(args);
|
new RmTypeDefs().run(args);
|
||||||
}
|
}
|
||||||
@@ -93,19 +101,30 @@ public class RmTypeDefs {
|
|||||||
System.out.println("Deleting @IntDef and @StringDef annotation class files");
|
System.out.println("Deleting @IntDef and @StringDef annotation class files");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Record typedef annotation names and files
|
||||||
for (File dir : dirs) {
|
for (File dir : dirs) {
|
||||||
find(dir);
|
checkFile(dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Rewrite the .class files for any classes that *contain* typedefs as innerclasses
|
||||||
|
rewriteOuterClasses();
|
||||||
|
|
||||||
|
// Removes the actual .class files for the typedef annotations
|
||||||
|
deleteAnnotationClasses();
|
||||||
|
|
||||||
System.exit(mHaveError ? -1 : 0);
|
System.exit(mHaveError ? -1 : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void find(File file) {
|
/**
|
||||||
|
* Visits the given directory tree recursively and calls {@link #checkClass(java.io.File)}
|
||||||
|
* for any .class files encountered
|
||||||
|
*/
|
||||||
|
private void checkFile(File file) {
|
||||||
if (file.isDirectory()) {
|
if (file.isDirectory()) {
|
||||||
File[] files = file.listFiles();
|
File[] files = file.listFiles();
|
||||||
if (files != null) {
|
if (files != null) {
|
||||||
for (File f : files) {
|
for (File f : files) {
|
||||||
find(f);
|
checkFile(f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (file.isFile()) {
|
} else if (file.isFile()) {
|
||||||
@@ -119,11 +138,15 @@ public class RmTypeDefs {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks the given .class file to see if it's a typedef annotation, and if so
|
||||||
|
* records that fact by calling {@link #addTypeDef(String, java.io.File)}
|
||||||
|
*/
|
||||||
private void checkClass(File file) {
|
private void checkClass(File file) {
|
||||||
try {
|
try {
|
||||||
byte[] bytes = Files.toByteArray(file);
|
byte[] bytes = Files.toByteArray(file);
|
||||||
ClassReader classReader = new ClassReader(bytes);
|
ClassReader classReader = new ClassReader(bytes);
|
||||||
classReader.accept(new MyVisitor(file), 0);
|
classReader.accept(new TypeDefVisitor(file), 0);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
System.err.println("Could not read " + file + ": " + e.getLocalizedMessage());
|
System.err.println("Could not read " + file + ": " + e.getLocalizedMessage());
|
||||||
System.exit(1);
|
System.exit(1);
|
||||||
@@ -142,9 +165,100 @@ public class RmTypeDefs {
|
|||||||
out.println(" -q,--quiet quiet");
|
out.println(" -q,--quiet quiet");
|
||||||
out.println(" -v,--verbose verbose");
|
out.println(" -v,--verbose verbose");
|
||||||
out.println(" -n,--dry-run dry-run only, leaves files alone");
|
out.println(" -n,--dry-run dry-run only, leaves files alone");
|
||||||
|
out.println(" --verify run extra diagnostics to verify file integrity");
|
||||||
}
|
}
|
||||||
|
|
||||||
private class MyVisitor extends ClassVisitor {
|
/**
|
||||||
|
* Records the given class name (internal name) and class file path as corresponding to a
|
||||||
|
* typedef annotation
|
||||||
|
* */
|
||||||
|
private void addTypeDef(String name, File file) {
|
||||||
|
mAnnotationClassFiles.add(file);
|
||||||
|
mAnnotationNames.add(name);
|
||||||
|
|
||||||
|
String fileName = file.getName();
|
||||||
|
int index = fileName.lastIndexOf('$');
|
||||||
|
if (index != -1) {
|
||||||
|
File parentFile = file.getParentFile();
|
||||||
|
assert parentFile != null : file;
|
||||||
|
File container = new File(parentFile, fileName.substring(0, index) + ".class");
|
||||||
|
if (container.exists()) {
|
||||||
|
mAnnotationOuterClassFiles.add(file);
|
||||||
|
} else {
|
||||||
|
System.err.println("Warning: Could not find outer class " + container
|
||||||
|
+ " for typedef " + file);
|
||||||
|
mHaveError = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rewrites the outer classes containing the typedefs such that they no longer refer to
|
||||||
|
* the (now removed) typedef annotation inner classes
|
||||||
|
*/
|
||||||
|
private void rewriteOuterClasses() {
|
||||||
|
for (File file : mAnnotationOuterClassFiles) {
|
||||||
|
byte[] bytes;
|
||||||
|
try {
|
||||||
|
bytes = Files.toByteArray(file);
|
||||||
|
} catch (IOException e) {
|
||||||
|
System.err.println("Could not read " + file + ": " + e.getLocalizedMessage());
|
||||||
|
mHaveError = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ClassWriter classWriter = new ClassWriter(ASM4);
|
||||||
|
ClassVisitor classVisitor = new ClassVisitor(ASM4, classWriter) {
|
||||||
|
@Override
|
||||||
|
public void visitInnerClass(String name, String outerName, String innerName,
|
||||||
|
int access) {
|
||||||
|
if (!mAnnotationNames.contains(name)) {
|
||||||
|
super.visitInnerClass(name, outerName, innerName, access);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
ClassReader reader = new ClassReader(bytes);
|
||||||
|
reader.accept(classVisitor, 0);
|
||||||
|
byte[] rewritten = classWriter.toByteArray();
|
||||||
|
try {
|
||||||
|
Files.write(rewritten, file);
|
||||||
|
} catch (IOException e) {
|
||||||
|
System.err.println("Could not write " + file + ": " + e.getLocalizedMessage());
|
||||||
|
mHaveError = true;
|
||||||
|
//noinspection UnnecessaryContinue
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs the actual deletion (or display, if in dry-run mode) of the typedef annotation
|
||||||
|
* files
|
||||||
|
*/
|
||||||
|
private void deleteAnnotationClasses() {
|
||||||
|
for (File mFile : mAnnotationClassFiles) {
|
||||||
|
if (mVerbose) {
|
||||||
|
if (mDryRun) {
|
||||||
|
System.out.println("Would delete " + mFile);
|
||||||
|
} else {
|
||||||
|
System.out.println("Deleting " + mFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!mDryRun) {
|
||||||
|
boolean deleted = mFile.delete();
|
||||||
|
if (!deleted) {
|
||||||
|
System.err.println("Could not delete " + mFile);
|
||||||
|
mHaveError = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Visitor which visits .class files and checks whether each class is a typedef annotation
|
||||||
|
* (and if so, calls {@link #addTypeDef(String, java.io.File)}
|
||||||
|
*/
|
||||||
|
private class TypeDefVisitor extends ClassVisitor {
|
||||||
|
|
||||||
/** Class file name */
|
/** Class file name */
|
||||||
private File mFile;
|
private File mFile;
|
||||||
@@ -161,7 +275,7 @@ public class RmTypeDefs {
|
|||||||
/** Does the annotation have source retention? Only applies if {@link #mAnnotation} */
|
/** Does the annotation have source retention? Only applies if {@link #mAnnotation} */
|
||||||
private boolean mSourceRetention;
|
private boolean mSourceRetention;
|
||||||
|
|
||||||
public MyVisitor(File file) {
|
public TypeDefVisitor(File file) {
|
||||||
super(ASM4);
|
super(ASM4);
|
||||||
mFile = file;
|
mFile = file;
|
||||||
}
|
}
|
||||||
@@ -203,20 +317,8 @@ public class RmTypeDefs {
|
|||||||
+ "with @Retention(RetentionPolicy.SOURCE)");
|
+ "with @Retention(RetentionPolicy.SOURCE)");
|
||||||
mHaveError = true;
|
mHaveError = true;
|
||||||
}
|
}
|
||||||
if (mVerbose) {
|
|
||||||
if (mDryRun) {
|
addTypeDef(mName, mFile);
|
||||||
System.out.println("Would delete " + mFile);
|
|
||||||
} else {
|
|
||||||
System.out.println("Deleting " + mFile);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!mDryRun) {
|
|
||||||
boolean deleted = mFile.delete();
|
|
||||||
if (!deleted) {
|
|
||||||
System.err.println("Could not delete " + mFile);
|
|
||||||
mHaveError = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,254 @@
|
|||||||
|
package com.android.tools.rmtypedefs;
|
||||||
|
|
||||||
|
import com.google.common.base.Charsets;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import com.google.common.io.Files;
|
||||||
|
|
||||||
|
import junit.framework.TestCase;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.core.compiler.batch.BatchCompiler;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.security.Permission;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@SuppressWarnings("SpellCheckingInspection")
|
||||||
|
public class RmTypeDefsTest extends TestCase {
|
||||||
|
public void test() throws IOException {
|
||||||
|
// Creates a test class containing various typedefs, as well as the @IntDef annotation
|
||||||
|
// itself (to make the test case independent of the SDK), and compiles this using
|
||||||
|
// ECJ. It then runs the RmTypeDefs tool on the resulting output directory, and
|
||||||
|
// finally verifies that the tool exits with a 0 exit code.
|
||||||
|
|
||||||
|
File dir = Files.createTempDir();
|
||||||
|
String testClass = ""
|
||||||
|
+ "package test.pkg;\n"
|
||||||
|
+ "\n"
|
||||||
|
+ "import android.annotation.IntDef;\n"
|
||||||
|
+ "\n"
|
||||||
|
+ "import java.lang.annotation.Retention;\n"
|
||||||
|
+ "import java.lang.annotation.RetentionPolicy;\n"
|
||||||
|
+ "\n"
|
||||||
|
+ "@SuppressWarnings({\"UnusedDeclaration\",\"JavaDoc\"})\n"
|
||||||
|
+ "public class TestClass {\n"
|
||||||
|
+ " /** @hide */\n"
|
||||||
|
+ " @Retention(RetentionPolicy.SOURCE)\n"
|
||||||
|
+ " @IntDef(flag = true,\n"
|
||||||
|
+ " value = {\n"
|
||||||
|
+ " DISPLAY_USE_LOGO,\n"
|
||||||
|
+ " DISPLAY_SHOW_HOME,\n"
|
||||||
|
+ " DISPLAY_HOME_AS_UP,\n"
|
||||||
|
+ " DISPLAY_SHOW_TITLE,\n"
|
||||||
|
+ " DISPLAY_SHOW_CUSTOM,\n"
|
||||||
|
+ " DISPLAY_TITLE_MULTIPLE_LINES\n"
|
||||||
|
+ " })\n"
|
||||||
|
+ " public @interface DisplayOptions {}\n"
|
||||||
|
+ "\n"
|
||||||
|
+ " public static final int DISPLAY_USE_LOGO = 0x1;\n"
|
||||||
|
+ " public static final int DISPLAY_SHOW_HOME = 0x2;\n"
|
||||||
|
+ " public static final int DISPLAY_HOME_AS_UP = 0x4;\n"
|
||||||
|
+ " public static final int DISPLAY_SHOW_TITLE = 0x8;\n"
|
||||||
|
+ " public static final int DISPLAY_SHOW_CUSTOM = 0x10;\n"
|
||||||
|
+ " public static final int DISPLAY_TITLE_MULTIPLE_LINES = 0x20;\n"
|
||||||
|
+ "\n"
|
||||||
|
+ " public void setDisplayOptions(@DisplayOptions int options) {\n"
|
||||||
|
+ " System.out.println(\"setDisplayOptions \" + options);\n"
|
||||||
|
+ " }\n"
|
||||||
|
+ " public void setDisplayOptions(@DisplayOptions int options, @DisplayOptions int mask) {\n"
|
||||||
|
+ " System.out.println(\"setDisplayOptions \" + options + \", mask=\" + mask);\n"
|
||||||
|
+ " }\n"
|
||||||
|
+ "\n"
|
||||||
|
+ " public static class StaticInnerClass {\n"
|
||||||
|
+ " int mViewFlags = 0;\n"
|
||||||
|
+ " static final int VISIBILITY_MASK = 0x0000000C;\n"
|
||||||
|
+ "\n"
|
||||||
|
+ " /** @hide */\n"
|
||||||
|
+ " @IntDef({VISIBLE, INVISIBLE, GONE})\n"
|
||||||
|
+ " @Retention(RetentionPolicy.SOURCE)\n"
|
||||||
|
+ " public @interface Visibility {}\n"
|
||||||
|
+ "\n"
|
||||||
|
+ " public static final int VISIBLE = 0x00000000;\n"
|
||||||
|
+ " public static final int INVISIBLE = 0x00000004;\n"
|
||||||
|
+ " public static final int GONE = 0x00000008;\n"
|
||||||
|
+ "\n"
|
||||||
|
+ " @Visibility\n"
|
||||||
|
+ " public int getVisibility() {\n"
|
||||||
|
+ " return mViewFlags & VISIBILITY_MASK;\n"
|
||||||
|
+ " }\n"
|
||||||
|
+ " }\n"
|
||||||
|
+ "\n"
|
||||||
|
+ " public static class Inherits extends StaticInnerClass {\n"
|
||||||
|
+ " @Override\n"
|
||||||
|
+ " @Visibility\n"
|
||||||
|
+ " public int getVisibility() {\n"
|
||||||
|
+ " return 0;\n"
|
||||||
|
+ " }\n"
|
||||||
|
+ " }\n"
|
||||||
|
+ "}\n";
|
||||||
|
String intdef = ""
|
||||||
|
+ "package android.annotation;\n"
|
||||||
|
+ "@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS)\n"
|
||||||
|
+ "@java.lang.annotation.Target({java.lang.annotation.ElementType.ANNOTATION_TYPE})\n"
|
||||||
|
+ "public @interface IntDef {\n"
|
||||||
|
+ " long[] value() default {};\n"
|
||||||
|
+ " boolean flag() default false;\n"
|
||||||
|
+ "}";
|
||||||
|
|
||||||
|
File srcDir = new File(dir, "test" + File.separator + "pkg");
|
||||||
|
boolean mkdirs = srcDir.mkdirs();
|
||||||
|
assertTrue(mkdirs);
|
||||||
|
File srcFile1 = new File(srcDir, "TestClass.java");
|
||||||
|
Files.write(testClass, srcFile1, Charsets.UTF_8);
|
||||||
|
|
||||||
|
srcDir = new File(dir, "android" + File.separator + "annotation");
|
||||||
|
mkdirs = srcDir.mkdirs();
|
||||||
|
assertTrue(mkdirs);
|
||||||
|
File srcFile2 = new File(srcDir, "IntDef.java");
|
||||||
|
Files.write(intdef, srcFile2, Charsets.UTF_8);
|
||||||
|
|
||||||
|
boolean compileSuccessful = BatchCompiler.compile(srcFile1 + " " + srcFile2 +
|
||||||
|
" -source 1.6 -target 1.6 -nowarn",
|
||||||
|
new PrintWriter(System.out),
|
||||||
|
new PrintWriter(System.err), null);
|
||||||
|
assertTrue(compileSuccessful);
|
||||||
|
|
||||||
|
assertEquals(""
|
||||||
|
+ "testDir/\n"
|
||||||
|
+ " testDir/android/\n"
|
||||||
|
+ " testDir/android/annotation/\n"
|
||||||
|
+ " testDir/android/annotation/IntDef.class\n"
|
||||||
|
+ " testDir/android/annotation/IntDef.java\n"
|
||||||
|
+ " testDir/test/\n"
|
||||||
|
+ " testDir/test/pkg/\n"
|
||||||
|
+ " testDir/test/pkg/TestClass$DisplayOptions.class\n"
|
||||||
|
+ " testDir/test/pkg/TestClass$Inherits.class\n"
|
||||||
|
+ " testDir/test/pkg/TestClass$StaticInnerClass$Visibility.class\n"
|
||||||
|
+ " testDir/test/pkg/TestClass$StaticInnerClass.class\n"
|
||||||
|
+ " testDir/test/pkg/TestClass.class\n"
|
||||||
|
+ " testDir/test/pkg/TestClass.java\n",
|
||||||
|
getDirectoryContents(dir));
|
||||||
|
|
||||||
|
// Trap System.exit calls:
|
||||||
|
System.setSecurityManager(new SecurityManager() {
|
||||||
|
@Override
|
||||||
|
public void checkPermission(Permission perm) {
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void checkPermission(Permission perm, Object context) {
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void checkExit(int status) {
|
||||||
|
throw new ExitException(status);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
RmTypeDefs.main(new String[]{"--verbose", dir.getPath()});
|
||||||
|
} catch (ExitException e) {
|
||||||
|
assertEquals(0, e.getStatus());
|
||||||
|
}
|
||||||
|
System.setSecurityManager(null);
|
||||||
|
|
||||||
|
// TODO: check that the classes are identical
|
||||||
|
// BEFORE removal
|
||||||
|
|
||||||
|
assertEquals(""
|
||||||
|
+ "testDir/\n"
|
||||||
|
+ " testDir/android/\n"
|
||||||
|
+ " testDir/android/annotation/\n"
|
||||||
|
+ " testDir/android/annotation/IntDef.class\n"
|
||||||
|
+ " testDir/android/annotation/IntDef.java\n"
|
||||||
|
+ " testDir/test/\n"
|
||||||
|
+ " testDir/test/pkg/\n"
|
||||||
|
+ " testDir/test/pkg/TestClass$Inherits.class\n"
|
||||||
|
+ " testDir/test/pkg/TestClass$StaticInnerClass.class\n"
|
||||||
|
+ " testDir/test/pkg/TestClass.class\n"
|
||||||
|
+ " testDir/test/pkg/TestClass.java\n",
|
||||||
|
getDirectoryContents(dir));
|
||||||
|
|
||||||
|
|
||||||
|
deleteDir(dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
String getDirectoryContents(File root) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
list(sb, root, "", 0, "testDir");
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void list(StringBuilder sb, File file, String prefix, int depth, String rootName) {
|
||||||
|
for (int i = 0; i < depth; i++) {
|
||||||
|
sb.append(" ");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!prefix.isEmpty()) {
|
||||||
|
sb.append(prefix);
|
||||||
|
}
|
||||||
|
String fileName = file.getName();
|
||||||
|
if (depth == 0 && rootName != null) { // avoid temp-name
|
||||||
|
fileName = rootName;
|
||||||
|
}
|
||||||
|
sb.append(fileName);
|
||||||
|
if (file.isDirectory()) {
|
||||||
|
sb.append('/');
|
||||||
|
sb.append('\n');
|
||||||
|
File[] files = file.listFiles();
|
||||||
|
if (files != null) {
|
||||||
|
List<File> children = Lists.newArrayList();
|
||||||
|
Collections.addAll(children, files);
|
||||||
|
Collections.sort(children, new Comparator<File>() {
|
||||||
|
@Override
|
||||||
|
public int compare(File o1, File o2) {
|
||||||
|
return o1.getName().compareTo(o2.getName());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
prefix = prefix + fileName + "/";
|
||||||
|
for (File child : children) {
|
||||||
|
list(sb, child, prefix, depth + 1, rootName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sb.append('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursive delete directory. Mostly for fake SDKs.
|
||||||
|
*
|
||||||
|
* @param root directory to delete
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||||
|
private static void deleteDir(File root) {
|
||||||
|
if (root.exists()) {
|
||||||
|
File[] files = root.listFiles();
|
||||||
|
if (files != null) {
|
||||||
|
for (File file : files) {
|
||||||
|
if (file.isDirectory()) {
|
||||||
|
deleteDir(file);
|
||||||
|
} else {
|
||||||
|
file.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
root.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ExitException extends SecurityException {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private final int mStatus;
|
||||||
|
|
||||||
|
public ExitException(int status) {
|
||||||
|
super("Unit test");
|
||||||
|
mStatus = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getStatus() {
|
||||||
|
return mStatus;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user