1 /*
   2  * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package jdk.nashorn.internal.tools.nasgen;
  27 
  28 import java.io.File;
  29 import java.io.FileInputStream;
  30 import java.io.FileOutputStream;
  31 import java.io.IOException;
  32 import java.io.PrintWriter;
  33 import jdk.internal.org.objectweb.asm.ClassReader;
  34 import jdk.internal.org.objectweb.asm.ClassWriter;
  35 import jdk.internal.org.objectweb.asm.Opcodes;
  36 import jdk.internal.org.objectweb.asm.util.CheckClassAdapter;
  37 
  38 /**
  39  * Main class for the "nasgen" tool.
  40  *
  41  */
  42 public class Main {
  43     /**
  44      * ASM version to be used by nasgen tool.
  45      */
  46     public static final int ASM_VERSION = Opcodes.ASM5;
  47 
  48     private static final boolean DEBUG = Boolean.getBoolean("nasgen.debug");
  49 
  50     private interface ErrorReporter {
  51         public void error(String msg);
  52     }
  53 
  54     /**
  55      * Public entry point for Nasgen if invoked from command line. Nasgen takes three arguments
  56      * in order: input directory, package list, output directory
  57      *
  58      * @param args argument vector
  59      */
  60     public static void main(final String[] args) {
  61         final ErrorReporter reporter = new ErrorReporter() {
  62             @Override
  63             public void error(final String msg) {
  64                 Main.error(msg, 1);
  65             }
  66         };
  67         if (args.length == 3) {
  68             processAll(args[0], args[1], args[2], reporter);
  69         } else {
  70             error("Usage: nasgen <input-dir> <package-list> <output-dir>", 1);
  71         }
  72     }
  73 
  74     private static void processAll(final String in, final String pkgList, final String out, final ErrorReporter reporter) {
  75         final File inDir = new File(in);
  76         if (!inDir.exists() || !inDir.isDirectory()) {
  77             reporter.error(in + " does not exist or not a directory");
  78             return;
  79         }
  80 
  81         final File outDir = new File(out);
  82         if (!outDir.exists() || !outDir.isDirectory()) {
  83             reporter.error(out + " does not exist or not a directory");
  84             return;
  85         }
  86 
  87         final String[] packages = pkgList.split(":");
  88         for (String pkg : packages) {
  89             pkg = pkg.replace('.', File.separatorChar);
  90             final File dir = new File(inDir, pkg);
  91             final File[] classes = dir.listFiles();
  92             for (final File clazz : classes) {
  93                 if (clazz.isFile() && clazz.getName().endsWith(".class")) {
  94                     if (! process(clazz, new File(outDir, pkg), reporter)) {
  95                         return;
  96                     }
  97                 }
  98             }
  99         }
 100     }
 101 
 102     private static boolean process(final File inFile, final File outDir, final ErrorReporter reporter) {
 103         try {
 104             byte[] buf = new byte[(int)inFile.length()];
 105 
 106             try (FileInputStream fin = new FileInputStream(inFile)) {
 107                 fin.read(buf);
 108             }
 109 
 110             final ScriptClassInfo sci = ClassGenerator.getScriptClassInfo(buf);
 111 
 112             if (sci != null) {
 113                 try {
 114                     sci.verify();
 115                 } catch (final Exception e) {
 116                     reporter.error(e.getMessage());
 117                     return false;
 118                 }
 119 
 120                 // create necessary output package dir
 121                 outDir.mkdirs();
 122 
 123                 // instrument @ScriptClass
 124                 final ClassWriter writer = ClassGenerator.makeClassWriter();
 125                 final ClassReader reader = new ClassReader(buf);
 126                 final ScriptClassInstrumentor inst = new ScriptClassInstrumentor(writer, sci);
 127                 reader.accept(inst, 0);
 128                 //noinspection UnusedAssignment
 129 
 130                 // write instrumented class
 131                 try (FileOutputStream fos = new FileOutputStream(new File(outDir, inFile.getName()))) {
 132                     buf = writer.toByteArray();
 133                     if (DEBUG) {
 134                         verify(buf);
 135                     }
 136                     fos.write(buf);
 137                 }
 138 
 139                 // simple class name without package prefix
 140                 String simpleName = inFile.getName();
 141                 simpleName = simpleName.substring(0, simpleName.indexOf(".class"));
 142 
 143                 if (sci.isPrototypeNeeded()) {
 144                     // generate prototype class
 145                     final PrototypeGenerator protGen = new PrototypeGenerator(sci);
 146                     buf = protGen.getClassBytes();
 147                     if (DEBUG) {
 148                         verify(buf);
 149                     }
 150                     try (FileOutputStream fos = new FileOutputStream(new File(outDir, simpleName + StringConstants.PROTOTYPE_SUFFIX + ".class"))) {
 151                         fos.write(buf);
 152                     }
 153                 }
 154 
 155                 if (sci.isConstructorNeeded()) {
 156                     // generate constructor class
 157                     final ConstructorGenerator consGen = new ConstructorGenerator(sci);
 158                     buf = consGen.getClassBytes();
 159                     if (DEBUG) {
 160                         verify(buf);
 161                     }
 162                     try (FileOutputStream fos = new FileOutputStream(new File(outDir, simpleName + StringConstants.CONSTRUCTOR_SUFFIX + ".class"))) {
 163                         fos.write(buf);
 164                     }
 165                 }
 166             }
 167             return true;
 168         } catch (final IOException | RuntimeException e) {
 169             if (DEBUG) {
 170                 e.printStackTrace(System.err);
 171             }
 172             reporter.error(e.getMessage());
 173 
 174             return false;
 175         }
 176     }
 177 
 178     private static void verify(final byte[] buf) {
 179         final ClassReader cr = new ClassReader(buf);
 180         CheckClassAdapter.verify(cr, false, new PrintWriter(System.err));
 181     }
 182 
 183     private static void error(final String msg, final int exitCode) {
 184         System.err.println(msg);
 185         System.exit(exitCode);
 186     }
 187 }