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 '&',
029 * such as '{@code &[Key 1]&Key2&[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].&[San Francisco]&CA&USA.&[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].&[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