aboutsummaryrefslogtreecommitdiffstats
path: root/trunk/infrastructure/net.appjet.bodylock
diff options
context:
space:
mode:
Diffstat (limited to 'trunk/infrastructure/net.appjet.bodylock')
-rw-r--r--trunk/infrastructure/net.appjet.bodylock/bodylock.scala291
-rw-r--r--trunk/infrastructure/net.appjet.bodylock/compressor.scala269
2 files changed, 560 insertions, 0 deletions
diff --git a/trunk/infrastructure/net.appjet.bodylock/bodylock.scala b/trunk/infrastructure/net.appjet.bodylock/bodylock.scala
new file mode 100644
index 0000000..e24d55c
--- /dev/null
+++ b/trunk/infrastructure/net.appjet.bodylock/bodylock.scala
@@ -0,0 +1,291 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS-IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.appjet.bodylock;
+
+import net.appjet.common.rhino.rhinospect;
+
+import scala.collection.mutable.{SynchronizedMap, ArrayBuffer, HashMap};
+
+import org.mozilla.javascript.{Context, Scriptable, ScriptableObject, Script, JavaScriptException, NativeJavaObject, WrappedException, IdScriptableObject};
+
+trait Executable {
+ def execute(scope: Scriptable): Object;
+}
+
+trait JSStackFrame {
+ def errorLine: Int; // 1-indexed.
+ def errorContext(rad: Int): (Int, Int, Seq[String]); // 1-indexed
+ def name: String;
+}
+
+class ExecutionException(message: String, cause: Throwable) extends RuntimeException(message, cause) {
+ def this(message: String) = this(message, null);
+}
+
+class JSRuntimeException(val message: String, val cause: Throwable) extends ExecutionException(message, cause) {
+ private val i_frames: Seq[JSStackFrame] = if (cause == null) List() else {
+ val ab = new ArrayBuffer[JSStackFrame];
+ for (elt <- cause.getStackTrace() if (elt.getFileName != null && BodyLock.map.filter(_.contains(elt.getFileName)).isDefined && elt.getLineNumber >= 0)) {
+ ab += new JSStackFrame {
+ val errorLine = elt.getLineNumber;
+ val name = elt.getFileName;
+ val code = BodyLock.map.getOrElse(Map[String, String]()).getOrElse(elt.getFileName, "").split("\n"); // 0-indexed.
+ def errorContext(rad: Int) = {
+ val start_i = Math.max(errorLine-rad, 1)-1;
+ val end_i = Math.min(errorLine+rad, code.length)-1;
+ (start_i+1, end_i+1, code.slice(start_i, end_i+1));
+ }
+ }
+ }
+ ab;
+ }
+ def frames = i_frames;
+}
+
+class JSCompileException(message: String, cause: org.mozilla.javascript.EvaluatorException) extends JSRuntimeException(message, cause) {
+ override val frames =
+ List(new JSStackFrame {
+ val errorLine = cause.lineNumber();
+ val name = cause.sourceName();
+ val code = BodyLock.map.getOrElse(Map[String, String]()).getOrElse(cause.sourceName(), "").split("\n"); // 0-indexed.
+ def errorContext(rad: Int) = {
+ val start_i = Math.max(errorLine-rad, 1)-1;
+ val end_i = Math.min(errorLine+rad, code.length)-1;
+ (start_i+1, end_i+1, code.slice(start_i, end_i+1));
+ }
+ }).concat(List(super.frames: _*));
+}
+
+private[bodylock] class InnerExecutable(val code: String, val script: Script) extends Executable {
+ def execute(scope: Scriptable) = try {
+ BodyLock.runInContext { cx =>
+ script.exec(cx, scope);
+ }
+ } catch {
+ case e: Throwable => {
+ val orig = BodyLock.unwrapExceptionIfNecessary(e);
+ orig match {
+ case e: JSRuntimeException => throw e;
+ case e: org.mortbay.jetty.RetryRequest => throw e;
+ case _ => throw new JSRuntimeException("Error while executing: "+orig.getMessage, orig);
+ }
+ }
+ }
+
+ override def toString() =
+ rhinospect.dumpFields(script, 1, "");
+}
+
+object CustomContextFactory extends org.mozilla.javascript.ContextFactory {
+ val wrapFactory = new org.mozilla.javascript.WrapFactory {
+ setJavaPrimitiveWrap(false); // don't wrap strings, numbers, booleans
+ }
+
+ class CustomContext() extends Context() {
+ setWrapFactory(wrapFactory);
+ }
+
+ override def makeContext(): Context = new CustomContext();
+}
+
+object BodyLock {
+ var map: Option[SynchronizedMap[String, String]] = None;
+
+ def runInContext[E](expr: Context => E): E = {
+ val cx = CustomContextFactory.enterContext();
+ try {
+ expr(cx);
+ } finally {
+ Context.exit();
+ }
+ }
+
+ def newScope = runInContext { cx =>
+ cx.initStandardObjects(null, true);
+ }
+ def subScope(scope: Scriptable) = runInContext { cx =>
+ val newObj = cx.newObject(scope).asInstanceOf[ScriptableObject];
+ newObj.setPrototype(scope);
+ newObj.setParentScope(null);
+ newObj;
+ }
+
+ def evaluateString(scope: Scriptable, source: String, sourceName: String,
+ lineno: Int /*, securityDomain: AnyRef = null */) = runInContext { cx =>
+ cx.evaluateString(scope, source, sourceName, lineno, null);
+ }
+ def compileString(source: String, sourceName: String, lineno: Int
+ /*, securityDomain: AnyRef = null */) = runInContext { cx =>
+ map.foreach(_(sourceName) = source);
+ try {
+ new InnerExecutable(source, compileToScript(source, sourceName, lineno));
+ } catch {
+ case e: org.mozilla.javascript.EvaluatorException => {
+ throw new JSCompileException(e.getMessage(), e);
+ }
+ }
+ }
+
+ private val classId = new java.util.concurrent.atomic.AtomicInteger(0);
+
+ private def compileToScript(source: String, sourceName: String, lineNumber: Int): Script = {
+ val className = "JS$"+sourceName.replaceAll("[^a-zA-Z0-9]", "\\$")+"$"+classId.incrementAndGet();
+ compilationutils.compileToScript(source, sourceName, lineNumber, className);
+ }
+
+ def executableFromBytes(bytes: Array[byte], className: String) =
+ new InnerExecutable("(source not available)", compilationutils.bytesToScript(bytes, className));
+
+ def unwrapExceptionIfNecessary(e: Throwable): Throwable = {
+ e match {
+ case e: JavaScriptException => e.getValue() match {
+ case njo: NativeJavaObject => Context.jsToJava(njo, classOf[Object]) match {
+ case e: Throwable => e;
+ case _ => e;
+ }
+ case ne: IdScriptableObject => new JSRuntimeException("Error: "+ne.get("message", ne), e);
+ case t: Throwable => t;
+ case _ => e;
+ }
+ case e: WrappedException => unwrapExceptionIfNecessary(e.getWrappedException());
+ case _ => e;
+ }
+ }
+}
+
+private[bodylock] object compilationutils {
+ class Loader(parent: ClassLoader) extends ClassLoader(parent) {
+ def this() = this(getClass.getClassLoader);
+ def defineClass(className: String, bytes: Array[Byte]): Class[_] = {
+ // call protected method
+ defineClass(className, bytes, 0, bytes.length);
+ }
+ }
+
+ def compileToBytes(source: String, sourceName: String, lineNumber: Int,
+ className: String): Array[Byte] = {
+ val environs = new org.mozilla.javascript.CompilerEnvirons;
+ BodyLock.runInContext(environs.initFromContext(_));
+ environs.setGeneratingSource(false);
+ val compiler = new org.mozilla.javascript.optimizer.ClassCompiler(environs);
+
+ // throws EvaluatorException
+ val result:Array[Object] =
+ compiler.compileToClassFiles(source, sourceName, lineNumber, className);
+
+ // result[0] is class name, result[1] is class bytes
+ result(1).asInstanceOf[Array[Byte]];
+ }
+
+ def compileToScript(source: String, sourceName: String, lineNumber: Int,
+ className: String): Script = {
+ bytesToScript(compileToBytes(source, sourceName, lineNumber, className), className);
+ }
+
+ def bytesToScript(bytes: Array[Byte], className: String): Script = {
+ (new Loader()).defineClass(className, bytes).newInstance.asInstanceOf[Script];
+ }
+}
+
+
+import java.io.File;
+import scala.collection.mutable.HashMap;
+import net.appjet.common.util.BetterFile;
+import net.appjet.common.cli._;
+
+object Compiler {
+ val optionsList = Array(
+ ("destination", true, "Destination for class files", "path"),
+ ("cutPrefix", true, "Drop this prefix from files", "path"),
+ ("verbose", false, "Print debug information", "")
+ );
+ val chosenOptions = new HashMap[String, String];
+ val options =
+ for (opt <- optionsList) yield
+ new CliOption(opt._1, opt._3, if (opt._2) Some(opt._4) else None)
+
+// var o = new Options;
+// for (m <- optionsList) {
+// o.addOption({
+// if (m._2) {
+// withArgName(m._4);
+// hasArg();
+// }
+// withDescription(m._3);
+// // withLongOpt(m.getName());
+// create(m._1);
+// });
+// }
+// o;
+// }
+
+ var verbose = true;
+ def vprintln(s: String) {
+ if (verbose) println(s);
+ }
+
+ def printUsage() {
+ println((new CliParser(options)).usage);
+ }
+ def extractOptions(args0: Array[String]) = {
+ val parser = new CliParser(options);
+ val (opts, args) =
+ try {
+ parser.parseOptions(args0);
+ } catch {
+ case e: ParseException => {
+ println("error: "+e.getMessage());
+ printUsage();
+ System.exit(1);
+ null;
+ }
+ }
+ for ((k, v) <- opts) {
+ chosenOptions(k) = v;
+ }
+ args
+ }
+ def compileSingleFile(src: File, dst: File) {
+ val source = BetterFile.getFileContents(src);
+ vprintln("to: "+dst.getPath());
+ val classBytes = compilationutils.compileToBytes(source, src.getName(), 1, dst.getName().split("\\.")(0));
+
+ val fos = new java.io.FileOutputStream(dst);
+ fos.write(classBytes);
+ }
+
+ def main(args0: Array[String]) {
+ // should contain paths, relative to PWD, of javascript files to compile.
+ val args = extractOptions(args0);
+ val dst = chosenOptions("destination");
+ val pre = chosenOptions.getOrElse("cutPrefix", "");
+ verbose = chosenOptions.getOrElse("verbose", "false") == "true";
+ for (p <- args) {
+ val srcFile = new File(p);
+ if (srcFile.getParent() != null && ! srcFile.getParent().startsWith(pre))
+ throw new RuntimeException("srcFile "+srcFile.getPath()+" doesn't start with "+pre);
+ val parentDir =
+ if (srcFile.getParent() != null) {
+ new File(dst+"/"+srcFile.getParent().substring(pre.length));
+ } else {
+ new File(dst);
+ }
+ parentDir.mkdirs();
+ compileSingleFile(srcFile, new File(parentDir.getPath()+"/JS$"+srcFile.getName().split("\\.").reverse.drop(1).reverse.mkString(".").replaceAll("[^a-zA-Z0-9]", "\\$")+".class"));
+ }
+ }
+}
diff --git a/trunk/infrastructure/net.appjet.bodylock/compressor.scala b/trunk/infrastructure/net.appjet.bodylock/compressor.scala
new file mode 100644
index 0000000..5041787
--- /dev/null
+++ b/trunk/infrastructure/net.appjet.bodylock/compressor.scala
@@ -0,0 +1,269 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS-IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.appjet.bodylock;
+
+import java.io.{StringWriter, StringReader}
+import net.appjet.common.util.BetterFile;
+
+object compressor {
+ def compress(code: String): String = {
+ import yuicompressor.org.mozilla.javascript.{ErrorReporter, EvaluatorException};
+ object MyErrorReporter extends ErrorReporter {
+ def warning(message:String, sourceName:String, line:Int, lineSource:String, lineOffset:Int) {
+ if (message startsWith "Try to use a single 'var' statement per scope.") return;
+ if (line < 0) System.err.println("\n[WARNING] " + message);
+ else System.err.println("\n[WARNING] " + line + ':' + lineOffset + ':' + message);
+ }
+ def error(message:String, sourceName:String, line:Int, lineSource:String, lineOffset:Int) {
+ if (line < 0) System.err.println("\n[ERROR] " + message);
+ else System.err.println("\n[ERROR] " + line + ':' + lineOffset + ':' + message);
+ java.lang.System.exit(1);
+ }
+ def runtimeError(message:String, sourceName:String, line:Int, lineSource:String, lineOffset:Int): EvaluatorException = {
+ error(message, sourceName, line, lineSource, lineOffset);
+ return new EvaluatorException(message);
+ }
+ }
+
+ val munge = true;
+ val verbose = false;
+ val optimize = true;
+ val wrap = true;
+ val compressor = new com.yahoo.platform.yui.compressor.JavaScriptCompressor(new StringReader(code), MyErrorReporter);
+ val writer = new StringWriter;
+ compressor.compress(writer, if (wrap) 100 else -1, munge, verbose, true, optimize);
+ writer.toString;
+ }
+
+ def main(args: Array[String]) {
+ for (fname <- args) {
+ try {
+ val src = BetterFile.getFileContents(fname);
+ val obfSrc = compress(src);
+ val fw = (new java.io.FileWriter(new java.io.File(fname)));
+ fw.write(obfSrc, 0, obfSrc.length);
+ fw.close();
+ } catch {
+ case e => {
+ println("Failed to compress: "+fname+". Quitting.");
+ e.printStackTrace();
+ System.exit(1);
+ }
+ }
+ }
+ }
+}
+
+
+// ignore these:
+
+// import java.io._;
+
+// def doMake {
+
+// lazy val isEtherPad = (args.length >= 2 && args(1) == "etherpad");
+// lazy val isNoHelma = (args.length >= 2 && args(1) == "nohelma");
+
+// def getFile(path:String): String = {
+// val builder = new StringBuilder(1000);
+// val reader = new BufferedReader(new FileReader(path));
+// val buf = new Array[Char](1024);
+// var numRead = 0;
+// while({ numRead = reader.read(buf); numRead } != -1) {
+// builder.append(buf, 0, numRead);
+// }
+// reader.close;
+// return builder.toString;
+// }
+
+// def putFile(str: String, path: String): Unit = {
+// val writer = new FileWriter(path);
+// writer.write(str);
+// writer.close;
+// }
+
+// def writeToString(func:(Writer=>Unit)): String = {
+// val writer = new StringWriter;
+// func(writer);
+// return writer.toString;
+// }
+
+// def compressJS(code: String, wrap: Boolean): String = {
+// import org.mozilla.javascript.{ErrorReporter, EvaluatorException};
+// object MyErrorReporter extends ErrorReporter {
+// def warning(message:String, sourceName:String, line:Int, lineSource:String, lineOffset:Int) {
+// if (message startsWith "Try to use a single 'var' statement per scope.") return;
+// if (line < 0) System.err.println("\n[WARNING] " + message);
+// else System.err.println("\n[WARNING] " + line + ':' + lineOffset + ':' + message);
+// }
+// def error(message:String, sourceName:String, line:Int, lineSource:String, lineOffset:Int) {
+// if (line < 0) System.err.println("\n[ERROR] " + message);
+// else System.err.println("\n[ERROR] " + line + ':' + lineOffset + ':' + message);
+// }
+// def runtimeError(message:String, sourceName:String, line:Int, lineSource:String, lineOffset:Int): EvaluatorException = {
+// error(message, sourceName, line, lineSource, lineOffset);
+// return new EvaluatorException(message);
+// }
+// }
+
+// val munge = true;
+// val verbose = false;
+// val optimize = true;
+// val compressor = new com.yahoo.platform.yui.compressor.JavaScriptCompressor(new StringReader(code), MyErrorReporter);
+// return writeToString(compressor.compress(_, if (wrap) 100 else -1, munge, verbose, true, !optimize));
+// }
+
+// def compressCSS(code: String, wrap: Boolean): String = {
+// val compressor = new com.yahoo.platform.yui.compressor.CssCompressor(new StringReader(code));
+// return writeToString(compressor.compress(_, if (wrap) 100 else -1));
+// }
+
+// import java.util.regex.{Pattern, Matcher, MatchResult};
+
+// def stringReplace(orig: String, regex: String, groupReferences:Boolean, func:(MatchResult=>String)): String = {
+// val buf = new StringBuffer;
+// val m = Pattern.compile(regex).matcher(orig);
+// while (m.find) {
+// var str = func(m);
+// if (! groupReferences) {
+// str = str.replace("\\", "\\\\").replace("$", "\\$");
+// }
+// m.appendReplacement(buf, str);
+// }
+// m.appendTail(buf);
+// return buf.toString;
+// }
+
+// def stringToExpression(str: String): String = {
+// val contents = str.replace("\\", "\\\\").replace("'", "\\'").replace("<", "\\x3c").replace("\n", "\\n").
+// replace("\r", "\\n").replace("\t", "\\t");
+// return "'"+contents+"'";
+// }
+
+// val srcDir = "www";
+// val destDir = "build";
+// var code = getFile(srcDir+"/ace2_outer.js");
+
+// val useCompression = true; //if (isEtherPad) false else true;
+
+// code = stringReplace(code, "\\$\\$INCLUDE_([A-Z_]+)\\([\"']([^\"']+)[\"']\\)", false, (m:MatchResult) => {
+// val includeType = m.group(1);
+// val path = m.group(2);
+// includeType match {
+// case "JS" => {
+// var subcode = getFile(srcDir+"/"+path);
+// subcode = subcode.replaceAll("var DEBUG=true;//\\$\\$[^\n\r]*", "var DEBUG=false;");
+// if (useCompression) subcode = compressJS(subcode, false);
+// "('<script type=\"text/javascript\">//<!--\\n'+" + stringToExpression(subcode) +
+// "+'//-->\\n</script>')";
+// }
+// case "CSS" => {
+// var subcode = getFile(srcDir+"/"+path);
+// if (useCompression) subcode = compressCSS(subcode, false);
+// "('<style type=\"text/css\">'+" + stringToExpression(subcode) + "+'</style>')";
+// }
+// case "JS_Q" => {
+// var subcode = getFile(srcDir+"/"+path);
+// subcode = subcode.replaceAll("var DEBUG=true;//\\$\\$[^\n\r]*", "var DEBUG=false;");
+// if (useCompression) subcode = compressJS(subcode, false);
+// "('(\\'<script type=\"text/javascript\">//<!--\\\\n\\'+'+" +
+// stringToExpression(stringToExpression(subcode)) +
+// "+'+\\'//-->\\\\n\\\\x3c/script>\\')')";
+// }
+// case "CSS_Q" => {
+// var subcode = getFile(srcDir+"/"+path);
+// if (useCompression) subcode = compressCSS(subcode, false);
+// "('(\\'<style type=\"text/css\">\\'+'+" + stringToExpression(stringToExpression(subcode)) +
+// "+'+\\'\\\\x3c/style>\\')')";
+// }
+// case ("JS_DEV" | "CSS_DEV") => "''";
+// case ("JS_Q_DEV" | "CSS_Q_DEV") => "'\\'\\''";
+// case _ => "$$INCLUDE_"+includeType+"(\"../www/"+path+"\")";
+// }
+// });
+
+// if (useCompression) code = compressJS(code, true);
+
+// putFile(code, destDir+"/ace2bare.js");
+
+// var wrapper = getFile(srcDir+"/ace2_wrapper.js");
+// if (useCompression) wrapper = compressJS(wrapper, true);
+// putFile(wrapper+"\n"+code, destDir+"/ace2.js");
+
+// var index = getFile(srcDir+"/index.html");
+// index = index.replaceAll("<!--\\s*DEBUG\\s*-->\\s*([\\s\\S]+?)\\s*<!--\\s*/DEBUG\\s*-->", "");
+// index = index.replaceAll("<!--\\s*PROD:\\s*([\\s\\S]+?)\\s*-->", "$1");
+// putFile(index, destDir+"/index.html");
+
+// putFile(getFile(srcDir+"/testcode.js"), destDir+"/testcode.js");
+
+// def copyFile(fromFile: String, toFile: String) {
+// if (0 != Runtime.getRuntime.exec("cp "+fromFile+" "+toFile).waitFor) {
+// printf("copy failed (%s -> %s).\n", fromFile, toFile);
+// }
+// }
+
+// if (isEtherPad) {
+// copyFile("build/ace2.js", "../../../etherpad/src/static/js/ace.js");
+// val easysync = getFile(srcDir+"/easy_sync.js");
+// putFile(easysync, "../../../etherpad/src/etherpad/collab/easysync.js");
+// }
+// else if (! isNoHelma) {
+// copyFile("build/ace2.js", "../helma_apps/appjet/protectedStatic/js/ace.js");
+// }
+// }
+
+// def remakeLoop {
+
+// def getStamp: Long = {
+// return new java.io.File("www").listFiles.
+// filter(! _.getName.endsWith("~")).
+// filter(! _.getName.endsWith("#")).
+// filter(! _.getName.startsWith(".")).map(_.lastModified).
+// reduceLeft(Math.max(_:Long,_:Long));
+// }
+
+// var madeStamp:Long = 0;
+// var errorStamp:Long = 0;
+// while (true) {
+// Thread.sleep(500);
+// val s = getStamp;
+// if (s > madeStamp && s != errorStamp) {
+// Thread.sleep(1000);
+// if (getStamp == s) {
+// madeStamp = s;
+// print("Remaking... ");
+// try {
+// doMake;
+// println("OK");
+// }
+// catch { case e => {
+// println("ERROR");
+// errorStamp = s;
+// } }
+// }
+// }
+// }
+
+// }
+
+// if (args.length >= 1 && args(0) == "auto") {
+// remakeLoop;
+// }
+// else {
+// doMake;
+// }