com.ibm.jaggr.core.impl.deps.DepUtils.java Source code

Java tutorial

Introduction

Here is the source code for com.ibm.jaggr.core.impl.deps.DepUtils.java

Source

/*
 * (C) Copyright 2012, IBM Corporation
 *
 * 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 com.ibm.jaggr.core.impl.deps;

import com.ibm.jaggr.core.util.BooleanTerm;
import com.ibm.jaggr.core.util.Features;
import com.ibm.jaggr.core.util.HasNode;
import com.ibm.jaggr.core.util.NodeUtil;
import com.ibm.jaggr.core.util.PathUtil;

import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;

import org.apache.commons.lang.StringUtils;

import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.regex.Pattern;

/**
 * Collection of utility classes used for processing Dependency trees
 */
public class DepUtils {

    static final Pattern hasPattern = Pattern.compile("(^|\\/)has!"); //$NON-NLS-1$

    /**
     * Removes URIs containing duplicate and non-orthogonal paths so that the
     * collection contains only unique and non-overlapping paths.
     *
     * @param uris collection of URIs
     *
     * @return a new collection with redundant paths removed
     */
    static public Collection<URI> removeRedundantPaths(Collection<URI> uris) {
        List<URI> result = new ArrayList<URI>();
        for (URI uri : uris) {
            String path = uri.getPath();
            if (!path.endsWith("/")) { //$NON-NLS-1$
                path += "/"; //$NON-NLS-1$
            }
            boolean addIt = true;
            for (int i = 0; i < result.size(); i++) {
                URI testUri = result.get(i);
                if (!StringUtils.equals(testUri.getScheme(), uri.getScheme())
                        || !StringUtils.equals(testUri.getHost(), uri.getHost())
                        || testUri.getPort() != uri.getPort()) {
                    continue;
                }
                String test = testUri.getPath();
                if (!test.endsWith("/")) { //$NON-NLS-1$
                    test += "/"; //$NON-NLS-1$
                }
                if (path.equals(test) || path.startsWith(test)) {
                    addIt = false;
                    break;
                } else if (test.startsWith(path)) {
                    result.remove(i);
                }
            }
            if (addIt)
                result.add(uri);
        }
        // Now copy the trimmed list back to the original
        return result;
    }

    /**
     * Maps a resource URI to a {@link DepTreeNode}. This routine finds the key
     * in <code>dependencies</code> that is an ancestor of
     * <code>requiestURI</code> and then looks for the {@link DepTreeNode} who's
     * name corresponds to descendant part of the URI path.
     *
     * @param requestUri
     *            The URI for the resource location being sought
     * @param dependencies
     *            Map of file path names to root {@link DepTreeNode}s
     * @return The node corresponding to <code>requestURI</code>
     */
    static public DepTreeNode getNodeForResource(URI requestUri, Map<URI, DepTreeNode> dependencies) {
        DepTreeNode result = null;
        /*
         * Iterate through the map entries and find the entry who's key is the same,
         * or an ancestor of, the specified path
         */
        for (Entry<URI, DepTreeNode> dependency : dependencies.entrySet()) {
            URI uri = dependency.getKey();
            if (requestUri.getScheme() == null && uri.getScheme() != null
                    || requestUri.getScheme() != null && !requestUri.getScheme().equals(uri.getScheme()))
                continue;

            if (requestUri.getHost() == null && uri.getHost() != null
                    || requestUri.getHost() != null && !requestUri.getHost().equals(uri.getHost()))
                continue;

            if (requestUri.getPath().equals(uri.getPath()) || requestUri.getPath().startsWith(uri.getPath())
                    && (uri.getPath().endsWith("/") || requestUri.getPath().charAt(uri.getPath().length()) == '/')) //$NON-NLS-1$
            {
                /*
                 * Found the entry.  Now find the node corresponding to the
                 * remainder of the path.
                 */
                if (requestUri.getPath().equals(uri.getPath())) {
                    return dependency.getValue();
                } else {
                    String modulePath = requestUri.getPath().substring(uri.getPath().length());
                    if (modulePath.startsWith("/")) { //$NON-NLS-1$
                        modulePath = modulePath.substring(1);
                    }
                    result = dependency.getValue().getDescendent(modulePath);
                }
                break;
            }
        }
        return result;
    }

    /**
     * Walks the parsed AST {@link Node} looking for the AMD define() function
     * call and extracts the require list of dependencies to a string array and
     * returns the buildReader.
     * <p>
     * Any require list entries that are not a string literal (e.g. an object
     * ref) are omitted from the returned array.
     *
     * @param node
     *            A parsed AST {@link Node} for a javascript file
     * @param dependentFeatures
     *            Output - any features specified using the has! loader plugin
     *            will be added to this set.
     * @return The String array of module dependencies.
     */
    static public Collection<String> parseDependencies(Node node, Set<String> dependentFeatures) {
        Collection<String> result = null;
        for (Node cursor = node.getFirstChild(); cursor != null; cursor = cursor.getNext()) {
            Node defineDeps = null, requireDeps = null;
            String condition;
            if ((condition = NodeUtil.conditionFromHasNode(cursor)) != null) {
                dependentFeatures.add(condition);
            } else if ((defineDeps = NodeUtil.moduleDepsFromDefine(cursor)) != null
                    || (requireDeps = NodeUtil.moduleDepsFromRequire(cursor)) != null) {
                Node dependencies = defineDeps != null ? defineDeps : requireDeps;
                // Found the array.  Now copy the string values to the buildReader.
                result = new LinkedHashSet<String>();
                Node strNode = dependencies.getFirstChild();
                while (strNode != null) {
                    if (strNode.getType() == Token.STRING) {
                        String mid = strNode.getString();
                        // Don't add module ids with invalid characters
                        if (!PathUtil.invalidChars.matcher(mid).find()) {
                            if (defineDeps != null) {
                                result.add(mid);
                            }
                            // if the id specifies a has loader plugin, then add the
                            // has dependencies to the dependencies list
                            if (hasPattern.matcher(mid).find()) {
                                int idx = mid.indexOf("!"); //$NON-NLS-1$
                                HasNode hasNode = new HasNode(mid.substring(idx + 1));
                                hasNode.evaluateAll(mid.substring(0, idx), Features.emptyFeatures,
                                        dependentFeatures, BooleanTerm.TRUE, null);
                            }
                        }
                    }
                    strNode = strNode.getNext();
                }
            }
            // Recursively call this method to process the child nodes
            if (cursor.hasChildren()) {
                Collection<String> childDeps = parseDependencies(cursor, dependentFeatures);
                if (childDeps != null) {
                    if (result != null) {
                        result.addAll(childDeps);
                    } else {
                        result = childDeps;
                    }
                }
            }
        }
        return result;
    }
}