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*/
020package org.olap4j.mdx;
021
022import org.olap4j.impl.*;
023import org.olap4j.type.Type;
024
025import 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 */
081public 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