001package io.ebean.enhance.ant; 002 003import io.ebean.enhance.common.InputStreamTransform; 004import io.ebean.enhance.Transformer; 005 006import java.io.File; 007import java.io.IOException; 008import java.lang.instrument.IllegalClassFormatException; 009import java.util.Collections; 010import java.util.LinkedHashSet; 011import java.util.Set; 012 013/** 014 * Transforms class files when they are on the file system. 015 * <p> 016 * Typically run as part of an ANT task rather than when Ebean is running. 017 * </p> 018 */ 019public class OfflineFileTransform { 020 021 protected final InputStreamTransform inputStreamTransform; 022 023 protected final String inDir; 024 025 protected TransformationListener listener; 026 027 /** 028 * Enhance the class file and replace the file with the the enhanced 029 * version of the class. 030 * 031 * @param transformer 032 * object that actually transforms the class bytes 033 * @param classLoader 034 * the ClassLoader used as part of the transformation 035 * @param inDir 036 * the root directory where the class files are located 037 */ 038 public OfflineFileTransform(Transformer transformer, ClassLoader classLoader, String inDir) { 039 this.inputStreamTransform = new InputStreamTransform(transformer, classLoader); 040 inDir = trimSlash(inDir); 041 this.inDir = inDir; 042 } 043 044 /** Register a listener to receive event notification */ 045 public void setListener(TransformationListener v) { 046 this.listener = v; 047 } 048 049 private String trimSlash(String dir) { 050 if (dir.endsWith("/")){ 051 return dir.substring(0, dir.length()-1); 052 } else { 053 return dir; 054 } 055 } 056 057 /** 058 * Process the packageNames as comma delimited string. 059 */ 060 public void process(String packageNames) { 061 062 if (packageNames == null) { 063 // just process all directories 064 processPackage(""); 065 return; 066 } 067 068 Set<String> pkgNames = new LinkedHashSet<String>(); 069 Collections.addAll(pkgNames, packageNames.split(",")); 070 071 process(pkgNames); 072 } 073 074 /** 075 * Process all the comma delimited list of packages. 076 * <p> 077 * Package names are effectively converted into a directory on the file 078 * system, and the class files are found and processed. 079 * </p> 080 */ 081 public void process(Set<String> packageNames) { 082 083 if (packageNames == null || packageNames.isEmpty()) { 084 // just process all directories 085 inputStreamTransform.log(2, "processing all directories (as no explicit packages)"); 086 processPackage(""); 087 return; 088 } 089 090 for (String pkgName : packageNames) { 091 092 String pkg = pkgName.trim().replace('.', '/'); 093 094 if (pkg.endsWith("**")) { 095 pkg = pkg.substring(0, pkg.length() - 2); 096 } else if (pkg.endsWith("*")) { 097 pkg = pkg.substring(0, pkg.length() - 1); 098 } 099 100 pkg = trimSlash(pkg); 101 102 processPackage(pkg); 103 } 104 } 105 106 private void processPackage(String dir) { 107 108 inputStreamTransform.log(3, "transform> pkg: " + dir); 109 110 String dirPath = inDir + "/" + dir; 111 File d = new File(dirPath); 112 if (!d.exists()) { 113 throw new RuntimeException("File not found " + dirPath + " currentDir:" + new File(".").getAbsolutePath()); 114 } 115 116 final File[] files = d.listFiles(); 117 if (files != null) { 118 for (final File file : files) { 119 try { 120 if (file.isDirectory()) { 121 final String subDir = dir + "/" + file.getName(); 122 processPackage(subDir); 123 } else { 124 final String fileName = file.getName(); 125 if (fileName.endsWith(".java")) { 126 // possibly a common mistake... mixing .java and .class 127 System.err.println("Expecting a .class file but got " + fileName + " ... ignoring"); 128 129 } else if (fileName.endsWith(".class")) { 130 transformFile(file); 131 } 132 } 133 } catch (final Exception e) { 134 throw new RuntimeException("Error transforming file " + file.getName(), e); 135 } 136 } 137 } else { 138 throw new RuntimeException("Can't read directory " + d.getName()); 139 } 140 } 141 142 private void transformFile(File file) throws IOException, IllegalClassFormatException { 143 144 String className = getClassName(file); 145 146 byte[] result = inputStreamTransform.transform(className, file); 147 148 if (result != null) { 149 InputStreamTransform.writeBytes(result, file); 150 if(listener!=null) { 151 listener.logEvent("Enhanced "+file); 152 } 153 } 154 } 155 156 private String getClassName(File file) { 157 String path = file.getPath(); 158 path = path.substring(inDir.length() + 1); 159 path = path.substring(0, path.length() - ".class".length()); 160 // for windows... replace the 161 return StringReplace.replace(path,"\\", "/"); 162 } 163}