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 java.lang.reflect.*; 016 017import org.apache.juneau.*; 018import org.apache.juneau.httppart.*; 019import org.apache.juneau.parser.*; 020import org.apache.juneau.urlencoding.*; 021 022/** 023 * Parses UON (a notation for URL-encoded query parameter values) text into POJO models. 024 * 025 * <h5 class='topic'>Media types</h5> 026 * 027 * Handles <code>Content-Type</code> types: <code><b>text/uon</b></code> 028 * 029 * <h5 class='topic'>Description</h5> 030 * 031 * This parser uses a state machine, which makes it very fast and efficient. 032 */ 033public class UonParser extends ReaderParser implements HttpPartParser { 034 035 //------------------------------------------------------------------------------------------------------------------- 036 // Configurable properties 037 //------------------------------------------------------------------------------------------------------------------- 038 039 private static final String PREFIX = "UonParser."; 040 041 /** 042 * Configuration property: Decode <js>"%xx"</js> sequences. 043 * 044 * <h5 class='section'>Property:</h5> 045 * <ul> 046 * <li><b>Name:</b> <js>"UonParser.decoding.b"</js> 047 * <li><b>Data type:</b> <code>Boolean</code> 048 * <li><b>Default:</b> <jk>false</jk> for {@link UonParser}, <jk>true</jk> for {@link UrlEncodingParser} 049 * <li><b>Session property:</b> <jk>false</jk> 050 * <li><b>Methods:</b> 051 * <ul> 052 * <li class='jm'>{@link UonParserBuilder#decoding(boolean)} 053 * <li class='jm'>{@link UonParserBuilder#decoding()} 054 * </ul> 055 * </ul> 056 * 057 * <h5 class='section'>Description:</h5> 058 * <p> 059 * Specify <jk>true</jk> if URI encoded characters should be decoded, <jk>false</jk> if they've already been decoded 060 * before being passed to this parser. 061 * 062 * <h5 class='section'>Example:</h5> 063 * <p class='bcode w800'> 064 * <jc>// Create a decoding UON parser.</jc> 065 * ReaderParser p = UonParser. 066 * .<jsm>create</jsm>() 067 * .decoding() 068 * .build(); 069 * 070 * <jc>// Same, but use property.</jc> 071 * ReaderParser p = UonParser. 072 * .<jsm>create</jsm>() 073 * .set(<jsf>UON_decoding</jsf>, <jk>true</jk>) 074 * .build(); 075 * 076 * <jc>// Produces: ["foo bar", "baz quz"].</jc> 077 * String[] foo = p.parse(<js>"@(foo%20bar,baz%20qux)"</js>, String[].<jk>class</jk>); 078 * </p> 079 */ 080 public static final String UON_decoding = PREFIX + "decoding.b"; 081 082 /** 083 * Configuration property: Validate end. 084 * 085 * <h5 class='section'>Property:</h5> 086 * <ul> 087 * <li><b>Name:</b> <js>"UonParser.validateEnd.b"</js> 088 * <li><b>Data type:</b> <code>Boolean</code> 089 * <li><b>Default:</b> <jk>false</jk> 090 * <li><b>Session property:</b> <jk>false</jk> 091 * <li><b>Methods:</b> 092 * <ul> 093 * <li class='jm'>{@link UonParserBuilder#validateEnd(boolean)} 094 * <li class='jm'>{@link UonParserBuilder#validateEnd()} 095 * </ul> 096 * </ul> 097 * 098 * <h5 class='section'>Description:</h5> 099 * <p> 100 * If <jk>true</jk>, after parsing a POJO from the input, verifies that the remaining input in 101 * the stream consists of only comments or whitespace. 102 * 103 * <h5 class='section'>Example:</h5> 104 * <p class='bcode w800'> 105 * <jc>// Create a parser using strict mode.</jc> 106 * ReaderParser p = UonParser. 107 * .<jsm>create</jsm>() 108 * .validateEnd() 109 * .build(); 110 * 111 * <jc>// Same, but use property.</jc> 112 * ReaderParser p = UonParser. 113 * .<jsm>create</jsm>() 114 * .set(<jsf>UON_validateEnd</jsf>, <jk>true</jk>) 115 * .build(); 116 * 117 * <jc>// Should fail because input has multiple POJOs.</jc> 118 * String in = <js>"(foo=bar)(baz=qux)"</js>; 119 * MyBean myBean = p.parse(in, MyBean.<jk>class</jk>); 120 * </p> 121 */ 122 public static final String UON_validateEnd = PREFIX + "validateEnd.b"; 123 124 //------------------------------------------------------------------------------------------------------------------- 125 // Predefined instances 126 //------------------------------------------------------------------------------------------------------------------- 127 128 /** Reusable instance of {@link UonParser}, all default settings. */ 129 public static final UonParser DEFAULT = new UonParser(PropertyStore.DEFAULT); 130 131 /** Reusable instance of {@link UonParser} with decodeChars set to true. */ 132 public static final UonParser DEFAULT_DECODING = new UonParser.Decoding(PropertyStore.DEFAULT); 133 134 135 //------------------------------------------------------------------------------------------------------------------- 136 // Predefined subclasses 137 //------------------------------------------------------------------------------------------------------------------- 138 139 /** Default parser, decoding. */ 140 public static class Decoding extends UonParser { 141 142 /** 143 * Constructor. 144 * 145 * @param ps The property store containing all the settings for this object. 146 */ 147 public Decoding(PropertyStore ps) { 148 super(ps.builder().set(UON_decoding, true).build()); 149 } 150 } 151 152 153 //------------------------------------------------------------------------------------------------------------------- 154 // Instance 155 //------------------------------------------------------------------------------------------------------------------- 156 157 private final boolean 158 decodeChars, validateEnd; 159 160 /** 161 * Constructor. 162 * 163 * @param ps 164 * The property store containing all the settings for this object. 165 */ 166 public UonParser(PropertyStore ps) { 167 this(ps, "text/uon"); 168 } 169 170 /** 171 * Constructor. 172 * 173 * @param ps 174 * The property store containing all the settings for this object. 175 * @param consumes 176 * The list of media types that this parser consumes (e.g. <js>"application/json"</js>, <js>"*​/json"</js>). 177 */ 178 public UonParser(PropertyStore ps, String...consumes) { 179 super(ps, consumes); 180 this.decodeChars = getBooleanProperty(UON_decoding, false); 181 this.validateEnd = getBooleanProperty(UON_validateEnd, false); 182 } 183 184 @Override /* Context */ 185 public UonParserBuilder builder() { 186 return new UonParserBuilder(getPropertyStore()); 187 } 188 189 /** 190 * Instantiates a new clean-slate {@link UonParserBuilder} object. 191 * 192 * <p> 193 * This is equivalent to simply calling <code><jk>new</jk> UonParserBuilder()</code>. 194 * 195 * <p> 196 * Note that this method creates a builder initialized to all default settings, whereas {@link #builder()} copies 197 * the settings of the object called on. 198 * 199 * @return A new {@link UonParserBuilder} object. 200 */ 201 public static UonParserBuilder create() { 202 return new UonParserBuilder(); 203 } 204 205 /** 206 * Create a UON parser session for parsing parameter values. 207 * 208 * @return A new parser session. 209 */ 210 protected final UonParserSession createParameterSession() { 211 return new UonParserSession(this, createDefaultSessionArgs(), false); 212 } 213 214 //----------------------------------------------------------------------------------------------------------------- 215 // Entry point methods 216 //----------------------------------------------------------------------------------------------------------------- 217 218 @Override /* Parser */ 219 public UonParserSession createSession(ParserSessionArgs args) { 220 return new UonParserSession(this, args); 221 } 222 223 @Override /* HttpPartParser */ 224 public UonParserSession createSession() { 225 return createSession(null); 226 } 227 228 @Override /* HttpPartParser */ 229 public UonParserSession createPartSession(ParserSessionArgs args) { 230 return new UonParserSession(this, args); 231 } 232 233 @Override /* HttpPartParser */ 234 public UonParserSession createPartSession() { 235 return createPartSession(null); 236 } 237 238 @Override /* HttpPartParser */ 239 public <T> T parse(HttpPartType partType, HttpPartSchema schema, String in, Class<T> toType) throws ParseException, SchemaValidationException { 240 return createPartSession().parse(partType, schema, in, toType); 241 } 242 243 @Override /* HttpPartParser */ 244 public <T> T parse(HttpPartType partType, HttpPartSchema schema, String in, Type toType, Type...toTypeArgs) throws ParseException, SchemaValidationException { 245 return createPartSession().parse(partType, schema, in, toType, toTypeArgs); 246 } 247 248 @Override /* HttpPartParser */ 249 public <T> T parse(HttpPartSchema schema, String in, Class<T> toType) throws ParseException, SchemaValidationException { 250 return createPartSession().parse(null, schema, in, toType); 251 } 252 253 @Override /* HttpPartParser */ 254 public <T> T parse(HttpPartSchema schema, String in, Type toType, Type...toTypeArgs) throws ParseException, SchemaValidationException { 255 return createPartSession().parse(null, schema, in, toType, toTypeArgs); 256 } 257 258 //----------------------------------------------------------------------------------------------------------------- 259 // Properties 260 //----------------------------------------------------------------------------------------------------------------- 261 262 /** 263 * Configuration property: Decode <js>"%xx"</js> sequences. 264 * 265 * @see #UON_decoding 266 * @return 267 * <jk>true</jk> if URI encoded characters should be decoded, <jk>false</jk> if they've already been decoded 268 * before being passed to this parser. 269 */ 270 protected final boolean isDecodeChars() { 271 return decodeChars; 272 } 273 274 /** 275 * Configuration property: Validate end. 276 * 277 * @see #UON_validateEnd 278 * @return 279 * <jk>true</jk> if after parsing a POJO from the input, verifies that the remaining input in 280 * the stream consists of only comments or whitespace. 281 */ 282 protected final boolean isValidateEnd() { 283 return validateEnd; 284 } 285 286 @Override /* Context */ 287 public ObjectMap asMap() { 288 return super.asMap() 289 .append("UonParser", new ObjectMap() 290 .append("decodeChars", decodeChars) 291 ); 292 } 293}