package com.stericson.RootTools.containers; import java.io.*; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; /* #ANNOTATIONS @SupportedAnnotationTypes("com.stericson.RootTools.containers.RootClass.Candidate") */ /* #ANNOTATIONS @SupportedSourceVersion(SourceVersion.RELEASE_6) */ public class RootClass /* #ANNOTATIONS extends AbstractProcessor */ { /* #ANNOTATIONS @Override public boolean process(Set<? extends TypeElement> typeElements, RoundEnvironment roundEnvironment) { processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "I was invoked!!!"); return false; } */ static String PATH_TO_DX = "/Users/Chris/Projects/android-sdk-macosx/build-tools/18.0.1/dx"; enum READ_STATE { STARTING, FOUND_ANNOTATION; }; public RootClass(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { // Note: rather than calling System.load("/system/lib/libandroid_runtime.so"); // which would leave a bunch of unresolved JNI references, // we are using the 'withFramework' class as a preloader. // So, yeah, russian dolls: withFramework > RootClass > actual method String className = args[0]; RootArgs actualArgs = new RootArgs(); actualArgs.args = new String[args.length - 1]; System.arraycopy(args, 1, actualArgs.args, 0, args.length - 1); Class<?> classHandler = Class.forName(className); Constructor<?> classConstructor = classHandler.getConstructor(RootArgs.class); classConstructor.newInstance(actualArgs); } public @interface Candidate {}; public class RootArgs { public String args[]; } static void displayError(Exception e) { // Not using system.err to make it easier to capture from // calling library. System.out.println("##ERR##" + e.getMessage() + "##"); e.printStackTrace(); } // I reckon it would be better to investigate classes using getAttribute() // however this method allows the developer to simply select "Run" on RootClass // and immediately re-generate the necessary jar file. static public class AnnotationsFinder { private final String AVOIDDIRPATH = "stericson" + File.separator + "RootTools" + File.separator; private List<File> classFiles; public AnnotationsFinder() throws IOException { System.out.println("Discovering root class annotations..."); classFiles = new ArrayList<File>(); lookup(new File("src"), classFiles); System.out.println("Done discovering annotations. Building jar file."); File builtPath = getBuiltPath(); if(null != builtPath) { // Android! Y U no have com.google.common.base.Joiner class? String rc1 = "com" + File.separator + "stericson" + File.separator + "RootTools" + File.separator + "containers" + File.separator + "RootClass.class"; String rc2 = "com" + File.separator + "stericson" + File.separator + "RootTools" + File.separator + "containers" + File.separator + "RootClass$RootArgs.class"; String rc3 = "com" + File.separator + "stericson" + File.separator + "RootTools" + File.separator + "containers" + File.separator + "RootClass$AnnotationsFinder.class"; String rc4 = "com" + File.separator + "stericson" + File.separator + "RootTools" + File.separator + "containers" + File.separator + "RootClass$AnnotationsFinder$1.class"; String rc5 = "com" + File.separator + "stericson" + File.separator + "RootTools" + File.separator + "containers" + File.separator + "RootClass$AnnotationsFinder$2.class"; String [] cmd; boolean onWindows = (-1 != System.getProperty("os.name").toLowerCase().indexOf("win")); if(onWindows) { StringBuilder sb = new StringBuilder( " " + rc1 + " " + rc2 + " " + rc3 + " " + rc4 + " " + rc5 ); for(File file:classFiles) { sb.append(" " + file.getPath()); } cmd = new String[] { "cmd", "/C", "jar cvf" + " anbuild.jar" + sb.toString() }; } else { ArrayList<String> al = new ArrayList<String>(); al.add("jar"); al.add("cf"); al.add("anbuild.jar"); al.add(rc1); al.add(rc2); al.add(rc3); al.add(rc4); al.add(rc5); for(File file:classFiles) { al.add(file.getPath()); } cmd = al.toArray(new String[al.size()]); } ProcessBuilder jarBuilder = new ProcessBuilder(cmd); jarBuilder.directory(builtPath); try { jarBuilder.start().waitFor(); } catch (IOException e) {} catch (InterruptedException e) {} File rawFolder = new File("res/raw"); if (!rawFolder.exists()) { rawFolder.mkdirs(); } System.out.println("Done building jar file. Creating dex file."); if(onWindows) { cmd = new String[] { "cmd", "/C", "dx --dex --output=res/raw/anbuild.dex " + builtPath + File.separator + "anbuild.jar" }; } else { cmd = new String[] { getPathToDx(), "--dex", "--output=res/raw/anbuild.dex", builtPath + File.separator + "anbuild.jar" }; } ProcessBuilder dexBuilder = new ProcessBuilder(cmd); try { dexBuilder.start().waitFor(); } catch (IOException e) {} catch (InterruptedException e) {} } System.out.println("All done. ::: anbuild.dex should now be in your project's res/raw/ folder :::"); } protected void lookup(File path, List<File> fileList) { String desourcedPath = path.toString().replace("src/", ""); File[] files = path.listFiles(); for(File file:files) { if(file.isDirectory()) { if(-1 == file.getAbsolutePath().indexOf(AVOIDDIRPATH)) { lookup(file, fileList); } } else { if(file.getName().endsWith(".java")) { if(hasClassAnnotation(file)) { final String fileNamePrefix = file.getName().replace(".java", ""); final File compiledPath = new File(getBuiltPath().toString() + File.separator + desourcedPath); File[] classAndInnerClassFiles = compiledPath.listFiles(new FilenameFilter() { @Override public boolean accept(File dir, String filename) { return filename.startsWith(fileNamePrefix); } }); for(final File matchingFile:classAndInnerClassFiles) { fileList.add(new File(desourcedPath + File.separator + matchingFile.getName())); } } } } } } protected boolean hasClassAnnotation(File file) { READ_STATE readState = READ_STATE.STARTING; Pattern p = Pattern.compile(" class ([A-Za-z0-9_]+)"); try { BufferedReader reader = new BufferedReader(new FileReader(file)); String line; while(null != (line = reader.readLine())) { switch(readState) { case STARTING: if(-1 < line.indexOf("@RootClass.Candidate")) readState = READ_STATE.FOUND_ANNOTATION; break; case FOUND_ANNOTATION: Matcher m = p.matcher(line); if(m.find()) { System.out.println(" Found annotated class: " + m.group(0)); return true; } else { System.err.println("Error: unmatched annotation in " + file.getAbsolutePath()); readState = READ_STATE.STARTING; } break; } } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return false; } protected String getPathToDx() throws IOException { String androidHome = System.getenv("ANDROID_HOME"); if(null == androidHome) { throw new IOException("Error: you need to set $ANDROID_HOME globally"); } String dxPath = null; File[] files = new File(androidHome + File.separator + "build-tools").listFiles(); int recentSdkVersion = 0; for(File file:files) { String fileName = null; if (file.getName().contains("-")) { String[] splitFileName = file.getName().split("-"); if (splitFileName[1].contains("W")) { char[] fileNameChars = splitFileName[1].toCharArray(); fileName = String.valueOf(fileNameChars[0]); } else { fileName = splitFileName[1]; } } else { fileName = file.getName(); } int sdkVersion; String[] sdkVersionBits = fileName.split("[.]"); sdkVersion = Integer.parseInt(sdkVersionBits[0]) * 10000; if(sdkVersionBits.length > 1) { sdkVersion += Integer.parseInt(sdkVersionBits[1]) * 100; if(sdkVersionBits.length > 2) { sdkVersion += Integer.parseInt(sdkVersionBits[2]); } } if(sdkVersion > recentSdkVersion) { String tentativePath = file.getAbsolutePath() + File.separator + "dx"; if(new File(tentativePath).exists()) { recentSdkVersion = sdkVersion; dxPath = tentativePath; } } } if(dxPath == null) { throw new IOException("Error: unable to find dx binary in $ANDROID_HOME"); } return dxPath; } protected File getBuiltPath() { File foundPath = null; File ideaPath = new File("out" + File.separator + "production"); // IntelliJ if(ideaPath.isDirectory()) { File[] children = ideaPath.listFiles(new FileFilter() { @Override public boolean accept(File pathname) { return pathname.isDirectory(); } }); if(children.length > 0) { foundPath = new File(ideaPath.getAbsolutePath() + File.separator + children[0].getName()); } } if(null == foundPath) { File eclipsePath = new File("bin" + File.separator + "classes"); // Eclipse IDE if(eclipsePath.isDirectory()) { foundPath = eclipsePath; } } return foundPath; } }; public static void main (String [] args) { try { if(args.length == 0) { new AnnotationsFinder(); } else { new RootClass(args); } } catch (Exception e) { displayError(e); } } }