/******************************************************************************/ /* File: resultset.beh */ /* */ /* Describes the Resultset class and the visitor used for printing out the */ /* contents of a resultset in XML format. */ /* */ /* The implementation for the Resultset class just includes a static */ /* specification for printing out its structure using the ResultPrintVisitor, */ /* and the method to turn a linear list of objects into a hierarchical form. */ /* */ /* The visitor class - ResultPrintVisitor - defines the traversal and action */ /* needed for generating the XML output. */ /* */ /******************************************************************************/ Resultset { public void print() bypassing -> *,tail,* to * (ResultPrintVisitor); {{ // Method to create a hierarchical structure from a linear array of objects. // It accomplishes this by iterating over each "child" object, locating its // corresponding "parent" object, and then adding the child to parent's list // of children. // It uses dynamic object instantiation and method invocation to achieve // this without knowing the actual names of the columns involved in the // specification of the relationship between the entities. public void createHierarchy(ClassGraph cg, HierarchyArgs hierArgs) { String parentColSearchStrategy = "from " + hierArgs.childTable + " bypassing {" + hierArgs.relationColumn + ", Nonempty_DBEntity_List}" + " to " + hierArgs.parentColumn; Iterator rows = cg.gather(this, "from Resultset" + " bypassing " + hierArgs.relationColumn + " to " + hierArgs.childTable).iterator(); DBEntity row; DBAttr parentColumn; while (rows.hasNext()) { row = (DBEntity) rows.next(); parentColumn = ((DBAttr) cg.fetch(row, parentColSearchStrategy)); if (parentColumn != null) { DBEntity parentRow = null; parentRow = findRecord(cg, hierArgs.relationColumn, hierArgs.parentTable, hierArgs.childColumn, parentColumn.get_value()); if (parentRow == null) continue; DBVirtualAttr link = (DBVirtualAttr) cg.fetch(parentRow, "from " + hierArgs.parentTable + " bypassing " + hierArgs.relationColumn + " to " + hierArgs.relationColumn); if (link == null) { DBEntity_List children = new DBEntity_List(); children.addElement(row); Object virtualAttr = (Object) Utils.instantiate(hierArgs.relationColumn); Utils.executeSetMethod(virtualAttr, "set_children", children); Utils.executeSetMethod(parentRow, "set_" + hierArgs.relationColumn, virtualAttr); } else link.get_children().addElement(row); } } } // Accessory method to locate an entity instance based on a specified // attribute-value pair private DBEntity findRecord(ClassGraph cg, String relationColumn, String targetEntity, String targetColumn, String targetValue) { Iterator rows = cg.gather(this, "from Resultset to " + targetEntity ).iterator(); String strategy = "from " + targetEntity + " bypassing {" + relationColumn + ", Nonempty_DBEntity_List}" + " to " + targetColumn; DBEntity row = null; while (rows.hasNext()) { row = (DBEntity) rows.next(); String value = ((DBAttr) cg.fetch(row, strategy)).get_value(); if (value.equals(targetValue)) return row; } return null; } }} } ResultPrintVisitor { {{ // The visitor utilizes the knowledge that all database table objects, // attribute objects and virtual attribute objects inherit from the abstract // classes DBEntity, DBAttr and DBVirtualAttr, respectively, to achieve a // quite concise but completely generic implementation of a specialized XML // Print Visitor for a database resultset. private int leftIndent = 0; private HashSet visitedList = new HashSet(); private boolean okToPrint = true; }} before Resultset {{ printStartTag(host); System.out.println(""); }} after Resultset {{ printEndTag(host); }} // Any object that is printed once as a descendant of some other object is // not printed again. // The "around" method doesn't correctly on abstract classes as yet, hence // we had to implement in this inefficient manner: the traversal is not // actually halted upon discovering that an object has already been // "visited" but the underlying tree is just not printed. // The better solution would just use the around method for the host // DBEntity instead of the the before and after methods, and would use // subtraversal.apply() conditionally to stop the sub-traversal if the // object has already been visited. before DBEntity {{ if (! wasVisited(host)) { markAsVisited(host); okToPrint = true; printStartTag(host); System.out.println(""); } else okToPrint = false; }} after DBEntity {{ if (okToPrint) printEndTag(host); }} before DBAttr {{ if (okToPrint) { indent(); System.out.print("<" + host.getClass().getName() + ">"); System.out.print(" " + host.get_value() + " "); System.out.println(""); } }} before DBVirtualAttr {{ if (okToPrint) { printStartTag(host); System.out.println(""); } }} after DBVirtualAttr {{ if (okToPrint) printEndTag(host); }} {{ private void indent() { String spaces = ""; for (int i = 0; i < leftIndent; i++) spaces = spaces + " "; System.out.print(spaces); } private void printStartTag(Object host) { indent(); System.out.print("<" + host.getClass().getName() + ">"); leftIndent++; } private void printEndTag(Object host) { leftIndent--; indent(); System.out.println(""); } private boolean wasVisited(Object host) { return visitedList.contains(host); } private void markAsVisited(Object host) { visitedList.add(host); } }} }