001 /* 002 // $Id: //open/mondrian/src/main/mondrian/olap/Walker.java#6 $ 003 // This software is subject to the terms of the Common Public License 004 // Agreement, available at the following URL: 005 // http://www.opensource.org/licenses/cpl.html. 006 // Copyright (C) 1999-2002 Kana Software, Inc. 007 // Copyright (C) 2001-2008 Julian Hyde and others 008 // All Rights Reserved. 009 // You must accept the terms of that agreement to use this software. 010 // 011 // jhyde, 1 March, 1999 012 */ 013 014 package mondrian.olap; 015 import java.io.PrintWriter; 016 import java.util.Enumeration; 017 import java.util.Stack; 018 019 /** 020 * Walks over a tree, returning nodes in prefix order. Objects which are an 021 * instance of {@link Walkable} supply their children using 022 * <code>getChildren()</code>; other objects are assumed to have no children. 023 * 024 * <p>If the tree is modified during the enumeration, strange things may happen. 025 * 026 * <p>Example use:<code><pre> 027 * Tree t; 028 * Walker w = new Walker(t); 029 * while (w.hasMoreElements()) { 030 * Tree node = (Tree) w.nextNode(); 031 * System.out.println(node.toString()); 032 * } 033 * </pre></code> 034 */ 035 public class Walker implements Enumeration { 036 /** 037 * The active parts of the tree from the root to nextNode are held in a 038 * stack. When the stack is empty, the enumeration finishes. currentFrame 039 * holds the frame of the 'current node' (the node last returned from 040 * nextElement()) because it may no longer be on the stack. 041 */ 042 private final Stack stack; 043 private Frame currentFrame; 044 private Object nextNode; 045 046 private class Frame { 047 Frame(Frame parent, Object node) { 048 this.parent = parent; 049 this.node = node; 050 this.children = getChildren(node); 051 this.childIndex = -1; // haven't visited first child yet 052 } 053 final Frame parent; 054 final Object node; 055 final Object[] children; 056 int childIndex; 057 } 058 059 public Walker(Walkable root) 060 { 061 stack = new Stack(); 062 currentFrame = null; 063 visit(null, root); 064 } 065 066 private void moveToNext() 067 { 068 if (stack.empty()) 069 return; 070 071 currentFrame = (Frame) stack.peek(); 072 073 // Unwind stack until we find a level we have not completed. 074 do { 075 Frame frame = (Frame) stack.peek(); 076 if (frame.children != null && 077 ++frame.childIndex < frame.children.length) { 078 // Here is an unvisited child. Visit it. 079 visit(frame, frame.children[frame.childIndex]); 080 return; 081 } 082 stack.pop(); 083 } while (!stack.empty()); 084 nextNode = null; 085 } 086 087 private void visit(Frame parent, Object node) 088 { 089 nextNode = node; 090 stack.addElement(new Frame(parent, node)); 091 } 092 093 public boolean hasMoreElements() 094 { 095 return nextNode != null; 096 } 097 098 public Object nextElement() 099 { 100 moveToNext(); 101 return currentFrame.node; 102 } 103 104 /** Tell walker that we don't want to visit any (more) children of this 105 * node. The next node visited will be (a return visit to) the node's 106 * parent. Not valid until nextElement() has been called. */ 107 public void prune() 108 { 109 if (currentFrame.children != null) { 110 currentFrame.childIndex = currentFrame.children.length; 111 } 112 //we need to make that next frame on the stack is not a child 113 //of frame we just pruned. if it is, we need to prune it too 114 if (this.hasMoreElements()) { 115 Object nextFrameParentNode = ((Frame)stack.peek()).parent.node; 116 if (nextFrameParentNode != currentFrame.node) { 117 return; 118 } 119 //delete the child of current member from the stack 120 stack.pop(); 121 if (currentFrame.parent != null) 122 currentFrame = currentFrame.parent; 123 nextElement(); 124 } 125 } 126 127 public void pruneSiblings() 128 { 129 prune(); 130 currentFrame = currentFrame.parent; 131 if (currentFrame != null) { 132 prune(); 133 } 134 } 135 136 137 /** returns the current object. Not valid until nextElement() has been 138 called. */ 139 public Object currentElement() 140 { 141 return currentFrame.node; 142 } 143 144 /** returns level in the tree of the current element (that is, last element 145 * returned from nextElement()). The level of the root element is 0. */ 146 public int level() 147 { 148 int i = 0; 149 for (Frame f = currentFrame; f != null; f = f.parent) 150 i++; 151 return i; 152 } 153 154 public final Object getParent() 155 { 156 return getAncestor(1); 157 } 158 159 public final Object getAncestor(int iDepth) 160 { 161 Frame f = getAncestorFrame(iDepth); 162 return f == null ? null : f.node; 163 } 164 165 /** returns the <code>iDepth</code>th ancestor of the current element */ 166 private Frame getAncestorFrame(int iDepth) 167 { 168 for (Frame f = currentFrame; f != null; f = f.parent) 169 if (iDepth-- == 0) 170 return f; 171 return null; 172 } 173 174 /** get the ordinal within its parent node of the current node. Returns 0 175 for the root element. Equivalent to getAncestorOrdinal(0). */ 176 public int getOrdinal() 177 { 178 // We can't use currentFrame.parent.iChild because moveToNext() may 179 // have changed it. 180 return currentFrame.parent == null ? 0 : 181 arrayFind(currentFrame.parent.children, currentFrame.node); 182 } 183 184 /** get the ordinal within its parent node of the <code>iDepth</code>th 185 * ancestor. */ 186 public int getAncestorOrdinal(int iDepth) 187 { 188 Frame f = getAncestorFrame(iDepth); 189 return f == null ? -1 : 190 f.parent == null ? 0 : 191 arrayFind(f.parent.children, f.node); 192 } 193 194 /** Override this function to prune the tree, or to allow objects which are 195 * not Walkable to have children. */ 196 public Object[] getChildren(Object node) 197 { 198 if (node instanceof Walkable) { 199 return ((Walkable) node).getChildren(); 200 } else { 201 return null; 202 } 203 } 204 205 private static int arrayFind(Object[] array, Object o) 206 { 207 for (int i = 0; i < array.length; i++) { 208 if (array[i] == o) { 209 return i; 210 } 211 } 212 return -1; 213 } 214 215 private static class Region implements Walkable 216 { 217 String name; 218 Region[] children; 219 220 Region(String name, Region[] children) 221 { 222 this.name = name; 223 this.children = children; 224 } 225 226 public Object[] getChildren() { 227 return children; 228 } 229 230 public static void walkUntil(Walker walker, String name) { 231 while (walker.hasMoreElements()) { 232 Region region = (Region) walker.nextElement(); 233 if (region.name.equals(name)) { 234 break; 235 } 236 } 237 } 238 }; 239 240 public static void main(String[] args) 241 { 242 PrintWriter pw = new PrintWriter(System.out); 243 Region usa = new Region( 244 "USA", new Region[] { 245 new Region("CA", new Region[] { 246 new Region("San Francisco", new Region[]{ 247 new Region("WesternAddition", new Region[]{ new Region("Haight", null)}), 248 new Region("Soma", null) 249 }), 250 new Region("Los Angeles", null)}), 251 new Region("WA", new Region[] { 252 new Region("Seattle", null), 253 new Region("Tacoma", null)})}); 254 255 Walker walker = new Walker(usa); 256 if (false) { 257 while (walker.hasMoreElements()) { 258 Region region = (Region) walker.nextElement(); 259 pw.println(region.name); 260 pw.flush(); 261 } 262 } 263 264 Region.walkUntil(walker, "CA"); 265 walker.prune(); 266 Region region = (Region) walker.nextElement(); // should be WA 267 pw.println(region.name); 268 pw.flush(); 269 270 walker = new Walker(usa); 271 Region.walkUntil(walker, "USA"); 272 walker.prune(); 273 region = (Region) walker.nextElement(); // should be null 274 if (region == null) 275 pw.println("null"); 276 pw.flush(); 277 278 walker = new Walker(usa); 279 Region.walkUntil(walker, "Los Angeles"); 280 walker.prune(); 281 region = (Region) walker.nextElement(); // should be WA 282 pw.println(region.name); 283 pw.flush(); 284 285 walker = new Walker(usa); 286 Region.walkUntil(walker, "Haight"); 287 walker.prune(); 288 region = (Region) walker.nextElement(); // should be Soma 289 pw.println(region.name); 290 pw.flush(); 291 292 walker = new Walker(usa); 293 Region.walkUntil(walker, "Soma"); 294 walker.prune(); 295 region = (Region) walker.nextElement(); // should be Los Angeles 296 pw.println(region.name); 297 pw.flush(); 298 299 walker = new Walker(usa); 300 Region.walkUntil(walker, "CA"); 301 walker.pruneSiblings(); 302 region = (Region) walker.nextElement(); // should be Los Angeles 303 if (region == null) { 304 pw.println("null"); 305 pw.flush(); 306 } 307 308 walker = new Walker(usa); 309 Region.walkUntil(walker, "Soma"); 310 walker.pruneSiblings(); 311 region = (Region) walker.nextElement(); // should be Los Angeles 312 if (region == null) { 313 pw.println("null"); 314 pw.flush(); 315 } 316 } 317 } 318 319 320 // End Walker.java