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}