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

com.squarespace.less.model.Block Maven / Gradle / Ivy

There is a newer version: 1.7.2
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.less.model;

import java.util.HashMap;
import java.util.Map;

import com.squarespace.less.core.Buffer;
import com.squarespace.less.core.FlexList;
import com.squarespace.less.core.LessUtils;


/**
 * Represents an array of rules.
 *
 * During evaluation some instructions such as {@link Import} or {@link MixinCall},
 * when produce one or more new rules.  These rules can be spliced into the
 * block, replacing the node which produced them.
 *
 * For this reason, blocks manage their arrays of objects directly.
 *
 * Each block contains a set of flags which are used to avoid unnecessary
 * scans of its rules during evaluation.
 */
public class Block extends BaseNode {

  /**
   * Variable cache needs to be rebuilt.
   */
  private static final byte FLAG_REBUILD_VARS = 0x01;

  /**
   * Block contains import statements which need to be evaluated.
   */
  private static final byte FLAG_HAS_IMPORTS = 0x02;

  /**
   * Block contains mixin calls which need to be evaluated.
   */
  private static final byte FLAG_HAS_MIXIN_CALLS = 0x04;

  /**
   * Initial capacity of the blocks array.
   */
  private static final int INITIAL_CAPACITY = 8;

  /**
   * List of nodes contained within this block.
   */
  protected final FlexList rules;

  /**
   * Charset directive, if any, associated with this block.
   */
  protected Directive charset;

  /**
   * Variable cache. Since variables can be dynamically added to a block
   * during execution, periodically this cache needs to be rebuilt so that
   * these new variables are found during evaluation.
   */
  protected Map variables;

  /**
   * Initial flags controlling this block. On creation we need to build the
   * variable cache.
   */
  protected byte flags = FLAG_REBUILD_VARS;

  /**
   * Constructs a block with the default initial capcity.
   */
  public Block() {
    this(INITIAL_CAPACITY);
  }

  /**
   * Constructs a block with the given initial capacity.
   */
  public Block(int initialCapacity) {
    rules = new FlexList<>(initialCapacity);
  }

  /**
   * Private constructor, used by the {@link Block#copy()} method.
   */
  private Block(FlexList rules, byte flags) {
    this.rules = rules;
    this.flags = flags;
  }

  /**
   * Sets the charset {@link Directive} for this block.
   */
  public void charset(Directive charset) {
    this.charset = charset;
  }

  /**
   * Returns the charset {@link Directive} for this block, if any.
   */
  public Directive charset() {
    return charset;
  }

  /**
   * Inserts a {@link Node} at the head of the block.
   */
  public void prependNode(Node node) {
    setFlags(node);
    rules.splice(0, 0, new Node[] { node });
  }

  /**
   * Appends a {@link Node} to the tail of the block.
   */
  public void appendNode(Node node) {
    setFlags(node);
    rules.append(node);
  }

  /**
   * Appends all {@link Node}s from the argument {@code Block} to
   * the tail of this instance.
   */
  public void appendBlock(Block block) {
    flags |= block.flags;
    rules.append(block.rules);
  }

  /**
   * Returns the internal {@link FlexList} holding the block's rules.
   */
  public FlexList rules() {
    return rules;
  }

  /**
   * Indicate whether this block contains {@link Import} node.
   */
  public boolean hasImports() {
    return (flags & FLAG_HAS_IMPORTS) != 0;
  }

  /**
   * Indicate whether this block contains a {@link MixinCall} node.
   */
  public boolean hasMixinCalls() {
    return (flags & FLAG_HAS_MIXIN_CALLS) != 0;
  }

  /**
   * Mark that the variable cache needs to be rebuilt.
   */
  public void resetVariableCache() {
    flags |= FLAG_REBUILD_VARS;
  }

  /**
   * Resolve a {@link Definition} with the given {@code name} against
   * the rules in this block.
   */
  public Definition resolveDefinition(String name) {
    if ((flags & FLAG_REBUILD_VARS) != 0) {
      buildVariables();
    }
    return variables.get(name);
  }

  /**
   * Builds the variable cache by locating all {@link Definition} nodes
   * within the block, and mapping them by name.
   */
  private void buildVariables() {
    if (variables == null) {
      variables = new HashMap<>();
    } else {
      variables.clear();
    }
    int size = rules.size();
    for (int i = 0; i < size; i++) {
      Node node = rules.get(i);
      if (!node.type().equals(NodeType.DEFINITION)) {
        continue;
      }
      Definition def = (Definition)node;
      variables.put(def.name(), def);
    }
    flags &= ~FLAG_REBUILD_VARS;
  }

  /**
   * Create a shallow copy of this block.
   */
  public Block copy() {
    return new Block(rules.copy(), flags);
  }

  /**
   * Sets this instance's flags by OR-ing with the arguments flags.
   */
  public void orFlags(Block block) {
    flags |= block.flags;
  }

  /**
   * Debug method - collects the definitions inside this block.
   */
  public String dumpDefs() {
    Buffer buf = new Buffer(4);
    dumpDefs(buf);
    return buf.toString();
  }

  /**
   * Debug method - collects the definitions inside this block.
   */
  public boolean dumpDefs(Buffer buf) {
    boolean output = false;
    int size = rules.size();
    for (int i = 0; i < size; i++) {
      Node node = rules.get(i);
      if (node.type().equals(NodeType.DEFINITION)) {
        String repr = node.toString().replaceAll("\\s+", " ");
        buf.indent().append(repr).append('\n');
        output = true;
      }
    }
    return output;
  }

  /**
   * See {@link Node#type()}
   */
  @Override
  public NodeType type() {
    return NodeType.BLOCK;
  }

  /**
   * See {@link Node#repr()}
   */
  @Override
  public void repr(Buffer buf) {
    int size = rules.size();
    for (int i = 0; i < size; i++) {
      Node rule = rules.get(i);
      if (rule == null) {
        continue;
      }
      if (!buf.compress()) {
        buf.indent();
      }
      rule.repr(buf);
      if (!(rule instanceof BlockNode) && !(rule instanceof Comment)) {
        if (!buf.compress() || i + 1 < size) {
          buf.append(';');
        }
        if (!buf.compress()) {
          buf.append('\n');
        }
      }
    }
  }

  /**
   * See {@link Node#modelRepr(Buffer)}
   */
  @Override
  public void modelRepr(Buffer buf) {
    if (rules != null) {
      ReprUtils.modelRepr(buf, "\n", true, rules);
    }
  }

  @Override
  public boolean equals(Object obj) {
    if (obj instanceof Block) {
      return LessUtils.safeEquals(rules, ((Block)obj).rules);
    }
    return false;
  }

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

  @Override
  public String toString() {
    return rules.toString();
  }

  /**
   * Sets this block's flags based on type of this node.
   */
  private void setFlags(Node node) {
    if (node instanceof Import) {
      flags |= FLAG_HAS_IMPORTS;
    } else if (node instanceof MixinCall) {
      flags |= FLAG_HAS_MIXIN_CALLS;
    }
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy