1 /* Copyright 2004 The Apache Software Foundation 2 * 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16 package org.apache.xmlbeans.soap; 17 18 import javax.xml.namespace.QName; 19 20 import java.util.ArrayList; 21 import java.util.List; 22 import java.util.Iterator; 23 24 import org.apache.xmlbeans.impl.values.XmlValueOutOfRangeException; 25 import org.apache.xmlbeans.impl.common.XmlWhitespace; 26 import org.apache.xmlbeans.impl.common.QNameHelper; 27 import org.apache.xmlbeans.impl.common.PrefixResolver; 28 29 public final class SOAPArrayType 30 { 31 // Example foo:bar[,][][,,][7,9] 32 // -> _type = QName(foo:bar) 33 // -> _ranks = {2,1,3} 34 // -> _dimensions = {7,9} 35 private QName _type; 36 private int[] _ranks; // if ranks is empty, it means there are no nested arrays 37 private int[] _dimensions; // Any dimension can be -1 to indicate "any". 38 39 /** 40 * True if the ranks for the passed SOAPArrayType 41 * are equal to this one. 42 * 43 * Does NOT compare the _type fields. 44 */ 45 public boolean isSameRankAs(SOAPArrayType otherType) 46 { 47 if (_ranks.length != otherType._ranks.length) 48 return false; 49 for (int i = 0; i < _ranks.length; i++) 50 { 51 if (_ranks[i] != otherType._ranks[i]) 52 return false; 53 } 54 if (_dimensions.length != otherType._dimensions.length) 55 return false; 56 return true; 57 } 58 59 /** 60 * Given SOAP 1.1-formatted index string, returns an array 61 * index. For example, given "[4,3,5]", returns an int array 62 * containing 4, 3, and 5. 63 */ 64 public static int[] parseSoap11Index(String inbraces) 65 { 66 inbraces = XmlWhitespace.collapse(inbraces, XmlWhitespace.WS_COLLAPSE); 67 if (!inbraces.startsWith("[") || !inbraces.endsWith("]")) 68 throw new IllegalArgumentException("Misformed SOAP 1.1 index: must be contained in braces []"); 69 return internalParseCommaIntString(inbraces.substring(1, inbraces.length() - 1)); 70 } 71 72 private static int[] internalParseCommaIntString(String csl) 73 { 74 List dimStrings = new ArrayList(); 75 int i = 0; 76 for (;;) 77 { 78 int j = csl.indexOf(',', i); 79 if (j < 0) 80 { 81 dimStrings.add(csl.substring(i)); 82 break; 83 } 84 dimStrings.add(csl.substring(i, j)); 85 i = j + 1; 86 } 87 88 int[] result = new int[dimStrings.size()]; 89 i = 0; 90 for (Iterator it = dimStrings.iterator(); it.hasNext(); i++) 91 { 92 String dimString = XmlWhitespace.collapse((String)it.next(), XmlWhitespace.WS_COLLAPSE); 93 if (dimString.equals("*") || dimString.equals("")) 94 { 95 result[i] = -1; 96 } 97 else 98 { 99 try 100 { 101 result[i] = Integer.parseInt(dimString); 102 } 103 catch (Exception e) 104 { 105 throw new XmlValueOutOfRangeException("Malformed integer in SOAP array index"); 106 } 107 } 108 } 109 return result; 110 } 111 112 /** 113 * Parses a SOAP 1.1 array type string. 114 * 115 * Since an array type string contains a QName, a prefix resolver 116 * must be passed. 117 */ 118 public SOAPArrayType(String s, PrefixResolver m) 119 { 120 int firstbrace = s.indexOf('['); 121 if (firstbrace < 0) 122 throw new XmlValueOutOfRangeException(); 123 124 // grab the QName 125 String firstpart = XmlWhitespace.collapse(s.substring(0, firstbrace), XmlWhitespace.WS_COLLAPSE); 126 int firstcolon = firstpart.indexOf(':'); 127 String prefix = ""; 128 if (firstcolon >= 0) 129 prefix = firstpart.substring(0, firstcolon); 130 131 String uri = m.getNamespaceForPrefix(prefix); 132 if (uri == null) 133 throw new XmlValueOutOfRangeException(); 134 135 _type = QNameHelper.forLNS(firstpart.substring(firstcolon + 1), uri); 136 137 initDimensions(s, firstbrace); 138 } 139 140 private static int[] EMPTY_INT_ARRAY = new int[0]; 141 142 /** 143 * Parses SOAP 1.1(advanced) array type strings. 144 * 145 * Since in SOAP 1.1(advanced) the dimension specification is separated from the 146 * QName for the underlying type, these are passed in separate 147 * arguments. 148 */ 149 public SOAPArrayType(QName name, String dimensions) 150 { 151 int firstbrace = dimensions.indexOf('['); 152 if (firstbrace < 0) 153 { 154 _type = name; 155 _ranks = EMPTY_INT_ARRAY; 156 dimensions = XmlWhitespace.collapse(dimensions, XmlWhitespace.WS_COLLAPSE); 157 String[] dimStrings = dimensions.split(" "); 158 for (int i = 0; i < dimStrings.length; i++) 159 { 160 String dimString = dimStrings[i]; 161 if (dimString.equals("*")) 162 { 163 _dimensions[i] = -1; 164 // _hasIndeterminateDimensions = true; 165 } 166 else 167 { 168 try 169 { 170 _dimensions[i] = Integer.parseInt(dimStrings[i]); 171 } 172 catch (Exception e) 173 { 174 throw new XmlValueOutOfRangeException(); 175 } 176 } 177 } 178 } 179 else 180 { 181 _type = name; 182 initDimensions(dimensions, firstbrace); 183 } 184 } 185 186 /** 187 * Given a nested SOAPArrayType and a set of dimensions for the outermost 188 * array, comes up with the right SOAPArrayType for the whole thing. 189 * 190 * E.g., 191 * Nested foo:bar[,][][,,][1,2] 192 * Dimensions [6,7,8] 193 * Result -> foo:bar[,][][,,][,][6,7,8] 194 */ 195 public SOAPArrayType(SOAPArrayType nested, int[] dimensions) 196 { 197 _type = nested._type; 198 199 _ranks = new int[nested._ranks.length + 1]; 200 System.arraycopy(nested._ranks, 0, _ranks, 0, nested._ranks.length); 201 _ranks[_ranks.length - 1] = nested._dimensions.length; 202 203 _dimensions = new int[dimensions.length]; 204 System.arraycopy(dimensions, 0, _dimensions, 0, dimensions.length); 205 } 206 207 /** 208 * Initialize dimensions based on SOAP11 parsed dimension substring 209 */ 210 private void initDimensions(String s, int firstbrace) 211 { 212 List braces = new ArrayList(); 213 int lastbrace = -1; 214 for (int i = firstbrace; i >= 0; ) 215 { 216 lastbrace = s.indexOf(']', i); 217 if (lastbrace < 0) 218 throw new XmlValueOutOfRangeException(); 219 braces.add(s.substring(i + 1, lastbrace)); 220 i = s.indexOf('[', lastbrace); 221 } 222 223 String trailer = s.substring(lastbrace + 1); 224 if (!XmlWhitespace.isAllSpace(trailer)) 225 throw new XmlValueOutOfRangeException(); 226 227 // now fill in rank array 228 _ranks = new int[braces.size() - 1]; 229 for (int i = 0; i < _ranks.length; i++) 230 { 231 String commas = (String)braces.get(i); 232 int commacount = 0; 233 for (int j = 0; j < commas.length(); j++) 234 { 235 char ch = commas.charAt(j); 236 if (ch == ',') 237 commacount += 1; 238 else if (!XmlWhitespace.isSpace(ch)) 239 throw new XmlValueOutOfRangeException(); 240 } 241 _ranks[i] = commacount + 1; 242 } 243 244 // finally fill in dimension array 245 _dimensions = internalParseCommaIntString((String)braces.get(braces.size() - 1)); 246 247 /* 248 for (int i = 0; i < _dimensions.length; i++) 249 { 250 if (_dimensions[i] < 0) 251 _hasIndeterminateDimensions = true; 252 } 253 */ 254 } 255 256 /** 257 * Returns the QName for the referenced type. 258 */ 259 public QName getQName() 260 { 261 return _type; 262 } 263 264 /** 265 * Returns the array of ranks for inner nested arrays. 266 * In SOAP 1.1-advanced, this is always an array of length zero. 267 * In SOAP 1.1, this array reflects the ranks of nested 268 * arrays. For example foo:bar[,][,,][][5,6] will produce 269 * a ranks result of 2, 3, 1. 270 */ 271 public int[] getRanks() 272 { 273 int[] result = new int[_ranks.length]; 274 System.arraycopy(_ranks, 0, result, 0, result.length); 275 return result; 276 } 277 278 /** 279 * Returns the array of dimensions. 280 */ 281 public int[] getDimensions() 282 { 283 int[] result = new int[_dimensions.length]; 284 System.arraycopy(_dimensions, 0, result, 0, result.length); 285 return result; 286 } 287 288 /** 289 * True if this array contains nested arrays. Equivalent 290 * to (getRanks().length > 0). 291 */ 292 public boolean containsNestedArrays() 293 { 294 return (_ranks.length > 0); 295 } 296 297 /** 298 * Returns the dimensions as a string, e.g., [,][2,3,4] 299 */ 300 public String soap11DimensionString() 301 { 302 return soap11DimensionString(_dimensions); 303 } 304 305 /** 306 * Given an actual set of dimensions that may differ from 307 * the default that is stored, outputs the soap arrayType 308 * string. 309 */ 310 public String soap11DimensionString(int[] actualDimensions) 311 { 312 StringBuffer sb = new StringBuffer(); 313 for (int i = 0; i < _ranks.length; i++) 314 { 315 sb.append('['); 316 for (int j = 1; j < _ranks[i]; j++) 317 sb.append(','); 318 sb.append(']'); 319 } 320 321 sb.append('['); 322 for (int i = 0; i < actualDimensions.length; i++) 323 { 324 if (i > 0) 325 sb.append(','); 326 if (actualDimensions[i] >= 0) 327 sb.append(actualDimensions[i]); 328 } 329 sb.append(']'); 330 return sb.toString(); 331 } 332 333 private SOAPArrayType() 334 { 335 } 336 337 /** 338 * SOAP 1.2 339 * Constructs a SOAPArrayType from soap-enc:itemType and 340 * soap-enc:arraySize attributes 341 * @param itemType the item type QName 342 * @param arraySize a string with dimentions like: * 3 4 343 * @return a SOAPArrayType to represent this 344 */ 345 public static SOAPArrayType newSoap12Array(QName itemType, String arraySize) 346 { 347 int [] ranks = EMPTY_INT_ARRAY; 348 arraySize = XmlWhitespace.collapse(arraySize, XmlWhitespace.WS_COLLAPSE); 349 String[] dimStrings = arraySize.split(" "); 350 int[] dimensions = new int[dimStrings.length]; 351 for (int i = 0; i < dimStrings.length; i++) 352 { 353 String dimString = dimStrings[i]; 354 if (i==0 && dimString.equals("*")) 355 { 356 dimensions[i] = -1; 357 // _hasIndeterminateDimensions = true; 358 } 359 else 360 { 361 try 362 { 363 dimensions[i] = Integer.parseInt(dimStrings[i]); 364 } 365 catch (Exception e) 366 { 367 throw new XmlValueOutOfRangeException(); 368 } 369 } 370 } 371 SOAPArrayType sot = new SOAPArrayType(); 372 sot._ranks = ranks; 373 sot._type = itemType; 374 sot._dimensions = dimensions; 375 return sot; 376 } 377 378 /** 379 * SOAP 1.2 380 * Given an actual set of dimensions that may differ from 381 * the default that is stored, outputs the soap arraySize 382 * string. 383 */ 384 public String soap12DimensionString(int[] actualDimensions) 385 { 386 StringBuffer sb = new StringBuffer(); 387 388 for (int i = 0; i < actualDimensions.length; i++) 389 { 390 if (i > 0) 391 sb.append(' '); 392 if (actualDimensions[i] >= 0) 393 sb.append(actualDimensions[i]); 394 } 395 return sb.toString(); 396 } 397 398 /** 399 * Constructs a SOAPArrayType reflecting the dimensions 400 * of the next nested array. 401 */ 402 public SOAPArrayType nestedArrayType() 403 { 404 if (!containsNestedArrays()) 405 throw new IllegalStateException(); 406 407 SOAPArrayType result = new SOAPArrayType(); 408 409 result._type = _type; 410 411 result._ranks = new int[_ranks.length - 1]; 412 System.arraycopy(_ranks, 0, result._ranks, 0, result._ranks.length); 413 414 result._dimensions = new int[_ranks[_ranks.length - 1]]; 415 for (int i = 0; i < result._dimensions.length; i++) 416 result._dimensions[i] = -1; 417 418 // result._hasIndeterminateDimensions = (result._dimensions.length > 0); 419 420 return result; 421 } 422 423 public int hashCode() 424 { 425 return (_type.hashCode() + _dimensions.length + _ranks.length + (_dimensions.length == 0 ? 0 : _dimensions[0])); 426 } 427 428 public boolean equals(Object obj) 429 { 430 if (obj == this) 431 return true; 432 433 if (!obj.getClass().equals(getClass())) 434 return false; 435 436 SOAPArrayType sat = (SOAPArrayType)obj; 437 438 if (!_type.equals(sat._type)) 439 return false; 440 441 if (_ranks.length != sat._ranks.length) 442 return false; 443 444 if (_dimensions.length != sat._dimensions.length) 445 return false; 446 447 for (int i = 0; i < _ranks.length; i++) 448 if (_ranks[i] != sat._ranks[i]) 449 return false; 450 451 for (int i = 0; i < _dimensions.length; i++) 452 if (_dimensions[i] != sat._dimensions[i]) 453 return false; 454 455 return true; 456 } 457 }