001 /* 002 // $Id: IdentifierNode.java 253 2009-06-30 03:06:10Z jhyde $ 003 // This software is subject to the terms of the Eclipse Public License v1.0 004 // Agreement, available at the following URL: 005 // http://www.eclipse.org/legal/epl-v10.html. 006 // Copyright (C) 2007-2009 Julian Hyde 007 // All Rights Reserved. 008 // You must accept the terms of that agreement to use this software. 009 */ 010 package org.olap4j.mdx; 011 012 import org.olap4j.impl.Olap4jUtil; 013 import org.olap4j.impl.UnmodifiableArrayList; 014 import org.olap4j.type.Type; 015 016 import java.util.*; 017 018 /** 019 * Multi-part identifier. 020 * 021 * <p>An identifier is immutable. 022 * 023 * <p>An identifer consists of one or more {@link Segment}s. A segment is 024 * either:<ul> 025 * <li>An unquoted value such as '{@code CA}', 026 * <li>A value quoted in brackets, such as '{@code [San Francisco]}', or 027 * <li>A key of one or more parts, each of which is prefixed with '&', 028 * such as '{@code &[Key 1]&Key2&[5]}'. 029 * </ul> 030 * 031 * <p>Segment types are indicated by the {@link Quoting} enumeration. 032 * 033 * <p>A key segment is of type {@link Quoting#KEY}, and has one or more 034 * component parts accessed via the 035 * {@link Segment#getKeyParts()} method. The parts 036 * are of type {@link Quoting#UNQUOTED} or {@link Quoting#QUOTED}. 037 * 038 * <p>A simple example is the identifier {@code Measures.[Unit Sales]}. It 039 * has two segments:<ul> 040 * <li>Segment #0 is 041 * {@link org.olap4j.mdx.IdentifierNode.Quoting#UNQUOTED UNQUOTED}, 042 * name "Measures"</li> 043 * <li>Segment #1 is 044 * {@link org.olap4j.mdx.IdentifierNode.Quoting#QUOTED QUOTED}, 045 * name "Unit Sales"</li> 046 * </ul> 047 * 048 * <p>A more complex example illustrates a compound key. The identifier {@code 049 * [Customers].[City].&[San Francisco]&CA&USA.&[cust1234]} 050 * contains four segments as follows: 051 * <ul> 052 * <li>Segment #0 is QUOTED, name "Customers"</li> 053 * <li>Segment #1 is QUOTED, name "City"</li> 054 * <li>Segment #2 is a {@link org.olap4j.mdx.IdentifierNode.Quoting#KEY KEY}. 055 * It has 3 sub-segments: 056 * <ul> 057 * <li>Sub-segment #0 is QUOTED, name "San Francisco"</li> 058 * <li>Sub-segment #1 is UNQUOTED, name "CA"</li> 059 * <li>Sub-segment #2 is UNQUOTED, name "USA"</li> 060 * </ul> 061 * </li> 062 * <li>Segment #3 is a KEY. It has 1 sub-segment: 063 * <ul> 064 * <li>Sub-segment #0 is QUOTED, name "cust1234"</li> 065 * </ul> 066 * </li> 067 * </ul> 068 * 069 * @version $Id: IdentifierNode.java 253 2009-06-30 03:06:10Z jhyde $ 070 * @author jhyde 071 */ 072 public class IdentifierNode 073 implements ParseTreeNode 074 { 075 private final List<Segment> segments; 076 077 /** 078 * Creates an identifier containing one or more segments. 079 * 080 * @param segments Array of Segments, each consisting of a name and quoting 081 * style 082 */ 083 public IdentifierNode(IdentifierNode.Segment... segments) { 084 if (segments.length < 1) { 085 throw new IllegalArgumentException(); 086 } 087 this.segments = UnmodifiableArrayList.asCopyOf(segments); 088 } 089 090 /** 091 * Creates an identifier containing a list of segments. 092 * 093 * @param segments List of segments 094 */ 095 public IdentifierNode(List<IdentifierNode.Segment> segments) { 096 if (segments.size() < 1) { 097 throw new IllegalArgumentException(); 098 } 099 this.segments = 100 new UnmodifiableArrayList<Segment>( 101 segments.toArray( 102 new Segment[segments.size()])); 103 } 104 105 public Type getType() { 106 // Can't give the type until we have resolved. 107 throw new UnsupportedOperationException(); 108 } 109 110 /** 111 * Returns the list of segments which consistitute this identifier. 112 * 113 * @return list of constituent segments 114 */ 115 public List<Segment> getSegmentList() { 116 return segments; 117 } 118 119 public ParseRegion getRegion() { 120 // Region is the span from the first segment to the last. 121 return sumSegmentRegions(segments); 122 } 123 124 /** 125 * Returns a region encompassing the regions of the first through the last 126 * of a list of segments. 127 * 128 * @param segments List of segments 129 * @return Region encompassed by list of segments 130 */ 131 private static ParseRegion sumSegmentRegions( 132 final List<? extends Segment> segments) 133 { 134 return ParseRegion.sum( 135 new AbstractList<ParseRegion>() { 136 public ParseRegion get(int index) { 137 return segments.get(index).getRegion(); 138 } 139 140 public int size() { 141 return segments.size(); 142 } 143 }); 144 } 145 146 /** 147 * Returns a new Identifier consisting of this one with another segment 148 * appended. Does not modify this Identifier. 149 * 150 * @param segment Name of segment 151 * @return New identifier 152 */ 153 public IdentifierNode append(IdentifierNode.Segment segment) { 154 List<IdentifierNode.Segment> newSegments = 155 new ArrayList<Segment>(segments); 156 newSegments.add(segment); 157 return new IdentifierNode(newSegments); 158 } 159 160 public <T> T accept(ParseTreeVisitor<T> visitor) { 161 return visitor.visit(this); 162 } 163 164 public void unparse(ParseTreeWriter writer) { 165 writer.getPrintWriter().print(toString()); 166 } 167 168 public String toString() { 169 return unparseIdentifierList(segments); 170 } 171 172 public IdentifierNode deepCopy() { 173 // IdentifierNode is immutable 174 return this; 175 } 176 177 /** 178 * Parses an MDX identifier into a list of segments. 179 * 180 * <p>Each segment is a name combined with a description of how the name 181 * was {@link Quoting quoted}. For example, 182 * 183 * <blockquote><code> 184 * parseIdentifier( 185 * "[Customers].USA.[South Dakota].[Sioux Falls].&[1245]") 186 * </code></blockquote> 187 * 188 * returns 189 * 190 * <blockquote><code> 191 * { Segment("Customers", QUOTED), 192 * Segment("USA", UNQUOTED), 193 * Segment("South Dakota", QUOTED), 194 * Segment("Sioux Falls", QUOTED), 195 * Segment("1245", KEY) } 196 * </code></blockquote> 197 * 198 * @see org.olap4j.metadata.Cube#lookupMember(String[]) 199 * 200 * @param identifier MDX identifier string 201 * 202 * @return List of name segments 203 * 204 * @throws IllegalArgumentException if the format of the identifier is 205 * invalid 206 */ 207 public static List<Segment> parseIdentifier(String identifier) { 208 if (!identifier.startsWith("[")) { 209 return Collections.<Segment>singletonList( 210 new NameSegment(null, identifier, Quoting.UNQUOTED)); 211 } 212 213 List<Segment> list = new ArrayList<Segment>(); 214 int i = 0; 215 Quoting type; 216 while (i < identifier.length()) { 217 if (identifier.charAt(i) != '&' && identifier.charAt(i) != '[') { 218 throw new IllegalArgumentException( 219 "Invalid member identifier '" + identifier + "'"); 220 } 221 222 if (identifier.charAt(i) == '&') { 223 i++; 224 type = Quoting.KEY; 225 } else { 226 type = Quoting.QUOTED; 227 } 228 229 if (identifier.charAt(i) != '[') { 230 throw new IllegalArgumentException( 231 "Invalid member identifier '" + identifier + "'"); 232 } 233 234 int j = getEndIndex(identifier, i + 1); 235 if (j == -1) { 236 throw new IllegalArgumentException( 237 "Invalid member identifier '" + identifier + "'"); 238 } 239 240 list.add( 241 new NameSegment( 242 null, 243 Olap4jUtil.replace( 244 identifier.substring(i + 1, j), "]]", "]"), 245 type)); 246 247 i = j + 2; 248 } 249 return list; 250 } 251 252 /** 253 * Returns the end of the current segment. 254 * 255 * @param s Identifier string 256 * @param i Start of identifier segment 257 * @return End of segment 258 */ 259 private static int getEndIndex(String s, int i) { 260 while (i < s.length()) { 261 char ch = s.charAt(i); 262 if (ch == ']') { 263 if (i + 1 < s.length() && s.charAt(i + 1) == ']') { 264 // found ]] => skip 265 i += 2; 266 } else { 267 return i; 268 } 269 } else { 270 i++; 271 } 272 } 273 return -1; 274 } 275 276 /** 277 * Returns string quoted in [...]. 278 * 279 * <p>For example, "San Francisco" becomes 280 * "[San Francisco]"; "a [bracketed] string" becomes 281 * "[a [bracketed]] string]". 282 * 283 * @param id Unquoted name 284 * @return Quoted name 285 */ 286 static String quoteMdxIdentifier(String id) { 287 StringBuilder buf = new StringBuilder(id.length() + 20); 288 quoteMdxIdentifier(id, buf); 289 return buf.toString(); 290 } 291 292 /** 293 * Returns a string quoted in [...], writing the results to a 294 * {@link StringBuilder}. 295 * 296 * @param id Unquoted name 297 * @param buf Builder to write quoted string to 298 */ 299 static void quoteMdxIdentifier(String id, StringBuilder buf) { 300 buf.append('['); 301 int start = buf.length(); 302 buf.append(id); 303 Olap4jUtil.replace(buf, start, "]", "]]"); 304 buf.append(']'); 305 } 306 307 /** 308 * Converts a sequence of identifiers to a string. 309 * 310 * <p>For example, {"Store", "USA", 311 * "California"} becomes "[Store].[USA].[California]". 312 * 313 * @param segments List of segments 314 * @return Segments as quoted string 315 */ 316 static String unparseIdentifierList(List<? extends Segment> segments) { 317 final StringBuilder buf = new StringBuilder(64); 318 for (int i = 0; i < segments.size(); i++) { 319 Segment segment = segments.get(i); 320 if (i > 0) { 321 buf.append('.'); 322 } 323 segment.toString(buf); 324 } 325 return buf.toString(); 326 } 327 328 /** 329 * Component in a compound identifier. It is described by its name and how 330 * the name is quoted. 331 * 332 * <p>For example, the identifier 333 * <code>[Store].USA.[New Mexico].&[45]</code> has four segments:<ul> 334 * <li>"Store", {@link IdentifierNode.Quoting#QUOTED}</li> 335 * <li>"USA", {@link IdentifierNode.Quoting#UNQUOTED}</li> 336 * <li>"New Mexico", {@link IdentifierNode.Quoting#QUOTED}</li> 337 * <li>"45", {@link IdentifierNode.Quoting#KEY}</li> 338 * </ul> 339 * 340 * <p>QUOTED and UNQUOTED segments are represented using a 341 * {@link org.olap4j.mdx.IdentifierNode.NameSegment NameSegment}; 342 * KEY segments are represented using a 343 * {@link org.olap4j.mdx.IdentifierNode.KeySegment KeySegment}. 344 * 345 * <p>To parse an identifier into a list of segments, use the method 346 * {@link IdentifierNode#parseIdentifier(String)}.</p> 347 */ 348 public interface Segment { 349 350 /** 351 * Returns a string representation of this Segment. 352 * 353 * <p>For example, "[Foo]", "&[123]", "Abc". 354 * 355 * @return String representation of this Segment 356 */ 357 String toString(); 358 359 /** 360 * Appends a string representation of this Segment to a StringBuffer. 361 * 362 * @param buf StringBuffer 363 */ 364 void toString(StringBuilder buf); 365 366 /** 367 * Returns the region of the source code which this Segment was created 368 * from, if it was created by parsing. 369 * 370 * @return region of source code 371 */ 372 ParseRegion getRegion(); 373 374 /** 375 * Returns how this Segment is quoted. 376 * 377 * @return how this Segment is quoted 378 */ 379 Quoting getQuoting(); 380 381 /** 382 * Returns the name of this Segment. 383 * Returns {@code null} if this Segment represents a key. 384 * 385 * @return name of this Segment 386 */ 387 String getName(); 388 389 /** 390 * Returns the key components, if this Segment is a key. (That is, 391 * if {@link #getQuoting()} returns 392 * {@link org.olap4j.mdx.IdentifierNode.Quoting#KEY}.) 393 * 394 * Returns null otherwise. 395 * 396 * @return Components of key, or null if this Segment is not a key 397 */ 398 List<NameSegment> getKeyParts(); 399 } 400 401 /** 402 * Component in a compound identifier that describes the name of an object. 403 * Optionally, the name is quoted in brackets. 404 * 405 * @see org.olap4j.mdx.IdentifierNode.KeySegment 406 */ 407 public static class NameSegment implements Segment { 408 final String name; 409 final IdentifierNode.Quoting quoting; 410 private final ParseRegion region; 411 412 /** 413 * Creates a segment with the given quoting and region. 414 * 415 * @param region Region of source code 416 * @param name Name 417 * @param quoting Quoting style 418 */ 419 public NameSegment( 420 ParseRegion region, 421 String name, 422 IdentifierNode.Quoting quoting) 423 { 424 this.region = region; 425 this.name = name; 426 this.quoting = quoting; 427 if (!(quoting == Quoting.QUOTED || quoting == Quoting.UNQUOTED)) { 428 throw new IllegalArgumentException(); 429 } 430 } 431 432 /** 433 * Creates a quoted segment, "[name]". 434 * 435 * @param name Name of segment 436 */ 437 public NameSegment(String name) { 438 this(null, name, Quoting.QUOTED); 439 } 440 441 public String toString() { 442 switch (quoting) { 443 case UNQUOTED: 444 return name; 445 case QUOTED: 446 return quoteMdxIdentifier(name); 447 default: 448 throw Olap4jUtil.unexpected(quoting); 449 } 450 } 451 452 public void toString(StringBuilder buf) { 453 switch (quoting) { 454 case UNQUOTED: 455 buf.append(name); 456 return; 457 case QUOTED: 458 quoteMdxIdentifier(name, buf); 459 return; 460 default: 461 throw Olap4jUtil.unexpected(quoting); 462 } 463 } 464 public ParseRegion getRegion() { 465 return region; 466 } 467 468 public String getName() { 469 return name; 470 } 471 472 public Quoting getQuoting() { 473 return quoting; 474 } 475 476 public List<NameSegment> getKeyParts() { 477 return null; 478 } 479 } 480 481 /** 482 * Segment that represents a key or compound key. 483 * 484 * <p>Such a segment appears in an identifier with each component prefixed 485 * with '&'. For example, in the identifier 486 * '{@code [Customer].[State].&[WA]&[USA]}', the third segment is a 487 * compound key whose parts are "@{code WA}" and "{@code USA}". 488 * 489 * @see org.olap4j.mdx.IdentifierNode.NameSegment 490 */ 491 public static class KeySegment implements Segment { 492 private final List<NameSegment> subSegmentList; 493 494 /** 495 * Creates a KeySegment with one or more sub-segments. 496 * 497 * @param subSegments Array of sub-segments 498 */ 499 public KeySegment(NameSegment... subSegments) { 500 if (subSegments.length < 1) { 501 throw new IllegalArgumentException(); 502 } 503 this.subSegmentList = UnmodifiableArrayList.asCopyOf(subSegments); 504 } 505 506 /** 507 * Creates a KeySegment a list of sub-segments. 508 * 509 * @param subSegmentList List of sub-segments 510 */ 511 public KeySegment(List<NameSegment> subSegmentList) { 512 if (subSegmentList.size() < 1) { 513 throw new IllegalArgumentException(); 514 } 515 this.subSegmentList = 516 new UnmodifiableArrayList<NameSegment>( 517 subSegmentList.toArray( 518 new NameSegment[subSegmentList.size()])); 519 } 520 521 public String toString() { 522 final StringBuilder buf = new StringBuilder(); 523 toString(buf); 524 return buf.toString(); 525 } 526 527 public void toString(StringBuilder buf) { 528 for (Segment segment : subSegmentList) { 529 buf.append('&'); 530 segment.toString(buf); 531 } 532 } 533 534 public ParseRegion getRegion() { 535 return sumSegmentRegions(subSegmentList); 536 } 537 538 public Quoting getQuoting() { 539 return Quoting.KEY; 540 } 541 542 public String getName() { 543 return null; 544 } 545 546 public List<NameSegment> getKeyParts() { 547 return subSegmentList; 548 } 549 } 550 551 /** 552 * Enumeration of styles by which the component of an identifier can be 553 * quoted. 554 */ 555 public enum Quoting { 556 557 /** 558 * Unquoted identifier, for example "Measures". 559 */ 560 UNQUOTED, 561 562 /** 563 * Quoted identifier, for example "[Measures]". 564 */ 565 QUOTED, 566 567 /** 568 * Identifier quoted with an ampersand and brackets to indicate a key 569 * value, for example the second segment in "[Employees].&[89]". 570 * 571 * <p>Such a segment has one or more sub-segments. Each segment is 572 * either quoted or unquoted. For example, the second segment in 573 * "[Employees].&[89]&[San Francisco]&CA&USA" has four sub-segments, 574 * two quoted and two unquoted. 575 */ 576 KEY, 577 } 578 } 579 580 // End IdentifierNode.java