001// *************************************************************************************************************************** 002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file * 003// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file * 004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance * 005// * with the License. You may obtain a copy of the License at * 006// * * 007// * http://www.apache.org/licenses/LICENSE-2.0 * 008// * * 009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an * 010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * 011// * specific language governing permissions and limitations under the License. * 012// *************************************************************************************************************************** 013package org.apache.juneau.uon; 014 015import static org.apache.juneau.internal.StringUtils.*; 016import static org.apache.juneau.uon.UonParser.*; 017 018import java.io.*; 019import java.lang.reflect.*; 020import java.util.*; 021 022import org.apache.juneau.*; 023import org.apache.juneau.internal.*; 024import org.apache.juneau.parser.*; 025import org.apache.juneau.transform.*; 026 027/** 028 * Session object that lives for the duration of a single use of {@link UonParser}. 029 * 030 * <p> 031 * This class is NOT thread safe. 032 * It is typically discarded after one-time use although it can be reused against multiple inputs. 033 */ 034@SuppressWarnings({ "unchecked", "rawtypes" }) 035public class UonParserSession extends ReaderParserSession { 036 037 // Characters that need to be preceded with an escape character. 038 private static final AsciiSet escapedChars = AsciiSet.create("~'\u0001\u0002"); 039 040 private static final char AMP='\u0001', EQ='\u0002'; // Flags set in reader to denote & and = characters. 041 042 043 private final boolean decodeChars, validateEnd; 044 045 /** 046 * Create a new session using properties specified in the context. 047 * 048 * @param ctx 049 * The context creating this session object. 050 * The context contains all the configuration settings for this object. 051 * @param args 052 * Runtime session arguments. 053 */ 054 protected UonParserSession(UonParser ctx, ParserSessionArgs args) { 055 super(ctx, args); 056 decodeChars = getProperty(UON_decoding, boolean.class, ctx.decodeChars); 057 validateEnd = getProperty(UON_validateEnd, boolean.class, ctx.validateEnd); 058 } 059 060 @Override /* Session */ 061 public ObjectMap asMap() { 062 return super.asMap() 063 .append("UonParser", new ObjectMap() 064 .append("decodeChars", decodeChars) 065 ); 066 } 067 068 /** 069 * Create a specialized parser session for parsing URL parameters. 070 * 071 * <p> 072 * The main difference is that characters are never decoded, and the {@link UonParser#UON_decoding} 073 * property is always ignored. 074 * 075 * @param ctx 076 * The context creating this session object. 077 * The context contains all the configuration settings for this object. 078 * @param args 079 * Runtime session arguments. 080 * @param decodeChars 081 * Whether to decode characters. 082 */ 083 protected UonParserSession(UonParser ctx, ParserSessionArgs args, boolean decodeChars) { 084 super(ctx, args); 085 this.decodeChars = decodeChars; 086 this.validateEnd = true; 087 } 088 089 @Override /* ParserSession */ 090 protected <T> T doParse(ParserPipe pipe, ClassMeta<T> type) throws Exception { 091 try (UonReader r = getUonReader(pipe, decodeChars)) { 092 T o = parseAnything(type, r, getOuter(), true, null); 093 validateEnd(r); 094 return o; 095 } 096 } 097 098 @Override /* ReaderParserSession */ 099 protected <K,V> Map<K,V> doParseIntoMap(ParserPipe pipe, Map<K,V> m, Type keyType, Type valueType) throws Exception { 100 try (UonReader r = getUonReader(pipe, decodeChars)) { 101 m = parseIntoMap(r, m, (ClassMeta<K>)getClassMeta(keyType), (ClassMeta<V>)getClassMeta(valueType), null); 102 validateEnd(r); 103 return m; 104 } 105 } 106 107 @Override /* ReaderParserSession */ 108 protected <E> Collection<E> doParseIntoCollection(ParserPipe pipe, Collection<E> c, Type elementType) throws Exception { 109 try (UonReader r = getUonReader(pipe, decodeChars)) { 110 c = parseIntoCollection(r, c, (ClassMeta<E>)getClassMeta(elementType), false, null); 111 validateEnd(r); 112 return c; 113 } 114 } 115 116 /** 117 * Workhorse method. 118 * 119 * @param eType The class type being parsed, or <jk>null</jk> if unknown. 120 * @param r The reader being parsed. 121 * @param outer The outer object (for constructing nested inner classes). 122 * @param isUrlParamValue 123 * If <jk>true</jk>, then we're parsing a top-level URL-encoded value which is treated a bit different than the 124 * default case. 125 * @param pMeta The current bean property being parsed. 126 * @return The parsed object. 127 * @throws Exception 128 */ 129 public <T> T parseAnything(ClassMeta<?> eType, UonReader r, Object outer, boolean isUrlParamValue, BeanPropertyMeta pMeta) throws Exception { 130 131 if (eType == null) 132 eType = object(); 133 PojoSwap<T,Object> swap = (PojoSwap<T,Object>)eType.getPojoSwap(this); 134 BuilderSwap<T,Object> builder = (BuilderSwap<T,Object>)eType.getBuilderSwap(this); 135 ClassMeta<?> sType = null; 136 if (builder != null) 137 sType = builder.getBuilderClassMeta(this); 138 else if (swap != null) 139 sType = swap.getSwapClassMeta(this); 140 else 141 sType = eType; 142 setCurrentClass(sType); 143 144 Object o = null; 145 146 int c = r.peekSkipWs(); 147 148 if (c == -1 || c == AMP) { 149 // If parameter is blank and it's an array or collection, return an empty list. 150 if (sType.isCollectionOrArray()) 151 o = sType.newInstance(); 152 else if (sType.isString() || sType.isObject()) 153 o = ""; 154 else if (sType.isPrimitive()) 155 o = sType.getPrimitiveDefault(); 156 // Otherwise, leave null. 157 } else if (sType.isVoid()) { 158 String s = parseString(r, isUrlParamValue); 159 if (s != null) 160 throw new ParseException(loc(r), "Expected ''null'' for void value, but was ''{0}''.", s); 161 } else if (sType.isObject()) { 162 if (c == '(') { 163 ObjectMap m = new ObjectMap(this); 164 parseIntoMap(r, m, string(), object(), pMeta); 165 o = cast(m, pMeta, eType); 166 } else if (c == '@') { 167 Collection l = new ObjectList(this); 168 o = parseIntoCollection(r, l, sType, isUrlParamValue, pMeta); 169 } else { 170 String s = parseString(r, isUrlParamValue); 171 if (c != '\'') { 172 if ("true".equals(s) || "false".equals(s)) 173 o = Boolean.valueOf(s); 174 else if (! "null".equals(s)) { 175 if (isNumeric(s)) 176 o = StringUtils.parseNumber(s, Number.class); 177 else 178 o = s; 179 } 180 } else { 181 o = s; 182 } 183 } 184 } else if (sType.isBoolean()) { 185 o = parseBoolean(r); 186 } else if (sType.isCharSequence()) { 187 o = parseString(r, isUrlParamValue); 188 } else if (sType.isChar()) { 189 String s = parseString(r, isUrlParamValue); 190 o = s == null ? null : s.charAt(0); 191 } else if (sType.isNumber()) { 192 o = parseNumber(r, (Class<? extends Number>)sType.getInnerClass()); 193 } else if (sType.isMap()) { 194 Map m = (sType.canCreateNewInstance(outer) ? (Map)sType.newInstance(outer) : new ObjectMap(this)); 195 o = parseIntoMap(r, m, sType.getKeyType(), sType.getValueType(), pMeta); 196 } else if (sType.isCollection()) { 197 if (c == '(') { 198 ObjectMap m = new ObjectMap(this); 199 parseIntoMap(r, m, string(), object(), pMeta); 200 // Handle case where it's a collection, but serialized as a map with a _type or _value key. 201 if (m.containsKey(getBeanTypePropertyName(sType))) 202 o = cast(m, pMeta, eType); 203 // Handle case where it's a collection, but only a single value was specified. 204 else { 205 Collection l = ( 206 sType.canCreateNewInstance(outer) 207 ? (Collection)sType.newInstance(outer) 208 : new ObjectList(this) 209 ); 210 l.add(m.cast(sType.getElementType())); 211 o = l; 212 } 213 } else { 214 Collection l = ( 215 sType.canCreateNewInstance(outer) 216 ? (Collection)sType.newInstance(outer) 217 : new ObjectList(this) 218 ); 219 o = parseIntoCollection(r, l, sType, isUrlParamValue, pMeta); 220 } 221 } else if (builder != null) { 222 BeanMap m = toBeanMap(builder.create(this, eType)); 223 m = parseIntoBeanMap(r, m); 224 o = m == null ? null : builder.build(this, m.getBean(), eType); 225 } else if (sType.canCreateNewBean(outer)) { 226 BeanMap m = newBeanMap(outer, sType.getInnerClass()); 227 m = parseIntoBeanMap(r, m); 228 o = m == null ? null : m.getBean(); 229 } else if (sType.canCreateNewInstanceFromString(outer)) { 230 String s = parseString(r, isUrlParamValue); 231 if (s != null) 232 o = sType.newInstanceFromString(outer, s); 233 } else if (sType.canCreateNewInstanceFromNumber(outer)) { 234 o = sType.newInstanceFromNumber(this, outer, parseNumber(r, sType.getNewInstanceFromNumberClass())); 235 } else if (sType.isArray() || sType.isArgs()) { 236 if (c == '(') { 237 ObjectMap m = new ObjectMap(this); 238 parseIntoMap(r, m, string(), object(), pMeta); 239 // Handle case where it's an array, but serialized as a map with a _type or _value key. 240 if (m.containsKey(getBeanTypePropertyName(sType))) 241 o = cast(m, pMeta, eType); 242 // Handle case where it's an array, but only a single value was specified. 243 else { 244 ArrayList l = new ArrayList(1); 245 l.add(m.cast(sType.getElementType())); 246 o = toArray(sType, l); 247 } 248 } else { 249 ArrayList l = (ArrayList)parseIntoCollection(r, new ArrayList(), sType, isUrlParamValue, pMeta); 250 o = toArray(sType, l); 251 } 252 } else if (c == '(') { 253 // It could be a non-bean with _type attribute. 254 ObjectMap m = new ObjectMap(this); 255 parseIntoMap(r, m, string(), object(), pMeta); 256 if (m.containsKey(getBeanTypePropertyName(sType))) 257 o = cast(m, pMeta, eType); 258 else 259 throw new ParseException(loc(r), "Class ''{0}'' could not be instantiated. Reason: ''{1}''", 260 sType.getInnerClass().getName(), sType.getNotABeanReason()); 261 } else if (c == 'n') { 262 r.read(); 263 parseNull(r); 264 } else { 265 throw new ParseException(loc(r), "Class ''{0}'' could not be instantiated. Reason: ''{1}''", 266 sType.getInnerClass().getName(), sType.getNotABeanReason()); 267 } 268 269 if (o == null && sType.isPrimitive()) 270 o = sType.getPrimitiveDefault(); 271 if (swap != null && o != null) 272 o = swap.unswap(this, o, eType); 273 274 if (outer != null) 275 setParent(eType, o, outer); 276 277 return (T)o; 278 } 279 280 private <K,V> Map<K,V> parseIntoMap(UonReader r, Map<K,V> m, ClassMeta<K> keyType, ClassMeta<V> valueType, 281 BeanPropertyMeta pMeta) throws Exception { 282 283 if (keyType == null) 284 keyType = (ClassMeta<K>)string(); 285 286 int c = r.read(); 287 if (c == -1 || c == AMP) 288 return null; 289 if (c == 'n') 290 return (Map<K,V>)parseNull(r); 291 if (c != '(') 292 throw new ParseException(loc(r), "Expected '(' at beginning of object."); 293 294 final int S1=1; // Looking for attrName start. 295 final int S2=2; // Found attrName end, looking for =. 296 final int S3=3; // Found =, looking for valStart. 297 final int S4=4; // Looking for , or ) 298 boolean isInEscape = false; 299 300 int state = S1; 301 K currAttr = null; 302 while (c != -1 && c != AMP) { 303 c = r.read(); 304 if (! isInEscape) { 305 if (state == S1) { 306 if (c == ')') 307 return m; 308 if (Character.isWhitespace(c)) 309 skipSpace(r); 310 else { 311 r.unread(); 312 Object attr = parseAttr(r, decodeChars); 313 currAttr = attr == null ? null : convertAttrToType(m, trim(attr.toString()), keyType); 314 state = S2; 315 c = 0; // Avoid isInEscape if c was '\' 316 } 317 } else if (state == S2) { 318 if (c == EQ || c == '=') 319 state = S3; 320 else if (c == -1 || c == ',' || c == ')' || c == AMP) { 321 if (currAttr == null) { 322 // Value was '%00' 323 r.unread(); 324 return null; 325 } 326 m.put(currAttr, null); 327 if (c == ')' || c == -1 || c == AMP) 328 return m; 329 state = S1; 330 } 331 } else if (state == S3) { 332 if (c == -1 || c == ',' || c == ')' || c == AMP) { 333 V value = convertAttrToType(m, "", valueType); 334 m.put(currAttr, value); 335 if (c == -1 || c == ')' || c == AMP) 336 return m; 337 state = S1; 338 } else { 339 V value = parseAnything(valueType, r.unread(), m, false, pMeta); 340 setName(valueType, value, currAttr); 341 m.put(currAttr, value); 342 state = S4; 343 c = 0; // Avoid isInEscape if c was '\' 344 } 345 } else if (state == S4) { 346 if (c == ',') 347 state = S1; 348 else if (c == ')' || c == -1 || c == AMP) { 349 return m; 350 } 351 } 352 } 353 isInEscape = isInEscape(c, r, isInEscape); 354 } 355 if (state == S1) 356 throw new ParseException(loc(r), "Could not find attribute name on object."); 357 if (state == S2) 358 throw new ParseException(loc(r), "Could not find '=' following attribute name on object."); 359 if (state == S3) 360 throw new ParseException(loc(r), "Dangling '=' found in object entry"); 361 if (state == S4) 362 throw new ParseException(loc(r), "Could not find ')' marking end of object."); 363 364 return null; // Unreachable. 365 } 366 367 private <E> Collection<E> parseIntoCollection(UonReader r, Collection<E> l, ClassMeta<E> type, boolean isUrlParamValue, BeanPropertyMeta pMeta) throws Exception { 368 369 int c = r.readSkipWs(); 370 if (c == -1 || c == AMP) 371 return null; 372 if (c == 'n') 373 return (Collection<E>)parseNull(r); 374 375 int argIndex = 0; 376 377 // If we're parsing a top-level parameter, we're allowed to have comma-delimited lists outside parenthesis (e.g. "&foo=1,2,3&bar=a,b,c") 378 // This is not allowed at lower levels since we use comma's as end delimiters. 379 boolean isInParens = (c == '@'); 380 if (! isInParens) { 381 if (isUrlParamValue) 382 r.unread(); 383 else 384 throw new ParseException(loc(r), "Could not find '(' marking beginning of collection."); 385 } else { 386 r.read(); 387 } 388 389 if (isInParens) { 390 final int S1=1; // Looking for starting of first entry. 391 final int S2=2; // Looking for starting of subsequent entries. 392 final int S3=3; // Looking for , or ) after first entry. 393 394 int state = S1; 395 while (c != -1 && c != AMP) { 396 c = r.read(); 397 if (state == S1 || state == S2) { 398 if (c == ')') { 399 if (state == S2) { 400 l.add((E)parseAnything(type.isArgs() ? type.getArg(argIndex++) : type.getElementType(), 401 r.unread(), l, false, pMeta)); 402 r.read(); 403 } 404 return l; 405 } else if (Character.isWhitespace(c)) { 406 skipSpace(r); 407 } else { 408 l.add((E)parseAnything(type.isArgs() ? type.getArg(argIndex++) : type.getElementType(), 409 r.unread(), l, false, pMeta)); 410 state = S3; 411 } 412 } else if (state == S3) { 413 if (c == ',') { 414 state = S2; 415 } else if (c == ')') { 416 return l; 417 } 418 } 419 } 420 if (state == S1 || state == S2) 421 throw new ParseException(loc(r), "Could not find start of entry in array."); 422 if (state == S3) 423 throw new ParseException(loc(r), "Could not find end of entry in array."); 424 425 } else { 426 final int S1=1; // Looking for starting of entry. 427 final int S2=2; // Looking for , or & or END after first entry. 428 429 int state = S1; 430 while (c != -1 && c != AMP) { 431 c = r.read(); 432 if (state == S1) { 433 if (Character.isWhitespace(c)) { 434 skipSpace(r); 435 } else { 436 l.add((E)parseAnything(type.isArgs() ? type.getArg(argIndex++) : type.getElementType(), 437 r.unread(), l, false, pMeta)); 438 state = S2; 439 } 440 } else if (state == S2) { 441 if (c == ',') { 442 state = S1; 443 } else if (Character.isWhitespace(c)) { 444 skipSpace(r); 445 } else if (c == AMP || c == -1) { 446 r.unread(); 447 return l; 448 } 449 } 450 } 451 } 452 453 return null; // Unreachable. 454 } 455 456 private <T> BeanMap<T> parseIntoBeanMap(UonReader r, BeanMap<T> m) throws Exception { 457 458 int c = r.readSkipWs(); 459 if (c == -1 || c == AMP) 460 return null; 461 if (c == 'n') 462 return (BeanMap<T>)parseNull(r); 463 if (c != '(') 464 throw new ParseException(loc(r), "Expected '(' at beginning of object."); 465 466 final int S1=1; // Looking for attrName start. 467 final int S2=2; // Found attrName end, looking for =. 468 final int S3=3; // Found =, looking for valStart. 469 final int S4=4; // Looking for , or } 470 boolean isInEscape = false; 471 472 int state = S1; 473 String currAttr = ""; 474 int currAttrLine = -1, currAttrCol = -1; 475 while (c != -1 && c != AMP) { 476 c = r.read(); 477 if (! isInEscape) { 478 if (state == S1) { 479 if (c == ')' || c == -1 || c == AMP) { 480 return m; 481 } 482 if (Character.isWhitespace(c)) 483 skipSpace(r); 484 else { 485 r.unread(); 486 currAttrLine= r.getLine(); 487 currAttrCol = r.getColumn(); 488 currAttr = parseAttrName(r, decodeChars); 489 if (currAttr == null) // Value was '%00' 490 return null; 491 state = S2; 492 } 493 } else if (state == S2) { 494 if (c == EQ || c == '=') 495 state = S3; 496 else if (c == -1 || c == ',' || c == ')' || c == AMP) { 497 m.put(currAttr, null); 498 if (c == ')' || c == -1 || c == AMP) 499 return m; 500 state = S1; 501 } 502 } else if (state == S3) { 503 if (c == -1 || c == ',' || c == ')' || c == AMP) { 504 if (! currAttr.equals(getBeanTypePropertyName(m.getClassMeta()))) { 505 BeanPropertyMeta pMeta = m.getPropertyMeta(currAttr); 506 if (pMeta == null) { 507 onUnknownProperty(r.getPipe(), currAttr, m, currAttrLine, currAttrCol); 508 } else { 509 Object value = convertToType("", pMeta.getClassMeta()); 510 pMeta.set(m, currAttr, value); 511 } 512 } 513 if (c == -1 || c == ')' || c == AMP) 514 return m; 515 state = S1; 516 } else { 517 if (! currAttr.equals(getBeanTypePropertyName(m.getClassMeta()))) { 518 BeanPropertyMeta pMeta = m.getPropertyMeta(currAttr); 519 if (pMeta == null) { 520 onUnknownProperty(r.getPipe(), currAttr, m, currAttrLine, currAttrCol); 521 parseAnything(object(), r.unread(), m.getBean(false), false, null); // Read content anyway to ignore it 522 } else { 523 setCurrentProperty(pMeta); 524 ClassMeta<?> cm = pMeta.getClassMeta(); 525 Object value = parseAnything(cm, r.unread(), m.getBean(false), false, pMeta); 526 setName(cm, value, currAttr); 527 pMeta.set(m, currAttr, value); 528 setCurrentProperty(null); 529 } 530 } 531 state = S4; 532 } 533 } else if (state == S4) { 534 if (c == ',') 535 state = S1; 536 else if (c == ')' || c == -1 || c == AMP) { 537 return m; 538 } 539 } 540 } 541 isInEscape = isInEscape(c, r, isInEscape); 542 } 543 if (state == S1) 544 throw new ParseException(loc(r), "Could not find attribute name on object."); 545 if (state == S2) 546 throw new ParseException(loc(r), "Could not find '=' following attribute name on object."); 547 if (state == S3) 548 throw new ParseException(loc(r), "Could not find value following '=' on object."); 549 if (state == S4) 550 throw new ParseException(loc(r), "Could not find ')' marking end of object."); 551 552 return null; // Unreachable. 553 } 554 555 private Object parseNull(UonReader r) throws Exception { 556 String s = parseString(r, false); 557 if ("ull".equals(s)) 558 return null; 559 throw new ParseException(loc(r), "Unexpected character sequence: ''{0}''", s); 560 } 561 562 /** 563 * Convenience method for parsing an attribute from the specified parser. 564 * 565 * @param r 566 * @param encoded 567 * @return The parsed object 568 * @throws Exception 569 */ 570 protected final Object parseAttr(UonReader r, boolean encoded) throws Exception { 571 Object attr; 572 attr = parseAttrName(r, encoded); 573 return attr; 574 } 575 576 /** 577 * Parses an attribute name from the specified reader. 578 * 579 * @param r 580 * @param encoded 581 * @return The parsed attribute name. 582 * @throws Exception 583 */ 584 protected final String parseAttrName(UonReader r, boolean encoded) throws Exception { 585 586 // If string is of form 'xxx', we're looking for ' at the end. 587 // Otherwise, we're looking for '&' or '=' or WS or -1 denoting the end of this string. 588 589 int c = r.peekSkipWs(); 590 if (c == '\'') 591 return parsePString(r); 592 593 r.mark(); 594 boolean isInEscape = false; 595 if (encoded) { 596 while (c != -1) { 597 c = r.read(); 598 if (! isInEscape) { 599 if (c == AMP || c == EQ || c == -1 || Character.isWhitespace(c)) { 600 if (c != -1) 601 r.unread(); 602 String s = r.getMarked(); 603 return ("null".equals(s) ? null : s); 604 } 605 } 606 else if (c == AMP) 607 r.replace('&'); 608 else if (c == EQ) 609 r.replace('='); 610 isInEscape = isInEscape(c, r, isInEscape); 611 } 612 } else { 613 while (c != -1) { 614 c = r.read(); 615 if (! isInEscape) { 616 if (c == '=' || c == -1 || Character.isWhitespace(c)) { 617 if (c != -1) 618 r.unread(); 619 String s = r.getMarked(); 620 return ("null".equals(s) ? null : trim(s)); 621 } 622 } 623 isInEscape = isInEscape(c, r, isInEscape); 624 } 625 } 626 627 // We should never get here. 628 throw new ParseException(loc(r), "Unexpected condition."); 629 } 630 631 632 /* 633 * Returns true if the next character in the stream is preceded by an escape '~' character. 634 */ 635 private static final boolean isInEscape(int c, ParserReader r, boolean prevIsInEscape) throws Exception { 636 if (c == '~' && ! prevIsInEscape) { 637 c = r.peek(); 638 if (escapedChars.contains(c)) { 639 r.delete(); 640 return true; 641 } 642 } 643 return false; 644 } 645 646 /** 647 * Parses a string value from the specified reader. 648 * 649 * @param r 650 * @param isUrlParamValue 651 * @return The parsed string. 652 * @throws Exception 653 */ 654 protected final String parseString(UonReader r, boolean isUrlParamValue) throws Exception { 655 656 // If string is of form 'xxx', we're looking for ' at the end. 657 // Otherwise, we're looking for ',' or ')' or -1 denoting the end of this string. 658 659 int c = r.peekSkipWs(); 660 if (c == '\'') 661 return parsePString(r); 662 663 r.mark(); 664 boolean isInEscape = false; 665 String s = null; 666 AsciiSet endChars = (isUrlParamValue ? endCharsParam : endCharsNormal); 667 while (c != -1) { 668 c = r.read(); 669 if (! isInEscape) { 670 // If this is a URL parameter value, we're looking for: & 671 // If not, we're looking for: &,) 672 if (endChars.contains(c)) { 673 r.unread(); 674 c = -1; 675 } 676 } 677 if (c == -1) 678 s = r.getMarked(); 679 else if (c == EQ) 680 r.replace('='); 681 else if (Character.isWhitespace(c) && ! isUrlParamValue) { 682 s = r.getMarked(0, -1); 683 skipSpace(r); 684 c = -1; 685 } 686 isInEscape = isInEscape(c, r, isInEscape); 687 } 688 689 if (isUrlParamValue) 690 s = StringUtils.trim(s); 691 692 return ("null".equals(s) ? null : trim(s)); 693 } 694 695 private static final AsciiSet endCharsParam = AsciiSet.create(""+AMP), endCharsNormal = AsciiSet.create(",)"+AMP); 696 697 698 /* 699 * Parses a string of the form "'foo'" 700 * All whitespace within parenthesis are preserved. 701 */ 702 private String parsePString(UonReader r) throws Exception { 703 704 r.read(); // Skip first quote. 705 r.mark(); 706 int c = 0; 707 708 boolean isInEscape = false; 709 while (c != -1) { 710 c = r.read(); 711 if (! isInEscape) { 712 if (c == '\'') 713 return trim(r.getMarked(0, -1)); 714 } 715 if (c == EQ) 716 r.replace('='); 717 isInEscape = isInEscape(c, r, isInEscape); 718 } 719 throw new ParseException(loc(r), "Unmatched parenthesis"); 720 } 721 722 private Boolean parseBoolean(UonReader r) throws Exception { 723 String s = parseString(r, false); 724 if (s == null || s.equals("null")) 725 return null; 726 if (s.equals("true")) 727 return true; 728 if (s.equals("false")) 729 return false; 730 throw new ParseException(loc(r), "Unrecognized syntax for boolean. ''{0}''.", s); 731 } 732 733 private Number parseNumber(UonReader r, Class<? extends Number> c) throws Exception { 734 String s = parseString(r, false); 735 if (s == null) 736 return null; 737 return StringUtils.parseNumber(s, c); 738 } 739 740 /* 741 * Call this method after you've finished a parsing a string to make sure that if there's any 742 * remainder in the input, that it consists only of whitespace and comments. 743 */ 744 private void validateEnd(UonReader r) throws Exception { 745 if (! validateEnd) 746 return; 747 while (true) { 748 int c = r.read(); 749 if (c == -1) 750 return; 751 if (! Character.isWhitespace(c)) 752 throw new ParseException(loc(r), "Remainder after parse: ''{0}''.", (char)c); 753 } 754 } 755 756 private static void skipSpace(ParserReader r) throws Exception { 757 int c = 0; 758 while ((c = r.read()) != -1) { 759 if (c <= 2 || ! Character.isWhitespace(c)) { 760 r.unread(); 761 return; 762 } 763 } 764 } 765 766 /** 767 * Returns a map identifying the current parse location. 768 * 769 * @param r The reader being read from. 770 * @return A map identifying the current parse location. 771 */ 772 protected final ObjectMap loc(UonReader r) { 773 return getLastLocation().append("line", r.getLine()).append("column", r.getColumn()); 774 } 775 776 /** 777 * Creates a {@link UonReader} from the specified parser pipe. 778 * 779 * @param pipe The parser input. 780 * @param decodeChars Whether the reader should automatically decode URL-encoded characters. 781 * @return A new {@link UonReader} object. 782 * @throws Exception 783 */ 784 @SuppressWarnings({ }) 785 public final UonReader getUonReader(ParserPipe pipe, boolean decodeChars) throws Exception { 786 Reader r = pipe.getReader(); 787 if (r instanceof UonReader) 788 return (UonReader)r; 789 return new UonReader(pipe, decodeChars); 790 } 791}