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 '&', 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 */ 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].&[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