gwt.ns.webworker.linker.WorkerCompilationLinker.java Source code

Java tutorial

Introduction

Here is the source code for gwt.ns.webworker.linker.WorkerCompilationLinker.java

Source

/*
 * Copyright 2010 Brendan Kenny
 * 
 * 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 gwt.ns.webworker.linker;

import gwt.ns.webworker.rebind.WorkerFactoryGenerator;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.Map;
import java.util.SortedMap;
import java.util.SortedSet;

import com.google.gwt.core.ext.LinkerContext;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.linker.AbstractLinker;
import com.google.gwt.core.ext.linker.ArtifactSet;
import com.google.gwt.core.ext.linker.CompilationResult;
import com.google.gwt.core.ext.linker.LinkerOrder;
import com.google.gwt.core.ext.linker.SelectionProperty;
import com.google.gwt.core.ext.linker.SymbolData;
import com.google.gwt.core.ext.linker.LinkerOrder.Order;
import com.google.gwt.core.ext.linker.impl.StandardCompilationResult;
import com.google.gwt.dev.util.Util;

/**
 * Linker partner to {@link WorkerFactoryGenerator}. All Worker modules
 * requested via the artifact set are compiled and the actual paths to the
 * scripts, strongly named based on the contents of the scripts themselves, are
 * inserted into native-worker supporting permutations.
 * 
 * <p>Note that this Linker can be invoked within a recursive compilation
 * by a Worker that needs to spawn a sub-Worker. In this case, the relative URL
 * contains no strong name (since it will share the Worker's directory) and the
 * request can be passed up to the original Linker, thus preventing repeated
 * compilations and unbounded recursion.</p>
 */
@LinkerOrder(Order.PRE)
public class WorkerCompilationLinker extends AbstractLinker {

    @Override
    public String getDescription() {
        return "Worker Compiler";
    }

    @Override
    public ArtifactSet link(TreeLogger logger, LinkerContext context, ArtifactSet artifacts)
            throws UnableToCompleteException {

        ArtifactSet toReturn = new ArtifactSet(artifacts);

        // get set of requests for insideWorker compilations from artifacts
        SortedSet<WorkerRequestArtifact> workerRequests = toReturn.find(WorkerRequestArtifact.class);
        if (workerRequests.size() == 0) {
            logger.log(TreeLogger.SPAM, "No Worker compilations requested. No action taken.");
            return toReturn; // early exit, sorry
        }

        // compile all requested workers
        // if this is a recursive call, requests were passed up to parent so
        // returned value is null
        SortedMap<WorkerRequestArtifact, String> workerScripts = WorkerCompiler.run(logger, workerRequests);

        // if they exist, deal with compiled scripts:
        if (workerScripts != null && workerScripts.size() != 0) {
            // directory strong name from all scripts
            byte[][] bytejs = Util.getBytes(workerScripts.values().toArray(new String[0]));
            String scriptDirStrongName = Util.computeStrongName(bytejs);

            // emit worker scripts
            for (Map.Entry<WorkerRequestArtifact, String> script : workerScripts.entrySet()) {
                WorkerRequestArtifact req = script.getKey();
                toReturn.add(emitString(logger, script.getValue(),
                        req.getRelativePath(scriptDirStrongName, File.separator)));
            }

            // get the set of current compilation results
            SortedSet<CompilationResult> compResults = toReturn.find(CompilationResult.class);

            /*
             * Reading the js from and writing it to a new CompilationResult is
             * expensive (possibly disk cached), so read once and write only if
             * altered.
             */
            for (CompilationResult compRes : compResults) {
                // assume all need modification
                // TODO: rule out emulated permutations via properties
                toReturn.remove(compRes);

                CompilationResult altered = replacePlaceholder(logger, compRes,
                        WorkerRequestArtifact.getPlaceholderStrongName(), scriptDirStrongName);
                toReturn.add(altered);
            }
        }

        return toReturn;
    }

    /**
     * Searches for all instances of a placeholder String in a
     * CompilationResult. If found, they are replaced as specified and a
     * new CompilationResult, with a newly generated strong name, is returned.
     * If no occurrences were found, the original CompilationResult is
     * returned.
     * 
     * @param logger
     * @param result CompilationResult to process
     * @param placeholder String to be replaced
     * @param replacement String to insert in place of placeholder
     * 
     * @return A CompilationResult suitable for emitting
     */
    public CompilationResult replacePlaceholder(TreeLogger logger, CompilationResult result, String placeholder,
            String replacement) {

        boolean needsMod = false;

        String[] js = result.getJavaScript();
        StringBuffer[] jsbuf = new StringBuffer[js.length];
        for (int i = 0; i < jsbuf.length; i++) {
            jsbuf[i] = new StringBuffer(js[i]);
        }

        // search and replace in all fragments
        for (StringBuffer fragment : jsbuf) {
            needsMod |= replaceAll(fragment, placeholder, replacement);
        }

        // by default, returning unaltered result
        CompilationResult toReturn = result;

        // code has been altered, need to create new CompilationResult
        if (needsMod) {
            logger.log(TreeLogger.SPAM,
                    "Compilation permutation " + result.getPermutationId() + " being modified.");

            // new js for compilation result
            byte[][] newcode = new byte[jsbuf.length][];
            for (int i = 0; i < jsbuf.length; i++) {
                newcode[i] = Util.getBytes(jsbuf[i].toString());
            }

            // new strong name
            String strongName = Util.computeStrongName(newcode);

            // same symbolMap copied over since none altered
            // symbolMap a little more complicated
            // can only get deserialized version, need to reserialize
            // code from com.google.gwt.dev.jjs.JavaToJavaScriptCompiler
            // TODO: turns out this can get pretty bad. Workaround?
            SymbolData[] symbolMap = result.getSymbolMap();
            byte[] serializedSymbolMap;
            try {
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                Util.writeObjectToStream(baos, (Object) symbolMap);
                serializedSymbolMap = baos.toByteArray();
            } catch (IOException e) {
                // should still never happen
                logger.log(TreeLogger.ERROR, "IOException while reserializing " + "SymbolMap.");
                throw new RuntimeException("Should never happen with in-memory stream", e);
            }

            StandardCompilationResult altered = new StandardCompilationResult(strongName, newcode,
                    serializedSymbolMap, result.getStatementRanges(), result.getPermutationId());

            // need to copy permutation properties to new result
            for (Map<SelectionProperty, String> propertyMap : result.getPropertyMap()) {
                altered.addSelectionPermutation(propertyMap);
            }

            toReturn = altered;

            logger.log(TreeLogger.SPAM, "Compilation permuation " + toReturn.getPermutationId()
                    + " altered to include path to worker script(s).");
        }

        return toReturn;
    }

    /**
     * Searches StringBuffer for all occurrences of search and replaces them
     * with replace.
     * 
     * @param buf StringBuffer to search
     * @param search Substring to search for
     * @param replace Replacement String if search is found
     * 
     * @return True if substring search was found.
     */
    protected static boolean replaceAll(StringBuffer buf, String search, String replace) {
        int len = search.length();
        boolean searchFound = false;
        for (int pos = buf.indexOf(search); pos >= 0; pos = buf.indexOf(search, pos + 1)) {
            buf.replace(pos, pos + len, replace);
            searchFound = true;
        }

        return searchFound;
    }
}