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