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                File[] files = d.listFiles();
117
118                File file = null;
119
120                try {
121                        for (int i = 0; i < files.length; i++) {
122                                file = files[i];
123                                if (file.isDirectory()) {
124          String subDir = dir + "/" + file.getName();
125          processPackage(subDir);
126                                } else {
127                                        String fileName = file.getName();
128                                        if (fileName.endsWith(".java")) {
129                                                // possibly a common mistake... mixing .java and .class
130                                                System.err.println("Expecting a .class file but got " + fileName + " ... ignoring");
131
132                                        } else if (fileName.endsWith(".class")) {
133                                                transformFile(file);
134                                        }
135                                }
136                        }
137
138                } catch (Exception e) {
139                        String fileName = file == null ? "null" : file.getName();
140                        throw new RuntimeException("Error transforming file " + fileName, e);
141                }
142
143        }
144
145        private void transformFile(File file) throws IOException, IllegalClassFormatException {
146
147                String className = getClassName(file);
148
149                byte[] result = inputStreamTransform.transform(className, file);
150
151                if (result != null) {
152                        InputStreamTransform.writeBytes(result, file);
153                        if(listener!=null) {
154                                listener.logEvent("Enhanced "+file);
155                        }
156                }
157        }
158
159        private String getClassName(File file) {
160                String path = file.getPath();
161                path = path.substring(inDir.length() + 1);
162                path = path.substring(0, path.length() - ".class".length());
163                // for windows... replace the
164                return StringReplace.replace(path,"\\", "/");
165        }
166}