diff --git a/src/org/olap4j/impl/IdentifierParser.java b/src/org/olap4j/impl/IdentifierParser.java new file mode 100644 index 0000000..b178b01 --- /dev/null +++ b/src/org/olap4j/impl/IdentifierParser.java @@ -0,0 +1,477 @@ +/* +// 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) 2010-2010 Julian Hyde +// All Rights Reserved. +// You must accept the terms of that agreement to use this software. +*/ +package org.olap4j.impl; + +import org.olap4j.mdx.IdentifierNode; +import org.olap4j.mdx.ParseRegion; + +import java.util.*; + +/** + * Utilities for parsing fully-qualified member names, tuples, member lists, + * and tuple lists. + * + *

NOTE: Like other classes in the org.olap4j.impl package, this class + * is not part of the public olap4j API. It is subject to change or removal + * without notice. It is provided in the hope that it will be useful to + * implementors of olap4j drivers. + * + * @version $Id: $ + * @author jhyde + */ +public class IdentifierParser { + // lexical states + private static final int START = 0; + private static final int BEFORE_SEG = 1; + private static final int IN_BRACKET_SEG = 2; + private static final int AFTER_SEG = 3; + private static final int IN_SEG = 4; + + private static char charAt(String s, int pos) { + return pos < s.length() ? s.charAt(pos) : 0; + } + + /** + * Parses a list of tuples (or a list of members). + * + * @param builder Builder, called back when each segment, member and tuple + * is complete. + * @param string String to parse + */ + public static void parseTupleList( + Builder builder, + String string) + { + int i = 0; + char c; + while ((c = charAt(string, i++)) == ' ') { + } + if (c != '{') { + throw fail(string, i, "{"); + } + while (true) { + i = parseTuple(builder, string, i); + while ((c = charAt(string, i++)) == ' ') { + } + if (c == ',') { + // fine + } else if (c == '}') { + // we're done + return; + } else { + throw fail(string, i, ", or }"); + } + } + } + + /** + * Parses a tuple, of the form '(member, member, ...)', and calls builder + * methods when finding a segment, member or tuple. + * + * @param builder Builder + * @param string String to parse + * @param i Position to start parsing in string + * @return Position where parsing ended in string + */ + public static int parseTuple( + Builder builder, + String string, + int i) + { + char c; + while ((c = charAt(string, i++)) == ' ') { + } + if (c != '(') { + throw fail(string, i, "("); + } + while (true) { + i = parseMember(builder, string, i); + while ((c = charAt(string, i++)) == ' ') { + } + if (c == ',') { + // fine + } else if (c == ')') { + builder.tupleComplete(); + break; + } + } + return i; + } + + public static void parseMemberList( + Builder builder, + String string) + { + int i = 0; + char c = charAt(string, i); + while (c > 0 && c <= ' ') { + c = charAt(string, ++i); + } + boolean leadingBrace = false; + boolean trailingBrace = false; + if (c == '{') { + leadingBrace = true; + ++i; + } + w: + while (true) { + i = parseMember(builder, string, i); + c = charAt(string, i); + while (c > 0 && c <= ' ') { + c = charAt(string, ++i); + } + switch (c) { + case 0: + break w; + case ',': + // fine + ++i; + break; + case '}': + // we're done + trailingBrace = true; + break w; + default: + throw fail(string, i, ", or }"); + } + } + if (leadingBrace != trailingBrace) { + throw new IllegalArgumentException( + "mismatched '{' and '}' in '" + string + "'"); + } + } + + public static int parseMember( + Builder builder, + String string, + int i) + { + int k = string.length(); + int state = START; + int start = 0; + Builder.Syntax syntax = Builder.Syntax.NAME; + char c; + + loop: + while (i < k + 1) { + c = charAt(string, i); + switch (state) { + case START: + case BEFORE_SEG: + switch (c) { + case '[': + ++i; + start = i; + state = IN_BRACKET_SEG; + break; + + case ' ': + // Skip whitespace, don't change state. + ++i; + break; + + case ',': + case '}': + case 0: + break loop; + + case '.': + // TODO: test this, case: ".abc" + throw new IllegalArgumentException("unexpected: '.'"); + + case '&': + // Note that we have seen ampersand, but don't change state. + ++i; + if (syntax != Builder.Syntax.NAME) { + throw new IllegalArgumentException("unexpected: '&'"); + } + syntax = Builder.Syntax.FIRST_KEY; + break; + + default: + // Carry on reading. + state = IN_SEG; + start = i; + break; + } + break; + + case IN_SEG: + switch (c) { + case ',': + case ')': + case '}': + case 0: + builder.segmentComplete( + null, + string.substring(start, i).trim(), + IdentifierNode.Quoting.UNQUOTED, + syntax); + state = AFTER_SEG; + break loop; + case '.': + builder.segmentComplete( + null, + string.substring(start, i).trim(), + IdentifierNode.Quoting.UNQUOTED, + syntax); + syntax = Builder.Syntax.NAME; + state = BEFORE_SEG; + ++i; + break; + case '&': + builder.segmentComplete( + null, + string.substring(start, i).trim(), + IdentifierNode.Quoting.UNQUOTED, + syntax); + syntax = Builder.Syntax.NEXT_KEY; + state = BEFORE_SEG; + ++i; + break; + default: + ++i; + } + break; + + case IN_BRACKET_SEG: + switch (c) { + case 0: + throw new IllegalArgumentException( + "Expected ']', in member identifier '" + string + "'"); + case ']': + if (charAt(string, i + 1) == ']') { + ++i; + // fall through + } else { + builder.segmentComplete( + null, + Olap4jUtil.replace( + string.substring(start, i), "]]", "]"), + IdentifierNode.Quoting.QUOTED, + syntax); + ++i; + state = AFTER_SEG; + break; + } + default: + // Carry on reading. + ++i; + } + break; + + case AFTER_SEG: + switch (c) { + case ' ': + // Skip over any spaces + // TODO: test this case: '[foo] . [bar]' + ++i; + break; + case '.': + state = BEFORE_SEG; + syntax = Builder.Syntax.NAME; + ++i; + break; + case '&': + state = BEFORE_SEG; + if (syntax == Builder.Syntax.FIRST_KEY) { + syntax = Builder.Syntax.NEXT_KEY; + } else { + syntax = Builder.Syntax.FIRST_KEY; + } + ++i; + break; + default: + // We're not looking at the start of a segment. Parse + // the member we've seen so far, then return. + break loop; + } + break; + + default: + throw new AssertionError("unexpected state: " + state); + } + } + + switch (state) { + case START: + return i; + case BEFORE_SEG: + throw new IllegalArgumentException( + "Expected identifier after '.', in member identifier '" + string + + "'"); + case IN_BRACKET_SEG: + throw new IllegalArgumentException( + "Expected ']', in member identifier '" + string + "'"); + } + // End of member. + builder.memberComplete(); + return i; + } + + private static IllegalArgumentException fail( + String string, + int i, + String expecting) + { + throw new IllegalArgumentException( + "expected '" + expecting + "' at position " + i + " in '" + + string + "'"); + } + + /** + * Parses an MDX identifier such as [Foo].[Bar].Baz.&Key&Key2 + * and returns the result as a list of segments. + * + * @param s MDX identifier + * @return List of segments + */ + public static List parseIdentifier(String s) { + final MemberBuilder builder = new MemberBuilder(); + int i = parseMember(builder, s, 0); + if (i < s.length()) { + throw new IllegalArgumentException( + "Invalid member identifier '" + s + "'"); + } + return builder.segmentList; + } + + /** + * Parses a string consisting of a sequence of MDX identifiers and returns + * the result as a list of compound identifiers, each of which is a list + * of segments. + * + *

For example, parseIdentifierList("{foo.bar, baz}") returns + * { {"foo", "bar"}, {"baz"} }. + * + *

The braces around the list are optional; + * parseIdentifierList("foo.bar, baz") returns the same result as the + * previous example. + * + * @param s MDX identifier list + * @return List of lists of segments + */ + public static List> parseIdentifierList( + String s) + { + final MemberListBuilder builder = new MemberListBuilder(); + parseMemberList(builder, s); + return builder.list; + } + + /** + * Callback that is called on completion of a structural element like a + * member or tuple. + * + *

Implementations might create a list of members or just create a list + * of unresolved names. + */ + public interface Builder { + /** + * Called when a tuple is complete. + */ + void tupleComplete(); + + /** + * Called when a member is complete. + */ + void memberComplete(); + + /** + * Called when a segment is complete. + * + *

For example, the identifier {@code [Time].1997.[Jan].&31} contains + * four name segments: "[Time]", "1997", "[Jan]" and "31". The first + * and third are quoted; the last has an ampersand signifying that it + * is a key. + * + * @param region Region of source code + * @param name Name + * @param quoting Quoting style + * @param syntax Whether this is a name segment, first part of a key + * segment, or contiuation of a key segment + */ + void segmentComplete( + ParseRegion region, + String name, + IdentifierNode.Quoting quoting, + Syntax syntax); + + enum Syntax { + NAME, + FIRST_KEY, + NEXT_KEY + } + } + + /** + * Implementation of {@link org.olap4j.impl.IdentifierParser.Builder} + * that collects the segments that make up the name of a memberin a list. + * It cannot handle tuples or lists of members. + */ + private static class MemberBuilder implements Builder { + final List subSegments; + final List segmentList; + + public MemberBuilder() { + segmentList = new ArrayList(); + subSegments = new ArrayList(); + } + + public void tupleComplete() { + throw new UnsupportedOperationException(); + } + + public void memberComplete() { + flushSubSegments(); + } + + private void flushSubSegments() { + if (!subSegments.isEmpty()) { + segmentList.add(new IdentifierNode.KeySegment(subSegments)); + subSegments.clear(); + } + } + + public void segmentComplete( + ParseRegion region, + String name, + IdentifierNode.Quoting quoting, + Syntax syntax) + { + final IdentifierNode.NameSegment segment = + new IdentifierNode.NameSegment( + region, name, quoting); + if (syntax != Syntax.NEXT_KEY) { + // If we were building a previous key, write it out. + // E.g. [Foo].&1&2.&3&4&5. + flushSubSegments(); + } + if (syntax == Syntax.NAME) { + segmentList.add(segment); + } else { + subSegments.add(segment); + } + } + } + + private static class MemberListBuilder extends MemberBuilder { + final List> list = + new ArrayList>(); + + public void memberComplete() { + super.memberComplete(); + list.add( + new ArrayList(segmentList)); + segmentList.clear(); + } + } +} + +// End IdentifierParser.java diff --git a/src/org/olap4j/mdx/IdentifierNode.java b/src/org/olap4j/mdx/IdentifierNode.java index ba6c51b..d337212 100644 --- a/src/org/olap4j/mdx/IdentifierNode.java +++ b/src/org/olap4j/mdx/IdentifierNode.java @@ -15,8 +15,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -import org.olap4j.impl.Olap4jUtil; -import org.olap4j.impl.UnmodifiableArrayList; +import org.olap4j.impl.*; import org.olap4j.type.Type; /** @@ -199,7 +198,7 @@ public IdentifierNode deepCopy() { * Segment("1245", KEY) } * * - * @see org.olap4j.metadata.Cube#lookupMember(String[]) + * @see org.olap4j.metadata.Cube#lookupMember(String...) * * @param identifier MDX identifier string * @@ -209,45 +208,9 @@ public IdentifierNode deepCopy() { * invalid */ public static List parseIdentifier(String identifier) { - String[] nodes = identifier.split("\\."); - List list = new ArrayList(); - for (String node : nodes) { - list.add(parseSegment(node)); - } - return list; - } - - /** - * Parses a MDX Identifier Node into a Segment - * @param node MDX Identifier Node - * @return Segment of Identifier Node - */ - private static Segment parseSegment(String node) { - Pattern patternQuoted = Pattern.compile("^\\[(.*)\\]$"); - // This pattern identifies a Key node - // For Example: "&CA" or "&[CA]" - Pattern patternKey = Pattern.compile("^&[\\[]{0,1}(.*)[\\]]{0,1}"); - Matcher quotedMatcher = patternQuoted.matcher(node); - Matcher keyMatcher = patternKey.matcher(node); - - Quoting type = Quoting.QUOTED; - String value; - - if (quotedMatcher.matches()) { - type = Quoting.QUOTED; - value = quotedMatcher.group(1); - } else if (keyMatcher.matches()) { - type = Quoting.KEY; - value = keyMatcher.group(1); - } else { - type = Quoting.UNQUOTED; - value = node; - } - - return new NameSegment(null, value, type); + return IdentifierParser.parseIdentifier(identifier); } - /** * Returns string quoted in [...]. * diff --git a/testsrc/org/olap4j/impl/Olap4jUtilTest.java b/testsrc/org/olap4j/impl/Olap4jUtilTest.java index 4079bf8..ed17d60 100644 --- a/testsrc/org/olap4j/impl/Olap4jUtilTest.java +++ b/testsrc/org/olap4j/impl/Olap4jUtilTest.java @@ -9,6 +9,8 @@ package org.olap4j.impl; import junit.framework.TestCase; +import org.olap4j.mdx.IdentifierNode; +import org.olap4j.mdx.ParseRegion; import java.util.*; @@ -340,6 +342,309 @@ private void checkParseFormattedCellValue( assertEquals("cell value", expectedCellValue, cellValue); assertEquals("properties", expectedProperties, map.toString()); } + + /** + * Tests the {@link IdentifierNode#parseIdentifier} method. + */ + public void testParseIdentifier() { + List segments = + IdentifierNode.parseIdentifier( + "[string].[with].[a [bracket]] in it]"); + assertEquals(3, segments.size()); + assertEquals("a [bracket] in it", segments.get(2).getName()); + + segments = + IdentifierNode.parseIdentifier( + "[Worklog].[All].[calendar-[LANGUAGE]].js]"); + assertEquals(3, segments.size()); + assertEquals("calendar-[LANGUAGE].js", segments.get(2).getName()); + + // allow spaces before, after and between + segments = IdentifierNode.parseIdentifier(" [foo] . [bar].[baz] "); + assertEquals(3, segments.size()); + assertEquals("foo", segments.get(0).getName()); + + // first segment not quoted + segments = IdentifierNode.parseIdentifier("Time.1997.[Q3]"); + assertEquals(3, segments.size()); + assertEquals("Time", segments.get(0).getName()); + assertEquals("1997", segments.get(1).getName()); + assertEquals("Q3", segments.get(2).getName()); + + // spaces ignored after unquoted segment + segments = IdentifierNode.parseIdentifier("[Time . Weekly ] . 1997 . [Q3]"); + assertEquals(3, segments.size()); + assertEquals("Time . Weekly ", segments.get(0).getName()); + assertEquals("1997", segments.get(1).getName()); + assertEquals("Q3", segments.get(2).getName()); + + // identifier ending in '.' is invalid + try { + segments = IdentifierNode.parseIdentifier("[foo].[bar]."); + fail("expected exception, got " + segments); + } catch (IllegalArgumentException e) { + assertEquals( + "Expected identifier after '.', in member identifier " + + "'[foo].[bar].'", + e.getMessage()); + } + + try { + segments = IdentifierNode.parseIdentifier("[foo].[bar"); + fail("expected exception, got " + segments); + } catch (IllegalArgumentException e) { + assertEquals( + "Expected ']', in member identifier '[foo].[bar'", + e.getMessage()); + } + + try { + segments = IdentifierNode.parseIdentifier("[Foo].[Bar], [Baz]"); + fail("expected exception, got " + segments); + } catch (IllegalArgumentException e) { + assertEquals( + "Invalid member identifier '[Foo].[Bar], [Baz]'", + e.getMessage()); + } + + // test case for bug 3036629, "Patch 328 breaks test". + segments = IdentifierNode.parseIdentifier( + "[ProductFilterDim].[Product Main Group Name].&[Maingroup (xyz)]"); + assertEquals(3, segments.size()); + final IdentifierNode.Segment s0 = segments.get(0); + assertEquals("ProductFilterDim", s0.getName()); + assertEquals(IdentifierNode.Quoting.QUOTED, s0.getQuoting()); + final IdentifierNode.Segment s1 = segments.get(1); + assertEquals("Product Main Group Name", s1.getName()); + assertEquals(IdentifierNode.Quoting.QUOTED, s1.getQuoting()); + assertTrue(segments.get(2) instanceof IdentifierNode.KeySegment); + IdentifierNode.KeySegment s2 = + (IdentifierNode.KeySegment) segments.get(2); + assertEquals(1, s2.getKeyParts().size()); + final IdentifierNode.NameSegment s2k0 = s2.getKeyParts().get(0); + assertEquals("Maingroup (xyz)", s2k0.getName()); + assertEquals(IdentifierNode.Quoting.QUOTED, s2k0.getQuoting()); + } + + /** + * Advanced test for the {@link IdentifierNode#parseIdentifier} method. + */ + public void testParseIdentifierAdvanced() { + List segments; + + // detailed example, per javadoc + // + // A more complex example illustrates a compound key. The identifier + // [Customers].[City].&[San Francisco]&CA&USA.&[cust1234] + // contains four segments as follows: + // + // * Segment #0 is QUOTED, name "Customers" + // * Segment #1 is QUOTED, name "City" + // * Segment #2 is a KEY. It has 3 sub-segments: + // ** Sub-segment #0 is QUOTED, name "San Francisco" + // ** Sub-segment #1 is UNQUOTED, name "CA" + // ** Sub-segment #2 is UNQUOTED, name "USA" + // * Segment #3 is a KEY. It has 1 sub-segment: + // ** Sub-segment #0 is QUOTED, name "cust1234" + segments = IdentifierNode.parseIdentifier( + "[Customers].[City].&[San Francisco]&CA&USA.&[cust1234]"); + assertEquals(4, segments.size()); + final IdentifierNode.Segment s0 = segments.get(0); + assertEquals("Customers", s0.getName()); + assertEquals(IdentifierNode.Quoting.QUOTED, s0.getQuoting()); + final IdentifierNode.Segment s1 = segments.get(1); + assertEquals("City", s1.getName()); + assertEquals(IdentifierNode.Quoting.QUOTED, s1.getQuoting()); + assertTrue(segments.get(2) instanceof IdentifierNode.KeySegment); + IdentifierNode.KeySegment s2 = + (IdentifierNode.KeySegment) segments.get(2); + assertEquals(3, s2.getKeyParts().size()); + final IdentifierNode.NameSegment s2k0 = s2.getKeyParts().get(0); + assertEquals("San Francisco", s2k0.getName()); + assertEquals( IdentifierNode.Quoting.QUOTED, s2k0.getQuoting()); + final IdentifierNode.NameSegment s2k1 = s2.getKeyParts().get(1); + assertEquals("CA", s2k1.getName()); + assertEquals( IdentifierNode.Quoting.QUOTED, s2k0.getQuoting()); + final IdentifierNode.NameSegment s2k2 = s2.getKeyParts().get(0); + assertEquals("San Francisco", s2k2.getName()); + assertEquals( IdentifierNode.Quoting.QUOTED, s2k2.getQuoting()); + IdentifierNode.KeySegment s3 = + (IdentifierNode.KeySegment) segments.get(3); + assertNull(s3.getName()); + assertEquals(1, s3.getKeyParts().size()); + final IdentifierNode.NameSegment s3k0 = s3.getKeyParts().get(0); + assertEquals("cust1234", s3k0.getName()); + assertEquals( IdentifierNode.Quoting.QUOTED, s3k0.getQuoting()); + } + + /** + * Tests the {@link IdentifierParser#parseIdentifierList(String)} method. + */ + public void testParseIdentifierList() { + List> list; + + list = IdentifierParser.parseIdentifierList("{foo, baz.baz}"); + assertEquals(2, list.size()); + assertEquals(1, list.get(0).size()); + assertEquals(2, list.get(1).size()); + + // now without braces + list = IdentifierParser.parseIdentifierList("foo, baz.baz"); + assertEquals(2, list.size()); + + // now with spaces + list = IdentifierParser.parseIdentifierList(" { foo , baz.baz } "); + assertEquals(2, list.size()); + + // now with spaces & without braces + list = IdentifierParser.parseIdentifierList(" { foo , baz.baz } "); + assertEquals(2, list.size()); + + // now with keys + list = IdentifierParser.parseIdentifierList( + "{foo , baz.&k0&k1.&m0 . boo}"); + assertEquals(2, list.size()); + assertEquals(1, list.get(0).size()); + assertEquals(4, list.get(1).size()); + assertEquals("baz", list.get(1).get(0).getName()); + final IdentifierNode.Segment id1s1 = list.get(1).get(1); + assertEquals(2, id1s1.getKeyParts().size()); + assertEquals("k0", id1s1.getKeyParts().get(0).getName()); + assertEquals("k1", id1s1.getKeyParts().get(1).getName()); + final IdentifierNode.Segment id1s2 = list.get(1).get(2); + assertEquals(1, id1s2.getKeyParts().size()); + assertEquals("m0", id1s2.getKeyParts().get(0).getName()); + assertEquals("boo", list.get(1).get(3).getName()); + assertEquals("[baz, &k0&k1, &m0, boo]", list.get(1).toString()); + + // now with mismatched braces + try { + list = IdentifierParser.parseIdentifierList(" { foo , baz.baz "); + fail("expected error, got " + list); + } catch (RuntimeException e) { + assertEquals( + "mismatched '{' and '}' in ' { foo , baz.baz '", + e.getMessage()); + } + + // now with mismatched braces + try { + list = IdentifierParser.parseIdentifierList(" foo , baz.baz } "); + fail("expected error, got " + list); + } catch (RuntimeException e) { + assertEquals( + "mismatched '{' and '}' in ' foo , baz.baz } '", + e.getMessage()); + } + + // empty string yields empty list + list = IdentifierParser.parseIdentifierList("{}"); + assertEquals(0, list.size()); + list = IdentifierParser.parseIdentifierList(" { } "); + assertEquals(0, list.size()); + list = IdentifierParser.parseIdentifierList(""); + assertEquals(0, list.size()); + list = IdentifierParser.parseIdentifierList(" \t\n"); + assertEquals(0, list.size()); + } + + public void testParseTupleList() { + final StringBuilder buf = new StringBuilder(); + final IdentifierParser.Builder builder = + new IdentifierParser.Builder() { + public void tupleComplete() { + buf.append(""); + } + + public void memberComplete() { + buf.append(""); + } + + public void segmentComplete( + ParseRegion region, + String name, + IdentifierNode.Quoting quoting, + Syntax syntax) + { + if (quoting == IdentifierNode.Quoting.QUOTED) { + buf.append("[").append(name).append("]"); + } else { + buf.append(name); + } + buf.append("<").append(syntax).append(">"); + } + }; + + // Set of tuples. + buf.setLength(0); + IdentifierParser.parseTupleList( + builder, "{([Foo]), ([Bar].[Baz].&k0&[k1].&[k2])}"); + assertEquals( + "[Foo]" + + "[Bar][Baz]" + + "k0[k1][k2]" + + "", + buf.toString()); + + // Set of members. + buf.setLength(0); + try { + IdentifierParser.parseTupleList(builder, "{[Foo], [Bar].[Baz]}"); + fail("expected error"); + } catch (IllegalArgumentException e) { + assertEquals( + "expected '(' at position 2 in '{[Foo], [Bar].[Baz]}'", + e.getMessage()); + } + + // Empty set. + // TODO: this shouldn't fail + buf.setLength(0); + try { + IdentifierParser.parseTupleList(builder, "{ }"); + fail("expected error"); + } catch (IllegalArgumentException e) { + assertEquals( + "expected '(' at position 3 in '{ }'", + e.getMessage()); + } + + // Empty set (no brackets). + // TODO: this shouldn't fail + buf.setLength(0); + try { + IdentifierParser.parseTupleList(builder, ""); + fail("expected error"); + } catch (IllegalArgumentException e) { + assertEquals( + "expected '{' at position 1 in ''", + e.getMessage()); + } + + // Set of mixed tuples & members. + // TODO: this shouldn't fail + buf.setLength(0); + try { + IdentifierParser.parseTupleList( + builder, "{([A], [Tuple]), [A].Member}"); + } catch (IllegalArgumentException e) { + assertEquals( + "expected '(' at position 18 in '{([A], [Tuple]), [A].Member}'", + e.getMessage()); + } + + // Same, but no braces. + // TODO: this shouldn't fail + buf.setLength(0); + try { + IdentifierParser.parseTupleList( + builder, "([A], [Tuple]), [A].Member"); + } catch (IllegalArgumentException e) { + assertEquals( + "expected '{' at position 1 in '([A], [Tuple]), [A].Member'", + e.getMessage()); + } + } } // End Olap4jUtilTest.java diff --git a/testsrc/org/olap4j/mdx/MdxTest.java b/testsrc/org/olap4j/mdx/MdxTest.java index 4513348..8dc4215 100644 --- a/testsrc/org/olap4j/mdx/MdxTest.java +++ b/testsrc/org/olap4j/mdx/MdxTest.java @@ -80,6 +80,9 @@ public void testParseIdentifier() { assertEquals( "a [bracket] in it", segments.get(2).getName()); + assertEquals( + IdentifierNode.Quoting.QUOTED, + segments.get(2).getQuoting()); segments = IdentifierNode.parseIdentifier( "[Worklog].[All].[calendar-[LANGUAGE]].js]"); @@ -88,21 +91,21 @@ public void testParseIdentifier() { "calendar-[LANGUAGE].js", segments.get(2).getName()); - try { - segments = IdentifierNode.parseIdentifier("[foo].bar"); - fail("expected exception, got " + segments); - } catch (IllegalArgumentException e) { - assertEquals( - "Invalid member identifier '[foo].bar'", - e.getMessage()); - } + segments = IdentifierNode.parseIdentifier("[foo].bar"); + assertEquals(2, segments.size()); + assertEquals( + IdentifierNode.Quoting.QUOTED, + segments.get(0).getQuoting()); + assertEquals( + IdentifierNode.Quoting.UNQUOTED, + segments.get(1).getQuoting()); try { segments = IdentifierNode.parseIdentifier("[foo].[bar"); fail("expected exception, got " + segments); - } catch (IllegalArgumentException e) { + } catch (RuntimeException e) { assertEquals( - "Invalid member identifier '[foo].[bar'", + "Expected ']', in member identifier '[foo].[bar'", e.getMessage()); } }