View Javadoc

1   /*
2    * ====================================================================
3    * This software is subject to the terms of the Common Public License
4    * Agreement, available at the following URL:
5    *   http://www.opensource.org/licenses/cpl.html .
6    * You must accept the terms of that agreement to use this software.
7    * ====================================================================
8    */
9   package com.eyeq.pivot4j.ui.table;
10  
11  import java.util.ArrayList;
12  import java.util.HashMap;
13  import java.util.Iterator;
14  import java.util.LinkedList;
15  import java.util.List;
16  import java.util.Map;
17  
18  import org.apache.commons.lang.ObjectUtils;
19  import org.olap4j.Axis;
20  import org.olap4j.OlapException;
21  import org.olap4j.Position;
22  import org.olap4j.metadata.Dimension.Type;
23  import org.olap4j.metadata.Hierarchy;
24  import org.olap4j.metadata.Level;
25  import org.olap4j.metadata.Measure;
26  import org.olap4j.metadata.Member;
27  import org.olap4j.metadata.Property;
28  
29  import com.eyeq.pivot4j.PivotException;
30  import com.eyeq.pivot4j.ui.aggregator.Aggregator;
31  import com.eyeq.pivot4j.util.OlapUtils;
32  import com.eyeq.pivot4j.util.TreeNode;
33  import com.eyeq.pivot4j.util.TreeNodeCallback;
34  
35  class TableHeaderNode extends TreeNode<TableAxisContext> {
36  
37  	private Position position;
38  
39  	private Member member;
40  
41  	private Property property;
42  
43  	private Hierarchy hierarchy;
44  
45  	private Integer colSpan;
46  
47  	private Integer rowSpan;
48  
49  	private Integer colIndex;
50  
51  	private Integer rowIndex;
52  
53  	private Integer maxRowIndex;
54  
55  	private Integer hierarchyDescendants;
56  
57  	private Integer memberChildren;
58  
59  	private boolean aggregation = false;
60  
61  	private Aggregator aggregator;
62  
63  	/**
64  	 * @param context
65  	 */
66  	TableHeaderNode(TableAxisContext context) {
67  		super(context);
68  	}
69  
70  	/**
71  	 * @return the position
72  	 */
73  	public Position getPosition() {
74  		return position;
75  	}
76  
77  	/**
78  	 * @param position
79  	 *            the position to set
80  	 */
81  	public void setPosition(Position position) {
82  		this.position = position;
83  	}
84  
85  	/**
86  	 * @return the member
87  	 */
88  	public Member getMember() {
89  		return member;
90  	}
91  
92  	/**
93  	 * @param member
94  	 *            the member to set
95  	 */
96  	public void setMember(Member member) {
97  		this.member = member;
98  	}
99  
100 	/**
101 	 * @return the level
102 	 */
103 	public Level getMemberLevel() {
104 		return member == null ? null : member.getLevel();
105 	}
106 
107 	/**
108 	 * @return the hierarchy
109 	 */
110 	public Hierarchy getHierarchy() {
111 		return hierarchy;
112 	}
113 
114 	/**
115 	 * @param hierarchy
116 	 *            the hierarchy to set
117 	 */
118 	public void setHierarchy(Hierarchy hierarchy) {
119 		this.hierarchy = hierarchy;
120 	}
121 
122 	/**
123 	 * @return the property
124 	 */
125 	public Property getProperty() {
126 		return property;
127 	}
128 
129 	/**
130 	 * @param property
131 	 *            the property to set
132 	 */
133 	public void setProperty(Property property) {
134 		this.property = property;
135 	}
136 
137 	public void clearCache() {
138 		this.colIndex = null;
139 		this.rowIndex = null;
140 		this.colSpan = null;
141 		this.rowSpan = null;
142 		this.maxRowIndex = null;
143 		this.hierarchyDescendants = null;
144 		this.memberChildren = null;
145 	}
146 
147 	public int getHierarchyIndex() {
148 		if (hierarchy == null) {
149 			return -1;
150 		}
151 		return getReference().getHierarchies().indexOf(hierarchy);
152 	}
153 
154 	public Level getRootLevel() {
155 		int index = getHierarchyIndex();
156 		if (index < 0) {
157 			return null;
158 		}
159 
160 		return getReference().getLevels(getHierarchy()).get(0);
161 	}
162 
163 	public int getMaxRowIndex() {
164 		if (maxRowIndex == null) {
165 			if (getChildCount() == 0) {
166 				this.maxRowIndex = getRowIndex();
167 			} else {
168 				this.maxRowIndex = 0;
169 
170 				for (TreeNode<TableAxisContext> child : getChildren()) {
171 					TableHeaderNode nodeChild = (TableHeaderNode) child;
172 					maxRowIndex = Math.max(maxRowIndex,
173 							nodeChild.getMaxRowIndex());
174 				}
175 			}
176 		}
177 
178 		return maxRowIndex;
179 	}
180 
181 	void addHierarhcyHeaders() {
182 		List<TreeNode<TableAxisContext>> children = new ArrayList<TreeNode<TableAxisContext>>(
183 				getChildren());
184 
185 		for (TreeNode<TableAxisContext> child : children) {
186 			TableHeaderNode nodeChild = (TableHeaderNode) child;
187 
188 			Hierarchy childHierarchy = nodeChild.getHierarchy();
189 
190 			if (childHierarchy != null
191 					&& !OlapUtils.equals(hierarchy, childHierarchy)) {
192 				int index = getChildren().indexOf(child);
193 
194 				removeChild(child);
195 
196 				TableHeaderNode hierarchyNode = new TableHeaderNode(
197 						getReference());
198 				hierarchyNode.setHierarchy(childHierarchy);
199 
200 				addChild(index, hierarchyNode);
201 				hierarchyNode.addChild(child);
202 			}
203 
204 			nodeChild.addHierarhcyHeaders();
205 		}
206 	}
207 
208 	void addParentMemberHeaders() {
209 		List<TreeNode<TableAxisContext>> children = new ArrayList<TreeNode<TableAxisContext>>(
210 				getChildren());
211 
212 		for (TreeNode<TableAxisContext> child : children) {
213 			TableHeaderNode nodeChild = (TableHeaderNode) child;
214 
215 			Member mem = nodeChild.getMember();
216 			if (mem != null) {
217 				int index = getChildren().indexOf(child);
218 
219 				removeChild(child);
220 
221 				TreeNode<TableAxisContext> childNode = child;
222 
223 				Member parent = mem;
224 
225 				while (parent != null) {
226 					parent = getReference().getParentMember(parent);
227 
228 					if (parent == null) {
229 						break;
230 					}
231 					TableHeaderNode parentNode = new TableHeaderNode(
232 							getReference());
233 					parentNode.setPosition(nodeChild.getPosition());
234 					parentNode.setHierarchy(parent.getHierarchy());
235 					parentNode.setMember(parent);
236 					parentNode.addChild(childNode);
237 
238 					childNode = parentNode;
239 				}
240 
241 				addChild(index, childNode);
242 			}
243 
244 			nodeChild.addParentMemberHeaders();
245 		}
246 	}
247 
248 	/**
249 	 * @param collector
250 	 */
251 	void addMemberProperties() {
252 		if (getReference().getAxis() != Axis.ROWS) {
253 			return;
254 		}
255 
256 		List<TreeNode<TableAxisContext>> children = null;
257 
258 		if (getMember() != null) {
259 			List<Level> levels = getReference().getLevels(getHierarchy());
260 
261 			int index = levels.indexOf(getMember().getLevel());
262 			int endIndex = getReference().getRenderer().getShowParentMembers() ? index + 1
263 					: levels.size();
264 
265 			List<Level> lowerLevels = levels.subList(index, endIndex);
266 
267 			for (Level level : lowerLevels) {
268 				List<Property> properties = getReference().getProperties(level);
269 
270 				if (!properties.isEmpty()) {
271 					children = new ArrayList<TreeNode<TableAxisContext>>(
272 							getChildren());
273 					clear();
274 
275 					TableHeaderNode parentNode = this;
276 
277 					for (Property prop : properties) {
278 						TableHeaderNode propertyNode = new TableHeaderNode(
279 								getReference());
280 						propertyNode.setPosition(position);
281 						propertyNode.setHierarchy(getHierarchy());
282 						propertyNode.setMember(getMember());
283 						propertyNode.setProperty(prop);
284 
285 						parentNode.addChild(propertyNode);
286 
287 						parentNode = propertyNode;
288 					}
289 
290 					for (TreeNode<TableAxisContext> child : children) {
291 						parentNode.addChild(child);
292 					}
293 				}
294 			}
295 		}
296 
297 		if (children == null) {
298 			children = getChildren();
299 		}
300 
301 		for (TreeNode<TableAxisContext> child : children) {
302 			TableHeaderNode nodeChild = (TableHeaderNode) child;
303 			nodeChild.addMemberProperties();
304 		}
305 	}
306 
307 	void mergeChildren() {
308 		List<TreeNode<TableAxisContext>> children = new ArrayList<TreeNode<TableAxisContext>>(
309 				getChildren());
310 
311 		TableHeaderNode lastChild = null;
312 
313 		for (TreeNode<TableAxisContext> child : children) {
314 			TableHeaderNode headerNode = (TableHeaderNode) child;
315 
316 			if (lastChild == null) {
317 				lastChild = headerNode;
318 				continue;
319 			}
320 
321 			if (lastChild.canMergeWith(headerNode)) {
322 				for (TreeNode<TableAxisContext> c : child.getChildren()) {
323 					lastChild.addChild(c);
324 				}
325 
326 				removeChild(child);
327 			} else {
328 				lastChild = headerNode;
329 			}
330 		}
331 
332 		for (TreeNode<TableAxisContext> child : getChildren()) {
333 			TableHeaderNode headerNode = (TableHeaderNode) child;
334 			headerNode.mergeChildren();
335 		}
336 	}
337 
338 	/**
339 	 * @param sibling
340 	 * @return
341 	 */
342 	protected boolean canMergeWith(TableHeaderNode sibling) {
343 		if (!OlapUtils.equals(hierarchy, sibling.getHierarchy())) {
344 			return false;
345 		}
346 
347 		if (!OlapUtils.equals(member, sibling.getMember())) {
348 			return false;
349 		}
350 
351 		if (!OlapUtils.equals(property, sibling.getProperty())) {
352 			return false;
353 		}
354 
355 		if (!OlapUtils.equals(property, sibling.getProperty())) {
356 			return false;
357 		}
358 
359 		if (aggregator == null) {
360 			if (sibling.getAggregator() != null) {
361 				return false;
362 			}
363 		} else {
364 			Aggregator other = sibling.getAggregator();
365 
366 			if (other == null) {
367 				return false;
368 			}
369 
370 			if (!ObjectUtils.equals(aggregator.getName(), other.getName())) {
371 				return false;
372 			}
373 
374 			if (!ObjectUtils.equals(aggregator.getLevel(), other.getLevel())) {
375 				return false;
376 			}
377 		}
378 
379 		return getRowSpan() == sibling.getRowSpan();
380 	}
381 
382 	public int getColIndex() {
383 		if (colIndex == null) {
384 			if (getParent() == null) {
385 				this.colIndex = 0;
386 				return colIndex;
387 			}
388 
389 			int index = ((TableHeaderNode) getParent()).getColIndex();
390 			int childIndex = getParent().getChildren().indexOf(this);
391 
392 			for (int i = 0; i < childIndex; i++) {
393 				index += getParent().getChildren().get(i).getWidth();
394 			}
395 
396 			this.colIndex = index;
397 		}
398 
399 		return colIndex;
400 	}
401 
402 	public int getRowIndex() {
403 		if (rowIndex == null) {
404 			if (getParent() == null) {
405 				this.rowIndex = 0;
406 				return rowIndex;
407 			} else {
408 				TableHeaderNode headerParent = (TableHeaderNode) getParent();
409 				this.rowIndex = headerParent.getRowIndex()
410 						+ headerParent.getRowSpan();
411 			}
412 		}
413 		return rowIndex;
414 	}
415 
416 	public int getColSpan() {
417 		if (colSpan == null) {
418 			this.colSpan = getWidth();
419 		}
420 
421 		return colSpan;
422 	}
423 
424 	public int getRowSpan() {
425 		if (rowSpan == null) {
426 			if ((member == null || property != null) && aggregator == null) {
427 				this.rowSpan = 1;
428 				return rowSpan;
429 			}
430 
431 			final Map<Hierarchy, Integer> maxSpans = new HashMap<Hierarchy, Integer>(
432 					getReference().getHierarchies().size());
433 
434 			if (aggregator != null) {
435 				getRoot().walkTree(new TreeNodeCallback<TableAxisContext>() {
436 
437 					@Override
438 					public int handleTreeNode(TreeNode<TableAxisContext> node) {
439 						TableHeaderNode nodeChild = (TableHeaderNode) node;
440 
441 						if (nodeChild.getMember() == null) {
442 							return TreeNodeCallback.CONTINUE;
443 						} else {
444 							Integer maxSpan = maxSpans.get(nodeChild
445 									.getHierarchy());
446 							if (maxSpan == null) {
447 								maxSpan = 0;
448 							}
449 
450 							int current = nodeChild.getHierarchyDescendents();
451 
452 							TableHeaderNode parent = nodeChild;
453 							while (parent != null) {
454 								parent = (TableHeaderNode) parent.getParent();
455 
456 								if (OlapUtils.equals(nodeChild.getHierarchy(),
457 										parent.getHierarchy())
458 										&& parent.getMember() == null) {
459 									current++;
460 								} else {
461 									break;
462 								}
463 							}
464 
465 							if (current > maxSpan) {
466 								maxSpans.put(nodeChild.getHierarchy(), current);
467 							}
468 						}
469 
470 						return TreeNodeCallback.CONTINUE;
471 					}
472 				});
473 			}
474 
475 			if (member == null) {
476 				int totalSpans = 0;
477 
478 				for (Integer span : maxSpans.values()) {
479 					totalSpans += span;
480 				}
481 
482 				this.rowSpan = totalSpans;
483 
484 				if (hierarchy != null) {
485 					for (Hierarchy hier : getReference().getHierarchies()) {
486 						if (OlapUtils.equals(hier, hierarchy)) {
487 							break;
488 						}
489 
490 						this.rowSpan -= maxSpans.get(hier);
491 					}
492 
493 					TableHeaderNode parent = this;
494 					while (true) {
495 						parent = (TableHeaderNode) parent.getParent();
496 
497 						if (parent == null
498 								|| !OlapUtils.equals(hierarchy,
499 										parent.getHierarchy())) {
500 							break;
501 						} else {
502 							this.rowSpan -= parent.getRowSpan();
503 						}
504 					}
505 				}
506 
507 				TableHeaderNode child = this;
508 				while (child != null) {
509 					if (child.getChildCount() > 0) {
510 						child = (TableHeaderNode) child.getChildren().get(0);
511 						this.rowSpan -= child.getRowSpan();
512 					} else {
513 						break;
514 					}
515 				}
516 			} else {
517 				final int[] childSpan = new int[] { 0 };
518 				final int[] maxSpan = new int[] { 0 };
519 
520 				walkChildrenAtColIndex(
521 						new TreeNodeCallback<TableAxisContext>() {
522 
523 							@Override
524 							public int handleTreeNode(
525 									TreeNode<TableAxisContext> node) {
526 								TableHeaderNode nodeChild = (TableHeaderNode) node;
527 
528 								if (nodeChild == TableHeaderNode.this) {
529 									return TreeNodeCallback.CONTINUE;
530 								} else if (OlapUtils.equals(hierarchy,
531 										nodeChild.getHierarchy())) {
532 									childSpan[0] += nodeChild.getRowSpan();
533 									return TreeNodeCallback.CONTINUE;
534 								} else {
535 									return TreeNodeCallback.BREAK;
536 								}
537 							}
538 						}, getColIndex());
539 
540 				getRoot().walkTree(new TreeNodeCallback<TableAxisContext>() {
541 
542 					@Override
543 					public int handleTreeNode(TreeNode<TableAxisContext> node) {
544 						TableHeaderNode nodeChild = (TableHeaderNode) node;
545 
546 						Level level = null;
547 						Member nodeMember = nodeChild.getMember();
548 
549 						if (nodeChild == TableHeaderNode.this) {
550 							return TreeNodeCallback.CONTINUE;
551 						} else if (nodeMember != null) {
552 							level = nodeMember.getLevel();
553 						} else if (nodeChild.getAggregator() != null) {
554 							level = nodeChild.getAggregator().getLevel();
555 						}
556 
557 						if (OlapUtils.equals(member.getLevel(), level)) {
558 							if (nodeMember != null
559 									&& (getReference().getAncestorMembers(
560 											nodeMember).contains(member) || getReference()
561 											.getAncestorMembers(member)
562 											.contains(nodeMember))) {
563 								return TreeNodeCallback.CONTINUE;
564 							}
565 
566 							int span = nodeChild.getHierarchyDescendents();
567 
568 							// Handling a corner case of #54
569 							if (aggregator == null
570 									&& nodeChild.getAggregator() != null
571 									&& member instanceof Measure
572 									&& getReference().getHierarchies().size() == 1) {
573 								span++;
574 							}
575 
576 							maxSpan[0] = Math.max(maxSpan[0], span);
577 						}
578 
579 						return TreeNodeCallback.CONTINUE;
580 					}
581 				});
582 
583 				this.rowSpan = Math.max(1, maxSpan[0] - childSpan[0]);
584 
585 				if (aggregator != null) {
586 					boolean child = false;
587 
588 					for (Hierarchy hier : getReference().getHierarchies()) {
589 						if (OlapUtils.equals(hier, hierarchy)) {
590 							child = true;
591 							continue;
592 						}
593 
594 						Type type;
595 
596 						try {
597 							type = hier.getDimension().getDimensionType();
598 						} catch (OlapException e) {
599 							throw new PivotException(e);
600 						}
601 
602 						if (child && type != Type.MEASURE) {
603 							this.rowSpan += maxSpans.get(hier);
604 						}
605 					}
606 				}
607 			}
608 		}
609 
610 		return rowSpan;
611 	}
612 
613 	/**
614 	 * @return the aggregation
615 	 */
616 	public boolean isAggregation() {
617 		return aggregation;
618 	}
619 
620 	/**
621 	 * @param aggregation
622 	 *            the aggregation to set
623 	 */
624 	public void setAggregation(boolean aggregation) {
625 		this.aggregation = aggregation;
626 	}
627 
628 	/**
629 	 * @return the aggregator
630 	 */
631 	public Aggregator getAggregator() {
632 		return aggregator;
633 	}
634 
635 	/**
636 	 * @param aggregator
637 	 *            the aggregator to set
638 	 */
639 	public void setAggregator(Aggregator aggregator) {
640 		this.aggregator = aggregator;
641 	}
642 
643 	public TableHeaderNode getHierarchyRoot() {
644 		TableHeaderNode parent = this;
645 		while (true) {
646 			TableHeaderNode node = (TableHeaderNode) parent.getParent();
647 
648 			if (node != null
649 					&& OlapUtils.equals(hierarchy, node.getHierarchy())) {
650 				parent = node;
651 			} else {
652 				break;
653 			}
654 		}
655 		return parent;
656 	}
657 
658 	public int getHierarchyDescendents() {
659 		if (member == null || getChildCount() == 0) {
660 			return 1;
661 		}
662 
663 		if (hierarchyDescendants == null) {
664 			int height = 1;
665 			for (TreeNode<TableAxisContext> child : getChildren()) {
666 				TableHeaderNode nodeChild = (TableHeaderNode) child;
667 				if (OlapUtils.equals(hierarchy, nodeChild.getHierarchy())) {
668 					height = Math.max(height,
669 							1 + nodeChild.getHierarchyDescendents());
670 				}
671 			}
672 			this.hierarchyDescendants = height;
673 		}
674 
675 		return hierarchyDescendants;
676 	}
677 
678 	protected List<Member> getMemberPath() {
679 		List<Member> path = new LinkedList<Member>();
680 
681 		TableHeaderNode node = (TableHeaderNode) getParent();
682 
683 		while (node != null) {
684 			path.add(0, node.getMember());
685 			node = (TableHeaderNode) node.getParent();
686 		}
687 
688 		return path;
689 	}
690 
691 	/**
692 	 * @param parentPath
693 	 * @param childPath
694 	 * @return
695 	 */
696 	private static boolean isSubPath(List<Member> parentPath,
697 			List<Member> childPath) {
698 		Iterator<Member> it = childPath.iterator();
699 		for (Member member : parentPath) {
700 			if (!OlapUtils.equals(member, it.next())) {
701 				return false;
702 			}
703 		}
704 
705 		return true;
706 	}
707 
708 	public int getMemberChildren() {
709 		if (member == null) {
710 			return 0;
711 		}
712 
713 		if (memberChildren == null) {
714 			final List<Member> path = getMemberPath();
715 
716 			final int[] childCount = new int[] { 0 };
717 
718 			final int depth = member.getDepth();
719 
720 			getRoot().walkChildren(new TreeNodeCallback<TableAxisContext>() {
721 
722 				@Override
723 				public int handleTreeNode(TreeNode<TableAxisContext> node) {
724 					TableHeaderNode nodeChild = (TableHeaderNode) node;
725 
726 					if (node == TableHeaderNode.this) {
727 						return TreeNodeCallback.CONTINUE;
728 					}
729 
730 					if (OlapUtils.equals(hierarchy, nodeChild.getHierarchy())) {
731 						List<Member> childPath = nodeChild.getMemberPath();
732 
733 						if (path.size() > childPath.size()
734 								|| !isSubPath(path, childPath)) {
735 							return TreeNodeCallback.CONTINUE;
736 						}
737 
738 						Member childMember = nodeChild.getMember();
739 
740 						if (childMember != null) {
741 							int childDepth = childMember.getDepth();
742 
743 							if (getReference().getAncestorMembers(childMember)
744 									.contains(member)) {
745 								childCount[0]++;
746 
747 								return TreeNodeCallback.CONTINUE_SIBLING;
748 							} else if (depth == childDepth) {
749 								if (!OlapUtils.equals(childMember, member)) {
750 									return TreeNodeCallback.CONTINUE_SIBLING;
751 								}
752 							} else if (depth < childDepth
753 									|| !getReference().getAncestorMembers(
754 											member).contains(childMember)) {
755 								return TreeNodeCallback.CONTINUE_SIBLING;
756 							}
757 						}
758 					} else if (nodeChild.getMember() != null) {
759 						TableHeaderNode parent = TableHeaderNode.this;
760 
761 						while (true) {
762 							parent = (TableHeaderNode) parent.getParent();
763 
764 							if (parent == null) {
765 								return TreeNodeCallback.CONTINUE_PARENT;
766 							}
767 
768 							Member parentMember = parent.getMember();
769 
770 							if (OlapUtils.equals(parent.getHierarchy(),
771 									nodeChild.getHierarchy())
772 									&& parentMember != null) {
773 								if (OlapUtils.equals(parentMember,
774 										nodeChild.getMember())
775 										|| getReference().getAncestorMembers(
776 												parentMember).contains(
777 												nodeChild.getMember())) {
778 									return TreeNodeCallback.CONTINUE;
779 								} else {
780 									return TreeNodeCallback.CONTINUE_SIBLING;
781 								}
782 							}
783 						}
784 					}
785 
786 					return TreeNodeCallback.CONTINUE;
787 				}
788 			});
789 
790 			this.memberChildren = childCount[0];
791 		}
792 
793 		return memberChildren;
794 	}
795 
796 	/**
797 	 * @param callbackHandler
798 	 * @param rowIndex
799 	 * @return
800 	 */
801 	public int walkChildrenAtRowIndex(
802 			TreeNodeCallback<TableAxisContext> callbackHandler, int rowIndex) {
803 		int code = 0;
804 		for (TreeNode<TableAxisContext> child : getChildren()) {
805 			TableHeaderNode nodeChild = (TableHeaderNode) child;
806 			int childIndex = nodeChild.getRowIndex();
807 
808 			if (rowIndex == childIndex) {
809 				code = callbackHandler.handleTreeNode(child);
810 				if (code >= TreeNodeCallback.CONTINUE_PARENT) {
811 					return code;
812 				}
813 			} else if (rowIndex > child.getLevel()) {
814 				nodeChild.walkChildrenAtRowIndex(callbackHandler, rowIndex);
815 			}
816 		}
817 		return code;
818 	}
819 
820 	/**
821 	 * @param colIndex
822 	 * @return
823 	 */
824 	public TableHeaderNode getLeafNodeAtColIndex(int colIndex) {
825 		if (getChildCount() == 0 && getColIndex() == colIndex) {
826 			return this;
827 		}
828 
829 		for (TreeNode<TableAxisContext> child : getChildren()) {
830 			TableHeaderNode nodeChild = (TableHeaderNode) child;
831 
832 			int startIndex = nodeChild.getColIndex();
833 			int endIndex = startIndex + nodeChild.getColSpan();
834 
835 			if (colIndex >= startIndex && colIndex < endIndex) {
836 				return nodeChild.getLeafNodeAtColIndex(colIndex);
837 			} else if (endIndex > colIndex) {
838 				break;
839 			}
840 		}
841 		return null;
842 	}
843 
844 	/**
845 	 * @param callbackHandler
846 	 * @param colIndex
847 	 */
848 	public int walkChildrenAtColIndex(
849 			TreeNodeCallback<TableAxisContext> callbackHandler, int colIndex) {
850 		int code = 0;
851 
852 		if (getColIndex() == colIndex) {
853 			code = callbackHandler.handleTreeNode(this);
854 			if (code >= TreeNodeCallback.CONTINUE_PARENT) {
855 				return code;
856 			}
857 		}
858 
859 		for (TreeNode<TableAxisContext> child : getChildren()) {
860 			TableHeaderNode nodeChild = (TableHeaderNode) child;
861 			int startIndex = nodeChild.getColIndex();
862 			int endIndex = startIndex + nodeChild.getColSpan();
863 
864 			if (colIndex < startIndex) {
865 				code = TreeNodeCallback.CONTINUE_SIBLING;
866 			} else if (colIndex >= endIndex) {
867 				code = TreeNodeCallback.CONTINUE_PARENT;
868 			} else {
869 				code = nodeChild.walkChildrenAtColIndex(callbackHandler,
870 						colIndex);
871 				break;
872 			}
873 		}
874 
875 		return code;
876 	}
877 
878 	/**
879 	 * @see java.lang.Object#toString()
880 	 */
881 	@Override
882 	public String toString() {
883 		if (member != null) {
884 			return member.getCaption();
885 		} else if (hierarchy != null) {
886 			return hierarchy.getCaption();
887 		} else {
888 			return getReference().getAxis().name();
889 		}
890 	}
891 }