diff --git a/src/org/olap4j/Axis.java b/src/org/olap4j/Axis.java index 53e69b7..8a7526f 100644 --- a/src/org/olap4j/Axis.java +++ b/src/org/olap4j/Axis.java @@ -24,60 +24,155 @@ * @version $Id$ * @since Oct 23, 2006 */ -public enum Axis { - UNUSED, - FILTER, - COLUMNS, - ROWS, - PAGES, - CHAPTERS, - SECTIONS; +public interface Axis { /** - * Returns the ordinal which is to be used for retrieving this axis from - * the {@link org.olap4j.CellSet#getAxes()}, or retrieving its - * coordinate from {@link Cell#getCoordinateList()}. + * Returns the name of this axis, e.g. "COLUMNS", "SLICER", "AXIS(17)". * - *

The axis ordinal is two less than the {@link #ordinal} value which - * every enum value possesses. Hence, {@link #UNUSED} is -2 - * and {@link #FILTER} is -1 (because they are not treated the same as the - * other axes), {@link #COLUMNS} is 0, {@link #ROWS} is 1, and so forth. - * - * @return Axis ordinal + * @return Name of the axis */ - public int axisOrdinal() { - return axisOrdinal; - } + String name(); /** - * Returns localized name for this Axis. + * Returns whether this is the filter (slicer) axis. * - * @param locale Locale for which to give the name - * @return localized name for this Axis + * @return whether this is the filter axis */ - public String getCaption(Locale locale) { - // todo: localize - return name(); - } + boolean isFilter(); /** - * Returns the axis with a given {@link #axisOrdinal()}. - * - * @param axisOrdinal Axis ordinal - * @return Axis whose {@link #axisOrdinal()} is as given + * @deprecated Will be removed before olap4j 1.0. */ - public static Axis forOrdinal(int axisOrdinal) { - Axis axis = values()[axisOrdinal + 2]; - assert axis.axisOrdinal() == axisOrdinal; - return axis; + public static final Standard UNUSED = null; + + /** + * @deprecated Will be removed before olap4j 1.0. + */ + public static final Standard NONE = null; + + /** + * Abbreviation for {@link org.olap4j.Axis.Standard#FILTER}. + */ + public static final Standard FILTER = Standard.FILTER; + public static final Standard COLUMNS = Standard.COLUMNS; + public static final Standard ROWS = Standard.ROWS; + public static final Standard PAGES = Standard.PAGES; + public static final Standard SECTIONS = Standard.SECTIONS; + public static final Standard CHAPTERS = Standard.CHAPTERS; + + /** + * Enumeration of standard, named axes descriptors. + */ + public enum Standard implements Axis { + /** Filter axis. */ + FILTER, + + /** COLUMNS axis, also known as X axis and AXIS(0). */ + COLUMNS, + + /** ROWS axis, also known as Y axis and AXIS(1). */ + ROWS, + + /** PAGES axis, also known as AXIS(2). */ + PAGES, + + /** CHAPTERS axis, also known as AXIS(3). */ + CHAPTERS, + + /** SECTIONS axis, also known as AXIS(4). */ + SECTIONS; + + public int axisOrdinal() { + return ordinal() - 1; + } + + public boolean isFilter() { + return this == FILTER; + } + + public String getCaption(Locale locale) { + // TODO: localize + return name(); + } } - private final int axisOrdinal = ordinal() - 2; + class Factory { + private static final Standard[] STANDARD_VALUES = Standard.values(); + + /** + * Returns the axis with a given {@code axisOrdinal}. + * + *

For example, {@code forOrdinal(0)} returns the COLUMNS axis; + * {@code forOrdinal(-1)} returns the SLICER axis; + * {@code forOrdinal(100)} returns AXIS(100). + * + * @param ordinal Axis ordinal + * @return Axis whose ordinal is as given + */ + public static Axis forOrdinal(final int ordinal) { + if (ordinal < -1) { + throw new IllegalArgumentException( + "Axis ordinal must be -1 or higher"); + } + if (ordinal + 1 < STANDARD_VALUES.length) { + return STANDARD_VALUES[ordinal + 1]; + } + return new Axis() { + public String toString() { + return name(); + } + + public String name() { + return "AXIS(" + ordinal + ")"; + } + + public boolean isFilter() { + return false; + } + + public int axisOrdinal() { + return ordinal; + } + + public String getCaption(Locale locale) { + // TODO: localize + return name(); + } + }; + } + } /** - * The largest legal value for {@link #forOrdinal(int)}. + * Returns the ordinal which is to be used for retrieving this axis from + * the {@link org.olap4j.CellSet#getAxes()}, or retrieving its + * coordinate from {@link Cell#getCoordinateList()}. + * + *

For example: + *

+ * + * @return ordinal of this axis + */ + int axisOrdinal(); + + /** + * Returns localized name for this Axis. + * + *

Examples: "FILTER", "ROWS", "COLUMNS", "AXIS(10)". + * + * @param locale Locale for which to give the name + * @return localized name for this Axis */ - public static final int MAX_ORDINAL = SECTIONS.axisOrdinal(); + String getCaption(Locale locale); } // End Axis.java diff --git a/src/org/olap4j/driver/xmla/XmlaOlap4jCellSet.java b/src/org/olap4j/driver/xmla/XmlaOlap4jCellSet.java index 4123f80..44e2c07 100644 --- a/src/org/olap4j/driver/xmla/XmlaOlap4jCellSet.java +++ b/src/org/olap4j/driver/xmla/XmlaOlap4jCellSet.java @@ -13,8 +13,8 @@ import org.olap4j.impl.Olap4jUtil; import static org.olap4j.driver.xmla.XmlaOlap4jUtil.*; import org.olap4j.metadata.*; + import org.w3c.dom.*; -import org.w3c.dom.ls.*; import org.xml.sax.SAXException; import java.io.*; @@ -178,13 +178,10 @@ void populate() throws OlapException { final Axis axis = lookupAxis(axisName); final XmlaOlap4jCellSetAxis cellSetAxis = new XmlaOlap4jCellSetAxis(this, axis); - switch (axis) { - case FILTER: + if (axis.isFilter()) { filterAxis = cellSetAxis; - break; - default: + } else { axisList.add(cellSetAxis); - break; } final Element tuplesNode = findChild(axisNode, MDDATASET_NS, "Tuples"); @@ -431,13 +428,10 @@ private XmlaOlap4jCellSetMetaData createMetaData(Element root) axis, hierarchyList, propertyList); - switch (axis) { - case FILTER: + if (axis.isFilter()) { filterAxisMetaData = axisMetaData; - break; - default: + } else { axisMetaDataList.add(axisMetaData); - break; } } final Element cellInfo = @@ -500,7 +494,7 @@ private Axis lookupAxis(String axisName) { if (axisName.startsWith("Axis")) { final Integer ordinal = Integer.valueOf(axisName.substring("Axis".length())); - return Axis.values()[Axis.COLUMNS.ordinal() + ordinal]; + return Axis.Factory.forOrdinal(ordinal); } else { return Axis.FILTER; } diff --git a/src/org/olap4j/driver/xmla/XmlaOlap4jCellSetAxis.java b/src/org/olap4j/driver/xmla/XmlaOlap4jCellSetAxis.java index 685f40b..61b7674 100644 --- a/src/org/olap4j/driver/xmla/XmlaOlap4jCellSetAxis.java +++ b/src/org/olap4j/driver/xmla/XmlaOlap4jCellSetAxis.java @@ -27,6 +27,12 @@ class XmlaOlap4jCellSetAxis implements CellSetAxis { private final List immutablePositions = Collections.unmodifiableList(positions); + /** + * Creates an XmlaOlap4jCellSetAxis. + * + * @param olap4jCellSet Cell set + * @param axis Axis identifier + */ public XmlaOlap4jCellSetAxis( XmlaOlap4jCellSet olap4jCellSet, Axis axis) @@ -45,10 +51,9 @@ public CellSet getCellSet() { public CellSetAxisMetaData getAxisMetaData() { final CellSetMetaData cellSetMetaData = olap4jCellSet.getMetaData(); - switch (axis) { - case FILTER: + if (axis.isFilter()) { return cellSetMetaData.getFilterAxisMetaData(); - default: + } else { return cellSetMetaData.getAxesMetaData().get( axis.axisOrdinal()); } diff --git a/src/org/olap4j/mdx/parser/impl/DefaultMdxParser.cup b/src/org/olap4j/mdx/parser/impl/DefaultMdxParser.cup index 19af14b..ae342e9 100644 --- a/src/org/olap4j/mdx/parser/impl/DefaultMdxParser.cup +++ b/src/org/olap4j/mdx/parser/impl/DefaultMdxParser.cup @@ -155,11 +155,11 @@ parser code {: null : new AxisNode( filter.getRegion(), false, Axis.FILTER, - Collections.EMPTY_LIST, filter); + Collections.emptyList(), filter); // sort axes by ordinal Collections.sort(axisList, new Comparator() { public int compare(AxisNode o1, AxisNode o2) { - return o1.getAxis().ordinal() - o2.getAxis().ordinal(); + return o1.getAxis().axisOrdinal() - o2.getAxis().axisOrdinal(); } }); return new SelectNode( @@ -277,6 +277,8 @@ terminal CASE, CAST, CELL, + CHAPTERS, + COLUMNS, DIMENSION, ELSE, EMPTY, @@ -291,7 +293,10 @@ terminal NULL, ON, OR, + PAGES, PROPERTIES, + ROWS, + SECTIONS, SELECT, SET, THEN, @@ -360,8 +365,9 @@ non terminal IdentifierNode cube_specification, member_name, set_name; +non terminal Axis.Standard + axis_name; non terminal String - axis_name, comp_op, keyword; non terminal IdentifierNode.Segment @@ -1431,25 +1437,25 @@ set_name ::= compound_id ; // ::= [NON EMPTY] [] ON axis_specification ::= non_empty_opt:b expression:s dim_props_opt:dp ON axis_name:a {: - Axis axis = Axis.valueOf(a.toUpperCase()); ParseRegion region = createRegion( bleft, bright, sleft, sright, dpleft, dpright, aleft, aright); - RESULT = new AxisNode(region, b, axis, emptyList(dp), s); + RESULT = new AxisNode(region, b, a, emptyList(dp), s); :} | non_empty_opt:b expression:s dim_props_opt:dp ON axis_number:n {: double d = n.doubleValue(); int index = (int)d; - // Legal axis ordinals run from 0 (COLUMS) to 4 (SECTIONS), - // inclusive. - if (index < 0 || index != d || index > Axis.MAX_ORDINAL) { + // AxisOrdinal values go from -2 to 4 for standard axis, but higher + // ordinals are allowed. The negative values represent + // special cases, so are ignored. + if (index < 0 || index != d) { throw new MdxParseException( createRegion(nleft, nright), - "Invalid axis specification. The axis number must be an integer between 0 and " + - Axis.MAX_ORDINAL + ", but it was " + d + "."); + "Invalid axis specification. The axis number must be a " + + "non-negative integer, but it was " + d + "."); } - Axis axis = Axis.forOrdinal(index); + Axis axis = Axis.Factory.forOrdinal(index); ParseRegion region = createRegion( bleft, bright, sleft, sright, dpleft, dpright, nleft, nright); RESULT = new AxisNode(region, b, axis, emptyList(dp), s); @@ -1476,8 +1482,20 @@ dim_props_opt ::= // | SECTIONS // | AXIS() axis_name ::= - identifier:i {: - RESULT = i.getName(); + COLUMNS {: + RESULT = Axis.COLUMNS; + :} + | ROWS {: + RESULT = Axis.ROWS; + :} + | PAGES {: + RESULT = Axis.PAGES; + :} + | SECTIONS {: + RESULT = Axis.SECTIONS; + :} + | CHAPTERS {: + RESULT = Axis.CHAPTERS; :} ; diff --git a/src/org/olap4j/mdx/parser/impl/Scanner.java b/src/org/olap4j/mdx/parser/impl/Scanner.java index 1412461..0066b98 100644 --- a/src/org/olap4j/mdx/parser/impl/Scanner.java +++ b/src/org/olap4j/mdx/parser/impl/Scanner.java @@ -238,9 +238,9 @@ private void initReswords() { initResword(DefaultMdxParserSym.CASE ,"CASE"); initResword(DefaultMdxParserSym.CELL ,"CELL"); // initResword(DefaultMdxParserSym.CELL_ORDINAL ,"CELL_ORDINAL"); -// initResword(DefaultMdxParserSym.CHAPTERS ,"CHAPTERS"); + initResword(DefaultMdxParserSym.CHAPTERS ,"CHAPTERS"); // initResword(DefaultMdxParserSym.CHILDREN ,"CHILDREN"); -// initResword(DefaultMdxParserSym.COLUMNS ,"COLUMNS"); + initResword(DefaultMdxParserSym.COLUMNS ,"COLUMNS"); // initResword(DefaultMdxParserSym.DESC ,"DESC"); initResword(DefaultMdxParserSym.DIMENSION ,"DIMENSION"); initResword(DefaultMdxParserSym.ELSE ,"ELSE"); @@ -270,13 +270,13 @@ private void initReswords() { initResword(DefaultMdxParserSym.NULL ,"NULL"); initResword(DefaultMdxParserSym.ON ,"ON"); initResword(DefaultMdxParserSym.OR ,"OR"); -// initResword(DefaultMdxParserSym.PAGES ,"PAGES"); + initResword(DefaultMdxParserSym.PAGES ,"PAGES"); // initResword(DefaultMdxParserSym.PARENT ,"PARENT"); // initResword(DefaultMdxParserSym.PREVMEMBER ,"PREVMEMBER"); initResword(DefaultMdxParserSym.PROPERTIES ,"PROPERTIES"); // initResword(DefaultMdxParserSym.RECURSIVE ,"RECURSIVE"); -// initResword(DefaultMdxParserSym.ROWS ,"ROWS"); -// initResword(DefaultMdxParserSym.SECTIONS ,"SECTIONS"); + initResword(DefaultMdxParserSym.ROWS ,"ROWS"); + initResword(DefaultMdxParserSym.SECTIONS ,"SECTIONS"); initResword(DefaultMdxParserSym.SELECT ,"SELECT"); initResword(DefaultMdxParserSym.SET ,"SET"); // initResword(DefaultMdxParserSym.SOLVE_ORDER ,"SOLVE_ORDER"); diff --git a/testsrc/org/olap4j/ConnectionTest.java b/testsrc/org/olap4j/ConnectionTest.java index 2cd8181..a0be2b0 100644 --- a/testsrc/org/olap4j/ConnectionTest.java +++ b/testsrc/org/olap4j/ConnectionTest.java @@ -707,7 +707,7 @@ private void checkCellSetMetaData( : cellSetMetaData.getAxesMetaData()) { ++k; - assertEquals(Axis.forOrdinal(k), axisMetaData.getAxisOrdinal()); + assertEquals(Axis.Factory.forOrdinal(k), axisMetaData.getAxisOrdinal()); assertEquals(k, axisMetaData.getAxisOrdinal().axisOrdinal()); assertTrue(axisMetaData.getHierarchies().size() > 0); for (Hierarchy hierarchy : axisMetaData.getHierarchies()) { diff --git a/testsrc/org/olap4j/OlapTest.java b/testsrc/org/olap4j/OlapTest.java index 4fc0d64..16da5ac 100644 --- a/testsrc/org/olap4j/OlapTest.java +++ b/testsrc/org/olap4j/OlapTest.java @@ -672,7 +672,7 @@ public static void axisToXml( Element dimensionsNode = doc.createElement("dimensions"); root.appendChild(dimensionsNode); - switch (axis.getLocation()) { + switch ((Axis.Standard) axis.getLocation()) { case COLUMNS: addAttribute("location", "across", root); break; diff --git a/testsrc/org/olap4j/test/ParserTest.java b/testsrc/org/olap4j/test/ParserTest.java index a75e78c..46bead9 100644 --- a/testsrc/org/olap4j/test/ParserTest.java +++ b/testsrc/org/olap4j/test/ParserTest.java @@ -138,7 +138,11 @@ public void testNegativeCases() throws Exception { assertParseQueryFails( "select [member] on ^axis(1.7)^ from sales", - "(?s).*The axis number must be an integer.*"); + "(?s).*The axis number must be a non-negative integer, but it was 1.7."); + + assertParseQueryFails( + "select [member] on ^foobar^ from sales", + "Syntax error at \\[1:20, 1:25\\], token 'foobar'"); assertParseQueryFails( "select [member] on axis(-^ ^1) from sales", @@ -148,21 +152,28 @@ public void testNegativeCases() throws Exception { "select [member] on axis(-^1^) from sales", "Syntax error at \\[1:26\\], token '-'"); - assertParseQueryFails( - "select [member] on ^axis(5)^ from sales", - "Invalid axis specification\\. The axis number must be an integer between 0 and 4, but it was 5\\.0\\."); + // used to be an error, but no longer + assertParseQuery( + "select [member] on axis(5) from sales", + TestContext.fold( + "SELECT\n" + + "[member] ON AXIS(5)\n" + + "FROM sales")); assertParseQueryFails( - "select [member] on axes(^0^) from sales", - "Syntax error at \\[1:25\\], token '\\('"); + "select [member] on ^axes^(0) from sales", + "Syntax error at \\[1:20, 1:23\\], token 'axes'"); assertParseQueryFails( "select [member] on ^0.5^ from sales", - "Invalid axis specification\\. The axis number must be an integer between 0 and 4, but it was 0\\.5\\."); + "Invalid axis specification\\. The axis number must be a non-negative integer, but it was 0\\.5\\."); - assertParseQueryFails( - "select [member] on ^555^ from sales", - "Invalid axis specification\\. The axis number must be an integer between 0 and 4, but it was 555\\.0\\."); + assertParseQuery( + "select [member] on 555 from sales", + TestContext.fold( + "SELECT\n" + + "[member] ON AXIS(555)\n" + + "FROM sales")); } public void testScannerPunc() { @@ -227,7 +238,7 @@ private void checkFails(MdxParser p, String query, String expected) { final ParseRegion.RegionAndSource ras = ParseRegion.findPos(query); try { SelectNode selectNode = p.parseSelect(ras.source); - fail("Must return an error"); + fail("Must return an error, got " + selectNode); } catch (Exception e) { checkEx(e, expected, ras); } @@ -403,10 +414,14 @@ public void testMultipleAxes() throws Exception { List axes = select.getAxisList(); assertEquals("Number of axes", 2, axes.size()); - assertEquals("Axis index name must be correct", - Axis.forOrdinal(0), axes.get(0).getAxis()); - assertEquals("Axis index name must be correct", - Axis.forOrdinal(1), axes.get(1).getAxis()); + assertEquals( + "Axis index name must be correct", + Axis.Factory.forOrdinal(0), + axes.get(0).getAxis()); + assertEquals( + "Axis index name must be correct", + Axis.Factory.forOrdinal(1), + axes.get(1).getAxis()); // now a similar query with axes reversed @@ -417,10 +432,14 @@ public void testMultipleAxes() throws Exception { axes = select.getAxisList(); assertEquals("Number of axes", 2, axes.size()); - assertEquals("Axis index name must be correct", - Axis.forOrdinal(0), axes.get(0).getAxis()); - assertEquals("Axis index name must be correct", - Axis.forOrdinal(1), axes.get(1).getAxis()); + assertEquals( + "Axis index name must be correct", + Axis.Factory.forOrdinal(0), + axes.get(0).getAxis()); + assertEquals( + "Axis index name must be correct", + Axis.Factory.forOrdinal(1), + axes.get(1).getAxis()); ParseTreeNode colsSetExpr = axes.get(0).getExpression(); assertNotNull("Column tuples", colsSetExpr);