ExtractAnnotationsDriver { @SuppressWarnings("MethodMayBeStatic") public void run(@NonNull String[] args) { List<String> classpath = Lists.newArrayList(); List<File> sources = Lists.newArrayList(); List<File> mergePaths = Lists.newArrayList(); List<File> apiFilters = null; File rmTypeDefs = null; boolean verbose = true; boolean allowMissingTypes = false; boolean allowErrors = false; boolean listFiltered = true; boolean skipClassRetention = false; String encoding = Charsets.UTF_8.name(); File output = null; File proguard = null; File typedefFile = null; long languageLevel = EcjParser.getLanguageLevel(1, 7); if (args.length == 1 && "--help".equals(args[0])) { usage(System.out); } if (args.length < 2) { usage(System.err); } for (int i = 0, n = args.length; i < n; i++) { String flag = args[i]; switch (flag) { case "--quiet": verbose = false; continue; case "--allow-missing-types": allowMissingTypes = true; continue; case "--allow-errors": allowErrors = true; continue; case "--hide-filtered": listFiltered = false; continue; case "--skip-class-retention": skipClassRetention = true; continue; } if (i == n - 1) { usage(System.err); } String value = args[i + 1]; i++; switch (flag) { case "--sources": sources = getFiles(value); break; case "--classpath": classpath = getPaths(value); break; case "--merge-zips": mergePaths = getFiles(value); break; case "--output": output = new File(value); if (output.exists()) { if (output.isDirectory()) { abort(output + " is a directory"); } boolean deleted = output.delete(); if (!deleted) { abort("Could not delete previous version of " + output); } } else if (output.getParentFile() != null && !output.getParentFile().exists()) { abort(output.getParentFile() + " does not exist"); } break; case "--proguard": proguard = new File(value); if (proguard.exists()) { if (proguard.isDirectory()) { abort(proguard + " is a directory"); } boolean deleted = proguard.delete(); if (!deleted) { abort("Could not delete previous version of " + proguard); } } else if (proguard.getParentFile() != null && !proguard.getParentFile() .exists()) { abort(proguard.getParentFile() + " does not exist"); } break; case "--typedef-file": typedefFile = new File(value); break; case "--encoding": encoding = value; break; case "--api-filter": if (apiFilters == null) { apiFilters = Lists.newArrayList(); } for (String path : Splitter.on(",").omitEmptyStrings().split(value)) { File apiFilter = new File(path); if (!apiFilter.isFile()) { String message = apiFilter + " does not exist or is not a file"; abort(message); } apiFilters.add(apiFilter); } break; case "--language-level": if ("1.6".equals(value)) { languageLevel = EcjParser.getLanguageLevel(1, 6); } else if ("1.7".equals(value)) { languageLevel = EcjParser.getLanguageLevel(1, 7); } else if ("1.8".equals(value)) { languageLevel = EcjParser.getLanguageLevel(1, 8); } else { abort("Unsupported language level " + value); } break; case "--rmtypedefs": rmTypeDefs = new File(value); if (!rmTypeDefs.isDirectory()) { abort(rmTypeDefs + " is not a directory"); } break; default: System.err .println("Unknown flag " + flag + ": Use --help for usage information"); break; } } if (sources.isEmpty()) { abort("Must specify at least one source path"); } if (classpath.isEmpty()) { abort("Must specify classpath pointing to at least android.jar or the framework"); } if (output == null && proguard == null) { abort("Must specify output path with --output or a proguard path with --proguard"); } ApiDatabase database = null; if (apiFilters != null && !apiFilters.isEmpty()) { try { List<String> lines = Lists.newArrayList(); for (File file : apiFilters) { lines.addAll(Files.readLines(file, Charsets.UTF_8)); } database = new ApiDatabase(lines); } catch (IOException e) { abort("Could not open API database " + apiFilters + ": " + e.getLocalizedMessage()); } } Extractor extractor = new Extractor(database, rmTypeDefs, verbose, !skipClassRetention, true); extractor.setListIgnored(listFiltered); EcjParser.EcjResult result = null; try { result = parseSources(sources, classpath, encoding, languageLevel); Collection<CompilationUnitDeclaration> units = result.getCompilationUnits(); boolean abort = false; int errorCount = 0; for (CompilationUnitDeclaration unit : units) { IProblem[] problems = unit.compilationResult().getAllProblems(); if (problems != null) { for (IProblem problem : problems) { if (problem.isError()) { errorCount++; String message = problem.getMessage(); if (allowMissingTypes) { if (message.contains("cannot be resolved")) { continue; } } System.out.println("Error: " + new String(problem.getOriginatingFileName()) + ":" + problem.getSourceLineNumber() + ": " + message); abort = !allowErrors; } } } } if (errorCount > 0) { System.err.println("Found " + errorCount + " errors"); } if (abort) { abort("Not extracting annotations (compilation problems encountered)"); } extractor.extractFromProjectSource(units); if (mergePaths != null) { for (File jar : mergePaths) { extractor.mergeExisting(jar); } } extractor.export(output, proguard); if (typedefFile != null) { extractor.writeTypedefFile(typedefFile); } if (rmTypeDefs != null) { if (typedefFile != null) { Extractor.removeTypedefClasses(rmTypeDefs, typedefFile); } else { extractor.removeTypedefClasses(); } } } catch (IOException e) { e.printStackTrace(); } finally { if (result != null) { result.dispose(); } } }  static void main(String[] args); @SuppressWarnings("MethodMayBeStatic") void run(@NonNull String[] args);  }
@Test public void testProGuard() throws Exception { checkValidEcj(); File androidJar = findAndroidJar(false); File project = createProject(mKeepTest, mKeepAnnotation); File output = mTemporaryFolder.newFile("proguard.cfg"); List<String> list = java.util.Arrays.asList( "--sources", new File(project, "src").getPath(), "--classpath", androidJar.getPath(), "--quiet", "--language-level", "1.6", "--proguard", output.getPath() ); String[] args = list.toArray(new String[list.size()]); assertNotNull(args); new ExtractAnnotationsDriver().run(args); assertEquals("" + "-keep class test.pkg.KeepTest {\n" + " java.lang.Object myField\n" + "}\n" + "\n" + "-keep class test.pkg.KeepTest {\n" + " void foo()\n" + "}\n" + "\n" + "-keep class test.pkg.KeepTest.MyAnnotation\n" + "\n" + "-keep class test.pkg.KeepTest.MyClass\n" + "\n" + "-keep enum test.pkg.KeepTest.MyEnum\n" + "\n" + "-keep interface test.pkg.KeepTest.MyInterface\n" + "\n" + "-keep interface test.pkg.KeepTest.MyInterface2 {\n" + " void paint2()\n" + "}\n" + "\n", Files.toString(output, Charsets.UTF_8)); deleteFile(project); }
@Test public void testIncludeClassRetention() throws Exception { checkValidEcj(); File androidJar = findAndroidJar(false); File project = createProject( mIntDefTest, mPermissionsTest, mManifest, mKeepAnnotation, mIntDefAnnotation, mIntRangeAnnotation, mPermissionAnnotation); File output = mTemporaryFolder.newFile("annotations.zip"); File proguard = mTemporaryFolder.newFile("proguard.cfg"); List<String> list = java.util.Arrays.asList( "--sources", new File(project, "src").getPath(), "--classpath", androidJar.getPath(), "--quiet", "--language-level", "1.6", "--output", output.getPath(), "--proguard", proguard.getPath() ); String[] args = list.toArray(new String[list.size()]); assertNotNull(args); new ExtractAnnotationsDriver().run(args); assertEquals("" + "-keep class test.pkg.IntDefTest {\n" + " void testIntDef(int)\n" + "}\n" + "\n", Files.toString(proguard, Charsets.UTF_8)); checkPackageXml("test.pkg", output, "" + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + "<root>\n" + " <item name=\"test.pkg.IntDefTest void setFlags(java.lang.Object, int) 1\">\n" + " <annotation name=\"android.support.annotation.IntDef\">\n" + " <val name=\"value\" val=\"{test.pkg.IntDefTest.STYLE_NORMAL, test.pkg.IntDefTest.STYLE_NO_TITLE, test.pkg.IntDefTest.STYLE_NO_FRAME, test.pkg.IntDefTest.STYLE_NO_INPUT}\" />\n" + " <val name=\"flag\" val=\"true\" />\n" + " </annotation>\n" + " </item>\n" + " <item name=\"test.pkg.IntDefTest void setStyle(int, int) 0\">\n" + " <annotation name=\"android.support.annotation.IntDef\">\n" + " <val name=\"value\" val=\"{test.pkg.IntDefTest.STYLE_NORMAL, test.pkg.IntDefTest.STYLE_NO_TITLE, test.pkg.IntDefTest.STYLE_NO_FRAME, test.pkg.IntDefTest.STYLE_NO_INPUT}\" />\n" + " </annotation>\n" + " <annotation name=\"android.support.annotation.IntRange\">\n" + " <val name=\"from\" val=\"20\" />\n" + " </annotation>\n" + " </item>\n" + " <item name=\"test.pkg.PermissionsTest CONTENT_URI\">\n" + " <annotation name=\"android.support.annotation.RequiresPermission.Read\">\n" + " <val name=\"value\" val=\"&quot;android.permission.MY_READ_PERMISSION_STRING&quot;\" />\n" + " </annotation>\n" + " <annotation name=\"android.support.annotation.RequiresPermission.Write\">\n" + " <val name=\"value\" val=\"&quot;android.permission.MY_WRITE_PERMISSION_STRING&quot;\" />\n" + " </annotation>\n" + " </item>\n" + " <item name=\"test.pkg.PermissionsTest void myMethod()\">\n" + " <annotation name=\"android.support.annotation.RequiresPermission\">\n" + " <val name=\"value\" val=\"&quot;android.permission.MY_PERMISSION_STRING&quot;\" />\n" + " </annotation>\n" + " </item>\n" + "</root>\n" + "\n"); deleteFile(project); }
@Test public void testSkipClassRetention() throws Exception { checkValidEcj(); File androidJar = findAndroidJar(false); File project = createProject( mIntDefTest, mPermissionsTest, mManifest, mKeepAnnotation, mIntDefAnnotation, mIntRangeAnnotation, mPermissionAnnotation); File output = mTemporaryFolder.newFile("annotations.zip"); File proguard = mTemporaryFolder.newFile("proguard.cfg"); List<String> list = java.util.Arrays.asList( "--sources", new File(project, "src").getPath(), "--classpath", androidJar.getPath(), "--quiet", "--skip-class-retention", "--language-level", "1.6", "--output", output.getPath(), "--proguard", proguard.getPath() ); String[] args = list.toArray(new String[list.size()]); assertNotNull(args); new ExtractAnnotationsDriver().run(args); checkPackageXml("test.pkg", output, "" + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + "<root>\n" + " <item name=\"test.pkg.IntDefTest void setFlags(java.lang.Object, int) 1\">\n" + " <annotation name=\"android.support.annotation.IntDef\">\n" + " <val name=\"value\" val=\"{test.pkg.IntDefTest.STYLE_NORMAL, test.pkg.IntDefTest.STYLE_NO_TITLE, test.pkg.IntDefTest.STYLE_NO_FRAME, test.pkg.IntDefTest.STYLE_NO_INPUT}\" />\n" + " <val name=\"flag\" val=\"true\" />\n" + " </annotation>\n" + " </item>\n" + " <item name=\"test.pkg.IntDefTest void setStyle(int, int) 0\">\n" + " <annotation name=\"android.support.annotation.IntDef\">\n" + " <val name=\"value\" val=\"{test.pkg.IntDefTest.STYLE_NORMAL, test.pkg.IntDefTest.STYLE_NO_TITLE, test.pkg.IntDefTest.STYLE_NO_FRAME, test.pkg.IntDefTest.STYLE_NO_INPUT}\" />\n" + " </annotation>\n" + " <annotation name=\"android.support.annotation.IntRange\">\n" + " <val name=\"from\" val=\"20\" />\n" + " </annotation>\n" + " </item>\n" + "</root>\n" + "\n"); deleteFile(project); }
@Test public void testWriteJarRecipeFile() throws Exception { checkValidEcj(); File androidJar = findAndroidJar(false); File project = createProject( mIntDefTest, mPermissionsTest, mManifest, mKeepAnnotation, mIntDefAnnotation, mIntRangeAnnotation, mPermissionAnnotation); File output = mTemporaryFolder.newFile("annotations.zip"); File proguard = mTemporaryFolder.newFile("proguard.cfg"); File typedefFile = mTemporaryFolder.newFile("typedefs.txt"); List<String> list = java.util.Arrays.asList( "--sources", new File(project, "src").getPath(), "--classpath", androidJar.getPath(), "--quiet", "--language-level", "1.6", "--output", output.getPath(), "--proguard", proguard.getPath(), "--typedef-file", typedefFile.getPath() ); String[] args = list.toArray(new String[list.size()]); assertNotNull(args); new ExtractAnnotationsDriver().run(args); assertEquals("" + "D test/pkg/IntDefTest$DialogFlags\n" + "D test/pkg/IntDefTest$DialogStyle\n", Files.toString(typedefFile, Charsets.UTF_8)); deleteFile(project); }