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