com.google.gwt.uibinder.rebind.XMLElement.java Source code

Java tutorial

Introduction

Here is the source code for com.google.gwt.uibinder.rebind.XMLElement.java

Source

/*
 * 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.gwt.uibinder.rebind;

/*
 * #%L
 * GwtMaterial
 * %%
 * Copyright (C) 2015 - 2016 GwtMaterialDesign
 * %%
 * 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.
 * #L%
 */

import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.JType;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
import com.google.gwt.core.ext.typeinfo.TypeOracleException;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.safehtml.shared.SafeHtml;
import com.google.gwt.uibinder.attributeparsers.AttributeParser;
import com.google.gwt.uibinder.attributeparsers.AttributeParsers;
import com.google.gwt.uibinder.elementparsers.SimpleInterpeter;
import org.w3c.dom.Attr;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * A wrapper for {@link Element} that limits the way parsers can interact with
 * the XML document, and provides some convenience methods.
 * <p>
 * The main function of this wrapper is to ensure that parsers can only read
 * elements and attributes by 'consuming' them, which removes the given value.
 * This allows for a natural hierarchy among parsers -- more specific parsers
 * will run first, and if they consume a value, less-specific parsers will not
 * see it.
 */
public class XMLElement {
    /**
     * Callback interface used by {@link #consumeInnerHtml(Interpreter)} and
     * {@link #consumeChildElements(Interpreter)}.
     */
    public interface Interpreter<T> {
        /**
         * Given an XMLElement, return its filtered value.
         * 
         * @throws UnableToCompleteException on invalid
         */
        T interpretElement(XMLElement elem) throws UnableToCompleteException;
    }

    /**
     * Extends {@link Interpreter} with a method to be called after all elements
     * have been processed.
     */
    public interface PostProcessingInterpreter<T> extends Interpreter<T> {
        String postProcess(String consumedText) throws UnableToCompleteException;
    }

    /**
     * Represents the source location where the XMLElement was declared.
     */
    public static class Location {
        private final String systemId;
        private final int lineNumber;

        public Location(String systemId, int lineNumber) {
            this.systemId = systemId;
            this.lineNumber = lineNumber;
        }

        public int getLineNumber() {
            return lineNumber;
        }

        public String getSystemId() {
            return systemId;
        }

        /**
         * For debugging use only.
         */
        @Override
        public String toString() {
            return systemId + ":" + lineNumber;
        }
    }

    static final String LOCATION_KEY = "gwtLocation";

    private static final Set<String> NO_END_TAG = new HashSet<String>();

    private static final String[] EMPTY = new String[] {};

    private static void clearChildren(Element elem) {
        // TODO(rjrjr) I'm nearly positive that anywhere this is called
        // we should instead be calling assertNoBody
        Node child;
        while ((child = elem.getFirstChild()) != null) {
            elem.removeChild(child);
        }
    }

    private final Element elem;
    private final AttributeParsers attributeParsers;
    private final TypeOracle oracle;

    private final MortalLogger logger;
    private final String debugString;

    private final DesignTimeUtils designTime;

    private final XMLElementProvider provider;

    private JType booleanType;
    private JType imageResourceType;
    private JType doubleType;
    private JType intType;
    private JType safeHtmlType;
    private JType stringType;

    {
        // from com/google/gxp/compiler/schema/html.xml
        NO_END_TAG.add("area");
        NO_END_TAG.add("base");
        NO_END_TAG.add("basefont");
        NO_END_TAG.add("br");
        NO_END_TAG.add("col");
        NO_END_TAG.add("frame");
        NO_END_TAG.add("hr");
        NO_END_TAG.add("img");
        NO_END_TAG.add("input");
        NO_END_TAG.add("isindex");
        NO_END_TAG.add("link");
        NO_END_TAG.add("meta");
        NO_END_TAG.add("param");
        NO_END_TAG.add("wbr");
    }

    XMLElement(Element elem, AttributeParsers attributeParsers, TypeOracle oracle, MortalLogger logger,
            DesignTimeUtils designTime, XMLElementProvider provider) {
        this.elem = elem;
        this.attributeParsers = attributeParsers;
        this.oracle = oracle;
        this.logger = logger;
        this.designTime = designTime;
        this.provider = provider;

        this.debugString = getOpeningTag();
    }

    /**
     * Ensure that the receiver has no attributes left.
     * 
     * @throws UnableToCompleteException if it does
     */
    public void assertNoAttributes() throws UnableToCompleteException {
        int numAtts = getAttributeCount();
        if (numAtts == 0) {
            return;
        }

        StringBuilder b = new StringBuilder();
        for (int i = 0; i < numAtts; i++) {
            if (i > 0) {
                b.append(", ");
            }
            b.append('"').append(getAttribute(i).getName()).append('"');
        }
        logger.die(this, "Unexpected attributes: %s", b);
    }

    /**
     * Require that the receiver's body is empty of text and has no child nodes.
     * 
     * @throws UnableToCompleteException if it isn't
     */
    public void assertNoBody() throws UnableToCompleteException {
        consumeChildElements(new Interpreter<Boolean>() {
            public Boolean interpretElement(XMLElement elem) throws UnableToCompleteException {
                logger.die(elem, "Found unexpected child element");
                return false; // unreachable
            }
        });
        assertNoText();
    }

    /**
     * Require that the receiver's body is empty of text.
     * 
     * @throws UnableToCompleteException if it isn't
     */
    public void assertNoText() throws UnableToCompleteException {
        SimpleInterpeter<String> nullInterpreter = new SimpleInterpeter<String>(null);
        String s = consumeInnerTextEscapedAsHtmlStringLiteral(nullInterpreter);
        if (!"".equals(s)) {
            logger.die(this, "Unexpected text in element: \"%s\"", s);
        }
    }

    /**
     * Consumes the given attribute as a literal or field reference. The type
     * parameter is required to determine how the value is parsed and validated.
     * 
     * @param name the attribute's full name (including prefix)
     * @param type the type this attribute is expected to provide
     * @return the attribute's value as a Java expression, or null if it is not
     *         set
     * @throws UnableToCompleteException on parse failure
     */
    public String consumeAttribute(String name, JType type) throws UnableToCompleteException {
        return consumeAttributeWithDefault(name, null, type);
    }

    /**
     * Consumes the given attribute as a literal or field reference. The type
     * parameter is required to determine how the value is parsed and validated.
     * 
     * @param name the attribute's full name (including prefix)
     * @param defaultValue the value to @return if the attribute was unset
     * @param type the type this attribute is expected to provide
     * @return the attribute's value as a Java expression, or the given default if
     *         it was unset
     * @throws UnableToCompleteException on parse failure
     */
    public String consumeAttributeWithDefault(String name, String defaultValue, JType type)
            throws UnableToCompleteException {
        return consumeAttributeWithDefault(name, defaultValue, new JType[] { type });
    }

    /**
     * Like {@link #consumeAttributeWithDefault(String, String, JType)}, but
     * accommodates more complex type signatures.
     */
    public String consumeAttributeWithDefault(String name, String defaultValue, JType... types)
            throws UnableToCompleteException {

        if (!hasAttribute(name)) {
            if (defaultValue != null) {
                designTime.putAttribute(this, name + ".default", defaultValue);
            }
            return defaultValue;
        }

        AttributeParser parser = attributeParsers.getParser(types);
        if (parser == null) {
            logger.die("Attribute '" + name + "' cannot be parsed, null parser. "
                    + "Please ensure the content of this attribute matches its assignment method.");
        }
        return consumeAttributeWithParser(name, parser);
    }

    /**
     * Convenience method for parsing the named attribute as a boolean value or
     * reference.
     * 
     * @return an expression that will evaluate to a boolean value in the
     *         generated code, or null if there is no such attribute
     * 
     * @throws UnableToCompleteException on unparseable value
     */
    public String consumeBooleanAttribute(String name) throws UnableToCompleteException {
        return consumeAttribute(name, getBooleanType());
    }

    /**
     * Convenience method for parsing the named attribute as a boolean value or
     * reference.
     * 
     * @param defaultValue value to return if attribute was not set
     * @return an expression that will evaluate to a boolean value in the
     *         generated code, or defaultValue if there is no such attribute
     * 
     * @throws UnableToCompleteException on unparseable value
     */
    public String consumeBooleanAttribute(String name, boolean defaultValue) throws UnableToCompleteException {
        return consumeAttributeWithDefault(name, Boolean.toString(defaultValue), getBooleanType());
    }

    /**
     * Consumes the named attribute as a boolean expression. This will not accept
     * {field.reference} expressions. Useful for values that must be resolved at
     * compile time, such as generated annotation values.
     * 
     * @return {@link Boolean#TRUE}, {@link Boolean#FALSE}, or null if no such
     *         attribute
     * 
     * @throws UnableToCompleteException on unparseable value
     */
    public Boolean consumeBooleanConstantAttribute(String name) throws UnableToCompleteException {
        String value = consumeRawAttribute(name);
        if (value == null) {
            return null;
        }
        if (value.equals("true") || value.equals("false")) {
            return Boolean.valueOf(value);
        }
        logger.die(this, "%s must be \"true\" or \"false\"", name);
        return null; // unreachable
    }

    /**
     * Consumes and returns all child elements.
     * 
     * @throws UnableToCompleteException if extra text nodes are found
     */
    public Iterable<XMLElement> consumeChildElements() throws UnableToCompleteException {
        Iterable<XMLElement> rtn = consumeChildElementsNoEmptyCheck();
        assertNoText();
        return rtn;
    }

    /**
     * Consumes and returns all child elements selected by the interpreter. Note
     * that text nodes are not elements, and so are not presented for
     * interpretation, and are not consumed.
     * 
     * @param interpreter Should return true for any child that should be consumed
     *          and returned by the consumeChildElements call
     * @throws UnableToCompleteException
     */
    public Collection<XMLElement> consumeChildElements(Interpreter<Boolean> interpreter)
            throws UnableToCompleteException {
        List<XMLElement> elements = new ArrayList<XMLElement>();
        List<Node> doomed = new ArrayList<Node>();

        NodeList childNodes = elem.getChildNodes();
        for (int i = 0; i < childNodes.getLength(); ++i) {
            Node childNode = childNodes.item(i);
            if (childNode.getNodeType() == Node.ELEMENT_NODE) {
                XMLElement childElement = provider.get((Element) childNode);
                if (interpreter.interpretElement(childElement)) {
                    elements.add(childElement);
                    doomed.add(childNode);
                }
            }
        }

        for (Node n : doomed) {
            elem.removeChild(n);
        }
        return elements;
    }

    /**
     * Convenience method for parsing the named attribute as an ImageResource
     * value or reference.
     * 
     * @return an expression that will evaluate to an ImageResource value in the
     *         generated code, or null if there is no such attribute
     * @throws UnableToCompleteException on unparseable value
     */
    public String consumeImageResourceAttribute(String name) throws UnableToCompleteException {
        return consumeAttribute(name, getImageResourceType());
    }

    /**
     * Consumes all child elements, and returns an HTML interpretation of them.
     * Trailing and leading whitespace is trimmed.
     * <p>
     * Each element encountered will be passed to the given Interpreter for
     * possible replacement. Escaping is performed to allow the returned text to
     * serve as a Java string literal used as input to a setInnerHTML call.
     * <p>
     * This call requires an interpreter to make sense of any special children.
     * The odds are you want to use
     * {@link com.google.gwt.uibinder.elementparsers.HtmlInterpreter} for an HTML
     * value, or {@link com.google.gwt.uibinder.elementparsers.TextInterpreter}
     * for text.
     * 
     * @param interpreter Called for each element, expected to return a string
     *          replacement for it, or null if it should be left as is
     */
    public String consumeInnerHtml(Interpreter<String> interpreter) throws UnableToCompleteException {
        if (interpreter == null) {
            throw new NullPointerException("interpreter must not be null");
        }
        StringBuffer buf = new StringBuffer();
        GetInnerHtmlVisitor.getEscapedInnerHtml(elem, buf, interpreter, provider);

        clearChildren(elem);
        return buf.toString().trim();
    }

    /**
     * Refines {@link #consumeInnerHtml(Interpreter)} to handle
     * PostProcessingInterpreter.
     */
    public String consumeInnerHtml(PostProcessingInterpreter<String> interpreter) throws UnableToCompleteException {
        String html = consumeInnerHtml((Interpreter<String>) interpreter);
        return interpreter.postProcess(html);
    }

    /**
     * Refines {@link #consumeInnerTextEscapedAsHtmlStringLiteral(Interpreter)} to
     * handle PostProcessingInterpreter.
     */
    public String consumeInnerText(PostProcessingInterpreter<String> interpreter) throws UnableToCompleteException {
        String text = consumeInnerTextEscapedAsHtmlStringLiteral(interpreter);
        return interpreter.postProcess(text);
    }

    /**
     * Consumes all child text nodes, and asserts that this element held only
     * text. Trailing and leading whitespace is trimmed, and escaped for use as a
     * string literal. Notice that HTML entities in the text are also escaped
     * <p>
     * This call requires an interpreter to make sense of any special children.
     * The odds are you want to use
     * {@link com.google.gwt.uibinder.elementparsers.TextInterpreter}
     *
     * @throws UnableToCompleteException If any elements present are not consumed
     *           by the interpreter
     */
    public String consumeInnerTextEscapedAsHtmlStringLiteral(Interpreter<String> interpreter)
            throws UnableToCompleteException {
        return consumeInnerTextEscapedAsHtmlStringLiteral(interpreter, true);
    }

    /**
     * Consumes all child text nodes, and asserts that this element held only
     * text. Trailing and leading whitespace is trimmed, and escaped for use as a
     * string literal. Notice that HTML entities in the text are NOT escaped
     * <p>
     * This call requires an interpreter to make sense of any special children.
     * The odds are you want to use
     * {@link com.google.gwt.uibinder.elementparsers.TextInterpreter}
     *
     * @throws UnableToCompleteException If any elements present are not consumed
     *           by the interpreter
     */
    public String consumeInnerTextEscapedAsStringLiteral(Interpreter<String> interpreter)
            throws UnableToCompleteException {
        return consumeInnerTextEscapedAsHtmlStringLiteral(interpreter, false);
    }

    /**
     * Convenience method for parsing the named attribute as a CSS length value.
     * 
     * @return a (double, Unit) pair literal, an expression that will evaluate to
     *         such a pair in the generated code, or null if there is no such
     *         attribute
     * 
     * @throws UnableToCompleteException on unparseable value
     */
    public String consumeLengthAttribute(String name) throws UnableToCompleteException {
        return consumeAttributeWithDefault(name, null, new JType[] { getDoubleType(), getUnitType() });
    }

    /**
     * Consumes all attributes, and returns a string representing the entire
     * opening tag. E.g., "<div able='baker'>"
     */
    public String consumeOpeningTag() {
        String rtn = getOpeningTag();

        for (int i = getAttributeCount() - 1; i >= 0; i--) {
            getAttribute(i).consumeRawValue();
        }
        return rtn;
    }

    /**
     * Consumes the named attribute and parses it to an unparsed, unescaped array
     * of Strings. The strings in the attribute may be comma or space separated
     * (or a mix of both).
     * 
     * @return array of String, empty if the attribute was not set.
     */
    public String[] consumeRawArrayAttribute(String name) {
        String raw = consumeRawAttribute(name, null);
        if (raw == null) {
            return EMPTY;
        }

        return raw.split("[,\\s]+");
    }

    /**
     * Consumes the given attribute and returns its trimmed value, or null if it
     * was unset. The returned string is not escaped.
     * 
     * @param name the attribute's full name (including prefix)
     * @return the attribute's value, or ""
     */
    public String consumeRawAttribute(String name) {
        if (!elem.hasAttribute(name)) {
            return null;
        }
        String value = elem.getAttribute(name);
        elem.removeAttribute(name);
        return value.trim();
    }

    /**
     * Consumes the given attribute and returns its trimmed value, or the given
     * default value if it was unset. The returned string is not escaped.
     * 
     * @param name the attribute's full name (including prefix)
     * @param defaultValue the value to return if the attribute was unset
     * @return the attribute's value, or defaultValue
     */
    public String consumeRawAttribute(String name, String defaultValue) {
        String value = consumeRawAttribute(name);
        if (value == null) {
            return defaultValue;
        }
        return value;
    }

    /**
     * Consumes the given required attribute as a literal or field reference. The
     * types parameters are required to determine how the value is parsed and
     * validated.
     * 
     * @param name the attribute's full name (including prefix)
     * @param types the type(s) this attribute is expected to provide
     * @return the attribute's value as a Java expression
     * @throws UnableToCompleteException on parse failure, or if the attribute is
     *           empty or unspecified
     */
    public String consumeRequiredAttribute(String name, JType... types) throws UnableToCompleteException {
        if (!hasAttribute(name)) {
            failRequired(name);
        }

        AttributeParser parser = attributeParsers.getParser(types);

        String value = parser.parse(this, consumeRequiredRawAttribute(name));
        designTime.putAttribute(this, name, value);
        return value;
    }

    /**
     * Convenience method for parsing the named required attribute as a double
     * value or reference.
     * 
     * @return a double literal, an expression that will evaluate to a double
     *         value in the generated code
     * 
     * @throws UnableToCompleteException on unparseable value, or if the attribute
     *           is empty or unspecified
     */
    public String consumeRequiredDoubleAttribute(String name) throws UnableToCompleteException {
        return consumeRequiredAttribute(name, getDoubleType());
    }

    /**
     * Convenience method for parsing the named required attribute as a integer
     * value or reference.
     * 
     * @return a integer literal, an expression that will evaluate to a integer
     *         value in the generated code
     * 
     * @throws UnableToCompleteException on unparseable value, or if the attribute
     *           is empty or unspecified
     */
    public String consumeRequiredIntAttribute(String name) throws UnableToCompleteException {
        return consumeRequiredAttribute(name, getIntType());
    }

    /**
     * Consumes the named attribute, or dies if it is missing.
     */
    public String consumeRequiredRawAttribute(String name) throws UnableToCompleteException {
        String value = consumeRawAttribute(name);
        if (value == null) {
            failRequired(name);
        }
        return value;
    }

    /**
     * Convenience method for parsing the named attribute as a
     * {@link SafeHtml SafeHtml} value or
     * reference.
     *
     * @return an expression that will evaluate to a
     *         {@link SafeHtml SafeHtml} value in
     *         the generated code, or null if there is no such attribute
     * @throws UnableToCompleteException on unparseable value
     */
    public String consumeSafeHtmlAttribute(String name) throws UnableToCompleteException {
        return consumeAttribute(name, getSafeHtmlType());
    }

    /**
     * Consumes an attribute as either a SafeUri or a String. Used in HTML
     * contexts.
     * 
     * @return an expression that will evaluate to a SafeUri value in the
     *         generated code, or null if there is no such attribute
     * @throws UnableToCompleteException on unparseable value
     */
    public String consumeSafeUriOrStringAttribute(String name) throws UnableToCompleteException {
        return consumeAttributeWithParser(name, attributeParsers.getSafeUriInHtmlParser());
    }

    /**
     * Consumes a single child element, ignoring any text nodes and throwing an
     * exception if no child is found, or more than one child element is found.
     * 
     * @throws UnableToCompleteException on no children, or too many
     */
    public XMLElement consumeSingleChildElement() throws UnableToCompleteException {
        XMLElement ret = null;
        for (XMLElement child : consumeChildElements()) {
            if (ret != null) {
                logger.die(this, "Element may only contain a single child element, but " + "found %s and %s.", ret,
                        child);
            }

            ret = child;
        }

        if (ret == null) {
            logger.die(this, "Element must have a single child element");
        }

        return ret;
    }

    /**
     * Consumes the named attribute and parses it to an array of String
     * expressions. The strings in the attribute may be comma or space separated
     * (or a mix of both).
     * 
     * @return array of String expressions, empty if the attribute was not set.
     * @throws UnableToCompleteException on unparseable value
     */
    public String[] consumeStringArrayAttribute(String name) throws UnableToCompleteException {
        AttributeParser parser = attributeParsers.getParser(getStringType());

        String[] strings = consumeRawArrayAttribute(name);
        for (int i = 0; i < strings.length; i++) {
            strings[i] = parser.parse(this, strings[i]);
        }
        designTime.putAttribute(this, name, strings);
        return strings;
    }

    /**
     * Convenience method for parsing the named attribute as a String value or
     * reference.
     * 
     * @return an expression that will evaluate to a String value in the generated
     *         code, or null if there is no such attribute
     * @throws UnableToCompleteException on unparseable value
     */
    public String consumeStringAttribute(String name) throws UnableToCompleteException {
        return consumeAttribute(name, getStringType());
    }

    /**
     * Convenience method for parsing the named attribute as a String value or
     * reference.
     * 
     * @return an expression that will evaluate to a String value in the generated
     *         code, or the given defaultValue if there is no such attribute
     * @throws UnableToCompleteException on unparseable value
     */
    public String consumeStringAttribute(String name, String defaultValue) throws UnableToCompleteException {
        return consumeAttributeWithDefault(name, defaultValue, getStringType());
    }

    /**
     * Returns the unprocessed, unescaped, raw inner text of the receiver. Dies if
     * the receiver has non-text children.
     * <p>
     * You probably want to use
     * {@link #consumeInnerTextEscapedAsHtmlStringLiteral} instead.
     * 
     * @return the text
     * @throws UnableToCompleteException if it held anything other than text nodes
     */
    public String consumeUnescapedInnerText() throws UnableToCompleteException {
        final NodeList children = elem.getChildNodes();
        if (children.getLength() < 1) {
            return "";
        }
        if (children.getLength() > 1 || Node.TEXT_NODE != children.item(0).getNodeType()) {
            logger.die(this, "Element must contain only text");
        }
        Text t = (Text) children.item(0);
        return t.getTextContent();
    }

    /**
     * Get the attribute at the given index. If you are consuming attributes,
     * remember to traverse them in reverse.
     */
    public XMLAttribute getAttribute(int i) {
        return new XMLAttribute(XMLElement.this, (Attr) elem.getAttributes().item(i));
    }

    /**
     * Get the attribute with the given name.
     * 
     * @return the attribute, or null if there is none of that name
     */
    public XMLAttribute getAttribute(String name) {
        Attr attr = elem.getAttributeNode(name);
        if (attr == null) {
            return null;
        }
        return new XMLAttribute(this, attr);
    }

    /**
     * Returns the number of attributes this element has.
     */
    public int getAttributeCount() {
        return elem.getAttributes().getLength();
    }

    public String getClosingTag() {
        if (NO_END_TAG.contains(elem.getTagName())) {
            return "";
        }
        return String.format("</%s>", elem.getTagName());
    }

    /**
     * Returns the design time path of this element, in form of indexes from root,
     * such as "0/0/1/0".
     */
    public String getDesignTimePath() {
        return designTime.getPath(elem);
    }

    /**
     * Gets this element's local name (sans namespace prefix).
     */
    public String getLocalName() {
        return elem.getLocalName();
    }

    public Location getLocation() {
        return (Location) elem.getUserData(LOCATION_KEY);
    }

    /**
     * Gets this element's namespace URI.
     */
    public String getNamespaceUri() {
        return elem.getNamespaceURI();
    }

    /**
     * Returns the parent element, or null if parent is null or a node type other
     * than Element.
     */
    public XMLElement getParent() {
        Node parent = elem.getParentNode();
        if (parent == null || Node.ELEMENT_NODE != parent.getNodeType()) {
            return null;
        }
        return provider.get((Element) parent);
    }

    public String getPrefix() {
        return elem.getPrefix();
    }

    /**
     * Determines whether the element has a given attribute.
     */
    public boolean hasAttribute(String name) {
        return elem.hasAttribute(name);
    }

    public boolean hasChildNodes() {
        return elem.hasChildNodes();
    }

    public String lookupPrefix(String prefix) {
        return elem.lookupPrefix(prefix);
    }

    public void setAttribute(String name, String value) {
        elem.setAttribute(name, value);
    }

    @Override
    public String toString() {
        return debugString;
    }

    private String consumeAttributeWithParser(String name, AttributeParser parser)
            throws UnableToCompleteException {
        String value = parser.parse(this, consumeRawAttribute(name));
        designTime.putAttribute(this, name, value);
        return value;
    }

    private Iterable<XMLElement> consumeChildElementsNoEmptyCheck() {
        try {
            Iterable<XMLElement> rtn = consumeChildElements(new SimpleInterpeter<Boolean>(true));
            return rtn;
        } catch (UnableToCompleteException e) {
            throw new RuntimeException("Impossible exception", e);
        }
    }

    /**
     * Consumes all child text nodes, and asserts that this element held only
     * text. Trailing and leading whitespace is trimmed, and escaped for use as a
     * string literal. If escapeHtmlEntities is true, HTML Entities are also escaped.
     * <p>
     * This call requires an interpreter to make sense of any special children.
     * The odds are you want to use
     * {@link com.google.gwt.uibinder.elementparsers.TextInterpreter}
     *
     * @throws UnableToCompleteException If any elements present are not consumed
     *           by the interpreter
     */
    private String consumeInnerTextEscapedAsHtmlStringLiteral(Interpreter<String> interpreter,
            boolean escapeHtmlEntities) throws UnableToCompleteException {
        if (interpreter == null) {
            throw new NullPointerException("interpreter must not be null");
        }
        StringBuffer buf = new StringBuffer();

        if (escapeHtmlEntities) {
            GetInnerTextVisitor.getHtmlEscapedInnerText(elem, buf, interpreter, provider);
        } else {
            GetInnerTextVisitor.getEscapedInnerText(elem, buf, interpreter, provider);
        }

        // Make sure there are no children left but empty husks
        for (XMLElement child : consumeChildElementsNoEmptyCheck()) {
            if (child.hasChildNodes() || child.getAttributeCount() > 0) {
                logger.die(this,
                        "Illegal child %s in a text-only context. "
                                + "Perhaps you are trying to use unescaped HTML "
                                + "where text is required, as in a HasText widget?",
                        child);
            }
        }

        clearChildren(elem);
        return buf.toString().trim();
    }

    private void failRequired(String name) throws UnableToCompleteException {
        logger.die(this, "Missing required attribute \"%s\"", name);
    }

    private JType getBooleanType() {
        if (booleanType == null) {
            try {
                booleanType = oracle.parse("boolean");
            } catch (TypeOracleException e) {
                throw new RuntimeException(e);
            }
        }
        return booleanType;
    }

    private JType getDoubleType() {
        if (doubleType == null) {
            try {
                doubleType = oracle.parse("double");
            } catch (TypeOracleException e) {
                throw new RuntimeException(e);
            }
        }
        return doubleType;
    }

    private JType getImageResourceType() {
        if (imageResourceType == null) {
            imageResourceType = oracle.findType(ImageResource.class.getCanonicalName());
        }
        return imageResourceType;
    }

    private JType getIntType() {
        if (intType == null) {
            try {
                intType = oracle.parse("int");
            } catch (TypeOracleException e) {
                throw new RuntimeException(e);
            }
        }
        return intType;
    }

    private String getOpeningTag() {
        StringBuilder b = new StringBuilder().append("<").append(elem.getTagName());

        NamedNodeMap attrs = elem.getAttributes();
        for (int i = 0; i < attrs.getLength(); i++) {
            Attr attr = (Attr) attrs.item(i);
            b.append(
                    String.format(" %s='%s'", attr.getName(), UiBinderWriter.escapeAttributeText(attr.getValue())));
        }
        b.append(">");
        return b.toString();
    }

    private JType getSafeHtmlType() {
        if (safeHtmlType == null) {
            safeHtmlType = oracle.findType(SafeHtml.class.getName());
        }
        return safeHtmlType;
    }

    private JType getStringType() {
        if (stringType == null) {
            stringType = oracle.findType(String.class.getCanonicalName());
        }
        return stringType;
    }

    private JClassType getUnitType() {
        return oracle.findType(Unit.class.getCanonicalName()).isEnum();
    }
}