/*
* Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores
* CA 94065 USA or visit www.oracle.com if you need additional information or
* have any questions.
*/
package com.sun.lwuit.xml;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;
/**
* The Element class defines a single XML element with its attributes and children.
* Due to its hierarchial nature, this class can be used for a single "leaf" Element, for more complex elements (with child elements), and up to describing the entire document.
*
* @author Ofir Leitner
*/
public class Element {
/**
* A constant that can be used for the get descendants methods to denote infinite recursion
*/
public static final int DEPTH_INFINITE = Integer.MAX_VALUE;
/**
* True if this is a text element, false otherwise
*/
private boolean textElement;
/**
* The element's name (or text for text elements)
*/
private String name;
/**
* A vector containing this element's children
*/
private Vector children;
/**
* This element's parent
*/
private Element parent;
/**
* A hashtable containing this element's attributes
*/
private Hashtable attributes;
boolean isComment;
/**
* Constructs and Element without specifying a name
* This can be used by subclasses that do not require name assigments.
*/
protected Element() {
}
/**
* Constructs an Element with the specified name
*
* @param tagName The tag name
*/
public Element(String tagName) {
this.name=tagName;
}
/**
* Constructs an Element (or a text element) with the specified name or text.
*
* @param tagName The tag name, or in the case of a text element the element's text
* @param isTextElement true for a text element, false otherwise
*/
public Element(String tagName,boolean isTextElement) {
this(tagName);
textElement=isTextElement;
}
/**
* Returns true if this is a text element, false otherwise
*
* @return true if this is a text element, false otherwise
*/
public boolean isTextElement() {
return textElement;
}
/**
* Returns this Element's tag name
*
* @return the Element's tag name
* @throws IllegalStateException if this is a text element
*/
public String getTagName() {
if (textElement) {
throw new IllegalStateException("Text elements do not have a tag name");
}
return name;
}
/**
* Returns the attributes Hashtable
*
* @return the attributes Hashtable
*/
protected Hashtable getAttributes() {
return attributes;
}
/**
* Adds the specified attribute and value to this Element if it is supported for the Element and has a valid value.
* This method allows creating a key that is non-string to be used by subclasses that optimize attributes retrieval
*
* @param id The attribute ID
* @param value The attribute value
*/
protected void setAttribute(Object id,String value) {
if (attributes==null) {
attributes=new Hashtable();
}
attributes.put(id,value);
}
/**
* Adds the specified Element as a child to this element.
* If the specified element was found to be unsupported (i.e. it's ID is TAG_UNSUPPORTED, it is not added.
*
* @param childElement The child element
*/
public void addChild(Element childElement) {
setChildParent(childElement);
children.addElement(childElement);
//childElement.setParent(this);
}
/**
* Sets this element parent, done interanlly in addChild
*
* @param parent The element's parent
*/
protected void setParent(Element parent) {
this.parent=parent;
}
/**
* Returns this Element's parent
*
* @return this Element's parent
*/
public Element getParent() {
return parent;
}
/**
Returns the number of this Element's children
*
* @return the number of this Element's children
*/
public int getNumChildren() {
if (children==null) {
return 0;
}
return children.size();
}
/**
* Returns the internal children vector
*
* @return the children vector
*/
protected Vector getChildren() {
return children;
}
/**
* Sets the children vector of this Element
*
* @param children The vector to set as this Element's children
*/
protected void setChildren(Vector children) {
this.children=children;
}
/**
* Sets the name or text (for text elements) of this tag
*
* @param name The name or text of this tag
*/
protected void setTagName(String name) {
this.name=name;
}
/**
* Sets this element as a text element
*
* @param textElement true to set this as a text element, false otherwise
*/
protected void setTextElement(boolean textElement) {
this.textElement = textElement;
}
/**
* Returns the Element's child positioned at the specified index
*
* @param index The requested index
* @return child number index of this ELement
* @throws ArrayIndexOutOfBoundsException if the index is bigger than the children's count or smaller than 0
*/
public Element getChildAt(int index) {
if ((index<0) || (children==null) || (index>=children.size())) {
throw new ArrayIndexOutOfBoundsException();
}
return (Element)children.elementAt(index);
}
/**
* Returns an Element's child by a tag name
*
* @param name The child's tag name
* @return the first child with the specified name, or null if not found
*/
public Element getFirstChildByTagName(String name) {
if (children==null) {
return null;
}
int i=0;
Element found=null;
while ((found==null) && (i<children.size())) {
Element child=(Element)children.elementAt(i);
if ((!child.textElement) && (child.getTagName().equalsIgnoreCase(name))) {
found=child;
} else {
i++;
}
}
return found;
}
/**
* Returns the element with the specified ID
*
* @param id The id to find
* @return An element with the id, or null if none found
*/
public Element getElementById(String id) {
String thisId = getAttribute("id");
if ((thisId!=null) && (thisId.equals(id))) {
return this;
}
if (children!=null) {
int i=0;
while (i<children.size()) {
Element child=(Element)children.elementAt(i);
Element match=child.getElementById(id);
if (match!=null) {
return match;
}
i++;
}
}
return null;
}
// private void getElementByIdInternal() {
//
// }
private void getDescendantsByTagNameInternal(Vector v,String name,int depth) {
if (children!=null) {
int i=0;
while (i<children.size()) {
Element child=(Element)children.elementAt(i);
if (depth>0) {
child.getDescendantsByTagNameInternal(v, name,depth-1);
}
if ((!child.textElement) && (child.getTagName().equalsIgnoreCase(name))) {
v.addElement(child);
}
i++;
}
}
}
/**
* Returns all descendants with the specified tag name
*
* @param name The tag name to look for
* @param depth The search depth (1 - children, 2 - grandchildren .... DEPTH_INFINITE - for all descendants)
*
* @return A vector containing descendants with the specified tag name
*/
public Vector getDescendantsByTagName(String name,int depth) {
if (depth<1) {
throw new IllegalArgumentException("Depth must be 1 or higher");
}
if (children==null) {
return null;
}
Vector v=new Vector();
getDescendantsByTagNameInternal(v, name, depth);
return v;
}
/**
* Returns all descendants with the specified tag name
*
* @param name The tag name to look for
* @return A vector containing descendants with the specified tag name
*/
public Vector getDescendantsByTagName(String name) {
return getDescendantsByTagName(name, DEPTH_INFINITE);
}
/**
* Returns all children with the specified tag name
*
* @param name The tag name to look for
* @return A vector containing children with the specified tag name
*/
public Vector getChildrenByTagName(String name) {
return getDescendantsByTagName(name, 1);
}
private void getTextDescendantsInternal(Vector v,String text,boolean caseSensitive,int depth) {
if (children==null) {
return;
}
int i=0;
while (i<children.size()) {
Element child=(Element)children.elementAt(i);
if (depth>0) {
child.getTextDescendantsInternal(v, text, caseSensitive, depth-1);
}
if (child.textElement) {
if (text!=null) {
String childText=child.getText();
if (!caseSensitive) {
childText=childText.toLowerCase();
}
int index=childText.indexOf(text);
if (index!=-1) {
v.addElement(child);
}
} else { // if text==null, it means we want all text children
v.addElement(child);
}
}
i++;
}
}
/**
* Returns all text descendants containing the specified text
*
* @param text The text to look for (null to return all text children)
* @param caseSensitive true to perform a case sensitive match, false to ignore case
* @param depth The search depth (1 - children, 2 - grandchildren .... DEPTH_INFINITE - for all descendants)
* @return A vector containing decensants containing the specified text
*/
public Vector getTextDescendants(String text,boolean caseSensitive,int depth) {
if (depth<1) {
throw new IllegalArgumentException("Depth must be 1 or higher");
}
if (children==null) {
return null;
}
if ((!caseSensitive) && (text!=null)) {
text=text.toLowerCase();
}
Vector v=new Vector();
getTextDescendantsInternal(v,text,caseSensitive,depth);
return v;
}
/**
* Returns all text descendants containing the specified text
*
* @param text The text to look for (null to return all text children)
* @param caseSensitive true to perform a case sensitive match, false to ignore case
* @return A vector containing decensants containing the specified text
*/
public Vector getTextDescendants(String text,boolean caseSensitive) {
return getTextDescendants(text, caseSensitive, DEPTH_INFINITE);
}
/**
* Returns all children with the specified text
*
* @param text The text to look for (null to return all text children)
* @param caseSensitive true to perform a case sensitive match, false to ignore case
* @return A vector containing children containing the specified text
*/
public Vector getTextChildren(String text,boolean caseSensitive) {
return getTextDescendants(text, caseSensitive, 1);
}
/**
* Returns true if the specified element is contained in this element's hierarchy (meaning it is one of its descendants)
*
* @param element The element to look for
* @return true if this element contains the specified element, false otherwise
*/
public boolean contains(Element element) {
if (this==element) {
return true;
}
if (children!=null) {
int i=0;
while (i<children.size()) {
Element child=(Element)children.elementAt(i);
if (child.contains(element)) {
return true;
}
i++;
}
}
return false;
}
/**
* Adds the specified attribute and value to this Element if it is supported for the Element and has a valid value.
*
* @param attribute The attribute's name
* @param value The attribute's value
*
* @return a positive error code or -1 if attribute is supported and valid
*/
public int setAttribute(String attribute,String value) {
if (textElement) {
throw new IllegalStateException("Text elements cannot have attributes");
}
setAttribute((Object)attribute, value);
/*if (attributes==null) {
attributes=new Hashtable();
}
attributes.put(attribute, value);*/
return -1;
}
/**
* Removes the specified attribute
*
* @param attribute The attribute to remove
*/
public void removeAttribute(String attribute) {
removeAttribute((Object)attribute);
}
/**
* Removes the specified attribute if it exist in this Element
* This method allows creating a key that is non-string to be used by subclasses that optimize attributes retrieval
*
* @param id The attribute ID
*/
protected void removeAttribute(Object id) {
if (attributes!=null) {
attributes.remove(id);
if (attributes.isEmpty()) {
attributes=null;
}
}
}
/**
* Returns the attribute value by its name (or null if it wasn't defined for this element)
*
* @param name The attribute id
* @return the attribute value by its name (or null if it wasn't defined for this element)
*/
public String getAttribute(String name) {
if (attributes==null) {
return null;
}
return (String)attributes.get(name);
}
private void setChildParent(Element child) {
if (textElement) {
throw new IllegalStateException("Text elements cannot have children");
}
if (child.getParent()!=null) {
throw new IllegalStateException("An Element can't have two parents.");
}
if (children==null) {
children=new Vector();
}
child.setParent(this);
}
/**
* Removes the child at the given index
*
* @param index The child's index
*/
public void removeChildAt(int index) {
if ((index<0) || (children==null) || (index>=children.size())) {
throw new ArrayIndexOutOfBoundsException();
}
Element child=(Element)children.elementAt(index);
child.setParent(null);
children.removeElementAt(index);
}
/**
* Returns the child index
*
* @param child The child element to look for
* @return The child position, or -1 if the child does not belong to this element.
*/
public int getChildIndex(Element child) {
int result=-1;
if (children!=null) {
for(int i=0;i<children.size();i++) {
if (child==children.elementAt(i)) {
result=i;
break;
}
}
}
return result;
}
/**
* Inserts the given child at the specified index
*
* @param child The child to insert
* @param index The index to insert it at
*/
public void insertChildAt(Element child,int index) {
setChildParent(child);
children.insertElementAt(child, index);
}
/**
* Replaces one child with another
*
* @param oldChild The child to replace (Must belong to this element, otherwise a call to this method will have no effect)
* @param newChild The child to replace it with
*/
public void replaceChild(Element oldChild,Element newChild) {
if (children!=null) {
setChildParent(newChild);
int index=children.indexOf(oldChild);
if (index!=-1) {
children.insertElementAt(newChild, index);
removeChildAt(index+1);
// children.removeElement(oldChild);
// oldChild.setParent(null);
return;
}
}
throw new IllegalArgumentException("The oldChild element specified must be this element's child");
}
/**
* Returns the text of this element (for text elements only)
*
* @return the text of this element (for text elements only)
* @throws IllegalStateException if this is not a text element
*/
public String getText() {
if (!textElement) {
throw new IllegalStateException("Only text elements can get text");
}
return name;
}
/**
* Sets the text of this element to the specified string (For text elements only)
*
* @param str The text to set
* @throws IllegalStateException if this is not a text element
*/
public void setText(String str) {
if (!textElement) {
throw new IllegalStateException("Only text elements can set text");
}
name=str;
}
/**
* Returns a printable string representing this element
*
* @return a printable string representing this element
*/
public String toString() {
return toString("");
}
/**
* A recursive method for creating a printout of a full tag with its entire hierarchy.
* This is used by the public method toString().
*
* @param spacing Increased by one in each recursion phase to provide with indentation
* @return the printout of this tag
*/
private String toString(String spacing) {
String str=spacing;
if (!textElement) {
str+="<"+getTagName();
if (attributes!=null) {
for(Enumeration e=attributes.keys();e.hasMoreElements();) {
String attrStr=(String)e.nextElement();
String val=(String)attributes.get(attrStr);
str+=" "+attrStr+"='"+val+"'";
}
}
str+=">\n";
if (children!=null) {
for(int i=0;i<children.size();i++) {
str+=((Element)children.elementAt(i)).toString(spacing+' ');
}
}
str+=spacing+"</"+getTagName()+">\n";
} else {
str+="'"+name+"'\n";
}
return str;
}
}