All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.squarespace.template.ReferenceScanner Maven / Gradle / Ivy

There is a newer version: 2.10.0
Show newest version
/**
 * Copyright (c) 2014 SQUARESPACE, 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.squarespace.template;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.squarespace.template.Instructions.AlternatesWithInst;
import com.squarespace.template.Instructions.IfInst;
import com.squarespace.template.Instructions.IfPredicateInst;
import com.squarespace.template.Instructions.PredicateInst;
import com.squarespace.template.Instructions.RepeatedInst;
import com.squarespace.template.Instructions.RootInst;
import com.squarespace.template.Instructions.SectionInst;
import com.squarespace.template.Instructions.TextInst;
import com.squarespace.template.Instructions.VariableInst;


/**
 * Scans an instruction tree recursively, collecting counts of the number of
 * instructions, predicates and formatters, as well as the tree of variable
 * references.
 */
public class ReferenceScanner {

  private final References refs = new References();

  public References references() {
    return refs;
  }

  /**
   * Extracts reference metrics from a single instruction.
   */
  public void extract(Instruction inst) {
    String name = null;
    if (inst == null) {
      return;
    }
    if (!(inst instanceof RootInst)) {
      refs.increment(inst);
    }
    switch (inst.getType()) {

      case TEXT:
        refs.textBytes += ((TextInst)inst).getView().length();
        break;

      case ALTERNATES_WITH:
        AlternatesWithInst alternatesWith = (AlternatesWithInst)inst;
        extractBlock(alternatesWith.getConsequent());
        extract(alternatesWith.getAlternative());
        break;

      case IF:
      {
        BlockInstruction blockInst = (BlockInstruction)inst;
        refs.addIfInstruction(blockInst);
        if (inst instanceof IfInst) {
          IfInst ifInst = (IfInst)inst;
          for (Object[] var : ifInst.getVariables()) {
            name = ReprEmitter.get(var);
            refs.addVariable(name);
          }
        } else {
          IfPredicateInst ifInst = (IfPredicateInst)inst;
          refs.increment(ifInst.getPredicate());
        }
        extractBlock(blockInst.getConsequent());
        extract(blockInst.getAlternative());
        break;
      }

      case OR_PREDICATE:
      case PREDICATE:
        PredicateInst predicateInst = (PredicateInst)inst;
        Predicate predicate = predicateInst.getPredicate();
        if (predicate != null) {
          refs.increment(predicate);
          predicate.addReferences(predicateInst.getArguments(), refs);
        }

        extractBlock(predicateInst.getConsequent());
        extract(predicateInst.getAlternative());
        break;

      case REPEATED:
        RepeatedInst repeated = (RepeatedInst)inst;
        name = ReprEmitter.get(repeated.getVariable());
        refs.pushSection(name);
        extractBlock(repeated.getConsequent());
        extract(repeated.getAlternative());
        extract(repeated.getAlternatesWith());
        refs.popSection();
        break;

      case ROOT:
        extractBlock(((RootInst)inst).getConsequent());
        break;

      case SECTION:
        SectionInst section = (SectionInst)inst;
        name = ReprEmitter.get(section.getVariable());
        refs.pushSection(name);
        extractBlock(section.getConsequent());
        extract(section.getAlternative());
        refs.popSection();
        break;

      case VARIABLE:
        VariableInst varInst = (VariableInst)inst;
        name = ReprEmitter.get(varInst.getVariables());
        refs.addVariable(name);
        for (FormatterCall call : varInst.getFormatters()) {
          refs.increment(call.getFormatter());
        }
        break;

      default:
        break;

    }
  }

  /**
   * Iterates over all instructions in the block, extracting metrics from each.
   */
  private void extractBlock(Block block) {
    if (block != null) {
      List instructions = block.getInstructions();
      if (instructions != null) {
        for (Instruction inst : instructions) {
          extract(inst);
        }
      }
    }
  }

  /**
   * Holds a set of metrics on all instructions referenced in a template.
   */
  public static class References {

    private Deque variables = new ArrayDeque<>();

    private ObjectNode currentNode = JsonUtils.createObjectNode();

    private Map instructions = new HashMap<>();

    private Map formatters = new HashMap<>();

    private Map predicates = new HashMap<>();

    private List ifVariants = new ArrayList<>();

    private int textBytes;

    /**
     * Renders a JSON report containing the collected metrics.
     */
    public ObjectNode report() {
      ObjectNode res = JsonUtils.createObjectNode();
      convert(res.putObject("instructions"), instructions);
      convert(res.putObject("formatters"), formatters);
      convert(res.putObject("predicates"), predicates);
      res.set("variables", currentNode);
      res.put("textBytes", textBytes);
      // NOTE: this is temporary - phensley
      convert(res.putArray("ifInstructions"), ifVariants);
      return res;
    }

    public void increment(Instruction inst) {
      if (inst != null) {
        increment(instructions, inst.getType().name());
      }
    }

    public void increment(Predicate predicate) {
      if (predicate != null) {
        increment(predicates, predicate.identifier());
      }
    }

    public void increment(Formatter formatter) {
      if (formatter != null) {
        increment(formatters, formatter.identifier());
      }
    }

    public void increment(Map counter, String key) {
      Integer value = counter.get(key);
      if (value == null) {
        value = 0;
      }
      counter.put(key, value + 1);
    }

    /**
     * Adds a variable to the current scope.
     */
    public void addVariable(String name) {
      JsonNode node = currentNode.path(name);
      if (node.isMissingNode()) {
        currentNode.putNull(name);
      }
    }

    // NOTE: This is temporary, to assess the .if instruction variants out there and
    // assess the impact of migration. Will remove this once data is gathered. - phensley
    private void addIfInstruction(BlockInstruction inst) {
      StringBuilder buf = new StringBuilder();
      if (inst instanceof IfInst) {
        ReprEmitter.emit((IfInst)inst, buf, false);
      } else {
        ReprEmitter.emit((IfPredicateInst)inst, buf, false);
      }
      ifVariants.add(buf.toString());
    }

    /**
     * Pushes one variable scope level.
     */
    private void pushSection(String name) {
      JsonNode node = currentNode.path(name);
      ObjectNode obj = null;
      if (node.isObject()) {
        obj = (ObjectNode)node;
      } else {
        obj = currentNode.putObject(name);
      }
      variables.push(currentNode);
      currentNode = obj;
    }

    /**
     * Pops one variable scope level.
     */
    private void popSection() {
      currentNode = variables.pop();
    }

    private void convert(ObjectNode res, Map map) {
      for (Entry entry : map.entrySet()) {
        res.put(entry.getKey(), entry.getValue());
      }
    }

    private void convert(ArrayNode res, List list) {
      for (String elem : list) {
        res.add(elem);
      }
    }

  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy