com.google.caja.ancillary.linter.LiveSet.java Source code

Java tutorial

Introduction

Here is the source code for com.google.caja.ancillary.linter.LiveSet.java

Source

// Copyright (C) 2008 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.caja.ancillary.linter;

import com.google.caja.parser.AncestorChain;
import com.google.caja.parser.ParseTreeNode;
import com.google.caja.parser.js.Declaration;
import com.google.caja.parser.js.Reference;
import com.google.caja.util.Pair;
import com.google.common.collect.Sets;

import java.util.Collections;
import java.util.Iterator;
import java.util.Set;

/**
 * The set of symbols that are definitely live at a point in a program.
 * Symbols are represented as name, scope pairs.
 * <p>
 * When a {@code LiveSet} is associate with an AST, it means that that is the
 * set of symbols definitely live when control enters that node.
 * <p>
 * NOTE:
 * There is a group of types <tt>(LiveSet, ExitModes, ExitMode)</tt>
 * that take care that methods which derive new values of the same type
 * return this if no changes would be made.  This is an optimization to avoid
 * excessive object creation.  Since they all do this,
 * {@code VariableLiveness.liveness} does not need to create any new objects
 * in the common case where a node introduces no live variables, and no new
 * exit modes.
 *
 * @author mikesamuel@gmail.com
 */
final class LiveSet {
    static final LiveSet EMPTY = new LiveSet(Collections.<Pair<String, LexicalScope>>emptySet());

    // LexicalScopes compare for equality by identity.
    final Set<Pair<String, LexicalScope>> symbols;

    /**
     * Creates a new scope for a DOM root or function constructor.
     *
     * @param scopeRoot normally, a node such that there exists a
     *     {@link LexicalScope scope} LS where {@code LS.root.node == scopeRoot}.
     */
    LiveSet(ParseTreeNode scopeRoot) {
        Set<Pair<String, LexicalScope>> symbols = Sets.newLinkedHashSet();
        LexicalScope scope = ScopeAnalyzer.containingScopeForNode(scopeRoot);
        // Find the set of symbols defined by the overrideable method
        // LexicalScope.initScope that were defined because of this method, not
        // as the result of a declaration which we may encounter later.
        for (String symbolName : scope.symbols.symbolNames()) {
            SymbolTable.Symbol s = scope.symbols.getSymbol(symbolName);
            for (AncestorChain<?> decl : s.getDeclarations()) {
                if (decl.node == scopeRoot) {
                    symbols.add(Pair.pair(symbolName, scope));
                    break;
                }
            }
        }
        this.symbols = Collections.unmodifiableSet(symbols);
    }

    private LiveSet(Set<Pair<String, LexicalScope>> symbols) {
        this.symbols = Collections.unmodifiableSet(symbols);
    }

    /**
     * Yields a {@code LiveSet} with all symbols that occur in this or other.
     * This is typically used when statements are executed in series since
     * the next statement will receive any variables that became live as a result
     * of any previous statements.
     */
    LiveSet union(LiveSet other) {
        Set<Pair<String, LexicalScope>> usymbols = Sets.newLinkedHashSet(symbols);
        usymbols.addAll(other.symbols);
        return new LiveSet(usymbols);
    }

    /**
     * Yields a {@code LiveSet} with any symbols that occur in both this and
     * other.
     * This is typically used when execution branches, because the {@code LiveSet}
     * at the point branched paths converge is the set of variables that were
     * made live in all branches.
     */
    LiveSet intersection(LiveSet other) {
        Set<Pair<String, LexicalScope>> isymbols = Sets.newLinkedHashSet(symbols);
        isymbols.retainAll(other.symbols);
        return isymbols.isEmpty() ? EMPTY : new LiveSet(isymbols);
    }

    /** The set including all in this and any introduced by d. */
    LiveSet with(Declaration d) {
        return with(d.getIdentifierName(), ScopeAnalyzer.definingScopeForNode(d));
    }

    /**
     * The set including all in this and any referenced by r.
     * @param r typically the left-hand-side of an assignment.
     */
    LiveSet with(Reference r) {
        String name = r.getIdentifierName();
        LexicalScope scope = ScopeAnalyzer.containingScopeForNode(r);
        while (scope != null) {
            if (scope.symbols.getSymbol(name) != null) {
                return with(name, scope);
            }
            scope = scope.parent;
        }
        return this;
    }

    private LiveSet with(String name, LexicalScope scope) {
        Pair<String, LexicalScope> p = Pair.pair(name, scope);
        if (this.symbols.contains(p)) {
            return this;
        }
        Set<Pair<String, LexicalScope>> wsymbols = Sets.newLinkedHashSet(symbols);
        wsymbols.add(p);
        return new LiveSet(wsymbols);
    }

    /**
     * Filters out variables from an inner-scope that are no longer live
     * as the result of the scope having been exited.
     */
    LiveSet filter(LexicalScope containingScope) {
        Iterator<Pair<String, LexicalScope>> it = symbols.iterator();
        while (it.hasNext()) {
            Pair<String, LexicalScope> s = it.next();
            if (!isAncestorOf(s.b, containingScope)) {
                Set<Pair<String, LexicalScope>> filtered = Sets.newLinkedHashSet(symbols);
                filtered.remove(s);
                while (it.hasNext()) {
                    s = it.next();
                    if (!isAncestorOf(s.b, containingScope)) {
                        filtered.remove(s);
                    }
                }
                return filtered.isEmpty() ? EMPTY : new LiveSet(filtered);
            }
        }
        return this;
    }

    private static final boolean isAncestorOf(LexicalScope a, LexicalScope b) {
        return a == b || a.root.depth < b.root.depth;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append('(');
        String sep = "";
        for (Pair<String, LexicalScope> s : symbols) {
            int depth = s.b != null ? s.b.root.depth : -1;
            if (!(ScopeAnalyzer.ECMASCRIPT_BUILTINS.contains(s.a) && depth == 0)) {
                sb.append(sep).append(s.a).append('@').append(depth);
                sep = " ";
            }
        }
        return sb.append(')').toString();
    }

    @Override
    public boolean equals(Object o) {
        if (!(o instanceof LiveSet)) {
            return false;
        }
        return this.symbols.equals(((LiveSet) o).symbols);
    }

    @Override
    public int hashCode() {
        return symbols.hashCode();
    }
}