View Javadoc

1   package net.sourceforge.jparam.parser;
2   
3   import net.sourceforge.jparam.ConstantRegistry;
4   import net.sourceforge.jparam.JParamException;
5   import net.sourceforge.jparam.conversion.ConverterRegistry;
6   import net.sourceforge.jparam.conversion.weights.ScalarConversionWeight;
7   import net.sourceforge.jparam.util.Utils;
8   
9   /***
10   * A description of class TreeNode
11   */
12  public class SimpleLeaf implements TreeNode {
13  	public static final int CHAR = 0;
14  
15  	public static final int STRING = 1;
16  
17  	public static final int TENTATIVE = 2;
18  
19  	public static final int DECODED_STRING = 3;
20  
21  	private String value;
22  
23  	private int type;
24  
25  	/***
26  	 * Create a new TreeNode object.
27  	 */
28  	public SimpleLeaf(String value, int type) {
29  		this.value = value;
30  		this.type = type;
31  	}
32  
33  	public Object getConstructedValue(Class expectedType)
34  			throws JParamException {
35  		switch (type) {
36  		case CHAR:
37  			return new Character(decode_char(value));
38  		case STRING:
39  			return decode_string(value);
40  		case DECODED_STRING:
41  			return value;
42  		case TENTATIVE:
43  			return decode_tentative(expectedType, value);
44  		default:
45  			Utils.assert(false, "Impossible type: " + type);
46  			return null;
47  		}
48  	}
49  
50  	public ScalarConversionWeight getConversionWeight(Class expectedTypeClass) {
51  		ConverterRegistry cr = ConverterRegistry.getInstance();
52  		switch (type) {
53  		case CHAR:
54  			return cr.getConversionWeight(Character.class, expectedTypeClass);
55  		case STRING:
56  		case DECODED_STRING:
57  			return cr.getConversionWeight(String.class, expectedTypeClass);
58  		case TENTATIVE:
59  			int tentative_type = getTentativeType(value);
60  			Class tentativeClass;
61  			switch (tentative_type) {
62  			case T_INTEGER:
63  				tentativeClass = Integer.class;
64  				break;
65  			case T_DOUBLE:
66  				tentativeClass = Double.class;
67  				break;
68  			case T_FLOAT:
69  				tentativeClass = Float.class;
70  				break;
71  			case T_STRING:
72  				Object result = decode_tentative_string(value.substring(1));
73  				if (result != null) {
74  					tentativeClass = result.getClass();
75  				} else {
76  					if (expectedTypeClass.isPrimitive()) {
77  						return ScalarConversionWeight.CONV_IMPOSSIBLE;
78  					}
79  					
80  					return ScalarConversionWeight.CONV_STANDARD;
81  				}
82  				break;
83  			default:
84  				return ScalarConversionWeight.CONV_IMPOSSIBLE;
85  			}
86  			return cr.getConversionWeight(tentativeClass, expectedTypeClass);
87  		default:
88  			return ScalarConversionWeight.CONV_IMPOSSIBLE;
89  		}
90  	}
91  
92  	///////////////////////////////////////////////
93  	// Decoding Chars and Strings.
94  	///////////////////////////////////////////////
95  
96  	int hex_value(char digit) {
97  		if (digit >= '0' && digit <= '9')
98  			return digit - '0';
99  		if (digit >= 'A' && digit <= 'F')
100 			return 10 + digit - 'A';
101 		Utils.assert(digit >= 'a' && digit <= 'f',
102 				"invalid digit for hex value: " + digit);
103 		return 10 + digit - 'f';
104 	}
105 
106 	class StringWithPos {
107 		StringWithPos(String str, int pos) {
108 			this.str = str;
109 			this.pos = pos;
110 		}
111 
112 		public String str;
113 
114 		public int pos;
115 	}
116 
117 	char decode_next_char(StringWithPos swp) {
118 		Utils.assert(swp.pos < swp.str.length(), "Invalid position: " + swp.pos
119 				+ ", str=" + swp.str);
120 
121 		char c = swp.str.charAt(swp.pos++);
122 		if (c != '//')
123 			return c;
124 
125 		c = swp.str.charAt(swp.pos++);
126 		if (c == 'x' || c == 'X') {
127 			char most_digit = swp.str.charAt(swp.pos++);
128 			char least_digit = swp.str.charAt(swp.pos++);
129 			int uc = (hex_value(most_digit) << 4) | hex_value(least_digit);
130 			return (char) uc;
131 		}
132 
133 		switch (c) {
134 		case 'n':
135 			return '\n';
136 		case 't':
137 			return '\t';
138 		case 'v':
139 			return 11;
140 		case 'b':
141 			return '\b';
142 		case 'r':
143 			return '\r';
144 		case 'f':
145 			return '\f';
146 		case 'a':
147 			return 7;
148 		default:
149 			return c;
150 		}
151 	}
152 
153 	char decode_char(String str) {
154 		char decoded_char;
155 
156 		Utils.assert(str.charAt(0) == '\'', "String doesn't start with \':"
157 				+ str);
158 		StringWithPos swp = new StringWithPos(str, 1);
159 		decoded_char = decode_next_char(swp);
160 		Utils
161 				.assert(swp.pos == swp.str.length(),
162 						"Pos not at end of string, pos=" + swp.pos + ", str="
163 								+ swp.str); // make
164 		// sure
165 		// we
166 		// only
167 		// have
168 		// one
169 		// encoded
170 		// char
171 
172 		return decoded_char;
173 	}
174 
175 	String decode_string(String str) {
176 		String decoded_string = "";
177 
178 		Utils.assert(str.charAt(0) == '"', "String doesn't start with \":"
179 				+ str);
180 		StringWithPos swp = new StringWithPos(str, 1);
181 		while (swp.pos < swp.str.length()) {
182 			decoded_string += decode_next_char(swp);
183 		}
184 		// make sure we only have one encoded char
185 		Utils
186 				.assert(swp.pos == swp.str.length(),
187 						"Pos not at end of string, pos=" + swp.pos + ", str="
188 								+ swp.str);
189 
190 		return decoded_string;
191 	}
192 
193 	private final static int T_INTEGER = 0;
194 
195 	private final static int T_FLOAT = 1;
196 
197 	private final static int T_DOUBLE = 2;
198 
199 	private final static int T_STRING = 3;
200 
201 	private int getTentativeType(String value) {
202 		Utils.assert(!value.equals(""), "Empty string");
203 		int retVal;
204 		switch (value.charAt(0)) {
205 		case 'I':
206 			retVal = T_INTEGER;
207 			break;
208 		case 'R':
209 			char c = value.charAt(value.length() - 1);
210 			boolean isFloat = (c == 'f' || c == 'F');
211 			boolean isLongDouble = (c == 'l' || c == 'L');
212 
213 			if (isFloat) {
214 				retVal = T_FLOAT;
215 			} else if (isLongDouble) {
216 				retVal = T_DOUBLE;
217 			} else {
218 				retVal = T_DOUBLE;
219 			}
220 			break;
221 		case 'S':
222 			retVal = T_STRING;
223 			break;
224 		default:
225 			Utils.assert(false, "Invalid tentative type: " + value.charAt(0));
226 			retVal = -1;
227 		}
228 
229 		return retVal;
230 	}
231 
232 	///////////////////////////////////////////////
233 	// Decoding Tentative Values
234 	///////////////////////////////////////////////
235 	private Object decode_tentative(Class targetType, String value)
236 			throws JParamException {
237 		Utils.assert(!value.equals(""), "Empty string");
238 		int tentative_type = getTentativeType(value);
239 
240 		Object retVal;
241 		switch (tentative_type) {
242 		case T_INTEGER:
243 			Utils.assert(value.length() > 4, "invalid integer literal: "
244 					+ value);
245 
246 			int base = 0;
247 			switch (value.charAt(1)) {
248 			case 'D':
249 				base = 10;
250 				break;
251 			case 'H':
252 				base = 16;
253 				break;
254 			case 'O':
255 				base = 8;
256 				break;
257 			case 'B':
258 				base = 2;
259 				break;
260 			default:
261 				Utils.assert(false, "Unexpected base: " + value.charAt(1));
262 			}
263 			boolean isUnsigned = value.charAt(2) == 'U';
264 			boolean isLong = value.charAt(3) == 'L';
265 			retVal = decode_tentative_int(value.substring(4), base, isUnsigned,
266 					isLong);
267 			break;
268 		case T_FLOAT:
269 		case T_DOUBLE:
270 			char c = value.charAt(value.length() - 1);
271 			boolean isFloat = (c == 'f' || c == 'F');
272 			boolean isLongDouble = (c == 'l' || c == 'L');
273 
274 			if (isFloat || isLongDouble) {
275 				// remove modifier
276 				value = value.substring(1, value.length() - 1);
277 
278 				// long double is treated as double.
279 				retVal = decode_tentative_real(value, isFloat);
280 			} else {
281 				retVal = decode_tentative_real(value.substring(1), isFloat);
282 			}
283 			break;
284 		case T_STRING:
285 			retVal = decode_tentative_string(value.substring(1));
286 			break;
287 		default:
288 			Utils.assert(false, "Invalid tentative type: " + value.charAt(0));
289 			return null;
290 		}
291 		if ((retVal != null)
292 				&& (!targetType.isAssignableFrom(retVal.getClass()))) {
293 			return ConverterRegistry.getInstance().convert(targetType, retVal);
294 		}
295 
296 		return retVal;
297 	}
298 
299 	private Object decode_tentative_real(String value, boolean isFloat) {
300 		if (isFloat) {
301 			return new Float(value);
302 		}
303 
304 		return new Double(value);
305 	}
306 
307 	private Object decode_tentative_int(String value, int base,
308 			boolean isUnsigned, boolean isLong) {
309 		int nSuffix = 0;
310 		if (isLong)
311 			nSuffix++;
312 		if (isUnsigned)
313 			nSuffix++;
314 
315 		int n = value.length() - nSuffix;
316 		char c;
317 		int pos = 0;
318 
319 		// handle sign
320 		Utils.assert(n > pos, "Empty integer");
321 		boolean isNegative = false;
322 		c = value.charAt(pos);
323 		if ((c == '-') || (c == '+')) {
324 			pos++;
325 			if (c == '-')
326 				isNegative = true;
327 		}
328 
329 		// skip base prefix where relevant
330 		int nPrefix = 0;
331 		switch (base) {
332 		case 8:
333 		case 10:
334 			nPrefix = 0;
335 			break;
336 		case 2:
337 		case 16:
338 			nPrefix = 2;
339 			break;
340 		default:
341 			Utils.assert(false, "Invalid base: " + base);
342 		}
343 		pos += nPrefix;
344 
345 		// get string with sign, prefix chars, and suffix chars
346 		value = value.substring(pos, value.length() - nSuffix);
347 
348 		long l;
349 		try {
350 			l = Long.parseLong(value, base);
351 		} catch (NumberFormatException e) {
352 			throw new RuntimeException("Cannot convert '" + value
353 					+ "' to an integer.");
354 		}
355 		if (isNegative)
356 			l = -l;
357 		if (l > Integer.MAX_VALUE) {
358 			throw new RuntimeException("Integer too big - " + value);
359 		}
360 		return new Integer((int) l);
361 	}
362 
363 	private Object decode_tentative_string(String value) {
364 		if (ConstantRegistry.getInstance().isConstant(value)) {
365 			return ConstantRegistry.getInstance().getConstant(value);
366 		}
367 
368 		return value;
369 	}
370 
371 	public String toString() {
372 		return "SimpleLeaf, value=" + value + ", type=" + type;
373 	}
374 }