(children));
- this.source = source;
- this.operation = operation;
- }
-
- /**
- * Creates a QueryEvent with no children.
- *
- * @param operation Even type
- * @param source Query node that generated this event
- */
- QueryEvent(
- QueryEvent.Type operation,
- QueryNode source)
- {
- this.children = null;
- this.source = source;
- this.operation = operation;
- }
-
- /**
- * Returns the object that generated this event.
- */
- public QueryNode getSource() {
- return source;
- }
-
- /**
- * Returns the event type.
- */
- // REVIEW: consider renaming to 'getEventType', or rename enum Type to
- // Operation.
- public QueryEvent.Type getOperation() {
- return operation;
- }
-
- /**
- * Returns a map of objects affected by the event and
- * their index in the list of the source children.
- *
- * If the event is of type {@link QueryEvent.Type#SELECTION_CHANGED},
- * this method will return null because the source object was affected
- * and not the children.
- */
- // REVIEW: 'children' is already plural. Consider renaming to 'getChildren'
- // or 'getChildNodes'.
- public Map getChildrens() {
- return children;
- }
-}
-
-// End QueryEvent.java
+/*
+// $Id: $
+// This software is subject to the terms of the Eclipse Public License v1.0
+// Agreement, available at the following URL:
+// http://www.eclipse.org/legal/epl-v10.html.
+// Copyright (C) 2009-2009 Julian Hyde
+// All Rights Reserved.
+// You must accept the terms of that agreement to use this software.
+*/
+package org.olap4j.query;
+
+import java.util.*;
+
+/**
+ * Describes which changes were performed to the query model.
+ *
+ * @author Luc Boudreau
+ * @version $Id: $
+ */
+public final class QueryEvent {
+
+ /**
+ * Describes the nature of the event.
+ */
+ public static enum Type {
+ /**
+ * Event where one or more children of a QueryNode were removed.
+ */
+ CHILDREN_REMOVED,
+ /**
+ * Event where one or more nodes were added as children of a QueryNode.
+ */
+ CHILDREN_ADDED,
+ /**
+ * Event where a Selection object operator was changed.
+ */
+ SELECTION_CHANGED
+ }
+
+ private final QueryNode source;
+ private final QueryEvent.Type operation;
+ private final Map children;
+
+ /**
+ * Creates a QueryEvent with a single child.
+ *
+ * @param operation Even type
+ * @param source Query node that generated this event
+ * @param child Child node
+ */
+ QueryEvent(
+ QueryEvent.Type operation,
+ QueryNode source,
+ QueryNode child,
+ int index)
+ {
+ this.children = Collections.singletonMap(index, child);
+ this.source = source;
+ this.operation = operation;
+ }
+
+ /**
+ * Creates a QueryEvent with multiple children.
+ *
+ * @param operation Even type
+ * @param source Query node that generated this event
+ * @param children Child nodes and their indexes within the parent
+ */
+ QueryEvent(
+ QueryEvent.Type operation,
+ QueryNode source,
+ Map children)
+ {
+ // copy the map, and make immutable
+ this.children =
+ Collections.unmodifiableMap(
+ new HashMap(children));
+ this.source = source;
+ this.operation = operation;
+ }
+
+ /**
+ * Creates a QueryEvent with no children.
+ *
+ * @param operation Even type
+ * @param source Query node that generated this event
+ */
+ QueryEvent(
+ QueryEvent.Type operation,
+ QueryNode source)
+ {
+ this.children = null;
+ this.source = source;
+ this.operation = operation;
+ }
+
+ /**
+ * Returns the object that generated this event.
+ */
+ public QueryNode getSource() {
+ return source;
+ }
+
+ /**
+ * Returns the event type.
+ */
+ // REVIEW: consider renaming to 'getEventType', or rename enum Type to
+ // Operation.
+ public QueryEvent.Type getOperation() {
+ return operation;
+ }
+
+ /**
+ * Returns a map of objects affected by the event and
+ * their index in the list of the source children.
+ *
+ * If the event is of type {@link QueryEvent.Type#SELECTION_CHANGED},
+ * this method will return null because the source object was affected
+ * and not the children.
+ */
+ // REVIEW: 'children' is already plural. Consider renaming to 'getChildren'
+ // or 'getChildNodes'.
+ public Map getChildrens() {
+ return children;
+ }
+}
+
+// End QueryEvent.java
diff --git a/src/org/olap4j/query/QueryNode.java b/src/org/olap4j/query/QueryNode.java
index 31c292c..68fd80d 100644
--- a/src/org/olap4j/query/QueryNode.java
+++ b/src/org/olap4j/query/QueryNode.java
@@ -1,37 +1,37 @@
-/*
-// $Id: $
-// This software is subject to the terms of the Eclipse Public License v1.0
-// Agreement, available at the following URL:
-// http://www.eclipse.org/legal/epl-v10.html.
-// Copyright (C) 2007-2009 Julian Hyde
-// All Rights Reserved.
-// You must accept the terms of that agreement to use this software.
-*/
-package org.olap4j.query;
-
-/**
- * Describes what methods a Query Node must implement
- * in order to support listeners. Olap4j's query model
- * provides an abstract implementation of interface.
- *
- * @author Luc Boudreau
- */
-interface QueryNode {
-
- /**
- * Registers a new listener for a QueryNode.
- * @param l The new listener object, implementation of QueryNodeListener
- * @see org.olap4j.query.QueryNodeListener
- */
- public void addQueryNodeListener(QueryNodeListener l);
-
- /**
- * De-registers a new listener for a QueryNode.
- * If the listener object passed as a parameter was not registered,
- * the method will return silently.
- * @param l The listener object to de-register.
- * @see org.olap4j.query.QueryNodeListener
- */
- public void removeQueryNodeListener(QueryNodeListener l);
-}
+/*
+// $Id: $
+// This software is subject to the terms of the Eclipse Public License v1.0
+// Agreement, available at the following URL:
+// http://www.eclipse.org/legal/epl-v10.html.
+// Copyright (C) 2007-2009 Julian Hyde
+// All Rights Reserved.
+// You must accept the terms of that agreement to use this software.
+*/
+package org.olap4j.query;
+
+/**
+ * Describes what methods a Query Node must implement
+ * in order to support listeners. Olap4j's query model
+ * provides an abstract implementation of interface.
+ *
+ * @author Luc Boudreau
+ */
+interface QueryNode {
+
+ /**
+ * Registers a new listener for a QueryNode.
+ * @param l The new listener object, implementation of QueryNodeListener
+ * @see org.olap4j.query.QueryNodeListener
+ */
+ public void addQueryNodeListener(QueryNodeListener l);
+
+ /**
+ * De-registers a new listener for a QueryNode.
+ * If the listener object passed as a parameter was not registered,
+ * the method will return silently.
+ * @param l The listener object to de-register.
+ * @see org.olap4j.query.QueryNodeListener
+ */
+ public void removeQueryNodeListener(QueryNodeListener l);
+}
// End QueryNode.java
\ No newline at end of file
diff --git a/src/org/olap4j/query/QueryNodeImpl.java b/src/org/olap4j/query/QueryNodeImpl.java
index 79bff49..fe9fbd7 100644
--- a/src/org/olap4j/query/QueryNodeImpl.java
+++ b/src/org/olap4j/query/QueryNodeImpl.java
@@ -1,177 +1,177 @@
-/*
-// $Id:$
-// This software is subject to the terms of the Eclipse Public License v1.0
-// Agreement, available at the following URL:
-// http://www.eclipse.org/legal/epl-v10.html.
-// Copyright (C) 2009-2009 Julian Hyde
-// All Rights Reserved.
-// You must accept the terms of that agreement to use this software.
-*/
-package org.olap4j.query;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Abstract implementation of QueryNode that
- * implements operations to support listeners.
- *
- * @author Luc Boudreau
- * @version $Id: $
- */
-abstract class QueryNodeImpl implements QueryNode {
-
- private final List listeners =
- new ArrayList();
-
- public void addQueryNodeListener(QueryNodeListener l) {
- this.listeners.add(l);
- }
-
- public void removeQueryNodeListener(QueryNodeListener l) {
- this.listeners.remove(l);
- }
-
- /**
- * Subclasses should call this helper method to
- * notify its listeners that a child was added.
- *
- * @param child Child that was added
- * @param index The index at which it was added
- */
- protected void notifyAdd(QueryNode child, int index)
- {
- assert child != null;
- QueryEvent event = new QueryEvent(
- QueryEvent.Type.CHILDREN_ADDED,
- this,
- child,
- index);
- notifyAddInternal(event);
- }
-
- /**
- * Subclasses should call this helper method to
- * notify it's listeners that children were added.
- *
- * @param children A map of indexes and children QueryNode
- * objects that were added
- */
- protected void notifyAdd(Map children)
- {
- assert children != null;
- QueryEvent event = new QueryEvent(
- QueryEvent.Type.CHILDREN_ADDED,
- this,
- children);
- notifyAddInternal(event);
- }
-
- private void notifyAddInternal(QueryEvent event) {
- // Must count backwards to prevent an exception
- // if a child removes itself from the listeners list
- // while this call is active.
- for (int cpt = this.listeners.size() - 1; cpt >= 0; cpt--) {
- this.listeners.get(cpt).childrenAdded(event);
- }
- }
-
- /**
- * Subclasses should call this helper method to
- * notify its listeners that a child was removed.
- *
- * @param child Child that was removed
- * @param index Index of child
- */
- protected void notifyRemove(QueryNode child, int index)
- {
- assert child != null;
- QueryEvent event = new QueryEvent(
- QueryEvent.Type.CHILDREN_REMOVED,
- this,
- child,
- index);
- notifyRemoveInternal(event);
- }
-
- /**
- * Subclasses should call this helper method to
- * notify its listeners that children were added.
- *
- * @param children A map of indexes and children QueryNode
- * objects that were removed
- */
- protected void notifyRemove(Map children)
- {
- assert children != null;
- QueryEvent event = new QueryEvent(
- QueryEvent.Type.CHILDREN_REMOVED,
- this,
- children);
- notifyRemoveInternal(event);
- }
-
- private void notifyRemoveInternal(QueryEvent event) {
- // Must count backwards to prevent an exception
- // if a child removes itself from the listeners list
- // while this call is active.
- for (int cpt = this.listeners.size() - 1; cpt >= 0; cpt--) {
- this.listeners.get(cpt).childrenRemoved(event);
- }
- }
-
- /**
- * Subclasses should call this helper method to
- * notify its listeners that a child selection
- * object has a new operator value.
- *
- * @param child Child that was updated
- * @param index The index of the child among its siblings
- */
- protected void notifyChange(QueryNode child, int index)
- {
- assert child != null;
- QueryEvent event = new QueryEvent(
- QueryEvent.Type.SELECTION_CHANGED,
- this,
- child,
- index);
- notifyChangeInternal(event);
- }
-
- /**
- * Subclasses should call this helper method to
- * notify its listeners that children selections
- * object has a new operator value.
- *
- * @param children A map of indexes and children QueryNode
- * objects that were updated
- */
- protected void notifyChange(Map children)
- {
- assert children != null;
- QueryEvent event = new QueryEvent(
- QueryEvent.Type.SELECTION_CHANGED,
- this,
- children);
- notifyChangeInternal(event);
- }
-
- private void notifyChangeInternal(QueryEvent event) {
- // Must count backwards to prevent an exception
- // if a child removes itself from the listeners list
- // while this call is active.
- for (int cpt = this.listeners.size() - 1; cpt >= 0; cpt--) {
- this.listeners.get(cpt).selectionChanged(event);
- }
- }
-
- void clearListeners() {
- this.listeners.clear();
- }
-
- abstract void tearDown();
-}
-
-// End QueryNodeImpl.java
+/*
+// $Id:$
+// This software is subject to the terms of the Eclipse Public License v1.0
+// Agreement, available at the following URL:
+// http://www.eclipse.org/legal/epl-v10.html.
+// Copyright (C) 2009-2009 Julian Hyde
+// All Rights Reserved.
+// You must accept the terms of that agreement to use this software.
+*/
+package org.olap4j.query;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Abstract implementation of QueryNode that
+ * implements operations to support listeners.
+ *
+ * @author Luc Boudreau
+ * @version $Id: $
+ */
+abstract class QueryNodeImpl implements QueryNode {
+
+ private final List listeners =
+ new ArrayList();
+
+ public void addQueryNodeListener(QueryNodeListener l) {
+ this.listeners.add(l);
+ }
+
+ public void removeQueryNodeListener(QueryNodeListener l) {
+ this.listeners.remove(l);
+ }
+
+ /**
+ * Subclasses should call this helper method to
+ * notify its listeners that a child was added.
+ *
+ * @param child Child that was added
+ * @param index The index at which it was added
+ */
+ protected void notifyAdd(QueryNode child, int index)
+ {
+ assert child != null;
+ QueryEvent event = new QueryEvent(
+ QueryEvent.Type.CHILDREN_ADDED,
+ this,
+ child,
+ index);
+ notifyAddInternal(event);
+ }
+
+ /**
+ * Subclasses should call this helper method to
+ * notify it's listeners that children were added.
+ *
+ * @param children A map of indexes and children QueryNode
+ * objects that were added
+ */
+ protected void notifyAdd(Map children)
+ {
+ assert children != null;
+ QueryEvent event = new QueryEvent(
+ QueryEvent.Type.CHILDREN_ADDED,
+ this,
+ children);
+ notifyAddInternal(event);
+ }
+
+ private void notifyAddInternal(QueryEvent event) {
+ // Must count backwards to prevent an exception
+ // if a child removes itself from the listeners list
+ // while this call is active.
+ for (int cpt = this.listeners.size() - 1; cpt >= 0; cpt--) {
+ this.listeners.get(cpt).childrenAdded(event);
+ }
+ }
+
+ /**
+ * Subclasses should call this helper method to
+ * notify its listeners that a child was removed.
+ *
+ * @param child Child that was removed
+ * @param index Index of child
+ */
+ protected void notifyRemove(QueryNode child, int index)
+ {
+ assert child != null;
+ QueryEvent event = new QueryEvent(
+ QueryEvent.Type.CHILDREN_REMOVED,
+ this,
+ child,
+ index);
+ notifyRemoveInternal(event);
+ }
+
+ /**
+ * Subclasses should call this helper method to
+ * notify its listeners that children were added.
+ *
+ * @param children A map of indexes and children QueryNode
+ * objects that were removed
+ */
+ protected void notifyRemove(Map children)
+ {
+ assert children != null;
+ QueryEvent event = new QueryEvent(
+ QueryEvent.Type.CHILDREN_REMOVED,
+ this,
+ children);
+ notifyRemoveInternal(event);
+ }
+
+ private void notifyRemoveInternal(QueryEvent event) {
+ // Must count backwards to prevent an exception
+ // if a child removes itself from the listeners list
+ // while this call is active.
+ for (int cpt = this.listeners.size() - 1; cpt >= 0; cpt--) {
+ this.listeners.get(cpt).childrenRemoved(event);
+ }
+ }
+
+ /**
+ * Subclasses should call this helper method to
+ * notify its listeners that a child selection
+ * object has a new operator value.
+ *
+ * @param child Child that was updated
+ * @param index The index of the child among its siblings
+ */
+ protected void notifyChange(QueryNode child, int index)
+ {
+ assert child != null;
+ QueryEvent event = new QueryEvent(
+ QueryEvent.Type.SELECTION_CHANGED,
+ this,
+ child,
+ index);
+ notifyChangeInternal(event);
+ }
+
+ /**
+ * Subclasses should call this helper method to
+ * notify its listeners that children selections
+ * object has a new operator value.
+ *
+ * @param children A map of indexes and children QueryNode
+ * objects that were updated
+ */
+ protected void notifyChange(Map children)
+ {
+ assert children != null;
+ QueryEvent event = new QueryEvent(
+ QueryEvent.Type.SELECTION_CHANGED,
+ this,
+ children);
+ notifyChangeInternal(event);
+ }
+
+ private void notifyChangeInternal(QueryEvent event) {
+ // Must count backwards to prevent an exception
+ // if a child removes itself from the listeners list
+ // while this call is active.
+ for (int cpt = this.listeners.size() - 1; cpt >= 0; cpt--) {
+ this.listeners.get(cpt).selectionChanged(event);
+ }
+ }
+
+ void clearListeners() {
+ this.listeners.clear();
+ }
+
+ abstract void tearDown();
+}
+
+// End QueryNodeImpl.java
diff --git a/src/org/olap4j/query/QueryNodeListener.java b/src/org/olap4j/query/QueryNodeListener.java
index f7d2578..09cc3a3 100644
--- a/src/org/olap4j/query/QueryNodeListener.java
+++ b/src/org/olap4j/query/QueryNodeListener.java
@@ -1,47 +1,47 @@
-/*
-// $Id:$
-// This software is subject to the terms of the Eclipse Public License v1.0
-// Agreement, available at the following URL:
-// http://www.eclipse.org/legal/epl-v10.html.
-// Copyright (C) 2009-2009 Julian Hyde
-// All Rights Reserved.
-// You must accept the terms of that agreement to use this software.
-*/
-package org.olap4j.query;
-
-/**
- * Objects that want to be notified of changes to the Query Model structure
- * have to implement this interface.
- *
- * @author Luc Boudreau
- * @version $Id: $
- */
-public interface QueryNodeListener {
- /**
- * Invoked when one or more children of a QueryNode are removed
- * from its list.
- *
- * @param event Describes in detail the actual event that just happened.
- */
- public void childrenRemoved(QueryEvent event);
-
- /**
- * Invoked when one or more children are added to a QueryNode
- * list of children.
- *
- * @param event Describes in detail the actual event that just happened.
- */
- public void childrenAdded(QueryEvent event);
-
- /**
- * Invoked when a selection operator has changed. This does not mean
- * that a Selection object was either added or removed from a Dimension,
- * it only means that its operator value was modified.
- *
- * @param event Describes in detail the actual event that just happened.
- * @see org.olap4j.query.Selection
- **/
- public void selectionChanged(QueryEvent event);
-}
-
-// End QueryNodeListener.java
+/*
+// $Id:$
+// This software is subject to the terms of the Eclipse Public License v1.0
+// Agreement, available at the following URL:
+// http://www.eclipse.org/legal/epl-v10.html.
+// Copyright (C) 2009-2009 Julian Hyde
+// All Rights Reserved.
+// You must accept the terms of that agreement to use this software.
+*/
+package org.olap4j.query;
+
+/**
+ * Objects that want to be notified of changes to the Query Model structure
+ * have to implement this interface.
+ *
+ * @author Luc Boudreau
+ * @version $Id: $
+ */
+public interface QueryNodeListener {
+ /**
+ * Invoked when one or more children of a QueryNode are removed
+ * from its list.
+ *
+ * @param event Describes in detail the actual event that just happened.
+ */
+ public void childrenRemoved(QueryEvent event);
+
+ /**
+ * Invoked when one or more children are added to a QueryNode
+ * list of children.
+ *
+ * @param event Describes in detail the actual event that just happened.
+ */
+ public void childrenAdded(QueryEvent event);
+
+ /**
+ * Invoked when a selection operator has changed. This does not mean
+ * that a Selection object was either added or removed from a Dimension,
+ * it only means that its operator value was modified.
+ *
+ * @param event Describes in detail the actual event that just happened.
+ * @see org.olap4j.query.Selection
+ **/
+ public void selectionChanged(QueryEvent event);
+}
+
+// End QueryNodeListener.java
diff --git a/src/org/olap4j/query/RectangularCellSetFormatter.java b/src/org/olap4j/query/RectangularCellSetFormatter.java
index fdb6163..c9fc749 100644
--- a/src/org/olap4j/query/RectangularCellSetFormatter.java
+++ b/src/org/olap4j/query/RectangularCellSetFormatter.java
@@ -1,612 +1,612 @@
-/*
-// $Id$
-// This software is subject to the terms of the Eclipse Public License v1.0
-// Agreement, available at the following URL:
-// http://www.eclipse.org/legal/epl-v10.html.
-// Copyright (C) 2009-2009 Julian Hyde
-// All Rights Reserved.
-// You must accept the terms of that agreement to use this software.
-*/
-package org.olap4j.query;
-
-import org.olap4j.*;
-import org.olap4j.metadata.Member;
-import org.olap4j.impl.CoordinateIterator;
-import org.olap4j.impl.Olap4jUtil;
-
-import java.io.PrintWriter;
-import java.util.*;
-
-/**
- * Formatter that can convert a {@link CellSet} into a two-dimensional text
- * layout.
- *
- * With non-compact layout:
- *
- *
- * | 1997 |
- * | Q1 | Q2 |
- * | | 4 |
- * | Unit Sales | Store Sales | Unit Sales | Store Sales |
- * ----+----+---------+------------+-------------+------------+-------------+
- * USA | CA | Modesto | 12 | 34.5 | 13 | 35.60 |
- * | WA | Seattle | 12 | 34.5 | 13 | 35.60 |
- * | CA | Fresno | 12 | 34.5 | 13 | 35.60 |
- *
- *
- * With compact layout:
- *
- *
- * 1997
- * Q1 Q2
- * 4
- * Unit Sales Store Sales Unit Sales Store Sales
- * === == ======= ========== =========== ========== ===========
- * USA CA Modesto 12 34.5 13 35.60
- * WA Seattle 12 34.5 13 35.60
- * CA Fresno 12 34.5 13 35.60
- *
- *
- * This class is experimental. It is not part of the olap4j
- * specification and is subject to change without notice.
- *
- * @author jhyde
- * @version $Id$
- * @since Apr 15, 2009
-*/
-public class RectangularCellSetFormatter implements CellSetFormatter {
- private final boolean compact;
-
- /**
- * Creates a RectangularCellSetFormatter.
- *
- * @param compact Whether to generate compact output
- */
- public RectangularCellSetFormatter(boolean compact) {
- this.compact = compact;
- }
-
- public void format(CellSet cellSet, PrintWriter pw) {
- // Compute how many rows are required to display the columns axis.
- // In the example, this is 4 (1997, Q1, space, Unit Sales)
- final CellSetAxis columnsAxis;
- if (cellSet.getAxes().size() > 0) {
- columnsAxis = cellSet.getAxes().get(0);
- } else {
- columnsAxis = null;
- }
- AxisInfo columnsAxisInfo = computeAxisInfo(columnsAxis);
-
- // Compute how many columns are required to display the rows axis.
- // In the example, this is 3 (the width of USA, CA, Los Angeles)
- final CellSetAxis rowsAxis;
- if (cellSet.getAxes().size() > 1) {
- rowsAxis = cellSet.getAxes().get(1);
- } else {
- rowsAxis = null;
- }
- AxisInfo rowsAxisInfo = computeAxisInfo(rowsAxis);
-
- if (cellSet.getAxes().size() > 2) {
- int[] dimensions = new int[cellSet.getAxes().size() - 2];
- for (int i = 2; i < cellSet.getAxes().size(); i++) {
- CellSetAxis cellSetAxis = cellSet.getAxes().get(i);
- dimensions[i - 2] = cellSetAxis.getPositions().size();
- }
- for (int[] pageCoords : CoordinateIterator.iterate(dimensions)) {
- formatPage(
- cellSet,
- pw,
- pageCoords,
- columnsAxis,
- columnsAxisInfo,
- rowsAxis,
- rowsAxisInfo);
- }
- } else {
- formatPage(
- cellSet,
- pw,
- new int[] {},
- columnsAxis,
- columnsAxisInfo,
- rowsAxis,
- rowsAxisInfo);
- }
- }
-
- /**
- * Formats a two-dimensional page.
- *
- * @param cellSet Cell set
- * @param pw Print writer
- * @param pageCoords Coordinates of page [page, chapter, section, ...]
- * @param columnsAxis Columns axis
- * @param columnsAxisInfo Description of columns axis
- * @param rowsAxis Rows axis
- * @param rowsAxisInfo Description of rows axis
- */
- private void formatPage(
- CellSet cellSet,
- PrintWriter pw,
- int[] pageCoords,
- CellSetAxis columnsAxis,
- AxisInfo columnsAxisInfo,
- CellSetAxis rowsAxis,
- AxisInfo rowsAxisInfo)
- {
- if (pageCoords.length > 0) {
- pw.println();
- for (int i = pageCoords.length - 1; i >= 0; --i) {
- int pageCoord = pageCoords[i];
- final CellSetAxis axis = cellSet.getAxes().get(2 + i);
- pw.print(axis.getAxisOrdinal() + ": ");
- final Position position =
- axis.getPositions().get(pageCoord);
- int k = -1;
- for (Member member : position.getMembers()) {
- if (++k > 0) {
- pw.print(", ");
- }
- pw.print(member.getUniqueName());
- }
- pw.println();
- }
- }
- // Figure out the dimensions of the blank rectangle in the top left
- // corner.
- final int yOffset = columnsAxisInfo.getWidth();
- final int xOffsset = rowsAxisInfo.getWidth();
-
- // Populate a string matrix
- Matrix matrix =
- new Matrix(
- xOffsset
- + (columnsAxis == null
- ? 1
- : columnsAxis.getPositions().size()),
- yOffset
- + (rowsAxis == null
- ? 1
- : rowsAxis.getPositions().size()));
-
- // Populate corner
- for (int x = 0; x < xOffsset; x++) {
- for (int y = 0; y < yOffset; y++) {
- matrix.set(x, y, "", false, x > 0);
- }
- }
-
- // Populate matrix with cells representing axes
- //noinspection SuspiciousNameCombination
- populateAxis(
- matrix, columnsAxis, columnsAxisInfo, true, xOffsset);
- populateAxis(
- matrix, rowsAxis, rowsAxisInfo, false, yOffset);
-
- // Populate cell values
- for (Cell cell : cellIter(pageCoords, cellSet)) {
- final List coordList = cell.getCoordinateList();
- int x = xOffsset;
- if (coordList.size() > 0) {
- x += coordList.get(0);
- }
- int y = yOffset;
- if (coordList.size() > 1) {
- y += coordList.get(1);
- }
- matrix.set(
- x, y, cell.getFormattedValue(), true, false);
- }
-
- int[] columnWidths = new int[matrix.width];
- int widestWidth = 0;
- for (int x = 0; x < matrix.width; x++) {
- int columnWidth = 0;
- for (int y = 0; y < matrix.height; y++) {
- MatrixCell cell = matrix.get(x, y);
- if (cell != null) {
- columnWidth =
- Math.max(columnWidth, cell.value.length());
- }
- }
- columnWidths[x] = columnWidth;
- widestWidth = Math.max(columnWidth, widestWidth);
- }
-
- // Create a large array of spaces, for efficient printing.
- char[] spaces = new char[widestWidth + 1];
- Arrays.fill(spaces, ' ');
- char[] equals = new char[widestWidth + 1];
- Arrays.fill(equals, '=');
- char[] dashes = new char[widestWidth + 3];
- Arrays.fill(dashes, '-');
-
- if (compact) {
- for (int y = 0; y < matrix.height; y++) {
- for (int x = 0; x < matrix.width; x++) {
- if (x > 0) {
- pw.print(' ');
- }
- final MatrixCell cell = matrix.get(x, y);
- final int len;
- if (cell != null) {
- if (cell.sameAsPrev) {
- len = 0;
- } else {
- if (cell.right) {
- int padding =
- columnWidths[x] - cell.value.length();
- pw.write(spaces, 0, padding);
- pw.print(cell.value);
- continue;
- }
- pw.print(cell.value);
- len = cell.value.length();
- }
- } else {
- len = 0;
- }
- if (x == matrix.width - 1) {
- // at last column; don't bother to print padding
- break;
- }
- int padding = columnWidths[x] - len;
- pw.write(spaces, 0, padding);
- }
- pw.println();
- if (y == yOffset - 1) {
- for (int x = 0; x < matrix.width; x++) {
- if (x > 0) {
- pw.write(' ');
- }
- pw.write(equals, 0, columnWidths[x]);
- }
- pw.println();
- }
- }
- } else {
- for (int y = 0; y < matrix.height; y++) {
- for (int x = 0; x < matrix.width; x++) {
- final MatrixCell cell = matrix.get(x, y);
- final int len;
- if (cell != null) {
- if (cell.sameAsPrev) {
- pw.print(" ");
- len = 0;
- } else {
- pw.print("| ");
- if (cell.right) {
- int padding =
- columnWidths[x] - cell.value.length();
- pw.write(spaces, 0, padding);
- pw.print(cell.value);
- pw.print(' ');
- continue;
- }
- pw.print(cell.value);
- len = cell.value.length();
- }
- } else {
- pw.print("| ");
- len = 0;
- }
- int padding = columnWidths[x] - len;
- ++padding;
- pw.write(spaces, 0, padding);
- }
- pw.println('|');
- if (y == yOffset - 1) {
- for (int x = 0; x < matrix.width; x++) {
- pw.write('+');
- pw.write(dashes, 0, columnWidths[x] + 2);
- }
- pw.println('+');
- }
- }
- }
- }
-
- /**
- * Populates cells in the matrix corresponding to a particular axis.
- *
- * @param matrix Matrix to populate
- * @param axis Axis
- * @param axisInfo Description of axis
- * @param isColumns True if columns, false if rows
- * @param offset Ordinal of first cell to populate in matrix
- */
- private void populateAxis(
- Matrix matrix,
- CellSetAxis axis,
- AxisInfo axisInfo,
- boolean isColumns,
- int offset)
- {
- if (axis == null) {
- return;
- }
- Member[] prevMembers = new Member[axisInfo.getWidth()];
- Member[] members = new Member[axisInfo.getWidth()];
- for (int i = 0; i < axis.getPositions().size(); i++) {
- final int x = offset + i;
- Position position = axis.getPositions().get(i);
- int yOffset = 0;
- final List memberList = position.getMembers();
- for (int j = 0; j < memberList.size(); j++) {
- Member member = memberList.get(j);
- final AxisOrdinalInfo ordinalInfo =
- axisInfo.ordinalInfos.get(j);
- while (member != null) {
- if (member.getDepth() < ordinalInfo.minDepth) {
- break;
- }
- final int y =
- yOffset
- + member.getDepth()
- - ordinalInfo.minDepth;
- members[y] = member;
- member = member.getParentMember();
- }
- yOffset += ordinalInfo.getWidth();
- }
- boolean same = true;
- for (int y = 0; y < members.length; y++) {
- Member member = members[y];
- same =
- same
- && i > 0
- && Olap4jUtil.equal(prevMembers[y], member);
- String value =
- member == null
- ? ""
- : member.getCaption(null);
- if (isColumns) {
- matrix.set(x, y, value, false, same);
- } else {
- if (same) {
- value = "";
- }
- //noinspection SuspiciousNameCombination
- matrix.set(y, x, value, false, false);
- }
- prevMembers[y] = member;
- members[y] = null;
- }
- }
- }
-
- /**
- * Computes a description of an axis.
- *
- * @param axis Axis
- * @return Description of axis
- */
- private AxisInfo computeAxisInfo(CellSetAxis axis)
- {
- if (axis == null) {
- return new AxisInfo(0);
- }
- final AxisInfo axisInfo =
- new AxisInfo(axis.getAxisMetaData().getHierarchies().size());
- int p = -1;
- for (Position position : axis.getPositions()) {
- ++p;
- int k = -1;
- for (Member member : position.getMembers()) {
- ++k;
- final AxisOrdinalInfo axisOrdinalInfo =
- axisInfo.ordinalInfos.get(k);
- final int topDepth =
- member.isAll()
- ? member.getDepth()
- : member.getHierarchy().hasAll()
- ? 1
- : 0;
- if (axisOrdinalInfo.minDepth > topDepth
- || p == 0)
- {
- axisOrdinalInfo.minDepth = topDepth;
- }
- axisOrdinalInfo.maxDepth =
- Math.max(
- axisOrdinalInfo.maxDepth,
- member.getDepth());
- }
- }
- return axisInfo;
- }
-
- /**
- * Returns an iterator over cells in a result.
- */
- private static Iterable cellIter(
- final int[] pageCoords,
- final CellSet cellSet)
- {
- return new Iterable() {
- public Iterator iterator() {
- int[] axisDimensions =
- new int[cellSet.getAxes().size() - pageCoords.length];
- assert pageCoords.length <= axisDimensions.length;
- for (int i = 0; i < axisDimensions.length; i++) {
- CellSetAxis axis = cellSet.getAxes().get(i);
- axisDimensions[i] = axis.getPositions().size();
- }
- final CoordinateIterator coordIter =
- new CoordinateIterator(axisDimensions, true);
- return new Iterator() {
- public boolean hasNext() {
- return coordIter.hasNext();
- }
-
- public Cell next() {
- final int[] ints = coordIter.next();
- final AbstractList intList =
- new AbstractList() {
- public Integer get(int index) {
- return index < ints.length
- ? ints[index]
- : pageCoords[index - ints.length];
- }
-
- public int size() {
- return pageCoords.length + ints.length;
- }
- };
- return cellSet.getCell(intList);
- }
-
- public void remove() {
- throw new UnsupportedOperationException();
- }
- };
- }
- };
- }
-
- /**
- * Description of a particular hierarchy mapped to an axis.
- */
- private static class AxisOrdinalInfo {
- int minDepth = 1;
- int maxDepth = 0;
-
- /**
- * Returns the number of matrix columns required to display this
- * hierarchy.
- */
- public int getWidth() {
- return maxDepth - minDepth + 1;
- }
- }
-
- /**
- * Description of an axis.
- */
- private static class AxisInfo {
- final List ordinalInfos;
-
- /**
- * Creates an AxisInfo.
- *
- * @param ordinalCount Number of hierarchies on this axis
- */
- AxisInfo(int ordinalCount) {
- ordinalInfos = new ArrayList(ordinalCount);
- for (int i = 0; i < ordinalCount; i++) {
- ordinalInfos.add(new AxisOrdinalInfo());
- }
- }
-
- /**
- * Returns the number of matrix columns required by this axis. The
- * sum of the width of the hierarchies on this axis.
- *
- * @return Width of axis
- */
- public int getWidth() {
- int width = 0;
- for (AxisOrdinalInfo info : ordinalInfos) {
- width += info.getWidth();
- }
- return width;
- }
- }
-
- /**
- * Two-dimensional collection of string values.
- */
- private class Matrix {
- private final Map, MatrixCell> map =
- new HashMap, MatrixCell>();
- private final int width;
- private final int height;
-
- /**
- * Creats a Matrix.
- *
- * @param width Width of matrix
- * @param height Height of matrix
- */
- public Matrix(int width, int height) {
- this.width = width;
- this.height = height;
- }
-
- /**
- * Sets the value at a particular coordinate
- *
- * @param x X coordinate
- * @param y Y coordinate
- * @param value Value
- */
- void set(int x, int y, String value) {
- set(x, y, value, false, false);
- }
-
- /**
- * Sets the value at a particular coordinate
- *
- * @param x X coordinate
- * @param y Y coordinate
- * @param value Value
- * @param right Whether value is right-justified
- * @param sameAsPrev Whether value is the same as the previous value.
- * If true, some formats separators between cells
- */
- void set(
- int x,
- int y,
- String value,
- boolean right,
- boolean sameAsPrev)
- {
- map.put(
- Arrays.asList(x, y),
- new MatrixCell(value, right, sameAsPrev));
- assert x >= 0 && x < width : x;
- assert y >= 0 && y < height : y;
- }
-
- /**
- * Returns the cell at a particular coordinate.
- *
- * @param x X coordinate
- * @param y Y coordinate
- * @return Cell
- */
- public MatrixCell get(int x, int y) {
- return map.get(Arrays.asList(x, y));
- }
- }
-
- /**
- * Contents of a cell in a matrix.
- */
- private static class MatrixCell {
- final String value;
- final boolean right;
- final boolean sameAsPrev;
-
- /**
- * Creates a matrix cell.
- *
- * @param value Value
- * @param right Whether value is right-justified
- * @param sameAsPrev Whether value is the same as the previous value.
- * If true, some formats separators between cells
- */
- MatrixCell(
- String value,
- boolean right,
- boolean sameAsPrev)
- {
- this.value = value;
- this.right = right;
- this.sameAsPrev = sameAsPrev;
- }
- }
-}
-
-// End RectangularCellSetFormatter.java
+/*
+// $Id$
+// This software is subject to the terms of the Eclipse Public License v1.0
+// Agreement, available at the following URL:
+// http://www.eclipse.org/legal/epl-v10.html.
+// Copyright (C) 2009-2009 Julian Hyde
+// All Rights Reserved.
+// You must accept the terms of that agreement to use this software.
+*/
+package org.olap4j.query;
+
+import org.olap4j.*;
+import org.olap4j.metadata.Member;
+import org.olap4j.impl.CoordinateIterator;
+import org.olap4j.impl.Olap4jUtil;
+
+import java.io.PrintWriter;
+import java.util.*;
+
+/**
+ * Formatter that can convert a {@link CellSet} into a two-dimensional text
+ * layout.
+ *
+ * With non-compact layout:
+ *
+ *
+ * | 1997 |
+ * | Q1 | Q2 |
+ * | | 4 |
+ * | Unit Sales | Store Sales | Unit Sales | Store Sales |
+ * ----+----+---------+------------+-------------+------------+-------------+
+ * USA | CA | Modesto | 12 | 34.5 | 13 | 35.60 |
+ * | WA | Seattle | 12 | 34.5 | 13 | 35.60 |
+ * | CA | Fresno | 12 | 34.5 | 13 | 35.60 |
+ *
+ *
+ * With compact layout:
+ *
+ *
+ * 1997
+ * Q1 Q2
+ * 4
+ * Unit Sales Store Sales Unit Sales Store Sales
+ * === == ======= ========== =========== ========== ===========
+ * USA CA Modesto 12 34.5 13 35.60
+ * WA Seattle 12 34.5 13 35.60
+ * CA Fresno 12 34.5 13 35.60
+ *
+ *
+ * This class is experimental. It is not part of the olap4j
+ * specification and is subject to change without notice.
+ *
+ * @author jhyde
+ * @version $Id$
+ * @since Apr 15, 2009
+*/
+public class RectangularCellSetFormatter implements CellSetFormatter {
+ private final boolean compact;
+
+ /**
+ * Creates a RectangularCellSetFormatter.
+ *
+ * @param compact Whether to generate compact output
+ */
+ public RectangularCellSetFormatter(boolean compact) {
+ this.compact = compact;
+ }
+
+ public void format(CellSet cellSet, PrintWriter pw) {
+ // Compute how many rows are required to display the columns axis.
+ // In the example, this is 4 (1997, Q1, space, Unit Sales)
+ final CellSetAxis columnsAxis;
+ if (cellSet.getAxes().size() > 0) {
+ columnsAxis = cellSet.getAxes().get(0);
+ } else {
+ columnsAxis = null;
+ }
+ AxisInfo columnsAxisInfo = computeAxisInfo(columnsAxis);
+
+ // Compute how many columns are required to display the rows axis.
+ // In the example, this is 3 (the width of USA, CA, Los Angeles)
+ final CellSetAxis rowsAxis;
+ if (cellSet.getAxes().size() > 1) {
+ rowsAxis = cellSet.getAxes().get(1);
+ } else {
+ rowsAxis = null;
+ }
+ AxisInfo rowsAxisInfo = computeAxisInfo(rowsAxis);
+
+ if (cellSet.getAxes().size() > 2) {
+ int[] dimensions = new int[cellSet.getAxes().size() - 2];
+ for (int i = 2; i < cellSet.getAxes().size(); i++) {
+ CellSetAxis cellSetAxis = cellSet.getAxes().get(i);
+ dimensions[i - 2] = cellSetAxis.getPositions().size();
+ }
+ for (int[] pageCoords : CoordinateIterator.iterate(dimensions)) {
+ formatPage(
+ cellSet,
+ pw,
+ pageCoords,
+ columnsAxis,
+ columnsAxisInfo,
+ rowsAxis,
+ rowsAxisInfo);
+ }
+ } else {
+ formatPage(
+ cellSet,
+ pw,
+ new int[] {},
+ columnsAxis,
+ columnsAxisInfo,
+ rowsAxis,
+ rowsAxisInfo);
+ }
+ }
+
+ /**
+ * Formats a two-dimensional page.
+ *
+ * @param cellSet Cell set
+ * @param pw Print writer
+ * @param pageCoords Coordinates of page [page, chapter, section, ...]
+ * @param columnsAxis Columns axis
+ * @param columnsAxisInfo Description of columns axis
+ * @param rowsAxis Rows axis
+ * @param rowsAxisInfo Description of rows axis
+ */
+ private void formatPage(
+ CellSet cellSet,
+ PrintWriter pw,
+ int[] pageCoords,
+ CellSetAxis columnsAxis,
+ AxisInfo columnsAxisInfo,
+ CellSetAxis rowsAxis,
+ AxisInfo rowsAxisInfo)
+ {
+ if (pageCoords.length > 0) {
+ pw.println();
+ for (int i = pageCoords.length - 1; i >= 0; --i) {
+ int pageCoord = pageCoords[i];
+ final CellSetAxis axis = cellSet.getAxes().get(2 + i);
+ pw.print(axis.getAxisOrdinal() + ": ");
+ final Position position =
+ axis.getPositions().get(pageCoord);
+ int k = -1;
+ for (Member member : position.getMembers()) {
+ if (++k > 0) {
+ pw.print(", ");
+ }
+ pw.print(member.getUniqueName());
+ }
+ pw.println();
+ }
+ }
+ // Figure out the dimensions of the blank rectangle in the top left
+ // corner.
+ final int yOffset = columnsAxisInfo.getWidth();
+ final int xOffsset = rowsAxisInfo.getWidth();
+
+ // Populate a string matrix
+ Matrix matrix =
+ new Matrix(
+ xOffsset
+ + (columnsAxis == null
+ ? 1
+ : columnsAxis.getPositions().size()),
+ yOffset
+ + (rowsAxis == null
+ ? 1
+ : rowsAxis.getPositions().size()));
+
+ // Populate corner
+ for (int x = 0; x < xOffsset; x++) {
+ for (int y = 0; y < yOffset; y++) {
+ matrix.set(x, y, "", false, x > 0);
+ }
+ }
+
+ // Populate matrix with cells representing axes
+ //noinspection SuspiciousNameCombination
+ populateAxis(
+ matrix, columnsAxis, columnsAxisInfo, true, xOffsset);
+ populateAxis(
+ matrix, rowsAxis, rowsAxisInfo, false, yOffset);
+
+ // Populate cell values
+ for (Cell cell : cellIter(pageCoords, cellSet)) {
+ final List coordList = cell.getCoordinateList();
+ int x = xOffsset;
+ if (coordList.size() > 0) {
+ x += coordList.get(0);
+ }
+ int y = yOffset;
+ if (coordList.size() > 1) {
+ y += coordList.get(1);
+ }
+ matrix.set(
+ x, y, cell.getFormattedValue(), true, false);
+ }
+
+ int[] columnWidths = new int[matrix.width];
+ int widestWidth = 0;
+ for (int x = 0; x < matrix.width; x++) {
+ int columnWidth = 0;
+ for (int y = 0; y < matrix.height; y++) {
+ MatrixCell cell = matrix.get(x, y);
+ if (cell != null) {
+ columnWidth =
+ Math.max(columnWidth, cell.value.length());
+ }
+ }
+ columnWidths[x] = columnWidth;
+ widestWidth = Math.max(columnWidth, widestWidth);
+ }
+
+ // Create a large array of spaces, for efficient printing.
+ char[] spaces = new char[widestWidth + 1];
+ Arrays.fill(spaces, ' ');
+ char[] equals = new char[widestWidth + 1];
+ Arrays.fill(equals, '=');
+ char[] dashes = new char[widestWidth + 3];
+ Arrays.fill(dashes, '-');
+
+ if (compact) {
+ for (int y = 0; y < matrix.height; y++) {
+ for (int x = 0; x < matrix.width; x++) {
+ if (x > 0) {
+ pw.print(' ');
+ }
+ final MatrixCell cell = matrix.get(x, y);
+ final int len;
+ if (cell != null) {
+ if (cell.sameAsPrev) {
+ len = 0;
+ } else {
+ if (cell.right) {
+ int padding =
+ columnWidths[x] - cell.value.length();
+ pw.write(spaces, 0, padding);
+ pw.print(cell.value);
+ continue;
+ }
+ pw.print(cell.value);
+ len = cell.value.length();
+ }
+ } else {
+ len = 0;
+ }
+ if (x == matrix.width - 1) {
+ // at last column; don't bother to print padding
+ break;
+ }
+ int padding = columnWidths[x] - len;
+ pw.write(spaces, 0, padding);
+ }
+ pw.println();
+ if (y == yOffset - 1) {
+ for (int x = 0; x < matrix.width; x++) {
+ if (x > 0) {
+ pw.write(' ');
+ }
+ pw.write(equals, 0, columnWidths[x]);
+ }
+ pw.println();
+ }
+ }
+ } else {
+ for (int y = 0; y < matrix.height; y++) {
+ for (int x = 0; x < matrix.width; x++) {
+ final MatrixCell cell = matrix.get(x, y);
+ final int len;
+ if (cell != null) {
+ if (cell.sameAsPrev) {
+ pw.print(" ");
+ len = 0;
+ } else {
+ pw.print("| ");
+ if (cell.right) {
+ int padding =
+ columnWidths[x] - cell.value.length();
+ pw.write(spaces, 0, padding);
+ pw.print(cell.value);
+ pw.print(' ');
+ continue;
+ }
+ pw.print(cell.value);
+ len = cell.value.length();
+ }
+ } else {
+ pw.print("| ");
+ len = 0;
+ }
+ int padding = columnWidths[x] - len;
+ ++padding;
+ pw.write(spaces, 0, padding);
+ }
+ pw.println('|');
+ if (y == yOffset - 1) {
+ for (int x = 0; x < matrix.width; x++) {
+ pw.write('+');
+ pw.write(dashes, 0, columnWidths[x] + 2);
+ }
+ pw.println('+');
+ }
+ }
+ }
+ }
+
+ /**
+ * Populates cells in the matrix corresponding to a particular axis.
+ *
+ * @param matrix Matrix to populate
+ * @param axis Axis
+ * @param axisInfo Description of axis
+ * @param isColumns True if columns, false if rows
+ * @param offset Ordinal of first cell to populate in matrix
+ */
+ private void populateAxis(
+ Matrix matrix,
+ CellSetAxis axis,
+ AxisInfo axisInfo,
+ boolean isColumns,
+ int offset)
+ {
+ if (axis == null) {
+ return;
+ }
+ Member[] prevMembers = new Member[axisInfo.getWidth()];
+ Member[] members = new Member[axisInfo.getWidth()];
+ for (int i = 0; i < axis.getPositions().size(); i++) {
+ final int x = offset + i;
+ Position position = axis.getPositions().get(i);
+ int yOffset = 0;
+ final List memberList = position.getMembers();
+ for (int j = 0; j < memberList.size(); j++) {
+ Member member = memberList.get(j);
+ final AxisOrdinalInfo ordinalInfo =
+ axisInfo.ordinalInfos.get(j);
+ while (member != null) {
+ if (member.getDepth() < ordinalInfo.minDepth) {
+ break;
+ }
+ final int y =
+ yOffset
+ + member.getDepth()
+ - ordinalInfo.minDepth;
+ members[y] = member;
+ member = member.getParentMember();
+ }
+ yOffset += ordinalInfo.getWidth();
+ }
+ boolean same = true;
+ for (int y = 0; y < members.length; y++) {
+ Member member = members[y];
+ same =
+ same
+ && i > 0
+ && Olap4jUtil.equal(prevMembers[y], member);
+ String value =
+ member == null
+ ? ""
+ : member.getCaption(null);
+ if (isColumns) {
+ matrix.set(x, y, value, false, same);
+ } else {
+ if (same) {
+ value = "";
+ }
+ //noinspection SuspiciousNameCombination
+ matrix.set(y, x, value, false, false);
+ }
+ prevMembers[y] = member;
+ members[y] = null;
+ }
+ }
+ }
+
+ /**
+ * Computes a description of an axis.
+ *
+ * @param axis Axis
+ * @return Description of axis
+ */
+ private AxisInfo computeAxisInfo(CellSetAxis axis)
+ {
+ if (axis == null) {
+ return new AxisInfo(0);
+ }
+ final AxisInfo axisInfo =
+ new AxisInfo(axis.getAxisMetaData().getHierarchies().size());
+ int p = -1;
+ for (Position position : axis.getPositions()) {
+ ++p;
+ int k = -1;
+ for (Member member : position.getMembers()) {
+ ++k;
+ final AxisOrdinalInfo axisOrdinalInfo =
+ axisInfo.ordinalInfos.get(k);
+ final int topDepth =
+ member.isAll()
+ ? member.getDepth()
+ : member.getHierarchy().hasAll()
+ ? 1
+ : 0;
+ if (axisOrdinalInfo.minDepth > topDepth
+ || p == 0)
+ {
+ axisOrdinalInfo.minDepth = topDepth;
+ }
+ axisOrdinalInfo.maxDepth =
+ Math.max(
+ axisOrdinalInfo.maxDepth,
+ member.getDepth());
+ }
+ }
+ return axisInfo;
+ }
+
+ /**
+ * Returns an iterator over cells in a result.
+ */
+ private static Iterable cellIter(
+ final int[] pageCoords,
+ final CellSet cellSet)
+ {
+ return new Iterable() {
+ public Iterator iterator() {
+ int[] axisDimensions =
+ new int[cellSet.getAxes().size() - pageCoords.length];
+ assert pageCoords.length <= axisDimensions.length;
+ for (int i = 0; i < axisDimensions.length; i++) {
+ CellSetAxis axis = cellSet.getAxes().get(i);
+ axisDimensions[i] = axis.getPositions().size();
+ }
+ final CoordinateIterator coordIter =
+ new CoordinateIterator(axisDimensions, true);
+ return new Iterator() {
+ public boolean hasNext() {
+ return coordIter.hasNext();
+ }
+
+ public Cell next() {
+ final int[] ints = coordIter.next();
+ final AbstractList intList =
+ new AbstractList() {
+ public Integer get(int index) {
+ return index < ints.length
+ ? ints[index]
+ : pageCoords[index - ints.length];
+ }
+
+ public int size() {
+ return pageCoords.length + ints.length;
+ }
+ };
+ return cellSet.getCell(intList);
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ };
+ }
+ };
+ }
+
+ /**
+ * Description of a particular hierarchy mapped to an axis.
+ */
+ private static class AxisOrdinalInfo {
+ int minDepth = 1;
+ int maxDepth = 0;
+
+ /**
+ * Returns the number of matrix columns required to display this
+ * hierarchy.
+ */
+ public int getWidth() {
+ return maxDepth - minDepth + 1;
+ }
+ }
+
+ /**
+ * Description of an axis.
+ */
+ private static class AxisInfo {
+ final List ordinalInfos;
+
+ /**
+ * Creates an AxisInfo.
+ *
+ * @param ordinalCount Number of hierarchies on this axis
+ */
+ AxisInfo(int ordinalCount) {
+ ordinalInfos = new ArrayList(ordinalCount);
+ for (int i = 0; i < ordinalCount; i++) {
+ ordinalInfos.add(new AxisOrdinalInfo());
+ }
+ }
+
+ /**
+ * Returns the number of matrix columns required by this axis. The
+ * sum of the width of the hierarchies on this axis.
+ *
+ * @return Width of axis
+ */
+ public int getWidth() {
+ int width = 0;
+ for (AxisOrdinalInfo info : ordinalInfos) {
+ width += info.getWidth();
+ }
+ return width;
+ }
+ }
+
+ /**
+ * Two-dimensional collection of string values.
+ */
+ private class Matrix {
+ private final Map, MatrixCell> map =
+ new HashMap, MatrixCell>();
+ private final int width;
+ private final int height;
+
+ /**
+ * Creats a Matrix.
+ *
+ * @param width Width of matrix
+ * @param height Height of matrix
+ */
+ public Matrix(int width, int height) {
+ this.width = width;
+ this.height = height;
+ }
+
+ /**
+ * Sets the value at a particular coordinate
+ *
+ * @param x X coordinate
+ * @param y Y coordinate
+ * @param value Value
+ */
+ void set(int x, int y, String value) {
+ set(x, y, value, false, false);
+ }
+
+ /**
+ * Sets the value at a particular coordinate
+ *
+ * @param x X coordinate
+ * @param y Y coordinate
+ * @param value Value
+ * @param right Whether value is right-justified
+ * @param sameAsPrev Whether value is the same as the previous value.
+ * If true, some formats separators between cells
+ */
+ void set(
+ int x,
+ int y,
+ String value,
+ boolean right,
+ boolean sameAsPrev)
+ {
+ map.put(
+ Arrays.asList(x, y),
+ new MatrixCell(value, right, sameAsPrev));
+ assert x >= 0 && x < width : x;
+ assert y >= 0 && y < height : y;
+ }
+
+ /**
+ * Returns the cell at a particular coordinate.
+ *
+ * @param x X coordinate
+ * @param y Y coordinate
+ * @return Cell
+ */
+ public MatrixCell get(int x, int y) {
+ return map.get(Arrays.asList(x, y));
+ }
+ }
+
+ /**
+ * Contents of a cell in a matrix.
+ */
+ private static class MatrixCell {
+ final String value;
+ final boolean right;
+ final boolean sameAsPrev;
+
+ /**
+ * Creates a matrix cell.
+ *
+ * @param value Value
+ * @param right Whether value is right-justified
+ * @param sameAsPrev Whether value is the same as the previous value.
+ * If true, some formats separators between cells
+ */
+ MatrixCell(
+ String value,
+ boolean right,
+ boolean sameAsPrev)
+ {
+ this.value = value;
+ this.right = right;
+ this.sameAsPrev = sameAsPrev;
+ }
+ }
+}
+
+// End RectangularCellSetFormatter.java
diff --git a/src/org/olap4j/query/Selection.java b/src/org/olap4j/query/Selection.java
index ca83741..ac140ff 100644
--- a/src/org/olap4j/query/Selection.java
+++ b/src/org/olap4j/query/Selection.java
@@ -13,7 +13,10 @@
import org.olap4j.metadata.Member;
/**
- * A selection of members from an OLAP dimension hierarchy.
+ * A selection of members from an OLAP dimension hierarchy. The selection
+ * is a conceptual list of members from a given hierarchy. Once a selection
+ * object is created, one can decide to include or exclude this selection
+ * of members from the resulting query.
*
* Concrete subclasses of this represent a real selection.
* Selections include things such as 'children of', 'siblings of',
diff --git a/src/org/olap4j/query/TraditionalCellSetFormatter.java b/src/org/olap4j/query/TraditionalCellSetFormatter.java
index 86e1587..14b5ac4 100644
--- a/src/org/olap4j/query/TraditionalCellSetFormatter.java
+++ b/src/org/olap4j/query/TraditionalCellSetFormatter.java
@@ -1,140 +1,140 @@
-/*
-// $Id$
-// This software is subject to the terms of the Eclipse Public License v1.0
-// Agreement, available at the following URL:
-// http://www.eclipse.org/legal/epl-v10.html.
-// Copyright (C) 2009-2009 Julian Hyde
-// All Rights Reserved.
-// You must accept the terms of that agreement to use this software.
-*/
-package org.olap4j.query;
-
-import org.olap4j.*;
-import org.olap4j.metadata.Member;
-
-import java.io.PrintWriter;
-import java.util.List;
-import java.util.ArrayList;
-
-/**
- * Formatter that can convert a {@link CellSet} into Mondrian's traditional
- * layout.
- *
- * This class is experimental. It is not part of the olap4j
- * specification and is subject to change without notice.
- *
- * @author jhyde
- * @version $Id$
- * @since Apr 15, 2009
- */
-public class TraditionalCellSetFormatter implements CellSetFormatter {
- public void format(
- CellSet cellSet,
- PrintWriter pw)
- {
- print(cellSet, pw);
- }
-
- /**
- * Prints a cell set.
- *
- * @param cellSet Cell set
- * @param pw Writer
- */
- private static void print(CellSet cellSet, PrintWriter pw) {
- pw.println("Axis #0:");
- printAxis(pw, cellSet.getFilterAxis());
- final List axes = cellSet.getAxes();
- final int axisCount = axes.size();
- for (int i = 0; i < axisCount; i++) {
- CellSetAxis axis = axes.get(i);
- pw.println("Axis #" + (i + 1) + ":");
- printAxis(pw, axis);
- }
- // Usually there are 3 axes: {filter, columns, rows}. Position is a
- // {column, row} pair. We call printRows with axis=2. When it
- // recurses to axis=-1, it prints.
- List pos = new ArrayList(axisCount);
- for (int i = 0; i < axisCount; i++) {
- pos.add(-1);
- }
- printRows(cellSet, pw, axisCount - 1, pos);
- }
-
- /**
- * Prints the rows of cell set.
- *
- * @param cellSet Cell set
- * @param pw Writer
- * @param axis Axis ordinal
- * @param pos Partial coordinate
- */
- private static void printRows(
- CellSet cellSet, PrintWriter pw, int axis, List pos)
- {
- CellSetAxis _axis = axis < 0
- ? cellSet.getFilterAxis()
- : cellSet.getAxes().get(axis);
- List positions = _axis.getPositions();
- final int positionCount = positions.size();
- for (int i = 0; i < positionCount; i++) {
- if (axis < 0) {
- if (i > 0) {
- pw.print(", ");
- }
- printCell(cellSet, pw, pos);
- } else {
- pos.set(axis, i);
- if (axis == 0) {
- int row =
- axis + 1 < pos.size()
- ? pos.get(axis + 1)
- : 0;
- pw.print("Row #" + row + ": ");
- }
- printRows(cellSet, pw, axis - 1, pos);
- if (axis == 0) {
- pw.println();
- }
- }
- }
- }
-
- /**
- * Prints an axis and its members.
- *
- * @param pw Print writer
- * @param axis Axis
- */
- private static void printAxis(PrintWriter pw, CellSetAxis axis) {
- List positions = axis.getPositions();
- for (Position position : positions) {
- boolean firstTime = true;
- pw.print("{");
- for (Member member : position.getMembers()) {
- if (! firstTime) {
- pw.print(", ");
- }
- pw.print(member.getUniqueName());
- firstTime = false;
- }
- pw.println("}");
- }
- }
-
- /**
- * Prints the formatted value of a Cell at a given position.
- *
- * @param cellSet Cell set
- * @param pw Print writer
- * @param pos Cell coordinates
- */
- private static void printCell(
- CellSet cellSet, PrintWriter pw, List pos)
- {
- Cell cell = cellSet.getCell(pos);
- pw.print(cell.getFormattedValue());
- }
-}
-
-// End TraditionalCellSetFormatter.java
+/*
+// $Id$
+// This software is subject to the terms of the Eclipse Public License v1.0
+// Agreement, available at the following URL:
+// http://www.eclipse.org/legal/epl-v10.html.
+// Copyright (C) 2009-2009 Julian Hyde
+// All Rights Reserved.
+// You must accept the terms of that agreement to use this software.
+*/
+package org.olap4j.query;
+
+import org.olap4j.*;
+import org.olap4j.metadata.Member;
+
+import java.io.PrintWriter;
+import java.util.List;
+import java.util.ArrayList;
+
+/**
+ * Formatter that can convert a {@link CellSet} into Mondrian's traditional
+ * layout.
+ *
+ * This class is experimental. It is not part of the olap4j
+ * specification and is subject to change without notice.
+ *
+ * @author jhyde
+ * @version $Id$
+ * @since Apr 15, 2009
+ */
+public class TraditionalCellSetFormatter implements CellSetFormatter {
+ public void format(
+ CellSet cellSet,
+ PrintWriter pw)
+ {
+ print(cellSet, pw);
+ }
+
+ /**
+ * Prints a cell set.
+ *
+ * @param cellSet Cell set
+ * @param pw Writer
+ */
+ private static void print(CellSet cellSet, PrintWriter pw) {
+ pw.println("Axis #0:");
+ printAxis(pw, cellSet.getFilterAxis());
+ final List axes = cellSet.getAxes();
+ final int axisCount = axes.size();
+ for (int i = 0; i < axisCount; i++) {
+ CellSetAxis axis = axes.get(i);
+ pw.println("Axis #" + (i + 1) + ":");
+ printAxis(pw, axis);
+ }
+ // Usually there are 3 axes: {filter, columns, rows}. Position is a
+ // {column, row} pair. We call printRows with axis=2. When it
+ // recurses to axis=-1, it prints.
+ List pos = new ArrayList(axisCount);
+ for (int i = 0; i < axisCount; i++) {
+ pos.add(-1);
+ }
+ printRows(cellSet, pw, axisCount - 1, pos);
+ }
+
+ /**
+ * Prints the rows of cell set.
+ *
+ * @param cellSet Cell set
+ * @param pw Writer
+ * @param axis Axis ordinal
+ * @param pos Partial coordinate
+ */
+ private static void printRows(
+ CellSet cellSet, PrintWriter pw, int axis, List pos)
+ {
+ CellSetAxis _axis = axis < 0
+ ? cellSet.getFilterAxis()
+ : cellSet.getAxes().get(axis);
+ List positions = _axis.getPositions();
+ final int positionCount = positions.size();
+ for (int i = 0; i < positionCount; i++) {
+ if (axis < 0) {
+ if (i > 0) {
+ pw.print(", ");
+ }
+ printCell(cellSet, pw, pos);
+ } else {
+ pos.set(axis, i);
+ if (axis == 0) {
+ int row =
+ axis + 1 < pos.size()
+ ? pos.get(axis + 1)
+ : 0;
+ pw.print("Row #" + row + ": ");
+ }
+ printRows(cellSet, pw, axis - 1, pos);
+ if (axis == 0) {
+ pw.println();
+ }
+ }
+ }
+ }
+
+ /**
+ * Prints an axis and its members.
+ *
+ * @param pw Print writer
+ * @param axis Axis
+ */
+ private static void printAxis(PrintWriter pw, CellSetAxis axis) {
+ List positions = axis.getPositions();
+ for (Position position : positions) {
+ boolean firstTime = true;
+ pw.print("{");
+ for (Member member : position.getMembers()) {
+ if (! firstTime) {
+ pw.print(", ");
+ }
+ pw.print(member.getUniqueName());
+ firstTime = false;
+ }
+ pw.println("}");
+ }
+ }
+
+ /**
+ * Prints the formatted value of a Cell at a given position.
+ *
+ * @param cellSet Cell set
+ * @param pw Print writer
+ * @param pos Cell coordinates
+ */
+ private static void printCell(
+ CellSet cellSet, PrintWriter pw, List pos)
+ {
+ Cell cell = cellSet.getCell(pos);
+ pw.print(cell.getFormattedValue());
+ }
+}
+
+// End TraditionalCellSetFormatter.java
diff --git a/testsrc/org/olap4j/OlapTest.java b/testsrc/org/olap4j/OlapTest.java
index 8ade31f..c3f5e97 100644
--- a/testsrc/org/olap4j/OlapTest.java
+++ b/testsrc/org/olap4j/OlapTest.java
@@ -199,16 +199,18 @@ public void testModel() {
Member productMember = cube.lookupMember("Product", "Drink");
// create some selections for Store
- storeQuery.select(
+ storeQuery.include(
Selection.Operator.CHILDREN, "Store", "USA");
// create some selections for Product
- productQuery.clearSelection();
- productQuery.select(Selection.Operator.CHILDREN, productMember);
- productQuery.select(Selection.Operator.CHILDREN, "Product", "Food");
+ productQuery.clearInclusions();
+ productQuery.include(
+ Selection.Operator.CHILDREN, productMember);
+ productQuery.include(
+ Selection.Operator.CHILDREN, "Product", "Food");
// create some selections for Time
- timeQuery.select(Selection.Operator.CHILDREN, "Time", "1997");
+ timeQuery.include(Selection.Operator.CHILDREN, "Time", "1997");
// place our dimensions on the axes
query.getAxis(Axis.COLUMNS).addDimension(productQuery);
@@ -249,11 +251,11 @@ public void testSelectionModes() {
// TEST CHILDREN SELECTION
QueryDimension productDimension = query.getDimension("Product");
- productDimension.select(
+ productDimension.include(
Selection.Operator.CHILDREN, "Product", "Drink");
QueryDimension measuresDimension = query.getDimension("Measures");
- measuresDimension.select("Measures", "Store Sales");
+ measuresDimension.include("Measures", "Store Sales");
query.getAxis(Axis.ROWS).addDimension(productDimension);
query.getAxis(Axis.COLUMNS).addDimension(measuresDimension);
@@ -271,8 +273,8 @@ public void testSelectionModes() {
// TEST ANCESTORS SELECTION
- productDimension.clearSelection();
- productDimension.select(
+ productDimension.clearInclusions();
+ productDimension.include(
Selection.Operator.ANCESTORS, "Product", "Drink");
query.validate();
@@ -288,8 +290,8 @@ public void testSelectionModes() {
// TEST DESCENDANTS SELECTION
- productDimension.clearSelection();
- productDimension.select(
+ productDimension.clearInclusions();
+ productDimension.include(
Selection.Operator.DESCENDANTS, "Product", "Drink");
query.validate();
@@ -305,8 +307,8 @@ public void testSelectionModes() {
// TEST INCLUDE_CHILDREN SELECTION
- productDimension.clearSelection();
- productDimension.select(
+ productDimension.clearInclusions();
+ productDimension.include(
Selection.Operator.INCLUDE_CHILDREN, "Product", "Drink");
query.validate();
@@ -322,8 +324,8 @@ public void testSelectionModes() {
// TEST SIBLINGS SELECTION
- productDimension.clearSelection();
- productDimension.select(
+ productDimension.clearInclusions();
+ productDimension.include(
Selection.Operator.SIBLINGS, "Product", "Drink");
query.validate();
@@ -353,18 +355,18 @@ public void testMultipleDimensionSelections() {
// create selections
QueryDimension productDimension = query.getDimension("Product");
- productDimension.select(
+ productDimension.include(
Selection.Operator.CHILDREN, "Product", "Drink");
QueryDimension storeDimension = query.getDimension("Store");
- storeDimension.select(
+ storeDimension.include(
Selection.Operator.INCLUDE_CHILDREN, "Store", "USA");
QueryDimension timeDimension = query.getDimension("Time");
- timeDimension.select(Selection.Operator.CHILDREN, "Time", "1997");
+ timeDimension.include(Selection.Operator.CHILDREN, "Time", "1997");
QueryDimension measuresDimension = query.getDimension("Measures");
- measuresDimension.select("Measures", "Store Sales");
+ measuresDimension.include("Measures", "Store Sales");
query.getAxis(Axis.ROWS).addDimension(productDimension);
query.getAxis(Axis.ROWS).addDimension(storeDimension);
@@ -400,11 +402,11 @@ public void testSwapAxes() {
// create selections
QueryDimension productDimension = query.getDimension("Product");
- productDimension.select(
+ productDimension.include(
Selection.Operator.CHILDREN, "Product", "Drink");
QueryDimension measuresDimension = query.getDimension("Measures");
- measuresDimension.select("Measures", "Store Sales");
+ measuresDimension.include("Measures", "Store Sales");
query.getAxis(Axis.ROWS).addDimension(productDimension);
query.getAxis(Axis.COLUMNS).addDimension(measuresDimension);
@@ -463,11 +465,11 @@ public void testSortDimension() {
// create selections
QueryDimension productDimension = query.getDimension("Product");
- productDimension.select(
+ productDimension.include(
Selection.Operator.INCLUDE_CHILDREN, "Product", "Drink");
QueryDimension measuresDimension = query.getDimension("Measures");
- measuresDimension.select("Measures", "Store Sales");
+ measuresDimension.include("Measures", "Store Sales");
query.getAxis(Axis.ROWS).addDimension(productDimension);
query.getAxis(Axis.COLUMNS).addDimension(measuresDimension);
@@ -537,19 +539,19 @@ public void testDimensionsOrder() {
// create selections
QueryDimension productDimension = query.getDimension("Product");
- productDimension.select(
+ productDimension.include(
Selection.Operator.CHILDREN, "Product", "Drink");
QueryDimension storeDimension = query.getDimension("Store");
- storeDimension.select(
+ storeDimension.include(
Selection.Operator.INCLUDE_CHILDREN, "Store", "USA");
QueryDimension timeDimension = query.getDimension("Time");
- timeDimension.select(Selection.Operator.CHILDREN, "Time", "1997");
+ timeDimension.include(Selection.Operator.CHILDREN, "Time", "1997");
QueryDimension measuresDimension = query.getDimension("Measures");
- measuresDimension.select("Measures", "Store Sales");
+ measuresDimension.include("Measures", "Store Sales");
query.getAxis(Axis.ROWS).addDimension(productDimension);
@@ -625,11 +627,11 @@ public void testQueryVersusParseTreeIndependence() {
Query query = new Query("my query", cube);
QueryDimension productDimension = query.getDimension("Product");
- productDimension.select(
+ productDimension.include(
Selection.Operator.INCLUDE_CHILDREN, "Product", "Drink");
QueryDimension measuresDimension = query.getDimension("Measures");
- measuresDimension.select("Measures", "Store Sales");
+ measuresDimension.include("Measures", "Store Sales");
query.getAxis(Axis.ROWS).addDimension(productDimension);
query.getAxis(Axis.COLUMNS).addDimension(measuresDimension);
@@ -656,16 +658,18 @@ public void testQueryVersusParseTreeIndependence() {
originalMdxString);
// change selections
- measuresDimension.select(
+ measuresDimension.include(
Selection.Operator.SIBLINGS, "Measures", "Customer Count");
- productDimension.select(
+ productDimension.include(
Selection.Operator.SIBLINGS,
"Product", "All Products", "Drink", "Alcoholic Beverages");
// Add something to crossjoin with
- query.getAxis(Axis.COLUMNS).addDimension(
+ query.getAxis(Axis.ROWS).addDimension(
query.getDimension("Gender"));
- query.getDimension("Gender").select(Operator.CHILDREN, "Gender",
+ query.getDimension("Gender").include(
+ Operator.CHILDREN,
+ "Gender",
"All Gender");
query.getAxis(Axis.UNUSED).addDimension(
@@ -681,6 +685,128 @@ public void testQueryVersusParseTreeIndependence() {
}
}
+ public void testExclusionModes() {
+ try {
+ Cube cube = getFoodmartCube("Sales");
+ if (cube == null) {
+ fail("Could not find Sales cube");
+ }
+
+ // Setup a base query.
+ Query query = new Query("my query", cube);
+ QueryDimension productDimension = query.getDimension("Product");
+ productDimension.include(
+ Selection.Operator.CHILDREN,
+ "Product", "Drink",
+ "Beverages");
+ productDimension.include(
+ Selection.Operator.CHILDREN,
+ "Product",
+ "Food",
+ "Frozen Foods");
+ QueryDimension measuresDimension = query.getDimension("Measures");
+ measuresDimension.include("Measures", "Sales Count");
+ QueryDimension timeDimension = query.getDimension("Time");
+ timeDimension.include("Time", "Year", "1997", "Q3", "7");
+ query.getAxis(Axis.ROWS).addDimension(productDimension);
+ query.getAxis(Axis.COLUMNS).addDimension(measuresDimension);
+ query.getAxis(Axis.FILTER).addDimension(timeDimension);
+ query.validate();
+
+ // Validate the generated MDX
+ String mdxString = query.getSelect().toString();
+ TestContext.assertEqualsVerbose(
+ "SELECT\n"
+ + "{[Measures].[Sales Count]} ON COLUMNS,\n"
+ + "{[Product].[All Products].[Drink].[Beverages].Children, [Product].[All Products].[Food].[Frozen Foods].Children} ON ROWS\n"
+ + "FROM [Sales]\n"
+ + "WHERE ([Time].[1997].[Q3].[7])",
+ mdxString);
+
+ // Validate the returned results
+ CellSet results = query.execute();
+ String resultsString = TestContext.toString(results);
+ TestContext.assertEqualsVerbose(
+ "Axis #0:\n"
+ + "{[Store].[All Stores], [Store Size in SQFT].[All Store Size in SQFTs], [Store Type].[All Store Types], [Time].[1997].[Q3].[7], [Promotion Media].[All Media], [Promotions].[All Promotions], [Customers].[All Customers], [Education Level].[All Education Levels], [Gender].[All Gender], [Marital Status].[All Marital Status], [Yearly Income].[All Yearly Incomes]}\n"
+ + "Axis #1:\n"
+ + "{[Measures].[Sales Count]}\n"
+ + "Axis #2:\n"
+ + "{[Product].[All Products].[Drink].[Beverages].[Carbonated Beverages]}\n"
+ + "{[Product].[All Products].[Drink].[Beverages].[Drinks]}\n"
+ + "{[Product].[All Products].[Drink].[Beverages].[Hot Beverages]}\n"
+ + "{[Product].[All Products].[Drink].[Beverages].[Pure Juice Beverages]}\n"
+ + "{[Product].[All Products].[Food].[Frozen Foods].[Breakfast Foods]}\n"
+ + "{[Product].[All Products].[Food].[Frozen Foods].[Frozen Desserts]}\n"
+ + "{[Product].[All Products].[Food].[Frozen Foods].[Frozen Entrees]}\n"
+ + "{[Product].[All Products].[Food].[Frozen Foods].[Meat]}\n"
+ + "{[Product].[All Products].[Food].[Frozen Foods].[Pizza]}\n"
+ + "{[Product].[All Products].[Food].[Frozen Foods].[Vegetables]}\n"
+ + "Row #0: 103\n"
+ + "Row #1: 65\n"
+ + "Row #2: 125\n"
+ + "Row #3: 100\n"
+ + "Row #4: 143\n"
+ + "Row #5: 185\n"
+ + "Row #6: 68\n"
+ + "Row #7: 81\n"
+ + "Row #8: 105\n"
+ + "Row #9: 212\n",
+ resultsString);
+
+ // Exclude the Carbonated Beverages because they are not good
+ // for your health.
+ query.getDimension("Product").exclude(
+ "Product",
+ "Drink",
+ "Beverages",
+ "Carbonated Beverages");
+
+ // Validate the generated MDX
+ query.validate();
+ mdxString = query.getSelect().toString();
+ TestContext.assertEqualsVerbose(
+ "SELECT\n"
+ + "{[Measures].[Sales Count]} ON COLUMNS,\n"
+ + "{Except({[Product].[All Products].[Drink].[Beverages].Children, [Product].[All Products].[Food].[Frozen Foods].Children}, {[Product].[All Products].[Drink].[Beverages].[Carbonated Beverages]})} ON ROWS\n"
+ + "FROM [Sales]\n"
+ + "WHERE ([Time].[1997].[Q3].[7])",
+ mdxString);
+
+ // Validate the returned results
+ results = query.execute();
+ resultsString = TestContext.toString(results);
+ TestContext.assertEqualsVerbose(
+ "Axis #0:\n"
+ + "{[Store].[All Stores], [Store Size in SQFT].[All Store Size in SQFTs], [Store Type].[All Store Types], [Time].[1997].[Q3].[7], [Promotion Media].[All Media], [Promotions].[All Promotions], [Customers].[All Customers], [Education Level].[All Education Levels], [Gender].[All Gender], [Marital Status].[All Marital Status], [Yearly Income].[All Yearly Incomes]}\n"
+ + "Axis #1:\n"
+ + "{[Measures].[Sales Count]}\n"
+ + "Axis #2:\n"
+ + "{[Product].[All Products].[Drink].[Beverages].[Drinks]}\n"
+ + "{[Product].[All Products].[Drink].[Beverages].[Hot Beverages]}\n"
+ + "{[Product].[All Products].[Drink].[Beverages].[Pure Juice Beverages]}\n"
+ + "{[Product].[All Products].[Food].[Frozen Foods].[Breakfast Foods]}\n"
+ + "{[Product].[All Products].[Food].[Frozen Foods].[Frozen Desserts]}\n"
+ + "{[Product].[All Products].[Food].[Frozen Foods].[Frozen Entrees]}\n"
+ + "{[Product].[All Products].[Food].[Frozen Foods].[Meat]}\n"
+ + "{[Product].[All Products].[Food].[Frozen Foods].[Pizza]}\n"
+ + "{[Product].[All Products].[Food].[Frozen Foods].[Vegetables]}\n"
+ + "Row #0: 65\n"
+ + "Row #1: 125\n"
+ + "Row #2: 100\n"
+ + "Row #3: 143\n"
+ + "Row #4: 185\n"
+ + "Row #5: 68\n"
+ + "Row #6: 81\n"
+ + "Row #7: 105\n"
+ + "Row #8: 212\n",
+ resultsString);
+ } catch (Exception e) {
+ e.printStackTrace();
+ fail();
+ }
+ }
+
public static void listHierarchies(Dimension dimension) {
// Get a list of hierarchy objects and dump their names
for (Hierarchy hierarchy : dimension.getHierarchies()) {
| | | | | | | |