/* DBBaseCategory.java This module represents an objectbase folder in the server's category hierarchy. Created: 11 August 1997 Module By: Jonathan Abbey, jonabbey@arlut.utexas.edu ----------------------------------------------------------------------- Ganymede Directory Management System Copyright (C) 1996-2013 The University of Texas at Austin Ganymede is a registered trademark of The University of Texas at Austin Contact information Web site: http://www.arlut.utexas.edu/gash2 Author Email: ganymede_author@arlut.utexas.edu Email mailing list: ganymede@arlut.utexas.edu US Mail: Computer Science Division Applied Research Laboratories The University of Texas at Austin PO Box 8029, Austin TX 78713-8029 Telephone: (512) 835-3200 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, see <http://www.gnu.org/licenses/>. */ package arlut.csd.ganymede.server; import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; import java.io.PrintWriter; import java.rmi.RemoteException; import java.util.Comparator; import java.util.Hashtable; import java.util.Vector; import arlut.csd.Util.TranslationService; import arlut.csd.Util.VecQuickSort; import arlut.csd.ganymede.common.CategoryTransport; import arlut.csd.ganymede.rmi.Base; import arlut.csd.ganymede.rmi.Category; import arlut.csd.ganymede.rmi.CategoryNode; /*------------------------------------------------------------------------------ class DBBaseCategory ------------------------------------------------------------------------------*/ /** * <p>A DBBaseCategory is a 'red folder node' in the server's category * and object hierarchy. The purpose of DBBaseCategory is to be able * to group object types with related purpose into a common folder for * display on the client.</p> * * <p>The {@link arlut.csd.ganymede.server.DBStore DBStore} contains a * tree of {@link arlut.csd.ganymede.rmi.CategoryNode CategoryNode}s, * each of which is either a {@link * arlut.csd.ganymede.server.DBObjectBase DBObjectBase} or a * DBBaseCategory. The {@link arlut.csd.ganymede.rmi.Category * Category} RMI interface is used by the server and the schema editor * to perform browsing and manipulating the server's category * tree.</p> */ public final class DBBaseCategory implements Category, CategoryNode { private final static boolean debug = false; /** * TranslationService object for handling string localization in the * Ganymede server. */ static final TranslationService ts = TranslationService.getTranslationService("arlut.csd.ganymede.server.DBBaseCategory"); private static Comparator comparator = new Comparator() { public int compare(Object a, Object b) { int valA, valB; if (a instanceof DBBaseCategory) { valA = ((DBBaseCategory) a).tmp_displayOrder; } else { valA = ((DBObjectBase) a).tmp_displayOrder; } if (b instanceof DBBaseCategory) { valB = ((DBBaseCategory) b).tmp_displayOrder; } else { valB = ((DBObjectBase) b).tmp_displayOrder; } if (valA < valB) { return -1; } else if (valB > valA) { return 1; } else { return 0; } } }; // private String name; private DBBaseCategory parent; private DBStore store; /** * The actual members of this category. Each member will be either * a {@link arlut.csd.ganymede.server.DBObjectBase DBObjectBase} or another * {@link arlut.csd.ganymede.server.DBBaseCategory DBBaseCategory}. */ private Vector<CategoryNode> contents; /** * <p>In order to keep compatibility with versions 1.17 and previous of * the ganymede.db file format, we'll keep this field so we can do a * sort after loading when reading an old file.</p> * * <p>Note that this is currently DBBaseCategory's only package private * field, because we share the use of these tmp_displayOrder fields * with DBObjectBase and DBObjectBaseField, and there's just little * upside in reworking the old compatibility code to use setters and * accessors for this purpose.</p> */ int tmp_displayOrder = -1; /** * We use this baseHash to keep a map of DBObjectBase.getKey() to * instances of DBObjectBase. addNodeAfter() uses this to find a * server-local DBObjectBase from a remote Base reference passed * us by the schema editor on the client. */ private Hashtable<Short, DBObjectBase> baseHash; /** * A reference to the DBSchemaEdit object that is editing us * for a client-side schema editor. */ private DBSchemaEdit editor = null; /* -- */ /** * Primary constructor. * * @param store DBStore that is managing us. We'll ask it to look up parents * for us. * @param name Name for this base * @param parent If we're not being constructed at the top level, who is our parent? */ public DBBaseCategory(DBStore store, String name, DBBaseCategory parent) throws RemoteException { this.setName(name); this.store = store; this.parent = parent; // All children of a top-level baseHash generated by recurseDown will // share a single baseHash so they can properly re-install a base if it // is moved around in the tree. if (parent != null) { this.baseHash = parent.baseHash; this.editor = parent.editor; } contents = new Vector<CategoryNode>(); Ganymede.rmi.publishObject(this); } /** * Default value constructor. This is used to construct a top-level category. * * @param store DBStore that is managing us. We'll ask it to look up parents * for us. * @param name Name for this category */ public DBBaseCategory(DBStore store, String name) throws RemoteException { this(store, name, null); } /** * Receive constructor. * * @param store DBStore that is managing us. We'll ask it to look up parents * for us. * @param in DataInput stream to load the db representation of this category from */ public DBBaseCategory(DBStore store, DataInput in) throws RemoteException, IOException { this.store = store; contents = new Vector<CategoryNode>(); receive(in, null); Ganymede.rmi.publishObject(this); } /** * Receive constructor. * * @param store DBStore that is managing us. We'll ask it to look up parents * for us. * @param in DataInput stream to load the db representation of this category from */ public DBBaseCategory(DBStore store, DataInput in, DBBaseCategory parent) throws RemoteException, IOException { this.store = store; contents = new Vector<CategoryNode>(); receive(in, parent); Ganymede.rmi.publishObject(this); } /** * Recursive duplication constructor. This constructor recurses down * through the newly created DBBaseCategory and creates copies of * the bases and categories therein. */ public DBBaseCategory(DBStore store, DBBaseCategory rootCategory, Hashtable<Short, DBObjectBase> baseHash, DBSchemaEdit editor) throws RemoteException { this.editor = editor; this.store = store; contents = new Vector<CategoryNode>(); this.baseHash = baseHash; setName(rootCategory.getName()); recurseDown(rootCategory, baseHash, editor); Ganymede.rmi.publishObject(this); } /** * <p>This method takes all the children of the passed-in category * (both {@link arlut.csd.ganymede.server.DBObjectBase DBObjectBase} objects * and contained {@link arlut.csd.ganymede.server.DBBaseCategory DBBaseCategory} * objects) and makes copies under this.</p> */ private void recurseDown(DBBaseCategory category, Hashtable<Short, DBObjectBase> baseHash, DBSchemaEdit editor) throws RemoteException { Vector<CategoryNode> children = category.getNodes(); DBObjectBase oldBase, newBase; DBBaseCategory oldCategory, newCategory; /* -- */ if (debug) { Ganymede.debug("** recurseDown"); if (editor == null) { Ganymede.debug("**#?!?!!! DBBaseCategory.recurseDown(): editor == null!!!"); } } for (CategoryNode node: children) { if (node instanceof DBObjectBase) { oldBase = (DBObjectBase) node; // a new copy, with the same objects under it newBase = new DBObjectBase(oldBase, editor); baseHash.put(newBase.getKey(), newBase); if (false) { Ganymede.debug("Created newBase " + newBase.getName() + " in recursive category tree duplication"); } // we want this base to be added to the current end of this category addNodeAfter(newBase, null); if (false) { Ganymede.debug("Added " + newBase.getName() + " to new category tree"); } } else if (node instanceof DBBaseCategory) { oldCategory = (DBBaseCategory) node; newCategory = (DBBaseCategory) newSubCategory(oldCategory.getName()); newCategory.editor = editor; if (false) { Ganymede.debug("Created newCategory " + newCategory.getName() + " in recursive category tree duplication"); } newCategory.recurseDown(oldCategory, baseHash, editor); } } } /** * This method is used when a schema editor is 'checking in' * a category tree. */ public synchronized void clearEditor() { this.editor = null; for (CategoryNode node: contents) { if (node instanceof DBBaseCategory) { ((DBBaseCategory) node).clearEditor(); } } } /** * <p>Emits this category and its contents to <out>, in * ganymede.db form.</p> */ synchronized void emit(DataOutput out) throws IOException { out.writeUTF(this.getPath()); out.writeInt(contents.size()); for (CategoryNode node: contents) { // in DBStore 2.0 and later, we emit all bases during our // DBBaseCategory dump. if (node instanceof DBBaseCategory) { out.writeBoolean(false); // it's a category ((DBBaseCategory) node).emit(out); } else if (node instanceof DBObjectBase) { out.writeBoolean(true); // it's a base ((DBObjectBase) node).emit(out, true); } } } /** * <p>Reads this category and its contents from <in>, in * ganymede.db form.</p> */ synchronized void receive(DataInput in, DBBaseCategory parent) throws IOException { String pathName; int count, lastSlash; /* -- */ // read in category name pathName = in.readUTF(); // we stopped using an explicitly stored display order field at // DBStore 2.0 if (store.isLessThan(2,0)) { tmp_displayOrder = in.readInt(); } else { tmp_displayOrder = -1; } // now parse our path name to get our path lastSlash = pathName.lastIndexOf('/'); // and take our leaf's name name = pathName.substring(lastSlash + 1); // and get our parent this.parent = parent; // how many nodes under this category? count = in.readInt(); if (false) { System.err.println("DBBaseCategory.receive(): reading in " + count + " subcategories and bases"); } for (int i = 0; i < count; i++) { // starting at 2.0, we started reading DBObjectBases in during // category reading. if (store.isAtLeast(2,0)) { if (in.readBoolean()) { DBObjectBase tempBase = new DBObjectBase(in, store); store.setBase(tempBase); // register in DBStore objectBases hash // we want to add this node to the end of this // category, since we are reading them in order. addNodeAfter(tempBase, null); } else { // we want to add this node to the end of this // category, since we are reading them in order. addNodeAfter(new DBBaseCategory(store, in), null); } } else { // we're reading an old file, and we'll never see a // DBObjectBase here addNodeAfter(new DBBaseCategory(store, in), null); } } } /** * <p>Emits this category and its contents to <out>, in * XML form.</p> */ synchronized void emitXML(XMLDumpContext xmlOut) throws IOException { xmlOut.startElementIndent("category"); xmlOut.attribute("name", getName()); xmlOut.skipLine(); // skip line after category start xmlOut.indentOut(); for (CategoryNode node: contents) { if (node instanceof DBBaseCategory) { ((DBBaseCategory) node).emitXML(xmlOut); xmlOut.skipLine(); } else if (node instanceof DBObjectBase) { ((DBObjectBase) node).emitXML(xmlOut); } } xmlOut.indentIn(); xmlOut.endElementIndent("category"); } /** * <p>Returns a serialized representation of the basic category * and base structure on the server.</p> * * <p>This method is synchronized to ensure a consistent category/base * structure during the serialization process.</p> * * @param session The {@link arlut.csd.ganymede.server.GanymedeSession * GanymedeSession} that we consult to determine the permissions to * be encoded in the returned {@link arlut.csd.ganymede.common.CategoryTransport CategoryTransport}. * * @param hideNonEditables If true, the CategoryTransport returned * will only include those object types that are editable by the * client. * * @see arlut.csd.ganymede.server.GanymedeSession */ public synchronized CategoryTransport getTransport(GanymedeSession session, boolean hideNonEditables) { CategoryTransport transport = new CategoryTransport(); addCategoryToTransport(transport, session, hideNonEditables); return transport; } /** * <p>This method is used to concatenate this DBBaseCategory's information to the passed-in * {@link arlut.csd.ganymede.common.CategoryTransport CategoryTransport} object for serialization to * the client.</p> * * <p>This is kind of icky code, really.. {@link arlut.csd.ganymede.server.DBBaseCategory DBBaseCategory}, * {@link arlut.csd.ganymede.server.DBObjectBase DBObjectBase}, {@link arlut.csd.ganymede.server.GanymedeSession GanymedeSession} * and {@link arlut.csd.ganymede.common.CategoryTransport CategoryTransport} * classes are all getting involved, here.</p> * * <p>The reason for that is that we want to provide a CategoryTransport object to the client that makes * no reference to the server-side GanymedeSession class, and proper generation of a CategoryTransport * requires making method calls to a GanymedeSession.</p> * * <p>The down side of that is that the actual structure of the CategoryTransport is generated across * two places.. here and in * {@link arlut.csd.ganymede.server.DBObjectBase#addBaseToTransport(arlut.csd.ganymede.common.CategoryTransport, * arlut.csd.ganymede.server.GanymedeSession)}.</p> */ private void addCategoryToTransport(CategoryTransport transport, GanymedeSession session, boolean hideNonEditables) { Vector<CategoryNode> contents; /* -- */ transport.addChunk("cat"); transport.addChunk(getName()); contents = getNodes(); if (contents.size() > 0) { transport.addChunk("<"); for (CategoryNode node: contents) { if (node instanceof DBObjectBase) { DBObjectBase base = (DBObjectBase) node; if (session == null || (hideNonEditables && session.getPermManager().getPerm(base.getTypeID(), true).isEditable()) || (!hideNonEditables && session.getPermManager().getPerm(base.getTypeID(), true).isVisible())) { base.addBaseToTransport(transport, session); } } else if (node instanceof DBBaseCategory) { DBBaseCategory subCategory = (DBBaseCategory) node; if (session == null || (hideNonEditables && subCategory.containsEditableBase(session)) || (!hideNonEditables && subCategory.containsVisibleBase(session))) { subCategory.addCategoryToTransport(transport, session, hideNonEditables); } } } } // terminate this category record transport.addChunk(">"); } /** * <p>Returns true if this category contains any bases editable by the given {@link arlut.csd.ganymede.server.GanymedeSession session}. * If session is null, we're supergash, so of course this category is going to contain * an editable base. */ private boolean containsEditableBase(GanymedeSession session) { Vector<CategoryNode> contents; /* -- */ if (session == null) { return true; // we're not filtering, return true immediately } contents = getNodes(); for (CategoryNode node: contents) { if (node instanceof DBObjectBase) { DBObjectBase base = (DBObjectBase) node; if (session.getPermManager().getPerm(base.getTypeID(), true).isEditable()) { return true; } } else if (node instanceof DBBaseCategory) { DBBaseCategory subCategory = (DBBaseCategory) node; if (subCategory.containsEditableBase(session)) { return true; } } } return false; } /** * <p>Returns true if this category contains any bases visible by the given {@link arlut.csd.ganymede.server.GanymedeSession session}. * If session is null, we're supergash, so of course this category is going to contain * a visible base. */ private boolean containsVisibleBase(GanymedeSession session) { Vector<CategoryNode> contents; boolean result = false; /* -- */ if (session == null) { return true; // we're not filtering, return true immediately } contents = getNodes(); for (CategoryNode node: contents) { if (node instanceof DBObjectBase) { DBObjectBase base = (DBObjectBase) node; if (session.getPermManager().getPerm(base.getTypeID(), true).isVisible()) { result = true; } } else if (node instanceof DBBaseCategory) { DBBaseCategory subCategory = (DBBaseCategory) node; result = subCategory.containsVisibleBase(session); } } return result; } /** * <p>Re-sorts this category's contents based on the * tmp_displayOrder field.</p> * * <p>We only use this when loading DBBaseCategory's from old-style * ganymede.db files. The modern way of doing things depends on the * order of categories within a contents Vector, and needs no * explicit tmp_displayOrder or sorting to be done.</p> */ public void resort() { new VecQuickSort(contents, comparator).sort(); if (false) { System.err.println("** Sorted category " + getPath()); for (CategoryNode x: contents) { if (x instanceof DBBaseCategory) { System.err.print("Cat[" + ((DBBaseCategory) x).tmp_displayOrder); System.err.println("] = " + ((DBBaseCategory) x).getPath()); } else if (x instanceof DBObjectBase) { System.err.print("Base[" + ((DBObjectBase) x).tmp_displayOrder); System.err.println("] = " + ((DBObjectBase) x).getName()); } } } // re-sort subcategories for (CategoryNode x: contents) { if (x instanceof DBBaseCategory) { ((DBBaseCategory) x).resort(); } } } /** * <p>Returns the full path to this category, with levels * in the hierarchy separated by '/'s.</p> * * @see arlut.csd.ganymede.rmi.Category */ public String getPath() { if (parent != null) { return parent.getPath() + "/" + name; } else { return "/" + name; } } /** * Returns the name of this category. * * @see arlut.csd.ganymede.rmi.Category */ public String getName() { return name; } public int hashCode() { return this.getPath().hashCode(); } public boolean equals(Object operand) { if (!(operand instanceof Category)) { return false; } try { return this.getPath().equals(((Category) operand).getPath()); } catch (RemoteException ex) { throw new RuntimeException(ex); } } /** * Sets the name of this node. The name must not include a '/' * character, but all other characters are acceptable. * * @see arlut.csd.ganymede.rmi.CategoryNode */ public boolean setName(String newName) { if (newName == null) { throw new IllegalArgumentException("DBBaseCategory can't have null name"); } if (newName.indexOf('/') != -1) { throw new IllegalArgumentException("DBBaseCategory name can't include /"); } if (parent != null) { if (!newName.equals(name)) { if (parent.contains(newName)) { throw new IllegalArgumentException("DBBaseCategory name conflicts with existing name in this category"); } } } this.name = newName; return true; } /** * This method returns the category that this * category node belongs to. * * @see arlut.csd.ganymede.rmi.Category * @see arlut.csd.ganymede.rmi.CategoryNode */ public Category getCategory() { return parent; } /** * This method tells the CategoryNode what it's containing * category is. * * @see arlut.csd.ganymede.rmi.CategoryNode */ public void setCategory(Category category) { DBBaseCategory cat; String path; /* -- */ if (category == null) { parent = null; return; } if (!(category instanceof DBBaseCategory)) { // we need a local reference try { path = category.getPath(); } catch (RemoteException ex) { throw new RuntimeException("couldn't get path of remote category: " + ex); } if (debug) { System.err.println("** Attempting to find local copy of category " + path); } // getCategoryNode could also return a DBObjectBase, but not // in this context if (editor == null) { cat = (DBBaseCategory) store.getCategoryNode(path); } else { cat = (DBBaseCategory) editor.getCategoryNode(path); } if (cat == null) { throw new RuntimeException("setCategory: couldn't find local parent category"); } parent = cat; } else { parent = (DBBaseCategory) category; } } /** * <p>This method is used to place a Category Node under us. This method * adds a new node into this category, after prevNodeName if prevNodeName * is not null, or at the end of the category if it is.</p> * * @param node Node to place under this category * @param prevNodeName the name of the node that the new node is to be added after, * must not be path-qualified. * * @see arlut.csd.ganymede.rmi.Category */ public synchronized void addNodeAfter(CategoryNode node, String prevNodeName) { if (node == null) { throw new IllegalArgumentException("Can't add a null node, not even after " + prevNodeName); } // make sure we've got a local reference if we're being given a // Base if ((node instanceof Base) && !(node instanceof DBObjectBase)) { node = getBaseFromBase((Base) node); } if (debug) { try { System.err.println("DBBaseCategory<" + getName() + ">.addNodeAfter(" + node.getPath() + "," + prevNodeName +")"); } catch (RemoteException ex) { throw new RuntimeException("Couldn't check node path: " + ex); } } // find our insertion point // if (debug) // { // System.err.println("DBBaseCategory.addNodeAfter(): searching to see if node is already in this category"); // } for (CategoryNode cNode: contents) { try { if (cNode.getName().equals(node.getName())) { throw new IllegalArgumentException("can't add a node that's already in the category"); } } catch (RemoteException ex) { throw new RuntimeException("Couldn't check node name: " + ex); } } // put our node into our content list // if prevNodeName is null, we're just going to add this node to // the end of our category list. if (prevNodeName == null) { contents.add(node); } else { for (int i = 0; i < contents.size(); i++) { CategoryNode cNode = contents.get(i); try { if (cNode.getName().equals(prevNodeName)) { contents.insertElementAt(node, i+1); break; } } catch (RemoteException ex) { throw new RuntimeException(ex.getMessage()); } } } // tell the node who's its daddy try { // if (debug) // { // System.err.println("DBBaseCategory.addNodeAfter(): setting category for node"); // } node.setCategory(this); } catch (RemoteException ex) { throw new RuntimeException("caught remote exception " + ex); } } /** * <p>This method is used to place a Category Node under us. This method * adds a new node into this category, before nextNodeName if nextNodeName * is not null, or at the beginning of the category if it is.</p> * * @param node Node to place under this category * @param nextNodeName the name of the node that the new node is to be added before, * must not be path-qualified. * * @see arlut.csd.ganymede.rmi.Category */ public synchronized void addNodeBefore(CategoryNode node, String nextNodeName) { if (node == null) { throw new IllegalArgumentException("Can't add a null node, not even before " + nextNodeName); } if (debug) { try { System.err.println("DBBaseCategory<" + getName() + ">.addNodeBefore(" + node.getPath() + "," + nextNodeName +")"); } catch (RemoteException ex) { throw new RuntimeException(ex.getMessage()); } } // make sure we've got a local reference if we're being given a // Base if ((node instanceof Base) && !(node instanceof DBObjectBase)) { node = getBaseFromBase((Base) node); } // find our insertion point // if (debug) // { // System.err.println("DBBaseCategory.addNodeBefore(): searching to see if node is already in this category"); // } for (CategoryNode cNode: contents) { try { if (cNode.getName().equals(node.getName())) { throw new IllegalArgumentException("can't add a node that's already in the category"); } } catch (RemoteException ex) { throw new RuntimeException("Couldn't check node name: " + ex); } } // put our node into our content list // if nextNodeName is null, we're just going to add this node to // the end of our category list. if (nextNodeName == null) { contents.insertElementAt(node, 0); } else { for (int i = 0; i < contents.size(); i++) { CategoryNode cNode = contents.get(i); try { if (cNode.getName().equals(nextNodeName)) { contents.insertElementAt(node, i); break; } } catch (RemoteException ex) { throw new RuntimeException(ex.getMessage()); } } } // tell the node who's its daddy try { // if (debug) // { // System.err.println("DBBaseCategory.addNodeBefore(): setting category for node"); // } node.setCategory(this); } catch (RemoteException ex) { throw new RuntimeException("caught remote exception " + ex); } } /** * <p>This method can be used to move a Category from another * Category to this Category, or to move a Category around within * this Category.</p> * * @param catPath the fully specified path of the node to be moved * @param prevNodeName the name of the node that the catPath node is * to be placed after in this category, or null if the node is to * be placed at the first element of this category * * @see arlut.csd.ganymede.rmi.Category */ public synchronized void moveCategoryNode(String catPath, String prevNodeName) { if (debug) { System.err.println("DBBaseCategory.moveCategoryNode(" + catPath + "," + prevNodeName + ")"); } CategoryNode categoryNode; DBBaseCategory oldCategory; try { categoryNode = editor.getCategoryNode(catPath); oldCategory = (DBBaseCategory) categoryNode.getCategory(); } catch (RemoteException ex) { Ganymede.logError(ex); throw new RuntimeException("wow, surprising remote local exception"); } if (oldCategory == this) { if (debug) { System.err.println("DBBaseCategory.moveCategoryNode(): moving node within category"); } contents.removeElement(categoryNode); } else { if (debug) { System.err.println("DBBaseCategory.moveCategoryNode(): moving node from " + oldCategory.getPath() + " to " + getPath()); } try { oldCategory.removeNode(categoryNode); } catch (RemoteException ex) { throw new RuntimeException("Local category threw a remote exception.. ? " + ex); } } try { categoryNode.setCategory(this); } catch (RemoteException ex) { throw new RuntimeException("Local category node threw a remote exception.. ? " + ex); } if (prevNodeName == null) { contents.insertElementAt(categoryNode, 0); } else { for (int i = 0; i < contents.size(); i++) { CategoryNode cNode = contents.get(i); try { if (cNode.getName().equals(prevNodeName)) { contents.insertElementAt(categoryNode, i+1); return; } } catch (RemoteException ex) { } } throw new RuntimeException("Couldn't move category node " + catPath + " after non-existent " + prevNodeName); } } /** * <p>This method is used to remove a Category Node from under us.</p> * * @see arlut.csd.ganymede.rmi.Category */ public synchronized void removeNode(CategoryNode node) throws RemoteException { int i, index = -1; /* -- */ if (node == null) { throw new IllegalArgumentException("Can't remove a null node"); } // find our deletion point if (debug) { try { Ganymede.debug("DBBaseCategory (" + getName() + ").removeNode(" + node.getPath() + ")"); } catch (RemoteException ex) { Ganymede.logError(ex); throw new RuntimeException("rmi local failure?" + ex.getMessage()); } } for (i = 0; i < contents.size(); i++) { if (debug) { try { Ganymede.debug(" examining: " + contents.get(i).getPath()); } catch (RemoteException ex) { Ganymede.logError(ex); throw new RuntimeException("rmi local failure?" + ex.getMessage()); } } if (contents.get(i).equals(node)) { index = i; } } if (index == -1) { throw new IllegalArgumentException("can't delete a node that's not in the category"); } // remove our node from our content list contents.removeElementAt(index); if (false) { if (node instanceof DBObjectBase) { DBObjectBase base = (DBObjectBase) node; if (!base.isEditing()) { System.err.println("DBBaseCategory.removeNode(): " + base.getName() + " has a null editor!"); } else { System.err.println("DBBaseCategory.removeNode(): " + base.getName() + " has a non-null editor!"); } } } // Sorry, kid, yer on your own now! node.setCategory(null); } /** * <p>This method is used to remove a Category Node from under us.</p> * * @see arlut.csd.ganymede.rmi.Category */ public synchronized void removeNode(String name) throws RemoteException { int i, index = -1; CategoryNode node = null; /* -- */ if (name == null) { throw new IllegalArgumentException("Can't remove a null name"); } // find our deletion point if (debug) { Ganymede.debug("DBBaseCategory (" + getName() + ").removeNode(" + name + ")"); } for (i = 0; i < contents.size() && (index == -1); i++) { if (debug) { Ganymede.debug(" examining: " + contents.get(i)); } node = contents.get(i); try { if (node.getName().equals(name)) { index = i; } } catch (RemoteException ex) { throw new RuntimeException("caught remote: " + ex); } } if (index == -1) { throw new IllegalArgumentException("can't delete a name that's not in the category"); } else if (debug) { System.err.println("DBBaseCategory.removeNode(): found node " + node); if (node instanceof DBObjectBase) { System.err.println("DBBaseCategory.removeNode(): node is DBObjectBase"); } else if (node instanceof Base) { System.err.println("DBBaseCategory.removeNode(): node is Base"); } else if (node instanceof DBBaseCategory) { System.err.println("DBBaseCategory.removeNode(): node is DBBaseCategory"); } else if (node instanceof Category) { System.err.println("DBBaseCategory.removeNode(): node is Category"); } else { System.err.println("DBBaseCategory.removeNode(): node is <unrecognized>"); } } // remove our node from our content list contents.removeElementAt(index); if (debug) { if (node instanceof DBObjectBase) { DBObjectBase base = (DBObjectBase) node; if (!base.isEditing()) { System.err.println("DBBaseCategory.removeNode(2): " + base.getName() + " has a null editor!"); } else { System.err.println("DBBaseCategory.removeNode(2): " + base.getName() + " has a non-null editor!"); } } } // Sorry, kid, yer on your own now! node.setCategory(null); } /** * <p>Returns a subcategory of name <name>.</p> * * @see arlut.csd.ganymede.rmi.Category */ public CategoryNode getNode(String name) { for (CategoryNode candidate: contents) { try { if (candidate.getName().equals(name)) { return candidate; } } catch (RemoteException ex) { throw new RuntimeException("caught remote: " + ex); } } return null; } /** * Returns child nodes * * @see arlut.csd.ganymede.rmi.Category */ public synchronized Vector<CategoryNode> getNodes() { return new Vector<CategoryNode>(contents); } /** * <p>This creates a new subcategory under this category, * with displayOrder after the last item currently in the * category. This method should only be called when * there are no nodes left to be added to the category * with prefixed displayOrder values.</p> */ public Category newSubCategory(String name) { DBBaseCategory bc; try { bc = new DBBaseCategory(store, name, this); } catch (RemoteException ex) { return null; } addNodeAfter(bc, null); return bc; } /** * <p>This creates a new subcategory under this category, * with displayOrder after the last item currently in the * category. This method should only be called when * there are no nodes left to be added to the category * with prefixed displayOrder values.</p> * * @see arlut.csd.ganymede.rmi.Category */ public Category newSubCategory() { DBBaseCategory bc; String name; int i; /* -- */ name = ts.l("newSubCategory.new_category"); // "New Category"; i = 2; while (this.contains(name)) { name = ts.l("newSubCategory.new_category_indexed", Integer.valueOf(i++)); } try { bc = new DBBaseCategory(store, name, this); } catch (RemoteException ex) { return null; } addNodeAfter(bc, null); return bc; } /** * <p>This method returns true if this * is a subcategory of <cat>.</p> * * @see arlut.csd.ganymede.rmi.Category */ public boolean isUnder(Category cat) { if (cat == null) { return false; } if (cat.equals(this)) { return true; } if (parent == null) { return false; } else { return parent.isUnder(cat); } } /** * <p>This method returns true if this category directly * contains a {@link arlut.csd.ganymede.rmi.CategoryNode CategoryNode} * with name <name></p> */ public synchronized boolean contains(String name) { for (CategoryNode node: contents) { try { if (node.getName().equals(name)) { return true; } } catch (RemoteException ex) { throw new RuntimeException("caught remote: " + ex); } } return false; } /** * This method returns a reference to the top of this category's * tree. */ public DBBaseCategory getRoot() { DBBaseCategory node = this; while (node.parent != null) { node = node.parent; } return node; } /** * <p>This method is used to convert an RMI remote reference * to a Base object to a reference to the local copy.</p> * * <p>Needed for RMI under JDK 1.1.</p> */ public DBObjectBase getBaseFromBase(Base base) { try { return getBaseFromKey(base.getTypeID()); } catch (RemoteException ex) { return null; } } /** * <p>This method is used to convert an RMI remote reference * to a Base object to a reference to the local copy.</p> * * <p>Needed for RMI under JDK 1.1.</p> */ public DBObjectBase getBaseFromKey(short id) { if (editor != null) { return baseHash.get(Short.valueOf(id)); } else { return store.getObjectBase(id); } } }