001/* 002 * Copyright 2011-2012 Stephen Connolly. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.jszip.rhino; 018 019import org.apache.commons.lang3.StringUtils; 020import org.apache.maven.plugin.logging.Log; 021import org.codehaus.plexus.util.FileUtils; 022import org.jszip.pseudo.io.PseudoFile; 023import org.jszip.pseudo.io.PseudoFileSystem; 024import org.mozilla.javascript.Context; 025import org.mozilla.javascript.ContextAction; 026import org.mozilla.javascript.JavaScriptException; 027import org.mozilla.javascript.Script; 028import org.mozilla.javascript.Scriptable; 029import org.mozilla.javascript.ScriptableObject; 030import org.mozilla.javascript.UniqueTag; 031import org.mozilla.javascript.tools.shell.Global; 032 033import java.io.File; 034import java.io.IOException; 035import java.util.ArrayList; 036import java.util.Arrays; 037import java.util.Iterator; 038import java.util.List; 039import java.util.Stack; 040 041/** 042 * An action for running r.js against a virtual filesystem. 043 */ 044public class OptimizeContextAction extends ScriptableObject implements ContextAction { 045 private final Global global; 046 private final File profileJs; 047 private final String source; 048 private final int lineNo; 049 private final PseudoFileSystem.Layer[] layers; 050 private final Log log; 051 052 public OptimizeContextAction(Log log, Global global, File profileJs, String source, int lineNo, 053 PseudoFileSystem.Layer... layers) { 054 this.log = log; 055 this.global = global; 056 this.profileJs = profileJs; 057 this.source = source; 058 this.lineNo = lineNo; 059 this.layers = layers; 060 } 061 062 public Object run(Context context) { 063 context.setErrorReporter(new MavenLogErrorReporter(log)); 064 PseudoFileSystem fileSystem = new PseudoFileSystem(layers); 065 context.putThreadLocal(Log.class, log); 066 fileSystem.installInContext(); 067 try { 068 069 if (log.isDebugEnabled()) { 070 log.debug("Virtual filesystem exposed to r.js:"); 071 Stack<Iterator<PseudoFile>> stack = new Stack<Iterator<PseudoFile>>(); 072 stack.push(Arrays.asList(fileSystem.root().listFiles()).iterator()); 073 while (!stack.isEmpty()) { 074 Iterator<PseudoFile> iterator = stack.pop(); 075 while (iterator.hasNext()) { 076 PseudoFile f = iterator.next(); 077 if (f.isFile()) { 078 log.debug(" " + f.getAbsolutePath() + " [file]"); 079 } else { 080 log.debug(" " + f.getAbsolutePath() + " [dir]"); 081 stack.push(iterator); 082 iterator = Arrays.asList(f.listFiles()).iterator(); 083 } 084 } 085 } 086 } 087 088 List<String> argsList = new ArrayList<String>(); 089 argsList.add("-o"); 090 argsList.add("/build/" + profileJs.getName()); 091 String appDir = null; 092 String baseUrl = "./"; 093 String dir = null; 094 try { 095 String profile = FileUtils.fileRead(profileJs, "UTF-8"); 096 Scriptable scope = context.newObject(global); 097 scope.setPrototype(global); 098 scope.setParentScope(null); 099 Object parsedProfile = context.evaluateString(scope, profile, profileJs.getName(), 0, null); 100 if (parsedProfile instanceof Scriptable) { 101 final Scriptable scriptable = (Scriptable) parsedProfile; 102 appDir = getStringWithDefault(scriptable, "appDir", null); 103 baseUrl = getStringWithDefault(scriptable, "baseUrl", "./"); 104 dir = getStringWithDefault(scriptable, "dir", null); 105 } 106 } catch (IOException e) { 107 log.debug("Cannot infer profile fixups", e); 108 } catch (JavaScriptException e) { 109 log.warn("JavaScript exception while parsing " + profileJs.getAbsolutePath() + ": " + e.details()); 110 } catch (Throwable e) { 111 log.warn("Cannot infer if profile needs appDir and dir remapping to virtual directory structure", e); 112 } 113 if (appDir == null) { 114 argsList.add("appDir=/virtual/"); 115 argsList.add("baseUrl=" + baseUrl); 116 } else if (!appDir.startsWith("/virtual/") && !appDir.equals("/virtual")) { 117 argsList.add("appDir=/virtual/" + StringUtils.removeEnd(StringUtils.removeStart(appDir, "/"),"/")+"/"); 118 argsList.add("baseUrl=" + baseUrl); 119 } 120 if (dir == null) { 121 argsList.add("dir=/target/"); 122 } else if (!dir.startsWith("/target/") && !dir.equals("/target")) { 123 argsList.add("dir=/target/" + StringUtils.removeEnd(StringUtils.removeStart(dir, "/"),"/")+"/"); 124 } 125 126 global.defineFunctionProperties(new String[]{"print", "quit"}, GlobalFunctions.class, 127 ScriptableObject.DONTENUM); 128 129 Script script = context.compileString(source, "r.js", lineNo, null); 130 script.getClass(); 131 132 Scriptable argsObj = context.newArray(global, argsList.toArray()); 133 global.defineProperty("arguments", argsObj, ScriptableObject.DONTENUM); 134 135 Scriptable scope = GlobalFunctions.createPseudoFileSystemScope(global, context); 136 137 log.info("Applying r.js profile " + profileJs.getPath()); 138 log.debug("Executing r.js with arguments: " + StringUtils.join(argsList, " ")); 139 GlobalFunctions.setExitCode(0); 140 script.exec(context, scope); 141 return GlobalFunctions.getExitCode(); 142 } finally { 143 fileSystem.removeFromContext(); 144 context.putThreadLocal(OptimizeContextAction.class, null); 145 } 146 } 147 148 private String getStringWithDefault(Scriptable scriptable, String name, String defaultValue) { 149 final Object object = scriptable.get(name, scriptable); 150 if (object instanceof String) { 151 return (String) object; 152 } 153 if (object instanceof UniqueTag) { 154 if (object == UniqueTag.NULL_VALUE) { 155 return null; 156 } 157 return defaultValue; 158 } else { 159 return object.toString(); 160 } 161 } 162 163 @Override 164 public String getClassName() { 165 return "global"; 166 } 167 168}