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}