/* * Copyright 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.template.soy.basetree; import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.collect.Lists; import java.util.List; /** * Mixin implementation of the parent-specific aspect of the ParentNode interface. Requires the * master to be a ParentNode. * * <p>Important: Do not use outside of Soy code (treat as superpackage-private). * * <p>The parameter N represents the interface or class that is the superclass of all possible * children for the master ParentNode. E.g. for a Soy parse tree node, N is usually SoyNode, but for * SoyFileSetNode N is SoyFileNode, for SoyFileNode N is TemplateNode, etc; for a Soy expression * parse tree, N is usually ExprNode. * */ public final class MixinParentNode<N extends Node> { /** Just spaces. */ private static final String SPACES = " "; /** The master node that delegates to this instance. */ private final ParentNode<N> master; /** The children of the master node (accessed via this instance). */ private final List<N> children; /** @param master The master node that delegates to this instance. */ public MixinParentNode(ParentNode<N> master) { this.master = checkNotNull(master); children = Lists.newArrayList(); } /** * Copy constructor. * * @param orig The node to copy. * @param newMaster The master node for the copy. */ public MixinParentNode(MixinParentNode<N> orig, ParentNode<N> newMaster, CopyState copyState) { this.master = newMaster; this.children = Lists.newArrayListWithCapacity(orig.children.size()); for (N origChild : orig.children) { @SuppressWarnings("unchecked") N newChild = (N) origChild.copy(copyState); this.children.add(newChild); newChild.setParent(this.master); } } /** * Gets the number of children. * * @return The number of children. */ public int numChildren() { return children.size(); } /** * Gets the child at the given index. * * @param index The index of the child to get. * @return The child at the given index. */ public N getChild(int index) { return children.get(index); } /** * Finds the index of the given child. * * @param child The child to find the index of. * @return The index of the given child, or -1 if the given child is not a child of this node. */ public int getChildIndex(N child) { return children.indexOf(child); } /** * Gets the list of children. * * <p>Note: The returned list is not a copy. Please do not modify the list directly. Instead, use * the other methods in this class that are intended for modifying children. Also, if you're * iterating over the children list as you're modifying it, then you should first make a copy of * the children list to iterate over, in order to avoid ConcurrentModificationException. * * @return The list of children. */ public List<N> getChildren() { return children; } /** * Adds the given child. * * @param child The child to add. */ public void addChild(N child) { tryRemoveFromOldParent(child); children.add(child); child.setParent(master); } /** * Adds the given child at the given index (shifting existing children if necessary). * * @param index The index to add the child at. * @param child The child to add. */ public void addChild(int index, N child) { tryRemoveFromOldParent(child); children.add(index, child); child.setParent(master); } /** * Removes the child at the given index. * * @param index The index of the child to remove. */ public void removeChild(int index) { N child = children.remove(index); child.setParent(null); } /** * Removes the given child. * * @param child The child to remove. */ public void removeChild(N child) { children.remove(child); child.setParent(null); } /** * Replaces the child at the given index with the given new child. * * @param index The index of the child to replace. * @param newChild The new child. */ public void replaceChild(int index, N newChild) { tryRemoveFromOldParent(newChild); N oldChild = children.set(index, newChild); oldChild.setParent(null); newChild.setParent(master); } /** * Replaces the given current child with the given new child. * * @param currChild The current child to be replaced. * @param newChild The new child. */ public void replaceChild(N currChild, N newChild) { replaceChild(getChildIndex(currChild), newChild); } /** Clears the list of children. */ public void clearChildren() { for (int i = 0; i < children.size(); i++) { children.get(i).setParent(null); } children.clear(); } /** * Adds the given children. * * @param children The children to add. */ @SuppressWarnings("unchecked") public void addChildren(List<? extends N> children) { // NOTE: if the input list comes from another node, this could cause // ConcurrentModificationExceptions as nodes are moved from one parent to another. To avoid // this we make a copy of the input list. for (Node child : children.toArray(new Node[0])) { addChild((N) child); } } /** * Adds the given children at the given index (shifting existing children if necessary). * * @param index The index to add the children at. * @param children The children to add. */ public void addChildren(int index, List<? extends N> children) { List<N> origChildren = Lists.newArrayList(this.children); int origNumChildren = this.children.size(); // Temporarily remove the original children from index onward (in reverse order). for (int i = origNumChildren - 1; i >= index; i--) { removeChild(i); } // Add the new children. addChildren(children); // Add back the original children that we temporarily removed (in correct order). addChildren(origChildren.subList(index, origNumChildren)); } /** * Appends the source strings for all the children to the given StringBuilder. * * @param sb The StringBuilder to which to append the children's source strings. */ public void appendSourceStringForChildren(StringBuilder sb) { for (N child : children) { sb.append(child.toSourceString()); } } /** * Builds a string that visually shows the subtree rooted at this node (for debugging). Each line * of the string will be indented by the given indentation amount. You should pass an indentation * of 0 unless this method is being called as part of building a larger tree string. * * @param indent The indentation for each line of the tree string (usually pass 0). * @return A string that visually shows the subtree rooted at this node. */ public String toTreeString(int indent) { StringBuilder sb = new StringBuilder(); sb.append(SPACES, 0, indent).append("[").append(master).append("]\n"); return sb.toString(); } private static <N extends Node> void tryRemoveFromOldParent(N child) { // Java's type system isn't sophisticated enough to type the return value of getParent() but // since it is the parent of N we know it can accept N as a child @SuppressWarnings("unchecked") ParentNode<? super N> oldParent = (ParentNode<? super N>) child.getParent(); if (oldParent != null) { oldParent.removeChild(child); } } }