/**
* Created on 10-Jan-2006
* Created by Allan Crooks
* Copyright (C) 2006 Aelitis, All Rights Reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* This program 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 for more details.
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* AELITIS, SAS au capital de 46,603.30 euros
* 8 Allee Lenotre, La Grille Royale, 78600 Le Mesnil le Roi, France.
*
*/
package org.gudy.azureus2.core3.xml.util;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.Map;
import java.util.Iterator;
import java.util.TreeMap;
import java.util.TreeSet;
public class XMLElement {
/**
* This is what the XMLElement holds. It is either:
* a) A single string (text_content); or
* b) A collection of XMLElements.
*
* Both are null at construction time - only one can be
* set.
*/
protected String text_content;
protected Collection<XMLElement> contents;
protected Map<String,String> attributes;
protected String tag_name;
protected boolean auto_order;
public XMLElement(String tag_name) {
this(tag_name, false);
}
public XMLElement(String tag_name, boolean auto_order) {
this.text_content = null;
this.attributes = null;
this.contents = null;
this.tag_name = tag_name;
this.auto_order = auto_order;
}
public String getTag() {
return tag_name;
}
public String getAttribute(String key) {
if (this.attributes == null) {return null;}
return (String)this.attributes.get(key);
}
public void addAttribute(String key, String value) {
if (attributes == null) {
this.attributes = new TreeMap<String,String>(ATTRIBUTE_COMPARATOR);
}
this.attributes.put(key, value);
}
public void addAttribute(String key, int value) {
this.addAttribute(key, String.valueOf(value));
}
public void addAttribute(String key, boolean value) {
this.addAttribute(key, (value) ? "yes" : "no");
}
/**
* Should be called setContent really - the code in the XML/HTTP plugin
* invokes this method under this name.
*/
public void addContent(String s) {
if (s == null)
throw new NullPointerException();
if (this.contents != null)
throw new IllegalStateException("cannot add text content to an XMLElement when it contains child XMLElement objects");
if (this.text_content != null)
throw new IllegalStateException("text content is already set, you cannot set it again");
this.text_content = s;
}
public void addContent(XMLElement e) {
if (e == null)
throw new NullPointerException();
if (this.text_content != null) {
throw new IllegalStateException("cannot add child XMLElement when it contains text content");
}
/**
* Initialise the appropriate collection as soon as we have some content.
*/
if (this.contents == null) {
if (!this.auto_order) {
this.contents = new ArrayList<XMLElement>();
}
else {
this.contents = new TreeSet<XMLElement>(CONTENT_COMPARATOR);
}
}
this.contents.add(e);
}
public void printTo(PrintWriter pw) {
printTo(pw, 0, false);
}
public void printTo(PrintWriter pw, boolean spaced_out) {
printTo(pw, 0, spaced_out);
}
public void printTo(PrintWriter pw, int indent) {
printTo(pw, indent, false);
}
public void printTo(PrintWriter pw, int indent, boolean spaced_out) {
for (int i=0; i<indent; i++) {pw.print(" ");}
/**
* No content results in a simple self-closed tag.
*/
if (this.attributes == null && this.contents == null && this.text_content == null) {
pw.print("<");
pw.print(this.tag_name);
pw.print(" />");
return;
}
pw.print("<");
pw.print(this.tag_name);
// Add attributes to the element.
if (this.attributes != null) {
Iterator<Map.Entry<String,String>> itr = this.attributes.entrySet().iterator();
while (itr.hasNext()) {
Map.Entry<String,String> entry = itr.next();
pw.print(" ");
pw.print(entry.getKey());
pw.print("=\"");
pw.print(quote(entry.getValue()));
pw.print("\"");
}
}
boolean needs_indented_close = (this.contents != null);
boolean needs_close_tag = needs_indented_close || this.text_content != null;
needs_indented_close = needs_indented_close || spaced_out;
needs_close_tag = needs_close_tag || spaced_out;
if (needs_indented_close) {pw.println(">");}
else if (needs_close_tag) {pw.print(">");}
else {pw.print(" />");}
// Add any text content.
if (this.text_content != null) {
if (spaced_out) {
for (int i=0; i<indent+2; i++) {pw.print(" ");}
pw.print(quote(this.text_content));
pw.println();
}
else {
pw.print(quote(this.text_content));
}
}
// Add child sub-elements.
if (this.contents != null) {
Iterator<XMLElement> itr = this.contents.iterator();
while (itr.hasNext()) {
XMLElement content_element = itr.next();
content_element.printTo(pw, indent+2, spaced_out);
}
}
if (needs_indented_close) {
for (int i=0; i<indent; i++) {pw.print(" ");}
}
if (needs_close_tag) {
pw.print("</");
pw.print(this.tag_name);
pw.println(">");
}
}
private String quote(String text) {
text = text.replaceAll( "&", "&" );
text = text.replaceAll( ">", ">" );
text = text.replaceAll( "<", "<" );
text = text.replaceAll( "\"", """ );
text = text.replaceAll( "--", "--" );
return text;
}
public XMLElement makeContent(String tag_name) {
return this.makeContent(tag_name, false);
}
public XMLElement makeContent(String tag_name, boolean auto_order) {
XMLElement content = new XMLElement(tag_name, auto_order);
this.addContent(content);
return content;
}
public void clear() {
this.text_content = null;
this.attributes = null;
this.contents = null;
}
public void setAutoOrdering(boolean mode) {
if (mode == this.auto_order) return;
this.auto_order = mode;
if (this.contents == null) return;
Collection<XMLElement> previous_contents = contents;
if (this.auto_order) {
this.contents = new TreeSet<XMLElement>(CONTENT_COMPARATOR);
this.contents.addAll(previous_contents);
}
else {
this.contents = new ArrayList<XMLElement>(previous_contents);
}
}
public String toString() {
return "XMLElement[" + this.tag_name + "]@" + Integer.toHexString(System.identityHashCode(this));
}
private static final Comparator<String> ATTRIBUTE_COMPARATOR = String.CASE_INSENSITIVE_ORDER;
private static class ContentComparator implements java.util.Comparator<XMLElement> {
public int compare(XMLElement xe1, XMLElement xe2) {
if (xe1 == null || xe2 == null) throw new NullPointerException();
/**
* This is necessary - we don't expect to deal with two elements which
* are fundamentally equal, but we may be asked to compare the same actual
* object against itself when we first populate the collection which uses
* this comparator.
*
* Ideally, we don't want to allow semantically equivalent objects in the
* collection, because it's not clear what we're expected to do when we're
* being asked to order elements. We don't expect, in the general use case,
* to have a caller trying to add the same element twice without providing
* a differing index attribute (what are they trying to achieve - two copies
* at the same time or for it to be silently dropped?)
*
* The behaviour where we compare the same object against itself was
* introduced here in Java 7.
* http://hg.openjdk.java.net/jdk7/tl/jdk/rev/bf37edb38fbb
*/
if (xe1 == xe2) {return 0;}
// Compare tag names.
int result = String.CASE_INSENSITIVE_ORDER.compare(xe1.getTag(), xe2.getTag());
if (result != 0) {return result;}
// Tag names are the same - compare index attributes.
int xe1_index = 0, xe2_index = 0;
try {
xe1_index = Integer.parseInt(xe1.getAttribute("index"));
xe2_index = Integer.parseInt(xe2.getAttribute("index"));
}
catch (NullPointerException ne) {
xe1_index = xe2_index = 0;
}
catch (NumberFormatException ne) {
xe1_index = xe2_index = 0;
}
if (xe1_index != xe2_index) {
return xe1_index - xe2_index;
}
/**
* This is the situation we want to avoid (the one I try to describe
* much earlier in the method) - the two elements aren't the same
* instance and don't differ enough or have enough information to
* describe a natural ordering.
*/
//
throw new IllegalArgumentException("Shouldn't be using sorting for contents if you have tags with same name and no index attribute! (tag: " + xe1.getTag() + ")");
}
}
private static Comparator<XMLElement> CONTENT_COMPARATOR = new ContentComparator();
}