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