/* * Copyright (c) 2013, the Dart project authors. * * Licensed under the Eclipse Public License v1.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.eclipse.org/legal/epl-v10.html * * 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.dart.engine.html.ast; import com.google.dart.engine.AnalysisEngine; import com.google.dart.engine.element.Element; import com.google.dart.engine.html.ast.visitor.ToSourceVisitor; import com.google.dart.engine.html.ast.visitor.XmlVisitor; import com.google.dart.engine.html.scanner.Token; import com.google.dart.engine.utilities.dart.ParameterKind; import com.google.dart.engine.utilities.io.PrintStringWriter; import com.google.dart.engine.utilities.translation.DartOmit; import com.google.dart.engine.utilities.translation.DartOptional; import java.util.ArrayList; import java.util.Iterator; import java.util.List; /** * The abstract class {@code XmlNode} defines behavior common to all XML/HTML nodes. * * @coverage dart.engine.html */ public abstract class XmlNode { /** * The parent of the node, or {@code null} if the node is the root of an AST structure. */ private XmlNode parent; /** * The element associated with this node or {@code null} if the receiver is not resolved. */ private Element element; /** * Use the given visitor to visit this node. * * @param visitor the visitor that will visit this node * @return the value returned by the visitor as a result of visiting this node */ public abstract <R> R accept(XmlVisitor<R> visitor); /** * Return the first token included in this node's source range. * * @return the first token or {@code null} if none */ public abstract Token getBeginToken(); /** * Return the element associated with this node. * * @return the element or {@code null} if the receiver is not resolved */ public Element getElement() { return element; } /** * Return the offset of the character immediately following the last character of this node's * source range. This is equivalent to {@code node.getOffset() + node.getLength()}. For an html * unit this will be equal to the length of the unit's source. * * @return the offset of the character just past the node's source range */ public int getEnd() { return getOffset() + getLength(); } /** * Return the last token included in this node's source range. * * @return the last token or {@code null} if none */ public abstract Token getEndToken(); /** * Return the number of characters in the node's source range. * * @return the number of characters in the node's source range */ public int getLength() { Token beginToken = getBeginToken(); Token endToken = getEndToken(); if (beginToken == null || endToken == null) { return -1; } return endToken.getOffset() + endToken.getLength() - beginToken.getOffset(); } /** * Return the offset from the beginning of the file to the first character in the node's source * range. * * @return the offset from the beginning of the file to the first character in the node's source * range */ public int getOffset() { Token beginToken = getBeginToken(); if (beginToken == null) { return -1; } return getBeginToken().getOffset(); } /** * Return this node's parent node, or {@code null} if this node is the root of an AST structure. * <p> * Note that the relationship between an AST node and its parent node may change over the lifetime * of a node. * * @return the parent of this node, or {@code null} if none */ public XmlNode getParent() { return parent; } /** * Set the element associated with this node. * * @param element the element */ public void setElement(Element element) { this.element = element; } @Override public String toString() { PrintStringWriter writer = new PrintStringWriter(); accept(new ToSourceVisitor(writer)); return writer.toString(); } /** * Use the given visitor to visit all of the children of this node. The children will be visited * in source order. * * @param visitor the visitor that will be used to visit the children of this node */ public abstract void visitChildren(XmlVisitor<?> visitor); /** * Make this node the parent of the given child node. * * @param child the node that will become a child of this node * @return the node that was made a child of this node */ protected <T extends XmlNode> T becomeParentOf(T child) { if (child != null) { XmlNode node = child; // Java 7 access rules require a temp of a concrete type. node.setParent(this); } return child; } /** * Make this node the parent of the given child nodes. * * @param children the nodes that will become the children of this node * @return the nodes that were made children of this node */ @DartOmit protected final <T extends XmlNode> List<T> becomeParentOfAll(List<T> children) { return becomeParentOfAll(children, null); } /** * Make this node the parent of the given child nodes. * * @param children the nodes that will become the children of this node * @param ifEmpty the (empty) nodes to return if "children" is empty * @return the nodes that were made children of this node */ protected final <T extends XmlNode> List<T> becomeParentOfAll(List<T> children, @DartOptional(kind = ParameterKind.NAMED) List<T> ifEmpty) { if (children == null || children.isEmpty()) { if (ifEmpty != null) { return ifEmpty; } } if (children != null) { for (Iterator<T> iter = children.iterator(); iter.hasNext();) { XmlNode node = iter.next(); // Java 7 access rules require a temp of a concrete type. node.setParent(this); } // This will create ArrayList for exactly given number of elements. return new ArrayList<T>(children); } return children; } /** * This method exists for debugging purposes only. */ private void appendIdentifier(StringBuilder builder, XmlNode node) { if (node instanceof XmlTagNode) { builder.append(((XmlTagNode) node).getTag()); } else if (node instanceof XmlAttributeNode) { builder.append(((XmlAttributeNode) node).getName()); } else { builder.append("htmlUnit"); } } /** * This method exists for debugging purposes only. */ private String buildRecursiveStructureMessage(XmlNode newParent) { StringBuilder builder = new StringBuilder(); builder.append("Attempt to create recursive structure: "); XmlNode current = newParent; while (current != null) { if (current != newParent) { builder.append(" -> "); } if (current == this) { builder.append('*'); appendIdentifier(builder, current); builder.append('*'); } else { appendIdentifier(builder, current); } current = current.getParent(); } return builder.toString(); } /** * Set the parent of this node to the given node. * * @param newParent the node that is to be made the parent of this node */ private void setParent(XmlNode newParent) { XmlNode current = newParent; while (current != null) { if (current == this) { AnalysisEngine.getInstance().getLogger().logError( "Circular structure while setting an XML node's parent", new IllegalArgumentException(buildRecursiveStructureMessage(newParent))); return; } current = current.getParent(); } parent = newParent; } }