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