From 651774aeefe9ff8d300fc690388241b4449b0b86 Mon Sep 17 00:00:00 2001 From: Peter Bernard West Date: Tue, 7 May 2002 04:37:53 +0000 Subject: [PATCH] Classes for complex data structures. git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/branches/FOP_0-20-0_Alt-Design@194769 13f79535-47bb-0310-9956-ffa450edef68 --- .../fop/datastructs/CircularBuffer.java | 96 ++ .../apache/fop/datastructs/ROClassArray.java | 39 + .../apache/fop/datastructs/ROIntArray.java | 57 + .../fop/datastructs/ROIntegerArray.java | 44 + .../apache/fop/datastructs/ROMethodArray.java | 38 + .../apache/fop/datastructs/ROStringArray.java | 35 + .../fop/datastructs/SyncedCircularBuffer.java | 176 +++ src/org/apache/fop/datastructs/TNode.java | 82 ++ src/org/apache/fop/datastructs/TNodeTest.java | 188 +++ src/org/apache/fop/datastructs/Tree.java | 1040 +++++++++++++++++ src/org/apache/fop/datastructs/package.html | 6 + 11 files changed, 1801 insertions(+) create mode 100644 src/org/apache/fop/datastructs/CircularBuffer.java create mode 100644 src/org/apache/fop/datastructs/ROClassArray.java create mode 100644 src/org/apache/fop/datastructs/ROIntArray.java create mode 100644 src/org/apache/fop/datastructs/ROIntegerArray.java create mode 100644 src/org/apache/fop/datastructs/ROMethodArray.java create mode 100644 src/org/apache/fop/datastructs/ROStringArray.java create mode 100644 src/org/apache/fop/datastructs/SyncedCircularBuffer.java create mode 100644 src/org/apache/fop/datastructs/TNode.java create mode 100644 src/org/apache/fop/datastructs/TNodeTest.java create mode 100644 src/org/apache/fop/datastructs/Tree.java create mode 100644 src/org/apache/fop/datastructs/package.html diff --git a/src/org/apache/fop/datastructs/CircularBuffer.java b/src/org/apache/fop/datastructs/CircularBuffer.java new file mode 100644 index 000000000..ec909294a --- /dev/null +++ b/src/org/apache/fop/datastructs/CircularBuffer.java @@ -0,0 +1,96 @@ +package org.apache.fop.datastructs; + +import java.util.NoSuchElementException; +import java.lang.IndexOutOfBoundsException; + +/* + * CircularBuffer.java + * $Id$ + * Created: Tue Nov 6 10:19:03 2001 + * + * Copyright (C) 2001 The Apache Software Foundation. All rights reserved. + * For details on use and redistribution please refer to the + * LICENSE file included with these sources. + * + * @author Peter B. West + * @version $Revision$ $Name$ + */ +/** + * A general circular buffer class. It stores and returns Object + * references. The buffer operations are not synchronized. + */ + +public class CircularBuffer { + + private final static int DEFAULTBUFSIZE = 32; + + private Object[] buf; + private int size = 0; + private int getptr = 0; + private int putptr = 0; + + /** + * Constructor taking a single argument; the size of the buffer. + */ + public CircularBuffer(int size) { + buf = new Object[size]; + this.size = size; + } + + /** + * No-argument constructor sets up a buffer with the default number of + * elements. + */ + public CircularBuffer() + throws IllegalArgumentException { + this(DEFAULTBUFSIZE); + } + + public boolean isFull() { + return ((putptr + 1) % size) == getptr; + } + + public boolean isEmpty() { + return putptr == getptr; + } + + /** + * get returns the next object from the buffer. + * Throws a NoSuchElementException if the buffer is empty. + */ + public Object get() + throws NoSuchElementException { + if (isEmpty()) { + throw new NoSuchElementException( + "CircularBuffer is empty."); + } + int tmpptr = getptr++; + if (getptr == size) getptr = 0; + return buf[tmpptr]; + } + + /** + * put adds an object to the buffer. + * Throws a NoSuchElementException if the buffer is full. + */ + public void put(Object thing) throws NoSuchElementException { + if (isFull()) { + throw new NoSuchElementException( + "CircularBuffer is full."); + } + buf[putptr++] = thing; + if (putptr == size) putptr = 0; + } + + /** + * oldest returns the next object that will be overwritten + * by a put. If the buffer is full, returns null. + */ + public Object oldest() { + if (isFull()) { + return null; + } + return buf[putptr]; + } + +} diff --git a/src/org/apache/fop/datastructs/ROClassArray.java b/src/org/apache/fop/datastructs/ROClassArray.java new file mode 100644 index 000000000..ee5ed23be --- /dev/null +++ b/src/org/apache/fop/datastructs/ROClassArray.java @@ -0,0 +1,39 @@ +package org.apache.fop.datastructs; + +import java.lang.Class; + +/** + * Provides a Read-Only array of Class. + * $Id$ + * + * Copyright (C) 2001 The Apache Software Foundation. All rights reserved. + * For details on use and redistribution please refer to the + * LICENSE file included with these sources. + * + * @author Peter B. West + * @version $Revision$ $Name$ + * + */ +public class ROClassArray { + + private Class[] carray; + + /** + * The length of the array; analogous to the length field of an + * array. + */ + public final int length; + + public ROClassArray(Class[] carray) { + this.carray = carray; + length = carray.length; + } + + /** + * @param index an int; the offset of the value to retrieve + * @return the Class at the index offset + */ + public Class get(int index) { + return carray[index]; + } +} diff --git a/src/org/apache/fop/datastructs/ROIntArray.java b/src/org/apache/fop/datastructs/ROIntArray.java new file mode 100644 index 000000000..30fcb9037 --- /dev/null +++ b/src/org/apache/fop/datastructs/ROIntArray.java @@ -0,0 +1,57 @@ +// $Id$ +package org.apache.fop.datastructs; + +/** + * Provides a Read-Only int array. + * + * Copyright (C) 2001 The Apache Software Foundation. All rights reserved. + * For details on use and redistribution please refer to the + * LICENSE file included with these sources. + * + * @author Peter B. West + * @version $Revision$ $Name$ + */ +public class ROIntArray { + + private int[] iarray; + + /** + * The length of the array; analogous to the length field of an + * array. + */ + public final int length; + + /** + * Initialise with single integer array. N.B. the ROIntArray is a + * reference to the initialising array. Any subsequent changes to the + * contents of the iniitialising array will be reflected in the + * ROIntArray. + * @param iarray an int[] + */ + public ROIntArray(int[] iarray) { + this.iarray = iarray; + length = iarray.length; + } + + /** + * Initialise with an array of arrays. The elements of the argument + * arrays are copied, in order, into the ROIntArray. + * @param iarrays an int[][] + */ + public ROIntArray(int[][] iarrays) { + int i, j, k; + i = 0; + for (j = 0; j < iarrays.length; j++) + for (k = 0; k < iarrays[j].length; k++) + iarray[i++] = iarrays[j][k]; + length = iarray.length; + } + + /** + * @param index an int; the offset of the value to retrieve + * @return the int at the index offset + */ + public int get(int index) { + return iarray[index]; + } +} diff --git a/src/org/apache/fop/datastructs/ROIntegerArray.java b/src/org/apache/fop/datastructs/ROIntegerArray.java new file mode 100644 index 000000000..d780ff4aa --- /dev/null +++ b/src/org/apache/fop/datastructs/ROIntegerArray.java @@ -0,0 +1,44 @@ +// $Id$ +package org.apache.fop.datastructs; + +import java.util.List; +import java.util.Collections; +import java.util.Arrays; + +/** + * Provides a Read-Only Integer array. + * + * Copyright (C) 2001 The Apache Software Foundation. All rights reserved. + * For details on use and redistribution please refer to the + * LICENSE file included with these sources. + * + * @author Peter B. West + * @version $Revision$ $Name$ + */ +public class ROIntegerArray { + + private Integer[] iarray; + + /** + * The length of the array; analogous to the length field of an + * array. + */ + public final int length; + + public ROIntegerArray(Integer[] iarray) { + this.iarray = iarray; + length = iarray.length; + } + + /** + * @param index an int; the offset of the value to retrieve + * @return the Integer at the index offset + */ + public Integer get(int index) { + return iarray[index]; + } + + public List immutableList () { + return Collections.unmodifiableList(Arrays.asList(iarray)); + } +} diff --git a/src/org/apache/fop/datastructs/ROMethodArray.java b/src/org/apache/fop/datastructs/ROMethodArray.java new file mode 100644 index 000000000..8e6e112bc --- /dev/null +++ b/src/org/apache/fop/datastructs/ROMethodArray.java @@ -0,0 +1,38 @@ +package org.apache.fop.datastructs; + +import java.lang.reflect.Method; + +/** + * Provides a Read-Only array of Method. + * $Id$ + * + * Copyright (C) 2001 The Apache Software Foundation. All rights reserved. + * For details on use and redistribution please refer to the + * LICENSE file included with these sources. + * + * @author Peter B. West + * @version $Revision$ $Name$ + */ +public class ROMethodArray { + + private Method[] marray; + + /** + * The length of the array; analogous to the length field of an + * array. + */ + public final int length; + + public ROMethodArray(Method[] marray) { + this.marray = marray; + length = marray.length; + } + + /** + * @param index an int; the offset of the value to retrieve + * @return the Method at the index offset + */ + public Method get(int index) { + return marray[index]; + } +} diff --git a/src/org/apache/fop/datastructs/ROStringArray.java b/src/org/apache/fop/datastructs/ROStringArray.java new file mode 100644 index 000000000..476462a54 --- /dev/null +++ b/src/org/apache/fop/datastructs/ROStringArray.java @@ -0,0 +1,35 @@ +package org.apache.fop.datastructs; + +/** + * Provides a Read-Only String array. + * + * Copyright (C) 2001 The Apache Software Foundation. All rights reserved. + * For details on use and redistribution please refer to the + * LICENSE file included with these sources. + * + * @author Peter B. West + * @version $Revision$ $Name$ + */ +public class ROStringArray { + + private String[] sarray; + + /** + * The length of the array; analogous to the length field of an + * array. + */ + public final int length; + + public ROStringArray(String[] sarray) { + this.sarray = sarray; + length = sarray.length; + } + + /** + * @param index an int; the offset of the value to retrieve + * @return the String at the index offset + */ + public String get(int index) { + return sarray[index]; + } +} diff --git a/src/org/apache/fop/datastructs/SyncedCircularBuffer.java b/src/org/apache/fop/datastructs/SyncedCircularBuffer.java new file mode 100644 index 000000000..5df3f0dfe --- /dev/null +++ b/src/org/apache/fop/datastructs/SyncedCircularBuffer.java @@ -0,0 +1,176 @@ +package org.apache.fop.datastructs; + +import java.util.NoSuchElementException; +import java.lang.IndexOutOfBoundsException; + +/* + * SyncedCircularBuffer.java + * $Id$ + * + * Created: Tue Nov 6 10:19:03 2001 + * + * Copyright (C) 2001 The Apache Software Foundation. All rights reserved. + * For details on use and redistribution please refer to the + * LICENSE file included with these sources. + * + * @author Peter B. West + * @version $Revision$ $Name$ + */ +/** + * A general synchronized circular buffer class. + * It stores and returns Object + * references. The buffer operations are synchronized and it + * behaves as a synchronized producer/consumer buffer. + *

+ * Warning: if the producer or consumer thread dies unexpectedly, + * without interrupting the complementary thread's wait(), that + * process will hang on the wait(). + */ +public class SyncedCircularBuffer { + + private final static int DEFAULTBUFSIZE = 32; + + private Object[] buf; + private int size = 0; + private int getptr = 0; + private int putptr = 0; + private boolean flush = false; + private Object pushBackBuf = null; + + /** + * No-argument constructor sets up a buffer with the default number of + * elements. + * The producer and consumer Threads default to the current + * thread at the time of instantiation. + */ + public SyncedCircularBuffer() throws IllegalArgumentException { + this(DEFAULTBUFSIZE); + } + + /** + * Constructor taking a single argument; the size of the buffer. + * @param size the size of the buffer. Must be > 1. + */ + public SyncedCircularBuffer(int size) throws IllegalArgumentException { + if (size < 1) throw new IllegalArgumentException + ("SyncedCircularBuffer size less than 1."); + buf = new Object[size]; + this.size = size; + } + + /** + * @return true if the buffer is full; i.e. if incrementing the + * put pointer would result in a spurious + * isEmpty() condition. + */ + public boolean isFull() { + return ((putptr + 1) % size) == getptr; + } + + /** + * @return true if the buffer is empty; i.e. if the put + * pointer is equal to the get pointer. + */ + public boolean isEmpty() { + return putptr == getptr; + } + + /** + * Push back an object into the buffer; generally this will be an + * object previously obtaiined by a get from the buffer. + *

This implementation supports a single entry pushback buffer.

+ * @param obj and Object + */ + synchronized public void pushBack (Object obj) + throws IndexOutOfBoundsException { + if (pushBackBuf != null) + throw new IndexOutOfBoundsException("pushBack buffer is full"); + pushBackBuf = obj; + } + + /** + * get returns the next object from the buffer. + * @exception NoSuchElementException if the buffer is empty. + * @exception InterruptedException if the wait() is interrupted. + */ + public Object get() throws NoSuchElementException, InterruptedException { + // Assume that an InterruptedException is a message to kill the + // consumer, sent if the producer terminates. Just die. + synchronized (this) { + Object obj; + if (Thread.interrupted()) { + throw new InterruptedException("Producer interrupted"); + } + if (pushBackBuf != null) { + obj = pushBackBuf; + pushBackBuf = null; + return obj; + } + + while (isEmpty()) { + // wait for the producer + this.wait(); + } + + int tmpptr = getptr++; + if (getptr == size) getptr = 0; + obj = buf[tmpptr]; + buf[tmpptr] = null; + if (isEmpty()) notifyAll(); + return obj; + } + } + + /** + * put adds an object to the buffer. + * If the buffer fills after this put, notifyAll(). + * Then while the consumer thread is still alive and the + * buffer has not emptied, wait() for the consumer. + * + * @param the Object to append to the buffer + * @exception NoSuchElementException if the buffer is full. + * @exception InterruptedException if the wait() is interrupted. + */ + public void put(Object thing) + throws NoSuchElementException, InterruptedException + { + // Assume that an InterruptedException is a message to kill the + // producer, sent if the consumer terminates. Just die. + synchronized (this) { + if (isFull()) { + throw new NoSuchElementException( + "SyncedCircularBuffer is full."); + } + if (Thread.interrupted()) { + throw new InterruptedException("Producer interrupted"); + } + + buf[putptr++] = thing; + if (putptr == size) putptr = 0; + // If the buffer is full + // notify the consumer that something is available + if (isFull() || flush) { + notifyAll(); + while (! isEmpty()) { + // Wait for the consumer + this.wait(); + } + flush = false; + } + } + } + + /** + * Notifies the consumer. Allows for processing of the buffer before + * it fills. + */ + public void flushBuffer() { + synchronized (this) { + if (! isEmpty()) { + flush = true; + notifyAll(); + } + } + } + +} diff --git a/src/org/apache/fop/datastructs/TNode.java b/src/org/apache/fop/datastructs/TNode.java new file mode 100644 index 000000000..c24bd3431 --- /dev/null +++ b/src/org/apache/fop/datastructs/TNode.java @@ -0,0 +1,82 @@ +/* + * TNode.java + * + * Created: Sat Oct 27 13:44:34 2001 + * + * Copyright (C) 2001 The Apache Software Foundation. All rights reserved. + * For details on use and redistribution please refer to the + * LICENSE file included with these sources. + * + * @author Peter B. West + * @version $Revision$ $Name$ + */ + +package org.apache.fop.datastructs; + +//import Tree; + +/** + * A testbed for Tree.Node. + */ +public class TNode extends Tree.Node { + + private Object content = null; + + public TNode (Tree tree) throws Tree.TreeException { + tree.super(); + } + + public TNode(Tree tree, TNode parent, int index) + throws Tree.TreeException { + tree.super(parent, index); + } + + public TNode(Tree tree, TNode parent) throws Tree.TreeException { + tree.super(parent); + } + + /** + * @param tree the enclosing Tree instance. Needed to enable + * the call to the superclass constructor. + * @param parent The parent TNode of this TNode. If null, + * this must be the root node. + * @param content An object which is the actual content of this node; + * the contents of the TNode. + */ + + public TNode(Tree tree, TNode parent, Object content) + throws Tree.TreeException { + tree.super(parent); + this.content = content; + } + + /** + * @param tree the enclosing Tree instance. Needed to enable + * the call to the superclass constructor. + * @param parent The parent TNode of this TNode. If null, + * this must be the root node. + * @param index int index of this child in the parent node. + * @param content An object which is the actual content of this node; + * the contents of the TNode. + */ + + public TNode(Tree tree, TNode parent, int index, Object content) + throws Tree.TreeException, IndexOutOfBoundsException { + tree.super(parent, index); + this.content = content; + } + + public Object getContent() { + return content; + } + + public void setContent(Object content) { + this.content = content; + } + + public void unsetContent() { + this.content = null; + } + + +}// TNode diff --git a/src/org/apache/fop/datastructs/TNodeTest.java b/src/org/apache/fop/datastructs/TNodeTest.java new file mode 100644 index 000000000..8c4ea9e6f --- /dev/null +++ b/src/org/apache/fop/datastructs/TNodeTest.java @@ -0,0 +1,188 @@ +package org.apache.fop.datastructs; + +import java.util.*; + +/* + * TNodeTest.java + * + * $Id$ + * + * Copyright (C) 2001 The Apache Software Foundation. All rights reserved. + * For details on use and redistribution please refer to the + * LICENSE file included with these sources. + * + * @author Peter B. West + * @version $Revision$ $Name$ + */ +/** + * A test class for TNode. + */ + +public class TNodeTest{ + //public TNodeTest (){} + + public static void main(String[] args) + throws Tree.TreeException { + Tree tree = new Tree(); + TNode root = new TNode(tree, null, "Root"); + TNode child1 = new TNode(tree, root, "1-1"); + TNode child2 = new TNode(tree, root, "1-2"); + TNode child3 = new TNode(tree, root, "1-3"); + TNode child2_1 = new TNode(tree, (TNode)root.getChild(1), "1-2-1"); + TNode child2_2 = new TNode(tree, (TNode)root.getChild(1), "1-2-2"); + TNode child3_1 = new TNode(tree, (TNode)root.getChild(2), "1-3-1"); + TNode child3_2 = new TNode(tree, (TNode)root.getChild(2), "1-3-2"); + TNode child3_3 = new TNode(tree, (TNode)root.getChild(2), "1-3-3"); + TNode child1_1 = new TNode(tree, (TNode)root.getChild(0), "1-1-1"); + System.out.println("Pre-order traversal:root:"); + preorder(root, tree.getModCount()); + System.out.println("Post-order traversal:root:"); + postorder(root, tree.getModCount()); + System.out.println("Preceding siblings 3-2"); + precedingsibling(child3_2); + System.out.println("Following siblings 3-2"); + followingsibling(child3_2); + System.out.println("Preceding siblings 2-2"); + precedingsibling(child2_2); + System.out.println("Following siblings 2-2"); + followingsibling(child2_2); + System.out.println("Preceding siblings 1"); + precedingsibling(child1); + System.out.println("Following siblings 1"); + followingsibling(child1); + System.out.println("Preceding siblings root"); + precedingsibling(root); + System.out.println("Following siblings root"); + followingsibling(root); + System.out.println("Pre-order traversal:2:"); + preorder(child2, tree.getModCount()); + System.out.println("Post-order traversal:3:"); + postorder(child3, tree.getModCount()); + System.out.println("Ancestors:3-2"); + ancestors(child3_2, tree.getModCount()); + + // Check the copySubTree function + System.out.println("copySubTree child3 to child2_1"); + child2_1.copySubTree(child3, 0); + System.out.println("Pre-order traversal:root:"); + preorder(root, tree.getModCount()); + System.out.println("copySubTree child3_3 to root"); + try { + root.copySubTree(child3_3, 0); + } catch (Tree.TreeException e) { + System.out.println("Caught TreeException: " + e.getMessage()); + } + + System.out.println("Pre-order traversal:root:"); + preorder(root, tree.getModCount()); + System.out.println("copySubTree child3 to child3_3"); + try { + child3_3.copySubTree(child3, 0); + } catch (Tree.TreeException e) { + System.out.println("Caught TreeException: " + e.getMessage()); + } + + System.out.println("Pre-order traversal:root:"); + preorder(root, tree.getModCount()); + + // Test the deleteSubTree method + System.out.println("deleteSubTree child2_1"); + int delcount = child2_1.deleteSubTree(); + System.out.println("# deleted: "+delcount); + + System.out.println("Pre-order traversal:root:"); + preorder(root, tree.getModCount()); + System.out.println("Post-order traversal:root:"); + postorder(root, tree.getModCount()); + // Test for fast-fail + System.out.println("Setting up PreOrder iterator"); + TNode.PreOrder iterator = root.new PreOrder(tree.getModCount()); + System.out.println("Adding child4"); + TNode child4 = new TNode(tree, root, "1-4"); + System.out.println("Iterating"); + try { + while (iterator.hasNext()) { + TNode next = (TNode) iterator.next(); + System.out.println((String)next.getContent()); + } + } catch (ConcurrentModificationException e) { + System.out.println("Comod exception caught"); + } // end of try-catch + System.out.println("Setting up FollowingSibling listIterator on 3-2"); + TNode.FollowingSibling listiterator = + child3_2.new FollowingSibling(); + System.out.println("Perturbing child3-2 parent; adding 3-4"); + TNode child3_4 = new TNode(tree, child3, "1-3-3"); + try { + while (listiterator.hasNext()) { + TNode next = (TNode) listiterator.next(); + System.out.println((String)next.getContent()); + } + } catch (ConcurrentModificationException e) { + System.out.println("Comod exception caught"); + } + + System.out.println("Setting up Ancestor Iterator on 1-1"); + TNode.Ancestor aiterator = + child1_1.new Ancestor(tree.getModCount()); + System.out.println("Perturbing root; adding 5"); + TNode child5 = new TNode(tree, root, "1-5"); + try { + while (aiterator.hasNext()) { + TNode next = (TNode) aiterator.next(); + System.out.println((String)next.getContent()); + } + } catch (ConcurrentModificationException e) { + System.out.println("Comod exception caught"); + } + + System.out.println("Delete all nodes"); + delcount = root.deleteSubTree(); + System.out.println("# deleted: "+delcount); + System.out.println("Pre-order traversal:root:"); + preorder((TNode)tree.getRoot(), tree.getModCount()); + } + + private static void preorder(TNode node, int age) { + TNode.PreOrder iterator = node.new PreOrder(age); + while (iterator.hasNext()) { + TNode next = (TNode) iterator.next(); + System.out.println((String)next.getContent()); + } + } + + private static void postorder(TNode node, int age) { + TNode.PostOrder iterator = node.new PostOrder(age); + while (iterator.hasNext()) { + TNode next = (TNode) iterator.next(); + System.out.println((String)next.getContent()); + } + } + + private static void ancestors(TNode node, int age) { + TNode.Ancestor iterator = node.new Ancestor(age); + while (iterator.hasNext()) { + TNode next = (TNode) iterator.next(); + System.out.println((String)next.getContent()); + } + } + + private static void followingsibling(TNode node) { + TNode.FollowingSibling iterator = + node.new FollowingSibling(); + while (iterator.hasNext()) { + TNode next = (TNode) iterator.next(); + System.out.println((String)next.getContent()); + } + } + + private static void precedingsibling(TNode node) { + TNode.PrecedingSibling iterator = + node.new PrecedingSibling(); + while (iterator.hasPrevious()) { + TNode previous = (TNode) iterator.previous(); + System.out.println((String)previous.getContent()); + } + } + +} // TNodeTest diff --git a/src/org/apache/fop/datastructs/Tree.java b/src/org/apache/fop/datastructs/Tree.java new file mode 100644 index 000000000..551e08d92 --- /dev/null +++ b/src/org/apache/fop/datastructs/Tree.java @@ -0,0 +1,1040 @@ +/* + * $Id$ + * + * Copyright (C) 2001 The Apache Software Foundation. All rights reserved. + * For details on use and redistribution please refer to the + * LICENSE file included with these sources. + * + */ + +package org.apache.fop.datastructs; + +import java.util.ArrayList; +import java.util.ConcurrentModificationException; +import java.util.NoSuchElementException; +import java.util.Iterator; +import java.util.ListIterator; + +// TODO: +// Should I provide a separate or supplementary exception for CoMods appends +// on the trailing edge of the tree? I.e., for each append, check if it is an +// append to a node which is the final sibling at any level of the tree. +// If so, set a copy the modCount value as the trailingEdgeModAge. +// If a ConcurrentModificationException is about to be thrown, check whether +// the trailingEdgeModAge is the same as the modCount. If so, throw a +// ConcurrentTreeAppendException instead. Probably, make that a subclass of +// ConcurrentModificationException so that the check can be done on the +// catch of the CoModEx. + +/** + * A generalised tree class with traversal Iterators. + * + *

The Tree class is analogous to one of the Collection + * classes. It provides a bag with a certain structure into which objects + * may be collected for manipulation.

+ * + *

The outer class, Tree, is the level at which are defined those fields + * and methods which are provided for the manipulation of the tree as a + * whole. The tree is actually comprised of a collection of Node + * elements.

+ * + *

The primary reasons for the existence of a separate Tree + * class is to provide an object for tree-wide synchronization, and to + * have a home for modCount for the provision of + * fast-fail iterators. For more details, see the + * discussion of modCount in AbstractList.

+ * + * @author Peter B. West + * @version $Revision$ $Name$ + */ + +public class Tree { + + /** + * The number of times the tree has been structurally modified. + * See the discussion of the modCount field in + * AbstractList. + */ + protected int modCount = 0; + protected int nodeCount = 0; + + protected Node root; + + public Tree() {} + + public int modified() { + // In the Tree class, this function updates the modCount + // N.B. This method is always called from within a synchronized + // method. + synchronized (this) { + return ++modCount; + } + } + + public int getModCount() { + synchronized (this) { + return modCount; + } + } + + public boolean modCountEqualTo(int value) { + synchronized (this) { + return value == modCount; + } + } + + public int size() { + return nodeCount; + } + + public boolean isEmpty() { + return nodeCount == 0; + } + + public Node getRoot() { + return root; + } + + public void unsetRoot() { + root = null; + } + + + public class TreeException extends Exception { + public TreeException(String message) { + super(message); + } + + } + + /** + *

Member class Node of class Tree provides the + * structure for a general-purpose tree.

+ *
+     * Node
+     * +-------------------------+
+     * |(Node) parent            |
+     * +-------------------------+
+     * |ArrayList                |
+     * |+-----------------------+|
+     * ||(Node) child 0         ||
+     * |+-----------------------+|
+     * |:                       :|
+     * |+-----------------------+|
+     * ||(Node) child n         ||
+     * |+-----------------------+|
+     * +-------------------------+
+     * 
+ *

ArrayList is used for the list of children because the + * synchronization is performed "manually" within the individual methods, + * and beause the fail-fast characteristics of the ArrayList + * iterator and listIterators is desired.

+ * + *

Note that there is no payload carried by the Node. This class must + * be subclassed to carry any actual node contents.

+ * + *

See Tree for the tree-wide support methods and fields.

+ */ + + public class Node implements Cloneable { + + private Node parent; + private ArrayList children; // ArrayList of Node + //protected Object content; + + /** + * No argument constructor. + * + * Assumes that this node is the root, and so will throw a + * TreeException when the root node in the enclosing + * Tree object is non-null. + */ + + public Node() + throws TreeException { + if (Tree.this.root != null) { + throw new TreeException( + "No arg constructor invalid when root exists"); + } + parent = null; + Tree.this.root = this; + children = new ArrayList(); + //content = null; + } + + /** + * @param parent Node which is the parent of this Node. if this is + * null, the generated Node is assumed to be the root + * node. If the Tree root node is already set, throws + * a TreeException. + * @param index int index of child in parent. + */ + + public Node(Node parent, int index) + throws TreeException, IndexOutOfBoundsException { + children = new ArrayList(); + //content = null; + if (parent == null) { + if (Tree.this.root != null) { + throw new TreeException("Null Node constructor " + + "invalid when root exists"); + } + this.parent = null; + Tree.this.root = this; + } + else { + // The parent must be a node in the current tree + if (parent.getTree() != Tree.this) { + throw new TreeException("Parent not in same tree"); + } + this.parent = parent; + // connect me to my parent + parent.addChild(index, this); + } + } + + /** + * @param parent Node which is the parent of this Node. if this is + * null, the generated Node is assumed to be the root + * node. If the Tree root node is already set, throws + * a TreeException. + */ + + public Node(Node parent) + throws TreeException, IndexOutOfBoundsException { + children = new ArrayList(); + //content = null; + if (parent == null) { + if (Tree.this.root != null) { + throw new TreeException("Null Node constructor " + + "invalid when root exists"); + } + this.parent = null; + Tree.this.root = this; + } + else { + // The parent must be a node in the current tree + if (parent.getTree() != Tree.this) { + throw new TreeException("Parent not in same tree"); + } + this.parent = parent; + // connect me to my parent + parent.addChild(this); + } + } + + + /** + * Appends a child Node to this node. Synchronized on the + * containing Tree object. + * + * Calls the modified method of the containing Tree to + * maintain the value of modCount. + * + * @param child Node to be added. + */ + + public void addChild(Node child) { + synchronized (Tree.this) { + children.add((Object) child); + Tree.this.modified(); + } + } + + /** + * Adds a child Node in this node at a specified index + * position. + * Synchronized on the containing Tree object. + * + * Calls the modified method of the containing Tree to + * maintain the value of modCount. + * + * @param index int position of new child + * @param child Node to be added. + */ + + public void addChild(int index, Node child) + throws IndexOutOfBoundsException { + synchronized (Tree.this) { + children.add(index, (Object) child); + Tree.this.modified(); + } + } + + /** + * Copies a subtree of this tree as a new child of this node. + * Synchronized on the containing Tree object. + * + * Calls the modified method of the containing Tree to + * maintain the value of modCount. + * + * Note that it is illegal to try to copy a subtree to one of + * its own descendents or itself. (This restriction could be lifted + * by creating a new Tree containing the subtree, and defining an + * attachTree() method to attach one Tree to another.) + * + * This is the public entry to copyCheckSubTree. It will always + * perform a check for the attempt to copy onto a descendent or + * self. It calls copyCheckSubTree. + * + * @param subtree Node at the root of the subtree to be added. + * @param index int index of child position in Node's children + */ + + public void copySubTree(Node subtree, int index) + throws TreeException, ConcurrentModificationException { + copyCheckSubTree(subtree, index, true); + } + + /** + * Copies a subtree of this tree as a new child of this node. + * Synchronized on the containing Tree object. + * + * Calls the modified method of the containing Tree to + * maintain the value of modCount. + * + * Note that it is illegal to try to copy a subtree to one of + * its own descendents or itself. (This restriction could be lifted + * by creating a new Tree containing the subtree, and defining an + * attachTree() method to attach one Tree to another.) + * + * WARNING: this version of the method assumes that Tree.Node + * will be subclassed; Tree.Node has no contents, so for + * the tree to carry any data the Node must be subclassed. As a + * result, this method copies nodes by performing a clone() + * operation on the nodes being copied, rather than issuing a + * new Tree.Node(..) call. It then adjusts the necessary + * references to position the cloned node under the correct parent. + * As part of this process, the method must create a new empty + * children ArrayList. if this is not done, + * subsequent addChild() operations on the node will affect + * the original children array. + * + * This warning applies to the contents of any subclassed + * Tree.Node. All references in the copied subtree will be to + * the objects from the original subtree. If this has undesirable + * effects, the method must be overridden so that the copied subtree + * can have its references adjusted after the copy. + * + * @param subtree Node at the root of the subtree to be added. + * @param index int index of child position in Node's children + * @param checkLoops boolean - should the copy been checked for + * loops. Set this to true on the first + * call. + */ + + private void copyCheckSubTree( + Node subtree, int index, boolean checkLoops) + throws TreeException, ConcurrentModificationException { + synchronized (Tree.this) { + Node newNode = null; + if (checkLoops) { + checkLoops = false; + if (subtree == this) { + throw new TreeException + ("Copying subtree onto itself."); + } + + // Check that subtree is not an ancestor of this. + Ancestor ancestors = + new Ancestor(Tree.this.getModCount()); + while (ancestors.hasNext()) { + if ((Node)ancestors.next() == subtree) { + throw new TreeException + ("Copying subtree onto descendent."); + } + } + } + + //Node newNode = new Node(this, index, subtree.getContent()); + // Clone (shallow copy) the head of the subtree + try { + newNode = (Node)subtree.clone(); + } catch (CloneNotSupportedException e) { + throw new TreeException( + "clone() not supported on Tree.Node"); + } + + // Attach the clone to this at the indicated child index + newNode.parent = this; + this.addChild(index, newNode); + // Clear the children arrayList + newNode.children = new ArrayList(newNode.numChildren()); + // Now iterate over the children of the root of the + // subtree, adding a copy to the newly created Node + Iterator iterator = subtree.nodeChildren(); + while (iterator.hasNext()) { + newNode.copyCheckSubTree((Node)iterator.next(), + newNode.numChildren(), + checkLoops); + } + Tree.this.modified(); + } + } + + + /** + * Removes the child Node at the specified index in the + * ArrayList. Synchronized on the enclosing Tree object. + * + * Calls the modified method of the containing Tree to + * maintain the value of modCount. + * + * @param index The int index of the child to be removed. + * @return the node removed. + */ + + public Node removeChildAtIndex(int index) { + synchronized (Tree.this) { + Node tmpNode = (Node) children.remove(index); + Tree.this.modified(); + return tmpNode; + } + } + + /** + * Removes the specified child Node from the children + * ArrayList. Synchronized on the enclosing Tree object. + * + * Implemented by calling removeChildAtIndex(). Relies + * on that method to call the modified method of the + * containing Tree to maintain the value of modCount. + * + * @param child The child node to be removed. + * @return the node removed. + */ + + public Node removeChild(Node child) + throws NoSuchElementException { + synchronized (Tree.this) { + int index = children.indexOf((Object) child); + if (index == -1) { + throw new NoSuchElementException(); + } + Node tmpNode = removeChildAtIndex(index); + // Note - no call to Tree.this.modified() here - + // done in removeChildAtindex() + return tmpNode; + } + } + + /** + * Deletes the entire subtree rooted on this from the + * Tree. The Tree is + * traversed in PostOrder, and each Node is removed in PostOrder. + * @return int count of Nodes deleted. + */ + public int deleteSubTree() { + synchronized (Tree.this) { + int count = delete(this); + Tree.this.modified(); + return count; + } + } + + /** + * N.B. this private method must only be called from the deleteSubTree + * method, which is synchronized. In itself, it is not synchronized. + * @param subtree Node at the root of the subtree to be deleted. + * @return int count of Nodes deleted. + */ + private int delete(Node subtree) { + int count = 0; + + while (subtree.numChildren() > 0) { + //System.out.println("# children "+subtree.numChildren()); + + count += delete((Node)subtree.children.get(0)); + } + // Delete this node + // nullify the parent reference + if (subtree.getTree().getRoot() != subtree) { + // Not the root node - remove from parent + subtree.getParent().removeChild(subtree); + subtree.unsetParent(); + } else { + subtree.getTree().unsetRoot(); + } // end of else + return ++count; + } + + public Tree getTree() { + return Tree.this; + } + + public Node getParent() { + return (Node) parent; + } + + public void unsetParent() { + parent = null; + } + + public Node getChild(int index) { + return (Node) children.get(index); + } + + public Iterator nodeChildren() { + return children.iterator(); + } + + public int numChildren() { + return children.size(); + } + + /** + * Class PreOrder is a member class of Node. + * + * It implements the Iterator interface, excluding the + * optional remove method. The iterator traverses its + * containing Tree from its containing Node in + * preorder order. + * + * The method is implemented recursively; + * at each node, a PreOrder object is instantiated to handle the + * node itself and to trigger the handing of the node's children. + * The node is returned first, and then for each child, a new + * PreOrder is instantiated. That iterator will terminate when the + * last descendant of that child has been returned. + * + * The iterator is fast-fail. If any modifications occur to + * the tree as a whole during the lifetime of the iterator, a + * subsequent call to next() will throw a + * ConcurrentModificationException. See the discussion of + * fast-fail iterators in AbstractList. + * + * The modCount field used to maintain information about + * structural modifcations is maintained for all nodes in the + * containing Tree instance. + */ + + class PreOrder implements Iterator { + private boolean selfNotReturned = true; + private int nextChildIndex = 0; // N.B. this must be kept as + // the index of the active child until that child is exhausted. + // At the start of proceedings, it may already point past the + // end of the (empty) child vector + + private int age; + private PreOrder nextChildIterator; + + /** + * Constructor + * + * @param age the current value of the modCount field in the + * Tree instance which includes this class instance. + */ + public PreOrder(int age) { + this.age = age; + hasNext(); // A call to set up the initial iterators + // so that a call to next() without a preceding call to + // hasNext() will behave sanely + } + + public boolean hasNext() { + // synchronize this against possible changes to the tree + synchronized (Tree.this) { + if (selfNotReturned) { + return true; + } + // self has been returned - are there any children? + // if so, we must always have an iterator available + // even unless it is exhausted. Assume it is set up this + // way by next(). The iterator has a chance to do this + // because self will always be returned first. + // The test of nextChildIndex must always be made because + // there may be no children, or the last child may be + // exhausted, hence no possibility of an + // iterator on the children of any child. + if (nextChildIndex < children.size()) { + return nextChildIterator.hasNext(); + } + else { // no kiddies + return false; + } + } + } + + public Object next() + throws NoSuchElementException, + ConcurrentModificationException { + synchronized (Tree.this) { + // synchronize the whole against changes to the tree + + // Check for ConcurrentModification + if (! Tree.this.modCountEqualTo(age)) { + throw new ConcurrentModificationException(); + } + + if (! hasNext()) { + throw new NoSuchElementException(); + } + if (selfNotReturned) { + selfNotReturned = false; + if (nextChildIndex < children.size()) { + // We have children - create an iterator + // for the first one + nextChildIterator = + ((Node) + (children.get(nextChildIndex))).new + PreOrder(age); + } + // else do nothing; + // the nextChildIndex test in hasNext() + // will prevent us from getting into trouble + return Node.this; + } + else { // self has been returned + // there must be a next available, or we would not have + // come this far + if (! nextChildIterator.hasNext()) { + // last iterator was exhausted; + // if there was another child available, an + //iterator would already have been set up. + // Every iterator will return at least one node - + // the node on which it is defined. + // So why did the initial hasNext succeed? + throw new NoSuchElementException( + "Cannot reach this"); + } + Object tempNode = nextChildIterator.next(); + // Check for exhaustion of the child + if (! nextChildIterator.hasNext()) { + // child iterator empty - another child? + if (++nextChildIndex < children.size()) { + nextChildIterator = + ((Node) + (children.get(nextChildIndex))).new + PreOrder(age); + } + else { + // nullify the iterator + nextChildIterator = null; + } + } + return (Node) tempNode; + } + } + } + + public void remove() + throws UnsupportedOperationException { + throw new UnsupportedOperationException(); + } + } + + + /** + * Class PostOrder is a member class of Node. + * + * It implements the Iterator interface, excluding the + * optional remove method. The iterator traverses its + * containing Tree from its containing Node in + * postorder order. + * + * The method is implemented recursively; + * at each node, a PostOrder object is instantiated to handle the + * node itself and to trigger the handing of the node's children. + * Firstly, for each child a new PostOrder is instantiated. + * That iterator will terminate when the last descendant of that + * child has been returned. finally, the node itself is returned. + * + * The iterator is fast-fail. iI any modifications occur to + * the tree as a whole during the lifetime of the iterator, a + * subsequent call to next() will throw a + * ConcurrentModificationException. See the discussion of + * fast-fail iterators in AbstractList. + * + * The modCount field used to maintain information about + * structural modifcations is maintained for all nodes in the + * containing Tree instance. + */ + + class PostOrder implements Iterator { + private boolean selfReturned = false; + private int nextChildIndex = 0; // N.B. this must be kept as + // the index of the active child until that child is exhausted. + // At the start of proceedings, it may already point past the + // end of the (empty) child vector + + private int age; + private PostOrder nextChildIterator; + + /** + * Constructor + * + * @param age the current value of the modCount field in the + * Tree instance which includes this class instance. + */ + public PostOrder(int age) { + this.age = age; + hasNext(); // A call to set up the initial iterators + // so that a call to next() without a preceding call to + // hasNext() will behave sanely + } + + public boolean hasNext() { + // Synchronize this against changes in the tree + synchronized (Tree.this) { + // self is always the last to go + if (selfReturned) { // nothing left + return false; + } + + // Check first for children, and set up an iterator if so + if (nextChildIndex < children.size()) { + if (nextChildIterator == null) { + nextChildIterator = + ((Node) + (children.get(nextChildIndex))).new + PostOrder(age); + } + // else an iterator exists. + // Assume that the next() method + // will keep the iterator current + } // end of Any children? + + return true; + } + } + + public Object next() + throws NoSuchElementException, + ConcurrentModificationException { + synchronized (Tree.this) { + // synchronize the whole against changes to the tree + + // Check for ConcurrentModification + if (! Tree.this.modCountEqualTo(age)) { + throw new ConcurrentModificationException(); + } + + if (! hasNext()) { + throw new NoSuchElementException(); + } + // Are there any children? + if (nextChildIndex < children.size()) { + // There will be an active iterator. Is it empty? + if (nextChildIterator.hasNext()) { + // children remain + Object tempNode = nextChildIterator.next(); + // now check for exhaustion of the iterator + if (! nextChildIterator.hasNext()) { + if (++nextChildIndex < children.size()) { + nextChildIterator = + ((Node) + (children.get(nextChildIndex))).new + PostOrder(age); + } + // else leave the iterator bumping on empty + // next call will return self + } + // else iterator not exhausted + // return the Node + return (Node) tempNode; + } + // else children exhausted - fall through + } + // No children - return self object + selfReturned = true; + nextChildIterator = null; + return Node.this; + } + } + + public void remove() + throws UnsupportedOperationException { + throw new UnsupportedOperationException(); + } + } + + + /** + * Class Ancestor is a member class of Node. + * + * It implements the Iterator interface, excluding the + * optional remove method. The iterator traverses the + * ancestors of its containing Node from the Node's immediate + * parent back to tge root Node of the containing Tree. + * + * The iterator is fast-fail. If any modifications occur to + * the tree as a whole during the lifetime of the iterator, a + * subsequent call to next() will throw a + * ConcurrentModificationException. See the discussion of + * fast-fail iterators in AbstractList. + * + * The modCount field used to maintain information about + * structural modifcations is maintained for all nodes in the + * containing Tree instance. + */ + + class Ancestor implements Iterator { + private Tree.Node nextAncestor; + private int age; + + /** + * Constructor + * + * @param age the current value of the modCount field in the + * Tree instance which includes this class instance. + */ + + public Ancestor(int age) { + this.age = age; + nextAncestor = Node.this.parent; + } + + public boolean hasNext() { + return nextAncestor != null; + } + + public Object next() + throws NoSuchElementException, + ConcurrentModificationException { + synchronized (Tree.this) { + // The tree is a + // potentially dymanic structure, which could be + // undergoing modification as this method is being + // executed, and it is possible that the Comod exception + // could be set to trigger while this call is in process. + if (! Tree.this.modCountEqualTo(age)) { + throw new ConcurrentModificationException(); + } + Tree.Node tmpNode = nextAncestor; + nextAncestor = tmpNode.parent; + return tmpNode; + } + } + + public void remove() + throws UnsupportedOperationException { + throw new UnsupportedOperationException(); + } + } + + + /** + * Class FollowingSibling is a member class of Node. + * + * It implements the ListIterator interface, but has reports + * UnsupportedOperationException for all methods except + * hasNext(), next() and nextIndex(). + * These methods are implemented as synchronized wrappers around the + * underlying ArrayList methods. + * + * The listIterator traverses those children in the parent node's + * children ArrayList which follow the subject + * node's entry in that array, using the next() method. + * + * The iterator is fail-fast. if any modification occur to + * the tree as a whole during the lifetime of the iterator, a + * subsequent call to next() will throw a + * ConcurrentModificationException. See the discussion of + * fast-fail iterators in AbstractList. + * + * The fail-fast ListIterator in ArrayList is the underlying + * mechanism for both the listIterator and the fail-fast + * behaviour. + */ + + class FollowingSibling implements ListIterator { + + private ListIterator listIterator; + private ArrayList rootDummy = new ArrayList(); + // An empty ArrayList for the root listIterator + // hasNext() will always return false + + public FollowingSibling() { + synchronized (Tree.this) { + // Set up iterator on the parent's arrayList of children + Tree.Node refNode = Node.this.parent; + if (refNode != null) { + // Not the root node; siblings may exist + // Set up iterator on the parent's children ArrayList + ArrayList siblings = refNode.children; + int index = siblings.indexOf((Object) Node.this); + // if this is invalid, we are in serious trouble + listIterator = siblings.listIterator(index + 1); + } // end of if (Node.this.parent != null) + else { + // Root node - no siblings + listIterator = rootDummy.listIterator(); + } + } + } + + public boolean hasNext() { + // Any CoMod exception will be thrown by the listIterator + // provided with ArrayList. It does not throw such exceptions + // on calls to hasNext(); + synchronized (Tree.this) { + return listIterator.hasNext(); + } + } + + public Object next() + throws NoSuchElementException, + ConcurrentModificationException { + synchronized (Tree.this) { + // N.B. synchronization here is still on the Tree + // rather than on the Node containing the children + // ArryList of interest. Other ArrayList operations + // throughout the Tree are synchronized on the Tree object + // itself, so exceptions cannot be made for these more + // directly Nodal operations. + return listIterator.next(); + } + } + + public int nextIndex() { + synchronized (Tree.this) { + return listIterator.nextIndex(); + } + } + + public void add(Object o) + throws UnsupportedOperationException { + throw new UnsupportedOperationException(); + } + + public void remove() + throws UnsupportedOperationException { + throw new UnsupportedOperationException(); + } + + public boolean hasPrevious() + throws UnsupportedOperationException { + throw new UnsupportedOperationException(); + } + + public Object previous() + throws UnsupportedOperationException { + throw new UnsupportedOperationException(); + } + + public int previousIndex() + throws UnsupportedOperationException { + throw new UnsupportedOperationException(); + } + + public void set(Object o) + throws UnsupportedOperationException { + throw new UnsupportedOperationException(); + } + } + + /** + * Class PrecedingSibling is a member class of Node. + * + * It implements the ListIterator interface, but has reports + * UnsupportedOperationException for all methods except + * hasPrevious(), previous() and + * previousIndex(). + * These methods are implemented as synchronized wrappers around the + * underlying ArrayList methods. + * + * The listIterator traverses those children in the parent node's + * children ArrayList which precede the subject + * node's entry in that array, using the previous() method. + * I.e., siblings are produced in reverse sibling order. + * + * The iterator is fail-fast. if any modification occur to + * the tree as a whole during the lifetime of the iterator, a + * subsequent call to previous() will throw a + * ConcurrentModificationException. See the discussion of + * fast-fail iterators in AbstractList. + * + * The fail-fast ListIterator in ArrayList is the underlying + * mechanism for both the listIterator and the fail-fast + * behaviour. + */ + + class PrecedingSibling implements ListIterator { + + private ListIterator listIterator; + private ArrayList rootDummy = new ArrayList(); + // An empty ArrayList for the root listIterator + // hasNext() will always return false + + public PrecedingSibling() { + synchronized (Tree.this) { + // Set up iterator on the parent's arrayList of children + Tree.Node refNode = Node.this.parent; + if (refNode != null) { + // Not the root node; siblings may exist + // Set up iterator on the parent's children ArrayList + ArrayList siblings = refNode.children; + int index = siblings.indexOf((Object) Node.this); + // if this is invalid, we are in serious trouble + listIterator = siblings.listIterator(index); + } // end of if (Node.this.parent != null) + else { + // Root node - no siblings + listIterator = rootDummy.listIterator(); + } + } + } + + public boolean hasPrevious() { + // Any CoMod exception will be thrown by the listIterator + // provided with ArrayList. It does not throw such exceptions + // on calls to hasNext(); + synchronized (Tree.this) { + return listIterator.hasPrevious(); + } + } + + public Object previous() + throws NoSuchElementException, + ConcurrentModificationException { + synchronized (Tree.this) { + // N.B. synchronization here is still on the Tree + // rather than on the Node containing the children + // ArryList of interest. Other ArrayList operations + // throughout the Tree are synchronized on the Tree object + // itself, so exceptions cannot be made for these more + // directly Nodal operations. + return listIterator.previous(); + } + } + + public int previousIndex() { + synchronized (Tree.this) { + return listIterator.previousIndex(); + } + } + + public void add(Object o) + throws UnsupportedOperationException { + throw new UnsupportedOperationException(); + } + + public void remove() + throws UnsupportedOperationException { + throw new UnsupportedOperationException(); + } + + public boolean hasNext() + throws UnsupportedOperationException { + throw new UnsupportedOperationException(); + } + + public Object next() + throws UnsupportedOperationException { + throw new UnsupportedOperationException(); + } + + public int nextIndex() + throws UnsupportedOperationException { + throw new UnsupportedOperationException(); + } + + public void set(Object o) + throws UnsupportedOperationException { + throw new UnsupportedOperationException(); + } + } + + } + +} diff --git a/src/org/apache/fop/datastructs/package.html b/src/org/apache/fop/datastructs/package.html new file mode 100644 index 000000000..049a883f5 --- /dev/null +++ b/src/org/apache/fop/datastructs/package.html @@ -0,0 +1,6 @@ + +org.apache.fop.datastructs Package + +

Classes representing complex data structures.

+ + -- 2.39.5