Package javaforce

Source Code of javaforce.XML$XMLTagPart

package javaforce;

import java.io.*;
import java.util.ArrayList;
import java.util.Iterator;
import javax.swing.tree.TreeModel;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreePath;
import javax.swing.event.TreeModelListener;
import javax.swing.event.TreeModelEvent;
import java.lang.reflect.Field;
import java.awt.Color;

/** XML is a TreeModel data model that encapsules a complete XML file.
* Each XML tag (element) is treated as a node in the tree.
* Once read() it can be viewed and edited with a JTree.
* Then you can write() it back to a file.
* XML will monitor changes made and update nodes as needed.
* The read() functions include a callback interface so you can further tweak
* the layout of the XML tree.<br>
* Typical XML Tag:<br>
*   &lt;name [arguments...]&gt; content | children &lt;/name&gt;<br>
* Singleton XML Tag: (no children)<br>
*   &lt;name [arguments...] /&gt;<br>
* Cavets:<br>
*   Only leaf nodes can contain actual data (content) (in other words @XmlMixed is not fully supported).<br>
*     Mixed tags are read, but when writen the content is lost.
*   There must be only one root tag.<br>
*   Support for the standard XML header is provided (see header).<br>
*/

public class XML implements TreeModelListener {
  private DefaultTreeModel treemodel;
  private class XMLTagPart {
    private String content;
    private String args;
    public XMLTagPart() {
      content = "";
      args = "";
    }
  }
  /** XMLEvent is an interface for a callback handler used during XML loading.
   */
  public interface XMLEvent {
    public void XMLTagAdded(XMLTag tag);
    public void XMLTagRenamed(XMLTag tag);
  };
  /** XMLAttr is one attribute that is listed in each XML tag.
   */
  public class XMLAttr {
    public String name, value;
    public XMLAttr() {
      name = "";
      value = "";
    }
  };
  /** XMLTag is one node in the tree that represents one XML element or 'tag'.
   * @param name  the XML tag name
   * @param args  an ArrayList of XMLAttr (arguments)
   * @param uname  the unique name of the tag.  Usually equals name unless another child with
   *   the same parent has the same name.  JTree uses uname to display the tags.
   * @param content  the data within the tags head/tail
   * @param isLeaf  set to force JTree to view node as a leaf
   * @param isNotLeaf  set to force JTree to view node that is expandable (even if it has no children)
   * @param isReadOnly  ignores edits from JTree
   */
  public class XMLTag extends DefaultMutableTreeNode {
    public String name = "";
    public ArrayList<XMLAttr> args;
    public String uname = ""//unique name (usually same as name)
    public String content = "";
    public boolean isSingle = false;
    public boolean isNotLeaf = false;
    public boolean isLeaf = false;
    public boolean isReadOnly = false;
    /** Constructs a new XMLTag
     */
    public XMLTag() {
      args = new ArrayList<XMLAttr>();
    }
    /** Returns the unique name of the tag.
     */
    public String toString() {
      return getName();
    }
    /** Returns the parent of the tag.
     * This method just overrides the default method but returns XMLTag.
     */
    public XMLTag getParent() {
      if (this == treemodel.getRoot()) return null//in case setRoot() moved the root up
      return (XMLTag)super.getParent();
    }
    /** Returns true if the node is a leaf.
     * This method just overrides the default method and allows better leaf control.
     */
    public boolean isLeaf() {
      if (isNotLeaf) return false;
      if (isLeaf) return true;
      return (getChildCount() == 0);
    }
    /** Returns a unique name for this node.
     */
    public String getName() {
      return uname;
    }
    /** Returns a real name for this node (may not be unique).
     */
    public String getXMLName() {
      return name;
    }
    /** Sets the name for this node.
     */
    public void setName(String newName) {
      //check if name="..." is in args, else use name (and update uname)
      XMLAttr attr;
      boolean ok = false;
      for(Iterator i = args.iterator(); i.hasNext();) {
        attr = (XMLAttr)i.next();
        if (attr.name.equals("name")) {attr.value = newName; ok = true; break;}
      }
      if (!ok) name = newName;
      setuname(this);
    }
    /** Returns the child tag at index.
     * This method just overrides the default method but returns XMLTag.
     */
    public XMLTag getChildAt(int index) {
      return (XMLTag)super.getChildAt(index);
    }
    /** Returns value of argument. */
    public String getArg(String name) {
      for(int a=0;a<args.size();a++) {
        if (args.get(a).name.equals(name)) return args.get(a).value;
      }
      return null;
    }
    /** Returns the content of this node. */
    public String getContent() {return content;}
  };
  /** The header tag.<br>
   * <?xml version="1.0" encoding="UTF-8" ?>
   */
  public XMLTag header = new XMLTag();
  /** The root tag.
   */
  public XMLTag root = new XMLTag();
  /** Constructs a new XML object.
   */
  public XML() {
    treemodel = new DefaultTreeModel(root);
    treemodel.addTreeModelListener(this);
    treemodel.setRoot(root);
  }
  /** Returns the TreeModel that can be passed to JTree constructor.
   */
  public TreeModel getTreeModel() {return treemodel;}
  public DefaultTreeModel getDefaultTreeModel() {return treemodel;}
  private final int XML_OPEN = 1;
  private final int XML_DATA = 2;
  private final int XML_CLOSE = 3;
  private final int XML_SINGLE = 4;
  private int type, nexttype;
  private XMLTagPart readtag(InputStream in) {
    boolean quote = false, isArgs = false;
    int ich;
    char ch;
    XMLTagPart tag = new XMLTagPart();

    type = XML_DATA;
    if (nexttype != -1) {type = nexttype; nexttype = -1;}
    while (true) {
      try {ich = in.read();} catch(Exception e) {break;}
      if (ich == -1) break;
      ch = (char)ich;
      switch (type) {
        case XML_OPEN:
        case XML_CLOSE:
        case XML_SINGLE:
          if (ch == '\"') {
            quote = !quote;
          }
          if (!quote) {
            if (ch == '/') {
              if (tag.content.length() == 0)
                type = XML_CLOSE;
              else
                type = XML_SINGLE;
              continue;
            }
            if (ch == '>') break;
          }
          if ((ch == ' ') && (!isArgs)) isArgs = true;
          if (isArgs) tag.args += ch; else tag.content += ch;
          continue;
        case XML_DATA:
          if ((ch == '<') && (!quote)) {
            if (tag.content.length() > 0) {nexttype = XML_OPEN; break;}
            type = XML_OPEN;
            continue;
          }
          if (ch == '\"') if (quote) quote = false; else quote = true;
          tag.content += ch;
          continue;
      }
      break;
    }
    if (tag.content.length() == 0) return null//EOF
    if (type == XML_DATA) tag.content = decodeSafe(tag.content);
    return tag;
  }
  private void string2args(XMLTag tag, String args) {
    //search for name="value"
    char ca[] = args.toCharArray();
    XMLAttr attr;
    String name, value;
    int length = args.length();
    int ep;
    tag.args.clear();
    for(int a=0;a<length;a++) {
      if (ca[a] == ' ') continue//skip spaces
      ep = args.indexOf('=', a);
      if (ep == -1) return;
      name = "";
      for(int b=a;b<ep;b++) name += ca[b];
      a = ep+1;
      value = "";
      if (ca[a] == '\"') {
        a++;
        ep = args.indexOf('\"', a);
        if (ep == -1) return;
        for(int b=a;b<ep;b++) value += ca[b];
        a = ep+1;
      } else {
        ep = args.indexOf(' ', a);
        if (ep == -1) ep = length-1;
        if (ep <= a) return;
        for(int b=a;b<ep;b++) value += ca[b];
        a = ep+1;
      }
      attr = new XMLAttr();
      attr.name = name;
      attr.value = value;
      tag.args.add(attr);
    }
  }
  private void setuname(XMLTag tag) {
    XMLTag parent = tag.getParent();
    String uname = tag.name;
    XMLAttr attr;
    for(Iterator i = tag.args.iterator(); i.hasNext();) {
      attr = (XMLAttr)i.next();
      if (attr.name.equalsIgnoreCase("name")) {uname = attr.value; break;}
    }
    String orguname = uname;
    if (parent == null) {
      tag.uname = uname;
      changedTag(tag);
      return;
    }
    boolean ok;
    int idx = 1;
    int size = parent.getChildCount();
    while (true) {
      ok = true;
      for(int a=0;a<size;a++) {
        XMLTag child = (XMLTag)parent.getChildAt(a);
        if (child == tag) continue;
        if (child.getName().equalsIgnoreCase(uname)) {ok = false; break;}
      }
      if (ok) break;
      uname = orguname + idx;
      idx++;
    }
    tag.uname = uname;
    changedTag(tag);
  }
  /** Reads the entire tree from a XML file from filename.
   * No call handler is used.
   * @param filename  name of file to load XML data from
   */
  public boolean read(String filename) {
    return read(filename, null);
  }
  /** Reads the entire tree from a XML file from filename.
   * @param filename  name of file to load XML data from
   * @param event  callback handler to process each loaded XML tag
   */
  public boolean read(String filename, XMLEvent event) {
    FileInputStream fis;
    boolean ret = false;
    try {
      fis = new FileInputStream(filename);
      ret = read(fis, event);
      fis.close();
    } catch (Exception e) {
      return false;
    }
    return ret;
  }
  /** Reads the entire tree from a XML file from the InputStream.
   * No callback handler is used.
   * @param in  InputStream to load XML data from
   */
  public boolean read(InputStream in) {
    return read(in, null);
  }
  /** Reads the entire tree from a XML file from the InputStream.
   * @param in  InputStream to load XML data from
   * @param event  callback handler to process each loaded XML tag
   */
  public boolean read(InputStream in, XMLEvent event) {
    this.event = event;
    deleteAll();
    type = XML_DATA;
    nexttype = -1;
    XMLTagPart tagpart;
    XMLTag tag = null, newtag;
    boolean bRoot = false;
    boolean bHeader = false;
    while (true) {
      tagpart = readtag(in);
      if (tagpart == null) break;
      switch (type) {
        case XML_OPEN:
          if (tagpart.content.startsWith("?xml")) {
            if (bHeader) {JFLog.log("Multiple XML headers found"); return false;//already read the XML header
            header.name = tagpart.content;
            header.uname = header.name;
            string2args(header, tagpart.args);
            if (event != null) event.XMLTagAdded(header);
            break;
          }
          //no break
        case XML_SINGLE:
          if (tag == null) {
            //root tag
            if (bRoot) {JFLog.log("Multiple root tags found"); return false;//already found a root tag
            bRoot = true;
            root.name = tagpart.content;
            root.uname = root.name;
            string2args(root, tagpart.args);
            if (event != null) event.XMLTagAdded(root);
            changedTag(root);
            tag = root;
          } else {
            newtag = new XMLTag();
            newtag.name = tagpart.content;
            string2args(newtag, tagpart.args);
            addTag(tag, newtag);
            if (type == XML_SINGLE)
              newtag.isSingle = true;
            else
              tag = newtag;
          }
          break;
        case XML_CLOSE:
          if (tag == null) {JFLog.log("XML tag closed but never opened"); return false;//bad xml file
          if (!tagpart.content.equalsIgnoreCase(tag.name)) {JFLog.log("XML tag closed doesn't match open"); return false;//unmatched closing tag
          tag = (XMLTag)tag.getParent();
          break;
        case XML_DATA:
          if (tag == null) continue//could happen after header and before root tag
          tag.content += tagpart.content;
          break;
      }
    }
    if (tag != null) {JFLog.log("XML tag left open"); return false;//tag left open
    return true;
  }
  private String args2string(XMLTag tag) {
    XMLAttr attr;
    int size = tag.args.size();
    String str = "", tmp;
    for(int a=0;a<size;a++) {
      attr = tag.args.get(a);
      tmp = " " + attr.name + "=\"" + attr.value + "\"";
      str += tmp;
    }
    return str;
  }
  private void writestr(OutputStream out, String str) {
//    if (str.length() == 0) return;
    try {out.write(str.getBytes());} catch (Exception e) {}
  }
  private int indent;
  private void writetag(OutputStream out, XMLTag tag) {
    String tmp;
    tmp = "";
    for(int a=0;a<indent;a++) tmp += ' ';
    writestr(out, tmp);
    int size = tag.getChildCount();
    String args;
    if (size > 0) {
      //write open tag w/ args + content
      args = args2string(tag);
      tmp = "<" + tag.name + args + ">\n";
      writestr(out, tmp);
      indent += 2;
      //write children
      for(int a=0;a<size;a++) writetag(out, (XMLTag)tag.getChildAt(a));
      //write close tag
      indent -= 2;
      tmp = "";
      for(int a=0;a<indent;a++) tmp += ' ';
      writestr(out, tmp);
      tmp = "</" + tag.name + ">\n";
      writestr(out, tmp);
    } else {
      args = args2string(tag);
      if (tag.isSingle) {
        tmp = "<" + tag.name + args + "/>\n";
      } else {
        tmp = "<" + tag.name + args + ">" + encodeSafe(tag.content) + "</" + tag.name + ">\n";
      }
      writestr(out, tmp);
    }
  }
  /** Writes the entire tree as a XML file to the filename.
   */
  public boolean write(String filename) {
    FileOutputStream fos;
    boolean ret = false;
    try {
      fos = new FileOutputStream(filename);
      ret = write(fos);
      fos.close();
    } catch (Exception e) {
      return false;
    }
    return ret;
  }
  /** Writes the entire tree as a XML file to the OutputStream.
   */
  public boolean write(OutputStream out) {
    String tmp, args;
    if (root.name.length() == 0) return false;
    if (header.name.length() > 0) {
      args = args2string(header);
      tmp = "<" + header.name + args + ">\n";
      writestr(out, tmp);
    }
    //write root header
    args = args2string(root);
    tmp = "<" + root.name + args + ">\n";
    writestr(out, tmp);
    int size = root.getChildCount();
    indent = 2;
    for(int a=0;a<size;a++) writetag(out, (XMLTag)root.getChildAt(a));
    //write root tail
    tmp = "</" + root.name + ">\n";
    writestr(out, tmp);
    return true;
  }
  private void clearTag(XMLTag tag) {
    tag.name = "";
    tag.args = new ArrayList<XMLAttr>();
    tag.uname = "";
    tag.content = "";
  }
  /** Deletes the entire tree and resets the root and header tags.
   */
  public void deleteAll() {
    deleteTag(root);
    clearTag(header);
    changedTag(header);
    clearTag(root);
    changedTag(root);
  }
  /** Deletes a node from the parent.
   * Also deletes all it's children.
   */
  public boolean deleteTag(XMLTag tag) {
    //remove children first
    while (tag.getChildCount() > 0) {
      deleteTag((XMLTag)tag.getChildAt(0));
    }
    //now remove itself from parent
    if (tag.getParent() == null) return true//can not delete root/header tag itself
    treemodel.removeNodeFromParent(tag);
    return true;
  }
  private XMLEvent event = null;
  /** Creates an empty node that can be inserted into the tree using addTag().
   */
  public XMLTag createTag() {
    return new XMLTag()//must call addTag() to add to the tree
  }
  /** Adds the node to the targetParent.
   */
  public XMLTag addTag(XMLTag targetParent, XMLTag tag) {
    treemodel.insertNodeInto(tag, targetParent, targetParent.getChildCount());
    setuname(tag);
    if (event != null) event.XMLTagAdded(tag);
    return tag;
  }
  /** Adds node with the name, args and content specified.
   * If another node already exists with the same name the new node's unique name will differ.
   */
  public XMLTag addTag(XMLTag targetParent, String name, String args, String content) {
    XMLTag newtag = new XMLTag();
    newtag.name = name;
    newtag.content = content;
    string2args(newtag, args);
    return addTag(targetParent, newtag);
  }
  /** Adds (a non-existing) or sets (an existing) node with the name, args and content specified.
   */
  public XMLTag addSetTag(XMLTag targetParent, String name, String args, String content) {
    XMLTag child;
    int len = targetParent.getChildCount();
    for(int a=0;a<len;a++) {
      child = targetParent.getChildAt(a);
      if (child.name.equals(name)) {setTag(child, name, args, content); return child;}
    }
    return addTag(targetParent, name, args, content);
  }
  /** Notify the treemodel that you changed a node.
   */
  public void changedTag(XMLTag tag) {
    treemodel.nodeChanged(tag);
  }
  /** Sets the name, args and contents of the true root node.
   */
  public void setRoot(String name, String args, String content) {
    root.name = name;
    string2args(root, args);
    root.content = content;
    changedTag(root);
  }
  /** Returns the unique name of a node.
   */
  public String getName(XMLTag tag) {
    return tag.getName();
  }
  /** Sets the name of a node.
   * It's unique name may differ when shown in a tree.
   */
  public void setName(XMLTag tag, String newName) {
    tag.setName(newName);
  }
  /** Returns a node based on the TreePath path;
   */
  public XMLTag getTag(TreePath path) {
    return getTag(path.getPath());
  }
  /** Returns a node based on the objs[] path.
   * Relative to virtual root tag. (see setRoot())
   */
  public XMLTag getTag(Object objs[]) {
    XMLTag tag = (XMLTag)treemodel.getRoot(), child;
    String name;
    if (objs == null || objs.length == 0) return null;
    name = tag.getName();
    if (!name.equals(objs[0].toString())) return null;
    int idx = 1;
    boolean ok;
    int cnt;
    while (idx < objs.length) {
      ok = false;
      cnt = tag.getChildCount();
      for(int i=0;i<cnt;i++) {
        child = (XMLTag)tag.getChildAt(i);
        name = child.getName();
        if (name.equals(objs[idx].toString())) {
          ok = true;
          idx++;
          tag = child;
          break;
        }
      }
      if (!ok) return null//next path element not found
    }
    return tag;
  }
  /** Sets the name, args and content for an existing node.
   */
  public void setTag(XMLTag tag, String name, String args, String content) {
    tag.name = name;
    string2args(tag, args);
    tag.content = content;
    if (tag.getParent() != null) setuname(tag); else tag.uname = name;
    if (event != null) event.XMLTagAdded(tag);
  }
  /** Sets the root node for the tree.
   * This doesn't effect the true root node.
   * This is usefull in hiding parts of a XML file from view when viewed in a JTree.
   */
  public void setRoot(XMLTag newRoot) {
    treemodel.setRoot(newRoot);
  }
  /** Writes all children of tag to a POJO Class.
   * Fields not found in POJO are not saved.<br>
   * Currently supports : int, boolean, String, Color.
   * @return Number of elements saved.
   */
  public int writeClass(XMLTag tag, Object pojo) {
    Class<?> c = pojo.getClass();
    Field f;
    int cnt = tag.getChildCount();
    int ret = 0;
    XMLTag child;
    String name, type;
    for(int a=0;a<cnt;a++) {
      try {
        child = tag.getChildAt(a);
        name = child.getName();
        f = c.getField(name);
        type = f.toGenericString();
        if (type.indexOf(" int ") != -1) {
          f.setInt(pojo, JF.atoi(child.content));
        } else if (type.indexOf(" boolean ") != -1) {
          f.setBoolean(pojo, child.content.equals("1") || child.content.equals("true"));
        } else if (type.indexOf(" java.lang.String ") != -1) {
          f.set(pojo, child.content);
        } else if (type.indexOf(" java.awt.Color ") != -1) {
          f.set(pojo, new Color(JF.atox(child.content)));
        } else {
          continue;
        }
        ret++;
      } catch (Exception e) {}
    }
    return ret;
  }
  /** Reads all fields from a POJO Class to children of tag.
   * Creates new children tags if needed.<br>
   * Currently supports : int, boolean, String, Color.
   * @return Number of elements loaded/created.
   */
  public int readClass(XMLTag tag, Object pojo) {
    Class<?> c = pojo.getClass();
    Field fs[];
    int ret = 0;
    XMLTag child = null;
    String name, type;
    try {fs = c.getFields();} catch (Exception e) {return 0;}
    int fieldcnt = fs.length;
    int cnt;
    boolean ok;  //child tag already exists with field's name
    for(int a=0;a<fieldcnt;a++) {
      try {
        ok = false;
        cnt = tag.getChildCount();
        name = fs[a].getName();
        for(int b=0;b<cnt;b++) {
          child = tag.getChildAt(b);
          if (child.getName().equals(name)) {
            ok = true;
            break;
          }
        }
        type = fs[a].toGenericString();
        if (type.indexOf(" int ") != -1) {
          if (ok) {
            child.content = "" + fs[a].getInt(pojo);
          } else {
            addTag(tag, name, "", "" + fs[a].getInt(pojo));
          }
        } else if (type.indexOf(" boolean ") != -1) {
          if (ok) {
            child.content = fs[a].getBoolean(pojo) ? "true" : "false";
          } else {
            addTag(tag, name, "", fs[a].getBoolean(pojo) ? "true" : "false");
          }
        } else if (type.indexOf(" java.lang.String ") != -1) {
          if (ok) {
            child.content = (String)fs[a].get(pojo);
          } else {
            addTag(tag, name, "", (String)fs[a].get(pojo));
          }
        } else if (type.indexOf(" java.awt.Color ") != -1) {
          if (ok) {
            child.content = Integer.toHexString( ((Color)fs[a].get(pojo)).getRGB()).substring(2);
          } else {
            addTag(tag, name, "", Integer.toHexString( ((Color)fs[a].get(pojo)).getRGB()).substring(2));
          }
        } else {
          continue;
        }
        ret++;
      } catch (Exception e) {}
    }
    return ret;
  }
  public void setEventListener(XMLEvent event) {
    this.event = event;
  }
  private String encodeSafe(String in) {
    return in.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;")//order matters here
  }
  private String decodeSafe(String in) {
    return in.replaceAll("&gt;", ">").replaceAll("&lt;", "<").replaceAll("&amp;", "&");
  }
//interface TreeModelListener
  public void treeNodesChanged(TreeModelEvent e) {
    XMLTag parent = (XMLTag)(e.getTreePath().getLastPathComponent());
    int indices[] = e.getChildIndices();
    if (indices == null || indices.length == 0) return;
    /*
     * If the event lists children, then the changed
     * node is the child of the node we've already
     * gotten.  Otherwise, the changed node and the
     * specified node are the same.
     */
    int index = indices[0];
    XMLTag tag = (XMLTag)(parent.getChildAt(index));
    if (tag.isReadOnly) return;
    if (tag.getUserObject() == null) return;
    tag.setName(tag.getUserObject().toString());
    if (event != null) event.XMLTagRenamed(tag);
  }
  public void treeNodesInserted(TreeModelEvent e) {
  }
  public void treeNodesRemoved(TreeModelEvent e) {
  }
  public void treeStructureChanged(TreeModelEvent e) {
  }
};
TOP

Related Classes of javaforce.XML$XMLTagPart

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.