zcommon/src/main/java/org/zkoss/idom/util/IDOMs.java
/* IDOMs.java
Purpose:
Description:
History:
2002/01/08 10:37:16, Create, Tom M. Yeh
Copyright (C) 2001 Potix Corporation. All Rights Reserved.
{{IS_RIGHT
This program is distributed under LGPL Version 2.1 in the hope that
it will be useful, but WITHOUT ANY WARRANTY.
}}IS_RIGHT
*/
package org.zkoss.idom.util;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.stream.StreamResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.zkoss.idom.Attribute;
import org.zkoss.idom.Document;
import org.zkoss.idom.Element;
import org.zkoss.idom.Group;
import org.zkoss.idom.Item;
import org.zkoss.idom.Text;
import org.zkoss.idom.transform.Transformer;
import org.zkoss.lang.Classes;
import org.zkoss.lang.Objects;
import org.zkoss.mesg.MCommon;
import org.zkoss.util.IllegalSyntaxException;
/**
* The iDOM relevant utilities.
*
* @author tomyeh
* @see org.zkoss.idom.Item
* @see org.zkoss.idom.Group
*/
public class IDOMs {
private static final Logger log = LoggerFactory.getLogger(IDOMs.class);
/** Returns the required element.
* @param elemnm the element name
*/
public static final Element getRequiredElement(Element e, String elemnm)
throws IllegalSyntaxException {
final Element sub = e.getElement(elemnm);
if (sub == null)
throw new IllegalSyntaxException(
MCommon.XML_ELEMENT_REQUIRED, new Object[] {elemnm, e.getLocator()});
return sub;
}
/** Returns the required element value.
* <p>Note: the returned value may be an empty string (if the element
* contains no text at all).
* @exception IllegalSyntaxException if the element is not found
*/
public static final String getRequiredElementValue(Element e, String elemnm)
throws IllegalSyntaxException {
final Element sub = e.getElement(elemnm);
if (sub == null)
throw new IllegalSyntaxException(
MCommon.XML_ELEMENT_REQUIRED, new Object[] {elemnm, e.getLocator()});
return sub.getText(true);
}
/** Returns the required attribute value.
* @exception IllegalSyntaxException if the element is not found
*/
public static final String getRequiredAttributeValue(Element e, String attrnm)
throws IllegalSyntaxException {
final Attribute attr = e.getAttributeItem(attrnm);
if (attr == null)
throw new IllegalSyntaxException(
MCommon.XML_ATTRIBUTE_REQUIRED, new Object[] {attrnm, e.getLocator()});
return attr.getValue();
}
/** Returns the first child element, or null if no child element at all.
*/
public static final Element getFirstElement(Group group) {
final Iterator it = group.getElements().iterator();
return it.hasNext() ? (Element)it.next(): null;
}
/** Returns the first element whose sub-element called "name" has the
* same content as the name argument, or null if not found.
*
* @param elems a list of elements to look for the specified name
*/
public static final Element findElement(List elems, String name) {
for (final Iterator it = elems.iterator(); it.hasNext();) {
final Element e = (Element)it.next();
if (Objects.equals(name, e.getElementValue("name", true)))
return e;
}
return null;
}
/** Parses a tree of parameter elements into a map.
*
* <p>The tree of parameter elements is as follows.
* <pre><code>
* <type>
* <name>any</name>
* <vaue>any</vaue>
* </type>
*
* @return the map after parsed (never null). An empty map is returned
* if no parameter is defined. The map is in the same order of the element tree.
*/
public static final
Map<String, String> parseParams(Element elm, String type, String name, String value) {
final Map<String, String> map = new LinkedHashMap<String, String>();
for (Iterator it = elm.getElements(type).iterator(); it.hasNext();) {
final Element el = (Element)it.next();
final String nm = getRequiredElementValue(el, name);
final String val = getRequiredElementValue(el, value);
map.put(nm, val);
}
return map;
}
/** Formats the specified element for better readability by
* adding white spaces.
*/
public static void format(Element e) {
//add proper spacing between consecutive elements
boolean elemFound = true;
for (final ListIterator<Item> it = e.getChildren().listIterator(); it.hasNext();) {
final Object o = it.next();
if (o instanceof Element) {
if (elemFound) { //insert space
it.previous();
it.add(new Text("\n\t"));
it.next();
} else {
elemFound = true;
}
format((Element)o); //recursive
} else {
elemFound = false;
}
}
}
/** Converts elements to their contents if the giving object is
* an element or an array or a collection of elements.
* One item of an collection might be another collection or array.
*/
@SuppressWarnings("unchecked")
public static final Object toContents(Object obj) {
if (obj instanceof Collection) {
Collection c = (Collection)obj;
boolean cvted = false;
Collection rets = new LinkedList();
for (Iterator it = c.iterator(); it.hasNext();) {
Object o = it.next();
Object o2 = toContents(o); //recursive
if (o != o2)
cvted = true;
rets.add(o2);
}
if (cvted)
return rets;
} else if (obj instanceof Object[]) {
Object[] ary = (Object[])obj;
boolean cvted = false;
Object[] rets = new Object[ary.length];
for (int j = 0; j < ary.length; ++j) {
Object o2 = toContents(ary[j]); //recursive
if (ary[j] != o2)
cvted = true;
rets[j] = o2;
}
if (cvted)
return rets;
} else if (obj instanceof Element) {
return ((Element)obj).getContent();
}
return obj;
}
/** Set the contents of elements.
* The val argument could be an array and a collection, and each
* item will be assigned to each of the list one-by-one.
*
* <p>Unlike {@link #toContents}, it handles only a collection of elements
* -- all items must be elements.
*
* @param elems the collection of elements
* @param val the value which could be an object, an array or a collection
*/
public static final void setContents(Collection<Element> elems, Object val) {
Object[] ary = null;
if (val instanceof Object[]) {
ary = (Object[])val;
} else if (val instanceof Collection) {
ary = ((Collection)val).toArray();
} else {
ary = new Object[] {val};
}
Iterator<Element> it = elems.iterator();
for (int j = 0; it.hasNext(); ++j) {
it.next().setContent(j < ary.length ? ary[j]: null);
}
}
/** Transforms a document to a string.
* The string is XML correct.
*/
public final static String toString(Document doc)
throws TransformerConfigurationException, TransformerException {
final StringWriter writer = new StringWriter();
new Transformer().transform(doc, new StreamResult(writer));
return writer.toString();
}
/**
* Print a readable tree of the specified group to System.out.
* It is for debug purpose and the generated format is <i>not</i> XML.
* To generate XML, uses {@link Transformer} or {@link #toString}.
* @see #toString
*/
public static final void dumpTree(Group group) {
dumpTree(System.out, group);
}
/**
* Print a readable tree of the specified group to the specified stream.
* It is for debug purpose and the generated format is <i>not</i> XML.
* To generate XML, uses {@link Transformer} or {@link #toString}.
* @see #toString
*/
public static final void dumpTree(PrintStream s, Group group) {
dumpTree(new PrintWriter(s, true), group);
}
/**
* Print a readable tree of the specified group to the specified writer.
* It is for debug purpose and the generated format is <i>not</i> XML.
* To generate XML, uses {@link Transformer} or {@link #toString}.
* @see #toString
*/
public static final void dumpTree(PrintWriter s, Group group) {
dumpTree(s, group, "");
}
private static final void
dumpTree(PrintWriter s, Item vtx, String prefix) {
s.print(prefix);
s.print(vtx);
if (vtx instanceof Group) {
prefix = prefix + " ";
for (Iterator it = ((Group)vtx).getChildren().iterator();
it.hasNext();)
dumpTree(s, (Item)it.next(), prefix);
}
}
/** Returns whether the loaded document's version is correct.
*
* <p>It assumes the version info is specified in the document in
* the following format:
*
* <pre></code>
<version>
<version-class>org.zkoss.zul.Version</version-class>
<version-uid>3.0.0</version-uid>
</version>
</code></pre>
*
* <p>Note: it returns true if the version info is not found.
*
* @param doc the document to check
* @param url the URL used to show the readable message if the
* version doesn't match
* @since 3.0.0
*/
public static boolean checkVersion(Document doc, URL url)
throws Exception {
final Element el = doc.getRootElement().getElement("version");
if (el != null) {
final String clsnm = IDOMs.getRequiredElementValue(el, "version-class");
final String uid = IDOMs.getRequiredElementValue(el, "version-uid");
final Class cls = Classes.forNameByThread(clsnm);
final Field fld = cls.getField("UID");
final String uidInClass = (String)fld.get(null);
if (uid.equals(uidInClass)) {
return true;
} else {
log.info("Ignore "+url+"\nCause: version not matched; expected="+uidInClass+", xml="+uid);
return false;
}
} else {
return true;
}
}
}