/* * $Id$ * * Copyright (c) 2000-2007 by Rodney Kinney * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License (LGPL) as published by the Free Software Foundation. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, copies are available * at http://www.opensource.org. */ /* * Copyright (c) 2003 by Rodney Kinney. All rights reserved. * Date: May 29, 2003 */ package VASSAL.chat.node; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.ListIterator; import java.util.logging.Logger; import VASSAL.tools.PropertiesEncoder; import VASSAL.tools.SequenceEncoder; /** * Base class for the hierarchical server model. * Each node has a name, a list of children, and arbitrary extra information * encoded in the {@link #getInfo} string. Each node can be identified * globally by a path name. Messages sent to a node generally broadcast to * all descendents of the node. */ public class Node implements MsgSender { private static Logger logger = Logger.getLogger(MsgSender.class.getName()); private String id; private String info; private Node parent; private List<Node> children = new ArrayList<Node>(); public Node(Node parent, String id, String info) { this.parent = parent; this.id = id; this.info = info; } public String getId() { return id; } public String getInfo() { return info; } public String getInfoProperty(String propName) { try { return new PropertiesEncoder(info).getProperties().getProperty(propName); } catch (IOException e) { return null; } } public void setInfo(String info) { this.info = info; } public void setParent(Node parent) { this.parent = parent; } public Node getParent() { return parent; } public void remove(Node child) { logger.finer("Removing "+child+" from "+this); //$NON-NLS-1$ //$NON-NLS-2$ children.remove(child); } public void add(Node child) { // FIXME: added this to find out what is calling add(null) if (child == null) { throw new NullPointerException("child == null"); } if (child.parent != null) { child.parent.remove(child); } logger.finer("Adding "+child+" to "+this); //$NON-NLS-1$ //$NON-NLS-2$ children.add(child); child.setParent(this); } public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof Node)) return false; final Node node = (Node) o; if (id != null ? !id.equals(node.id) : node.id != null) return false; return true; } public int hashCode() { return (id != null ? id.hashCode() : 0); } public String toString() { return super.toString()+"[id="+id+"]"; //$NON-NLS-1$ //$NON-NLS-2$ } /** * return the child node with the given id, or null if no match * @param id * @return */ public Node getChild(String id) { for (Node n : getChildren()) { if (id.equals(n.getId())) { return n; } } return null; } /** * Return the descendant node with the given path relative to this node * @param path * @return */ public Node getDescendant(String path) { Node n = this; SequenceEncoder.Decoder st = new SequenceEncoder.Decoder(path,'/'); while (st.hasMoreTokens() && n != null) { String id = st.nextToken(); n = n.getChild(id); } return n; } public void send(String msg) { for (Node n : getChildren()) { n.send(msg); } } public Node[] getLeafDescendants() { ArrayList<Node> l = new ArrayList<Node>(); addLeaves(this, l); return l.toArray(new Node[l.size()]); } private void addLeaves(Node base, List<Node> l) { if (base.isLeaf()) { l.add(base); } else { for (Node n : base.getChildren()) { addLeaves(n,l); } } } public boolean isLeaf() { return false; } public Node[] getChildren() { synchronized (children) { return children.toArray(new Node[children.size()]); } } /** * Constructs from a path name. * Instantiates parent nodes with appropriate names as necessary. * @param base the top-level ancestor of the node to be built. * Its name is not included in the path name * @param path * @return */ public static Node build(Node base, String path) { Node node = null; SequenceEncoder.Decoder st = new SequenceEncoder.Decoder(path, '/'); while (st.hasMoreTokens()) { String childId = st.nextToken(); node = base.getChild(childId); if (node == null) { node = new Node(base, childId, null); base.add(node); } base = node; } return node; } /** * Builds a Node from a pathAndInfo string * @see #getPathAndInfo * @param base * @param pathAndInfo * @return */ public Node buildWithInfo(Node base, String pathAndInfo) { Node node = null; SequenceEncoder.Decoder st = new SequenceEncoder.Decoder(pathAndInfo, '/'); while (st.hasMoreTokens()) { SequenceEncoder.Decoder st2 = new SequenceEncoder.Decoder(st.nextToken(),'='); String childId = st2.nextToken(); String childInfo = st2.nextToken(); node = base.getChild(childId); if (node == null) { node = new Node(base, childId, null); base.add(node); } node.setInfo(childInfo); base = node; } return node; } private List<Node> getPathList() { ArrayList<Node> path = new ArrayList<Node>(); for (Node n = this; n != null && n.getId() != null; n = n.getParent()) { path.add(n); } return path; } public String getPath() { synchronized (children) { SequenceEncoder se = new SequenceEncoder('/'); List<Node> path = getPathList(); for (ListIterator<Node> i = path.listIterator(path.size()); i.hasPrevious(); ) { se.append(i.previous().getId()); } return se.getValue(); } } /** * Return a string in the format parentId=parentInfo/childId=childInfo/... * @return */ public String getPathAndInfo() { synchronized (children) { SequenceEncoder se = new SequenceEncoder('/'); List<Node> path = getPathList(); for (ListIterator<Node> i = path.listIterator(path.size()-1); i.hasPrevious(); ) { Node n = i.previous(); SequenceEncoder se2 = new SequenceEncoder(n.getId(),'=').append(n.getInfo()); se.append(se2.getValue()); } return se.getValue(); } } }