com.google.common.css.compiler.passes.ResolveCustomFunctionNodes.java Source code

Java tutorial

Introduction

Here is the source code for com.google.common.css.compiler.passes.ResolveCustomFunctionNodes.java

Source

/*
 * 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 com.google.common.css.compiler.passes;

import com.google.common.base.Preconditions;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.css.compiler.ast.CssCompilerPass;
import com.google.common.css.compiler.ast.CssCustomFunctionNode;
import com.google.common.css.compiler.ast.CssFunctionNode;
import com.google.common.css.compiler.ast.CssLiteralNode;
import com.google.common.css.compiler.ast.CssValueNode;
import com.google.common.css.compiler.ast.DefaultTreeVisitor;
import com.google.common.css.compiler.ast.ErrorManager;
import com.google.common.css.compiler.ast.GssError;
import com.google.common.css.compiler.ast.GssFunction;
import com.google.common.css.compiler.ast.GssFunctionException;
import com.google.common.css.compiler.ast.MutatingVisitController;
import com.google.common.css.compiler.ast.Proxiable;

import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * This compiler pass replaces {@link CssCustomFunctionNode} instances with the
 * list of nodes returned by the GssFunction.
 *
 */
public class ResolveCustomFunctionNodes extends DefaultTreeVisitor implements CssCompilerPass {

    private final MutatingVisitController visitController;
    protected Map<String, GssFunction> functionMap;
    private final ErrorManager errorManager;
    private final boolean allowUnknownFunctions;
    private final Set<String> allowedNonStandardFunctions;

    /**
     * Constructs the pass.
     *
     * @param visitController The visit controller
     * @param errorManager The error manager
     * @param functionMap The map from function names to resolve to GSS functions
     */
    public ResolveCustomFunctionNodes(MutatingVisitController visitController, ErrorManager errorManager,
            Map<String, GssFunction> functionMap) {
        this(visitController, errorManager, functionMap, false /* allowUnknownFunctions */);
    }

    /**
     * Constructs the pass.
     *
     * @param visitController The visit controller
     * @param errorManager The error manager
     * @param functionMap The map from function names to resolve to GSS functions
     * @param allowUnknownFunctions Whether to allow unknown function calls,
     *     leaving them as is, instead of reporting an error
     */
    public ResolveCustomFunctionNodes(MutatingVisitController visitController, ErrorManager errorManager,
            Map<String, GssFunction> functionMap, boolean allowUnknownFunctions) {
        this(visitController, errorManager, functionMap, allowUnknownFunctions,
                ImmutableSet.<String>of() /* allowedNonStandardFunctions */);
    }

    /**
     * Constructs the pass.
     *
     * @param visitController The visit controller
     * @param errorManager The error manager
     * @param functionMap The map from function names to resolve to GSS functions
     * @param allowUnknownFunctions Whether to allow unknown function calls,
     *     leaving them as is, instead of reporting an error
     * @param allowedNonStandardFunctions functions that should not yield a
     *     warning if they appear in a stylesheet
     */
    public ResolveCustomFunctionNodes(MutatingVisitController visitController, ErrorManager errorManager,
            Map<String, GssFunction> functionMap, boolean allowUnknownFunctions,
            Set<String> allowedNonStandardFunctions) {
        Preconditions.checkNotNull(functionMap);
        this.visitController = visitController;
        this.errorManager = errorManager;
        this.functionMap = functionMap;
        this.allowUnknownFunctions = allowUnknownFunctions;
        this.allowedNonStandardFunctions = ImmutableSet.copyOf(allowedNonStandardFunctions);
    }

    @Override
    public void leaveFunctionNode(CssFunctionNode functionNode) {
        if (!(functionNode instanceof Proxiable)) {
            return;
        }

        CssCustomFunctionNode node = (CssCustomFunctionNode) functionNode;

        List<CssValueNode> functionResult = node.getResult();
        if (functionResult == null) {
            // Look up the function's name in the map.
            String functionName = node.getFunctionName();
            GssFunction function = functionMap.get(functionName);
            if (function == null) {
                if (!allowUnknownFunctions && !allowedNonStandardFunctions.contains(functionName)) {
                    errorManager.report(new GssError(String.format("Unknown function \"%s\"", functionName),
                            node.getSourceCodeLocation()));
                    visitController.removeCurrentNode();
                }
                return;
            }

            // Removes the commas from the arguments and groups arguments separated
            // by commas, e.g.:
            // verticalGradient(#fff, #f2f2f2, url(foo) left top)
            // has 7 initial arguments (2 commas, 5 words).
            // After the cleanup we end up with the 3 arguments separated by commas.
            // TODO(user): Clean this up once we get rid of the comma-as-argument hack.
            List<CssValueNode> arguments = Lists.newArrayList();
            CssValueNode lastArg = null;
            for (CssValueNode value : node.getArguments().childIterable()) {
                if (value.getValue() != null && value.getValue().equals(",")) {
                    lastArg = null;
                } else {
                    if (lastArg == null) {
                        arguments.add(value);
                        lastArg = value;
                    } else {
                        if (value instanceof CssLiteralNode && " ".equals(value.getValue())) {
                            // Here we are building a parse subtree that, when
                            // printed by most output passes, looks like the subtree
                            // we got as input, but actually consists of just a
                            // CssLiteralNode. This requires us to add some
                            // whitespace to the printed representations of the
                            // nodes we are merging, corresponding to the whitespace
                            // eaten by the parser between lexemes and generated by
                            // printing passes when flattening a tree back to a
                            // string. But functions are special: the parse tree
                            // includes CssLiteralNode(" ") to explicitly represent
                            // function argument delimiters. We need to avoid adding
                            // extra space here, just as printing passes do.
                        } else if (lastArg.getValue() == null) {
                            arguments.remove(lastArg);
                            lastArg = new CssLiteralNode(lastArg.toString() + " " + value.toString());
                            arguments.add(lastArg);
                        } else {
                            lastArg.setValue(lastArg.getValue() + " " + value.toString());
                        }
                    }
                }
            }

            Integer expArgNumber = function.getNumExpectedArguments();
            int argNumber = arguments.size();
            if (expArgNumber != null && expArgNumber.intValue() != argNumber) {
                errorManager
                        .report(new GssError("Function expects " + expArgNumber + " arguments but has " + argNumber,
                                node.getSourceCodeLocation()));
                visitController.removeCurrentNode();
                return;
            }

            try {
                functionResult = evaluateFunction(node, function, arguments, errorManager);
            } catch (GssFunctionException e) {
                visitController.removeCurrentNode();
                return;
            } catch (RuntimeException e) {
                errorManager
                        .report(new GssError(Throwables.getStackTraceAsString(e), node.getSourceCodeLocation()));
                visitController.removeCurrentNode();
                return;
            }
        }
        visitController.replaceCurrentBlockChildWith(functionResult, false);
    }

    /**
     * Evaluates the given function node.
     *
     * <p>Subclasses may sublcass this method to change the evaluation
     * process in some circumstances.
     *
     * @param node the function node to evaluate
     * @param function the GSS function matching this node
     * @param arguments the arguments of this node
     * @param errorManager the error manager passed into the GSS function call
     * @return the result of the evaluation as a list of value nodes
     * @throws GssFunctionException if the function call is invalid
     * @throws RuntimeException if the function call fails to complete
     */
    protected List<CssValueNode> evaluateFunction(CssCustomFunctionNode node, GssFunction function,
            List<CssValueNode> arguments, ErrorManager errorManager) throws GssFunctionException {

        List<CssValueNode> functionResult = function.getCallResultNodes(arguments, errorManager);
        node.setResult(functionResult);
        return functionResult;
    }

    @Override
    public void runPass() {
        visitController.startVisit(this);
    }
}