/* 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; } } }