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 '&amp;',
028     *     such as '{@code &amp;[Key 1]&amp;Key2&amp;[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].&amp;[San Francisco]&amp;CA&amp;USA.&amp;[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].&amp;[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].&amp;[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]", "&amp;[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 '&amp;'. For example, in the identifier
486         * '{@code [Customer].[State].&amp;[WA]&amp;[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