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