001    /*
002    // $Id: IdentifierNode.java 360 2010-10-28 18:14:32Z 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-2010 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 java.util.AbstractList;
013    import java.util.ArrayList;
014    import java.util.List;
015    
016    import org.olap4j.impl.*;
017    import org.olap4j.type.Type;
018    
019    /**
020     * Multi-part identifier.
021     *
022     * <p>An identifier is immutable.
023     *
024     * <p>An identifer consists of one or more {@link IdentifierSegment}s. A segment
025     * is either:<ul>
026     * <li>An unquoted value such as '{@code CA}',
027     * <li>A value quoted in brackets, such as '{@code [San Francisco]}', or
028     * <li>A key of one or more parts, each of which is prefixed with '&amp;',
029     *     such as '{@code &amp;[Key 1]&amp;Key2&amp;[5]}'.
030     * </ul>
031     *
032     * <p>Segment types are indicated by the {@link Quoting} enumeration.
033     *
034     * <p>A key segment is of type {@link Quoting#KEY}, and has one or more
035     * component parts accessed via the
036     * {@link IdentifierSegment#getKeyParts()} method. The parts
037     * are of type {@link Quoting#UNQUOTED} or {@link Quoting#QUOTED}.
038     *
039     * <p>A simple example is the identifier {@code Measures.[Unit Sales]}. It
040     * has two segments:<ul>
041     * <li>Segment #0 is
042     *     {@link Quoting#UNQUOTED UNQUOTED},
043     *     name "Measures"</li>
044     * <li>Segment #1 is
045     *     {@link Quoting#QUOTED QUOTED},
046     *     name "Unit Sales"</li>
047     * </ul>
048     *
049     * <p>A more complex example illustrates a compound key. The identifier {@code
050     * [Customers].[City].&amp;[San Francisco]&amp;CA&amp;USA.&amp;[cust1234]}
051     * contains four segments as follows:
052     * <ul>
053     * <li>Segment #0 is QUOTED, name "Customers"</li>
054     * <li>Segment #1 is QUOTED, name "City"</li>
055     * <li>Segment #2 is a {@link Quoting#KEY KEY}.
056     *     It has 3 sub-segments:
057     *     <ul>
058     *     <li>Sub-segment #0 is QUOTED, name "San Francisco"</li>
059     *     <li>Sub-segment #1 is UNQUOTED, name "CA"</li>
060     *     <li>Sub-segment #2 is UNQUOTED, name "USA"</li>
061     *     </ul>
062     * </li>
063     * <li>Segment #3 is a KEY. It has 1 sub-segment:
064     *     <ul>
065     *     <li>Sub-segment #0 is QUOTED, name "cust1234"</li>
066     *     </ul>
067     * </li>
068     * </ul>
069     *
070     * @version $Id: IdentifierNode.java 360 2010-10-28 18:14:32Z jhyde $
071     * @author jhyde
072     */
073    public class IdentifierNode
074        implements ParseTreeNode
075    {
076        private final List<IdentifierSegment> segments;
077    
078        /**
079         * Creates an identifier containing one or more segments.
080         *
081         * @param segments Array of Segments, each consisting of a name and quoting
082         * style
083         */
084        public IdentifierNode(IdentifierSegment... segments) {
085            if (segments.length < 1) {
086                throw new IllegalArgumentException();
087            }
088            this.segments = UnmodifiableArrayList.asCopyOf(segments);
089        }
090    
091        /**
092         * Creates an identifier containing a list of segments.
093         *
094         * @param segments List of segments
095         */
096        public IdentifierNode(List<IdentifierSegment> segments) {
097            if (segments.size() < 1) {
098                throw new IllegalArgumentException();
099            }
100            this.segments =
101                new UnmodifiableArrayList<IdentifierSegment>(
102                    segments.toArray(
103                        new IdentifierSegment[segments.size()]));
104        }
105    
106        public Type getType() {
107            // Can't give the type until we have resolved.
108            throw new UnsupportedOperationException();
109        }
110    
111        /**
112         * Returns the list of segments which consistitute this identifier.
113         *
114         * @return list of constituent segments
115         */
116        public List<IdentifierSegment> getSegmentList() {
117            return segments;
118        }
119    
120        public ParseRegion getRegion() {
121            // Region is the span from the first segment to the last.
122            return sumSegmentRegions(segments);
123        }
124    
125        /**
126         * Returns a region encompassing the regions of the first through the last
127         * of a list of segments.
128         *
129         * @param segments List of segments
130         * @return Region encompassed by list of segments
131         */
132        static ParseRegion sumSegmentRegions(
133            final List<? extends IdentifierSegment> segments)
134        {
135            return ParseRegion.sum(
136                new AbstractList<ParseRegion>() {
137                    public ParseRegion get(int index) {
138                        return segments.get(index).getRegion();
139                    }
140    
141                    public int size() {
142                        return segments.size();
143                    }
144                });
145        }
146    
147        /**
148         * Returns a new Identifier consisting of this one with another segment
149         * appended. Does not modify this Identifier.
150         *
151         * @param segment Name of segment
152         * @return New identifier
153         */
154        public IdentifierNode append(IdentifierSegment segment) {
155            List<IdentifierSegment> newSegments =
156                new ArrayList<IdentifierSegment>(segments);
157            newSegments.add(segment);
158            return new IdentifierNode(newSegments);
159        }
160    
161        public <T> T accept(ParseTreeVisitor<T> visitor) {
162            return visitor.visit(this);
163        }
164    
165        public void unparse(ParseTreeWriter writer) {
166            writer.getPrintWriter().print(this);
167        }
168    
169        public String toString() {
170            return unparseIdentifierList(segments);
171        }
172    
173        public IdentifierNode deepCopy() {
174            // IdentifierNode is immutable
175            return this;
176        }
177    
178        /**
179         * Parses an MDX identifier string into an
180         * {@link org.olap4j.mdx.IdentifierNode}.
181         *
182         * <p>It contains a list of {@link IdentifierSegment segments}, each
183         * of which is a name combined with a description of how the name
184         * was {@link Quoting quoted}. For example,
185         *
186         * <blockquote><code>
187         * parseIdentifier(
188         * "[Customers].USA.[South Dakota].[Sioux Falls].&amp;[1245]")
189         * </code></blockquote>
190         *
191         * returns an IdentifierNode consisting of the following segments:
192         *
193         * <code><ul>
194         * <li>NameSegment("Customers", quoted=true),
195         * <li>NameSegment("USA", quoted=false),
196         * <li>NameSegment("South Dakota", quoted=true),
197         * <li>NameSegment("Sioux Falls", quoted=true),
198         * <li>KeySegment( { NameSegment("1245", quoted=true) } )
199         * </ul></code>
200         *
201         * @see #ofNames(String...)
202         *
203         * @param identifier MDX identifier string
204         *
205         * @return Identifier parse tree node
206         *
207         * @throws IllegalArgumentException if the format of the identifier is
208         * invalid
209         */
210        public static IdentifierNode parseIdentifier(String identifier)  {
211            return new IdentifierNode(IdentifierParser.parseIdentifier(identifier));
212        }
213    
214        /**
215         * Converts an array of quoted name segments into an identifier.
216         *
217         * <p>For example,
218         *
219         * <blockquote><code>
220         * IdentifierNode.ofNames("Store", "USA", "CA")</code></blockquote>
221         *
222         * returns an IdentifierNode consisting of the following segments:
223         *
224         * <code><ul>
225         * <li>NameSegment("Customers", quoted=true),
226         * <li>NameSegment("USA", quoted=false),
227         * <li>NameSegment("South Dakota", quoted=true),
228         * <li>NameSegment("Sioux Falls", quoted=true),
229         * <li>KeySegment( { NameSegment("1245", quoted=true) } )
230         * </ul></code>
231         *
232         * @see #parseIdentifier(String)
233         *
234         * @param names Array of names
235         *
236         * @return Identifier parse tree node
237         */
238        public static IdentifierNode ofNames(String... names) {
239            final List<IdentifierSegment> list =
240                new ArrayList<IdentifierSegment>();
241            for (String name : names) {
242                list.add(new NameSegment(null, name, Quoting.QUOTED));
243            }
244            return new IdentifierNode(list);
245        }
246    
247        /**
248         * Returns string quoted in [...].
249         *
250         * <p>For example, "San Francisco" becomes
251         * "[San Francisco]"; "a [bracketed] string" becomes
252         * "[a [bracketed]] string]".
253         *
254         * @param id Unquoted name
255         * @return Quoted name
256         */
257        static String quoteMdxIdentifier(String id) {
258            StringBuilder buf = new StringBuilder(id.length() + 20);
259            quoteMdxIdentifier(id, buf);
260            return buf.toString();
261        }
262    
263        /**
264         * Returns a string quoted in [...], writing the results to a
265         * {@link StringBuilder}.
266         *
267         * @param id Unquoted name
268         * @param buf Builder to write quoted string to
269         */
270        static void quoteMdxIdentifier(String id, StringBuilder buf) {
271            buf.append('[');
272            int start = buf.length();
273            buf.append(id);
274            Olap4jUtil.replace(buf, start, "]", "]]");
275            buf.append(']');
276        }
277    
278        /**
279         * Converts a sequence of identifiers to a string.
280         *
281         * <p>For example, {"Store", "USA",
282         * "California"} becomes "[Store].[USA].[California]".
283         *
284         * @param segments List of segments
285         * @return Segments as quoted string
286         */
287        static String unparseIdentifierList(
288            List<? extends IdentifierSegment> segments)
289        {
290            final StringBuilder buf = new StringBuilder(64);
291            for (int i = 0; i < segments.size(); i++) {
292                IdentifierSegment segment = segments.get(i);
293                if (i > 0) {
294                    buf.append('.');
295                }
296                segment.toString(buf);
297            }
298            return buf.toString();
299        }
300    }
301    
302    // End IdentifierNode.java