com.google.caja.ancillary.opt.ConstantPooler.java Source code

Java tutorial

Introduction

Here is the source code for com.google.caja.ancillary.opt.ConstantPooler.java

Source

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

import com.google.caja.lexer.FilePosition;
import com.google.caja.parser.AncestorChain;
import com.google.caja.parser.MutableParseTreeNode;
import com.google.caja.parser.ParseTreeNode;
import com.google.caja.parser.Visitor;
import com.google.caja.parser.js.Block;
import com.google.caja.parser.js.Declaration;
import com.google.caja.parser.js.FunctionConstructor;
import com.google.caja.parser.js.Identifier;
import com.google.caja.parser.js.Literal;
import com.google.caja.parser.js.MultiDeclaration;
import com.google.caja.parser.js.ObjProperty;
import com.google.caja.parser.js.ObjectConstructor;
import com.google.caja.parser.js.Reference;
import com.google.caja.parser.js.RegexpLiteral;
import com.google.caja.parser.js.Statement;
import com.google.caja.parser.js.StringLiteral;
import com.google.caja.reporting.RenderContext;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;

import java.util.Collections;
import java.util.List;
import java.util.Map;

/**
 * Collects frequently used literals, e.g. long strings and the like, into
 * a constant pool at the top of each top level function.
 *
 * @author mikesamuel@gmail.com
 */
public class ConstantPooler {
    public static Block optimize(Block program) {
        program = (Block) program.clone();
        optimizeTopLevelFunctions(program);
        return program;
    }

    private static void optimizeTopLevelFunctions(ParseTreeNode node) {
        if (node instanceof FunctionConstructor) {
            optimizeWithin((FunctionConstructor) node);
        } else {
            for (ParseTreeNode child : node.children()) {
                optimizeTopLevelFunctions(child);
            }
        }
    }

    private static void optimizeWithin(FunctionConstructor fc) {
        final Map<LitVal, LitVal> uses = Maps.newLinkedHashMap();
        Block body = fc.getBody();
        body.acceptPreOrder(new Visitor() {
            public boolean visit(AncestorChain<?> chain) {
                if (chain.node instanceof Literal && !(chain.node instanceof RegexpLiteral)) {
                    AncestorChain<Literal> litAc = chain.cast(Literal.class);
                    LitVal key = new LitVal(litAc);
                    LitVal stored = uses.get(key);
                    if (stored == null) {
                        uses.put(key, stored = key);
                    }
                    stored.uses.add(litAc);
                } else if (chain.node instanceof ObjectConstructor) {
                    List<? extends ObjProperty> children = chain.cast(ObjectConstructor.class).node.children();
                    for (ObjProperty prop : children) {
                        visit(chain.child(prop).child(prop.children().get(1)));
                    }
                    return false;
                }
                return true;
            }
        }, null);
        List<Declaration> decls = Lists.newArrayList();
        FilePosition pos = FilePosition.startOf(body.getFilePosition());
        for (LitVal v : uses.values()) {
            // Size now = canonLen * nUses.
            // Size after = "var aa=".length + canonLen + ";".length + "aa" * nUses
            // Switch if now > after;
            //           canonLen * nUses > 8 + canonLen + 2 * nUses
            int requiredSavings = 30; // TUNING PARAMETER
            int canonLen = v.canonForm().length();
            int nUses = v.uses.size();
            if (canonLen * nUses > 8 + canonLen + 2 * nUses + requiredSavings) {
                // TODO(mikesamuel): choose a guaranteed non-interfering name.
                String name = "$_$__litpool__" + decls.size() + "$_$";
                decls.add(new Declaration(pos, new Identifier(pos, name), v.uses.get(0).node));
                for (AncestorChain<Literal> use : v.uses) {
                    Reference ref = new Reference(new Identifier(use.node.getFilePosition(), name));
                    use.parent.cast(MutableParseTreeNode.class).node.replaceChild(ref, use.node);
                }
            }
        }
        if (!decls.isEmpty()) {
            Statement first = body.children().get(0);
            MultiDeclaration md;
            if (first instanceof MultiDeclaration) {
                md = (MultiDeclaration) first;
            } else if (first instanceof Declaration) {
                md = new MultiDeclaration(FilePosition.span(pos, first.getFilePosition()),
                        Collections.singletonList((Declaration) first));
                body.replaceChild(md, first);
            } else if (decls.size() == 1) {
                body.insertBefore(decls.get(0), first);
                return;
            } else {
                md = new MultiDeclaration(pos, Collections.<Declaration>emptyList());
                body.insertBefore(md, first);
            }
            MutableParseTreeNode.Mutation mut = md.createMutation();
            Declaration firstDecl = md.children().get(0);
            for (Declaration decl : decls) {
                mut = mut.insertBefore(decl, firstDecl);
            }
            mut.execute();
        }
    }

    private static class LitVal {
        final Object canonValue;
        final List<AncestorChain<Literal>> uses = Lists.newArrayList();

        LitVal(AncestorChain<Literal> useAc) {
            Literal use = useAc.node;
            canonValue = use instanceof StringLiteral ? ((StringLiteral) use).getUnquotedValue() : use.getValue();
        }

        String canonForm() {
            StringBuilder sb = new StringBuilder();
            Literal use = uses.get(0).node;
            RenderContext rc = new RenderContext(use.makeRenderer(sb, null));
            use.render(rc);
            rc.getOut().noMoreTokens();
            return sb.toString();
        }

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

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