blob: bb4b01238495cf079dc26ebb5b3896af71e6697c [file] [log] [blame]
Felix Meschbergere3c0e4b2010-09-08 12:07:47 +00001/*
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +00002 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19package org.apache.felix.cm.file;
20
21
22import java.io.BufferedReader;
23import java.io.BufferedWriter;
24import java.io.IOException;
25import java.io.InputStream;
26import java.io.InputStreamReader;
27import java.io.OutputStream;
28import java.io.OutputStreamWriter;
29import java.io.PushbackReader;
30import java.io.Writer;
31import java.lang.reflect.Array;
Felix Meschbergeraeb8c5c2009-01-14 19:52:32 +000032import java.util.ArrayList;
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +000033import java.util.BitSet;
Felix Meschbergeraeb8c5c2009-01-14 19:52:32 +000034import java.util.Collection;
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +000035import java.util.Dictionary;
36import java.util.Enumeration;
37import java.util.HashMap;
38import java.util.Hashtable;
39import java.util.Iterator;
Felix Meschbergeraeb8c5c2009-01-14 19:52:32 +000040import java.util.List;
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +000041import java.util.Map;
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +000042
43
44/**
45 * The <code>ConfigurationHandler</code> class implements configuration reading
46 * form a <code>java.io.InputStream</code> and writing to a
47 * <code>java.io.OutputStream</code> on behalf of the
48 * {@link FilePersistenceManager} class.
Felix Meschbergere3c0e4b2010-09-08 12:07:47 +000049 *
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +000050 * <pre>
Felix Meschbergeraeb8c5c2009-01-14 19:52:32 +000051 * cfg = prop &quot;=&quot; value .
52 * prop = symbolic-name . // 1.4.2 of OSGi Core Specification
53 * symbolic-name = token { &quot;.&quot; token } .
54 * token = { [ 0..9 ] | [ a..z ] | [ A..Z ] | '_' | '-' } .
55 * value = [ type ] ( &quot;[&quot; values &quot;]&quot; | &quot;(&quot; values &quot;)&quot; | simple ) .
56 * values = simple { &quot;,&quot; simple } .
57 * simple = &quot;&quot;&quot; stringsimple &quot;&quot;&quot; .
58 * type = // 1-char type code .
59 * stringsimple = // quoted string representation of the value .
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +000060 * </pre>
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +000061 */
62public class ConfigurationHandler
63{
64 protected static final String ENCODING = "UTF-8";
65
66 protected static final int TOKEN_NAME = 'N';
67 protected static final int TOKEN_EQ = '=';
68 protected static final int TOKEN_ARR_OPEN = '[';
69 protected static final int TOKEN_ARR_CLOS = ']';
70 protected static final int TOKEN_VEC_OPEN = '(';
71 protected static final int TOKEN_VEC_CLOS = ')';
72 protected static final int TOKEN_COMMA = ',';
73 protected static final int TOKEN_VAL_OPEN = '"'; // '{';
74 protected static final int TOKEN_VAL_CLOS = '"'; // '}';
75
76 // simple types (string & primitive wrappers)
77 protected static final int TOKEN_SIMPLE_STRING = 'T';
78 protected static final int TOKEN_SIMPLE_INTEGER = 'I';
79 protected static final int TOKEN_SIMPLE_LONG = 'L';
80 protected static final int TOKEN_SIMPLE_FLOAT = 'F';
81 protected static final int TOKEN_SIMPLE_DOUBLE = 'D';
82 protected static final int TOKEN_SIMPLE_BYTE = 'X';
83 protected static final int TOKEN_SIMPLE_SHORT = 'S';
84 protected static final int TOKEN_SIMPLE_CHARACTER = 'C';
85 protected static final int TOKEN_SIMPLE_BOOLEAN = 'B';
86
87 // primitives
88 protected static final int TOKEN_PRIMITIVE_INT = 'i';
89 protected static final int TOKEN_PRIMITIVE_LONG = 'l';
90 protected static final int TOKEN_PRIMITIVE_FLOAT = 'f';
91 protected static final int TOKEN_PRIMITIVE_DOUBLE = 'd';
92 protected static final int TOKEN_PRIMITIVE_BYTE = 'x';
93 protected static final int TOKEN_PRIMITIVE_SHORT = 's';
94 protected static final int TOKEN_PRIMITIVE_CHAR = 'c';
95 protected static final int TOKEN_PRIMITIVE_BOOLEAN = 'b';
96
97 protected static final String CRLF = "\r\n";
98
99 protected static final Map type2Code;
100 protected static final Map code2Type;
101
102 // set of valid characters for "symblic-name"
103 private static final BitSet NAME_CHARS;
104 private static final BitSet TOKEN_CHARS;
105
106 static
107 {
108 type2Code = new HashMap();
109
110 // simple (exclusive String whose type code is not written)
111 type2Code.put( Integer.class, new Integer( TOKEN_SIMPLE_INTEGER ) );
112 type2Code.put( Long.class, new Integer( TOKEN_SIMPLE_LONG ) );
113 type2Code.put( Float.class, new Integer( TOKEN_SIMPLE_FLOAT ) );
114 type2Code.put( Double.class, new Integer( TOKEN_SIMPLE_DOUBLE ) );
115 type2Code.put( Byte.class, new Integer( TOKEN_SIMPLE_BYTE ) );
116 type2Code.put( Short.class, new Integer( TOKEN_SIMPLE_SHORT ) );
117 type2Code.put( Character.class, new Integer( TOKEN_SIMPLE_CHARACTER ) );
118 type2Code.put( Boolean.class, new Integer( TOKEN_SIMPLE_BOOLEAN ) );
119
120 // primitives
121 type2Code.put( Integer.TYPE, new Integer( TOKEN_PRIMITIVE_INT ) );
122 type2Code.put( Long.TYPE, new Integer( TOKEN_PRIMITIVE_LONG ) );
123 type2Code.put( Float.TYPE, new Integer( TOKEN_PRIMITIVE_FLOAT ) );
124 type2Code.put( Double.TYPE, new Integer( TOKEN_PRIMITIVE_DOUBLE ) );
125 type2Code.put( Byte.TYPE, new Integer( TOKEN_PRIMITIVE_BYTE ) );
126 type2Code.put( Short.TYPE, new Integer( TOKEN_PRIMITIVE_SHORT ) );
127 type2Code.put( Character.TYPE, new Integer( TOKEN_PRIMITIVE_CHAR ) );
128 type2Code.put( Boolean.TYPE, new Integer( TOKEN_PRIMITIVE_BOOLEAN ) );
129
130 // reverse map to map type codes to classes, string class mapping
131 // to be added manually, as the string type code is not written and
132 // hence not included in the type2Code map
133 code2Type = new HashMap();
134 for ( Iterator ti = type2Code.entrySet().iterator(); ti.hasNext(); )
135 {
136 Map.Entry entry = ( Map.Entry ) ti.next();
137 code2Type.put( entry.getValue(), entry.getKey() );
138 }
139 code2Type.put( new Integer( TOKEN_SIMPLE_STRING ), String.class );
140
141 NAME_CHARS = new BitSet();
142 for ( int i = '0'; i <= '9'; i++ )
143 NAME_CHARS.set( i );
144 for ( int i = 'a'; i <= 'z'; i++ )
145 NAME_CHARS.set( i );
146 for ( int i = 'A'; i <= 'Z'; i++ )
147 NAME_CHARS.set( i );
148 NAME_CHARS.set( '_' );
149 NAME_CHARS.set( '-' );
150 NAME_CHARS.set( '.' );
Felix Meschbergere3c0e4b2010-09-08 12:07:47 +0000151 NAME_CHARS.set( '\\' );
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000152
153 TOKEN_CHARS = new BitSet();
154 TOKEN_CHARS.set( TOKEN_EQ );
155 TOKEN_CHARS.set( TOKEN_ARR_OPEN );
156 TOKEN_CHARS.set( TOKEN_ARR_CLOS );
157 TOKEN_CHARS.set( TOKEN_VEC_OPEN );
158 TOKEN_CHARS.set( TOKEN_VEC_CLOS );
159 TOKEN_CHARS.set( TOKEN_COMMA );
160 TOKEN_CHARS.set( TOKEN_VAL_OPEN );
161 TOKEN_CHARS.set( TOKEN_VAL_CLOS );
162 TOKEN_CHARS.set( TOKEN_SIMPLE_STRING );
163 TOKEN_CHARS.set( TOKEN_SIMPLE_INTEGER );
164 TOKEN_CHARS.set( TOKEN_SIMPLE_LONG );
165 TOKEN_CHARS.set( TOKEN_SIMPLE_FLOAT );
166 TOKEN_CHARS.set( TOKEN_SIMPLE_DOUBLE );
167 TOKEN_CHARS.set( TOKEN_SIMPLE_BYTE );
168 TOKEN_CHARS.set( TOKEN_SIMPLE_SHORT );
169 TOKEN_CHARS.set( TOKEN_SIMPLE_CHARACTER );
170 TOKEN_CHARS.set( TOKEN_SIMPLE_BOOLEAN );
171
172 // primitives
173 TOKEN_CHARS.set( TOKEN_PRIMITIVE_INT );
174 TOKEN_CHARS.set( TOKEN_PRIMITIVE_LONG );
175 TOKEN_CHARS.set( TOKEN_PRIMITIVE_FLOAT );
176 TOKEN_CHARS.set( TOKEN_PRIMITIVE_DOUBLE );
177 TOKEN_CHARS.set( TOKEN_PRIMITIVE_BYTE );
178 TOKEN_CHARS.set( TOKEN_PRIMITIVE_SHORT );
179 TOKEN_CHARS.set( TOKEN_PRIMITIVE_CHAR );
180 TOKEN_CHARS.set( TOKEN_PRIMITIVE_BOOLEAN );
181 }
182
183
184 /**
185 * Writes the configuration data from the <code>Dictionary</code> to the
186 * given <code>OutputStream</code>.
187 * <p>
188 * This method writes at the current location in the stream and does not
189 * close the outputstream.
Felix Meschbergere3c0e4b2010-09-08 12:07:47 +0000190 *
Felix Meschbergeraeb8c5c2009-01-14 19:52:32 +0000191 * @param out
192 * The <code>OutputStream</code> to write the configurtion data
193 * to.
194 * @param properties
195 * The <code>Dictionary</code> to write.
196 * @throws IOException
197 * If an error occurrs writing to the output stream.
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000198 */
199 public static void write( OutputStream out, Dictionary properties ) throws IOException
200 {
201 BufferedWriter bw = new BufferedWriter( new OutputStreamWriter( out, ENCODING ) );
202
203 for ( Enumeration ce = properties.keys(); ce.hasMoreElements(); )
204 {
205 String key = ( String ) ce.nextElement();
206
Felix Meschbergeraeb8c5c2009-01-14 19:52:32 +0000207 // cfg = prop "=" value "." .
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000208 writeQuoted( bw, key );
209 bw.write( TOKEN_EQ );
210 writeValue( bw, properties.get( key ) );
211 bw.write( CRLF );
212 }
213
214 bw.flush();
215 }
216
217
218 /**
219 * Reads configuration data from the given <code>InputStream</code> and
220 * returns a new <code>Dictionary</code> object containing the data.
221 * <p>
Felix Meschbergeraeb8c5c2009-01-14 19:52:32 +0000222 * This method reads from the current location in the stream upto the end of
223 * the stream but does not close the stream at the end.
Felix Meschbergere3c0e4b2010-09-08 12:07:47 +0000224 *
Felix Meschbergeraeb8c5c2009-01-14 19:52:32 +0000225 * @param ins
226 * The <code>InputStream</code> from which to read the
227 * configuration data.
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000228 * @return A <code>Dictionary</code> object containing the configuration
Felix Meschbergeraeb8c5c2009-01-14 19:52:32 +0000229 * data. This object may be empty if the stream contains no
230 * configuration data.
231 * @throws IOException
232 * If an error occurrs reading from the stream. This exception
233 * is also thrown if a syntax error is encountered.
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000234 */
235 public static Dictionary read( InputStream ins ) throws IOException
236 {
237 return new ConfigurationHandler().readInternal( ins );
238 }
239
240
Felix Meschbergeraeb8c5c2009-01-14 19:52:32 +0000241 // private constructor, this class is not to be instantiated from the
242 // outside
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000243 private ConfigurationHandler()
244 {
245 }
246
Felix Meschbergeraeb8c5c2009-01-14 19:52:32 +0000247 // ---------- Configuration Input Implementation ---------------------------
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000248
249 private int token;
250 private String tokenValue;
251 private int line;
252 private int pos;
253
254
255 private Dictionary readInternal( InputStream ins ) throws IOException
256 {
257 BufferedReader br = new BufferedReader( new InputStreamReader( ins, ENCODING ) );
258 PushbackReader pr = new PushbackReader( br, 1 );
259
260 token = 0;
261 tokenValue = null;
262 line = 0;
263 pos = 0;
264
265 Hashtable configuration = new Hashtable();
266 token = 0;
267 while ( nextToken( pr ) == TOKEN_NAME )
268 {
269 String key = tokenValue;
270
271 // expect equal sign
272 if ( nextToken( pr ) != TOKEN_EQ )
273 {
274 throw readFailure( token, TOKEN_EQ );
275 }
276
277 // expect the token value
278 Object value = readValue( pr );
279 if ( value != null )
280 {
281 configuration.put( key, value );
282 }
283 }
284
285 return configuration;
286 }
287
288
289 /**
Felix Meschbergeraeb8c5c2009-01-14 19:52:32 +0000290 * value = type ( "[" values "]" | "(" values ")" | simple ) . values =
291 * value { "," value } . simple = "{" stringsimple "}" . type = // 1-char
292 * type code . stringsimple = // quoted string representation of the value .
Felix Meschbergere3c0e4b2010-09-08 12:07:47 +0000293 *
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000294 * @param pr
295 * @return
296 * @throws IOException
297 */
298 private Object readValue( PushbackReader pr ) throws IOException
299 {
300 // read (optional) type code
301 int type = read( pr );
302
303 // read value kind code if type code is not a value kinde code
304 int code;
305 if ( code2Type.containsKey( new Integer( type ) ) )
306 {
307 code = read( pr );
308 }
309 else
310 {
311 code = type;
312 type = TOKEN_SIMPLE_STRING;
313 }
314
315 switch ( code )
316 {
317 case TOKEN_ARR_OPEN:
318 return readArray( type, pr );
319
320 case TOKEN_VEC_OPEN:
Felix Meschbergeraeb8c5c2009-01-14 19:52:32 +0000321 return readCollection( type, pr );
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000322
323 case TOKEN_VAL_OPEN:
Felix Meschbergere3c0e4b2010-09-08 12:07:47 +0000324 Object value = readSimple( type, pr );
325 ensureNext( pr, TOKEN_VAL_CLOS );
326 return value;
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000327
328 default:
329 return null;
330 }
331 }
332
333
334 private Object readArray( int typeCode, PushbackReader pr ) throws IOException
335 {
Felix Meschbergeraeb8c5c2009-01-14 19:52:32 +0000336 List list = new ArrayList();
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000337 for ( ;; )
338 {
339 if ( !checkNext( pr, TOKEN_VAL_OPEN ) )
340 {
341 return null;
342 }
343
344 Object value = readSimple( typeCode, pr );
345 if ( value == null )
346 {
347 // abort due to error
348 return null;
349 }
350
Felix Meschbergere3c0e4b2010-09-08 12:07:47 +0000351 ensureNext( pr, TOKEN_VAL_CLOS );
352
Felix Meschbergeraeb8c5c2009-01-14 19:52:32 +0000353 list.add( value );
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000354
355 int c = read( pr );
356 if ( c == TOKEN_ARR_CLOS )
357 {
358 Class type = ( Class ) code2Type.get( new Integer( typeCode ) );
Felix Meschbergeraeb8c5c2009-01-14 19:52:32 +0000359 Object array = Array.newInstance( type, list.size() );
360 for ( int i = 0; i < list.size(); i++ )
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000361 {
Felix Meschbergeraeb8c5c2009-01-14 19:52:32 +0000362 Array.set( array, i, list.get( i ) );
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000363 }
364 return array;
365 }
366 else if ( c < 0 )
367 {
368 return null;
369 }
370 else if ( c != TOKEN_COMMA )
371 {
372 return null;
373 }
374 }
375 }
376
377
Felix Meschbergeraeb8c5c2009-01-14 19:52:32 +0000378 private Collection readCollection( int typeCode, PushbackReader pr ) throws IOException
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000379 {
Felix Meschbergeraeb8c5c2009-01-14 19:52:32 +0000380 Collection collection = new ArrayList();
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000381 for ( ;; )
382 {
383 if ( !checkNext( pr, TOKEN_VAL_OPEN ) )
384 {
385 return null;
386 }
387
388 Object value = readSimple( typeCode, pr );
389 if ( value == null )
390 {
391 // abort due to error
392 return null;
393 }
394
Felix Meschbergere3c0e4b2010-09-08 12:07:47 +0000395 ensureNext( pr, TOKEN_VAL_CLOS );
396
Felix Meschbergeraeb8c5c2009-01-14 19:52:32 +0000397 collection.add( value );
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000398
399 int c = read( pr );
400 if ( c == TOKEN_VEC_CLOS )
401 {
Felix Meschbergeraeb8c5c2009-01-14 19:52:32 +0000402 return collection;
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000403 }
404 else if ( c < 0 )
405 {
406 return null;
407 }
408 else if ( c != TOKEN_COMMA )
409 {
410 return null;
411 }
412 }
413 }
414
415
416 private Object readSimple( int code, PushbackReader pr ) throws IOException
417 {
418 switch ( code )
419 {
420 case -1:
421 return null;
422
423 case TOKEN_SIMPLE_STRING:
424 return readQuoted( pr );
425
426 // Simple/Primitive, only use wrapper classes
427 case TOKEN_SIMPLE_INTEGER:
428 case TOKEN_PRIMITIVE_INT:
429 return Integer.valueOf( readQuoted( pr ) );
430
431 case TOKEN_SIMPLE_LONG:
432 case TOKEN_PRIMITIVE_LONG:
433 return Long.valueOf( readQuoted( pr ) );
434
435 case TOKEN_SIMPLE_FLOAT:
436 case TOKEN_PRIMITIVE_FLOAT:
437 int fBits = Integer.parseInt( readQuoted( pr ) );
438 return new Float( Float.intBitsToFloat( fBits ) );
439
440 case TOKEN_SIMPLE_DOUBLE:
441 case TOKEN_PRIMITIVE_DOUBLE:
442 long dBits = Long.parseLong( readQuoted( pr ) );
443 return new Double( Double.longBitsToDouble( dBits ) );
444
445 case TOKEN_SIMPLE_BYTE:
446 case TOKEN_PRIMITIVE_BYTE:
447 return Byte.valueOf( readQuoted( pr ) );
448
449 case TOKEN_SIMPLE_SHORT:
450 case TOKEN_PRIMITIVE_SHORT:
451 return Short.valueOf( readQuoted( pr ) );
452
453 case TOKEN_SIMPLE_CHARACTER:
454 case TOKEN_PRIMITIVE_CHAR:
455 String cString = readQuoted( pr );
456 if ( cString != null && cString.length() > 0 )
457 {
458 return new Character( cString.charAt( 0 ) );
459 }
460 return null;
461
462 case TOKEN_SIMPLE_BOOLEAN:
463 case TOKEN_PRIMITIVE_BOOLEAN:
464 return Boolean.valueOf( readQuoted( pr ) );
465
466 // unknown type code
467 default:
468 return null;
469 }
470 }
471
472
Felix Meschbergere3c0e4b2010-09-08 12:07:47 +0000473 private void ensureNext( PushbackReader pr, int expected ) throws IOException
474 {
475 int next = read( pr );
476 if ( next != expected )
477 {
478 readFailure( next, expected );
479 }
480 }
481
482
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000483 private boolean checkNext( PushbackReader pr, int expected ) throws IOException
484 {
485 int next = read( pr );
486 if ( next < 0 )
487 {
488 return false;
489 }
490
491 if ( next == expected )
492 {
493 return true;
494 }
495
496 return false;
497 }
498
499
500 private String readQuoted( PushbackReader pr ) throws IOException
501 {
502 StringBuffer buf = new StringBuffer();
503 for ( ;; )
504 {
505 int c = read( pr );
506 switch ( c )
507 {
508 // escaped character
509 case '\\':
510 c = read( pr );
511 switch ( c )
512 {
513 // well known escapes
514 case 'b':
515 buf.append( '\b' );
516 break;
517 case 't':
518 buf.append( '\t' );
519 break;
520 case 'n':
521 buf.append( '\n' );
522 break;
523 case 'f':
524 buf.append( '\f' );
525 break;
526 case 'r':
527 buf.append( '\r' );
528 break;
529 case 'u':// need 4 characters !
530 char[] cbuf = new char[4];
531 if ( read( pr, cbuf ) == 4 )
532 {
533 c = Integer.parseInt( new String( cbuf ), 16 );
534 buf.append( ( char ) c );
535 }
536 break;
537
538 // just an escaped character, unescape
539 default:
540 buf.append( ( char ) c );
541 }
542 break;
543
544 // eof
545 case -1: // fall through
546
Felix Meschbergere3c0e4b2010-09-08 12:07:47 +0000547 // separator token
548 case TOKEN_EQ:
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000549 case TOKEN_VAL_CLOS:
Felix Meschbergere3c0e4b2010-09-08 12:07:47 +0000550 pr.unread( c );
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000551 return buf.toString();
552
Felix Meschbergere3c0e4b2010-09-08 12:07:47 +0000553 // no escaping
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000554 default:
555 buf.append( ( char ) c );
556 }
557 }
558 }
559
560
561 private int nextToken( PushbackReader pr ) throws IOException
562 {
563 int c = ignorableWhiteSpace( pr );
564
565 // immediately return EOF
566 if ( c < 0 )
567 {
568 return ( token = c );
569 }
570
571 // check whether there is a name
Felix Meschbergere3c0e4b2010-09-08 12:07:47 +0000572 if ( NAME_CHARS.get( c ) || !TOKEN_CHARS.get( c ) )
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000573 {
574 // read the property name
Felix Meschbergere3c0e4b2010-09-08 12:07:47 +0000575 pr.unread( c );
576 tokenValue = readQuoted( pr );
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000577 return ( token = TOKEN_NAME );
578 }
579
580 // check another token
581 if ( TOKEN_CHARS.get( c ) )
582 {
583 return ( token = c );
584 }
585
586 // unexpected character -> so what ??
587 return ( token = -1 );
588 }
589
590
591 private int ignorableWhiteSpace( PushbackReader pr ) throws IOException
592 {
593 int c = read( pr );
594 while ( c >= 0 && Character.isWhitespace( ( char ) c ) )
595 {
596 c = read( pr );
597 }
598 return c;
599 }
600
601
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000602 private int read( PushbackReader pr ) throws IOException
603 {
604 int c = pr.read();
605 if ( c == '\r' )
606 {
607 int c1 = pr.read();
608 if ( c1 != '\n' )
609 {
610 pr.unread( c1 );
611 }
612 c = '\n';
613 }
614
615 if ( c == '\n' )
616 {
617 line++;
618 pos = 0;
619 }
620 else
621 {
622 pos++;
623 }
624
625 return c;
626 }
627
628
629 private int read( PushbackReader pr, char[] buf ) throws IOException
630 {
631 for ( int i = 0; i < buf.length; i++ )
632 {
633 int c = read( pr );
634 if ( c >= 0 )
635 {
636 buf[i] = ( char ) c;
637 }
638 else
639 {
640 return i;
641 }
642 }
643
644 return buf.length;
645 }
646
647
648 private IOException readFailure( int current, int expected )
649 {
650 return new IOException( "Unexpected token " + current + "; expected: " + expected + " (line=" + line + ", pos="
651 + pos + ")" );
652 }
653
654
Felix Meschbergeraeb8c5c2009-01-14 19:52:32 +0000655 // ---------- Configuration Output Implementation --------------------------
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000656
657 private static void writeValue( Writer out, Object value ) throws IOException
658 {
659 Class clazz = value.getClass();
660 if ( clazz.isArray() )
661 {
662 writeArray( out, value );
663 }
Felix Meschbergeraeb8c5c2009-01-14 19:52:32 +0000664 else if ( value instanceof Collection )
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000665 {
Felix Meschbergeraeb8c5c2009-01-14 19:52:32 +0000666 writeCollection( out, ( Collection ) value );
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000667 }
668 else
669 {
670 writeType( out, clazz );
671 writeSimple( out, value );
672 }
673 }
674
675
676 private static void writeArray( Writer out, Object arrayValue ) throws IOException
677 {
678 int size = Array.getLength( arrayValue );
679 if ( size == 0 )
680 {
681 return;
682 }
683
684 writeType( out, arrayValue.getClass().getComponentType() );
685 out.write( TOKEN_ARR_OPEN );
686 for ( int i = 0; i < size; i++ )
687 {
688 if ( i > 0 )
689 out.write( TOKEN_COMMA );
690 writeSimple( out, Array.get( arrayValue, i ) );
691 }
692 out.write( TOKEN_ARR_CLOS );
693 }
694
695
Felix Meschbergeraeb8c5c2009-01-14 19:52:32 +0000696 private static void writeCollection( Writer out, Collection collection ) throws IOException
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000697 {
Felix Meschbergeraeb8c5c2009-01-14 19:52:32 +0000698 if ( collection.isEmpty() )
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000699 {
700 return;
701 }
702
Felix Meschbergeraeb8c5c2009-01-14 19:52:32 +0000703 Iterator ci = collection.iterator();
704 Object firstElement = ci.next();
705
706 writeType( out, firstElement.getClass() );
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000707 out.write( TOKEN_VEC_OPEN );
Felix Meschbergeraeb8c5c2009-01-14 19:52:32 +0000708 writeSimple( out, firstElement );
709
710 while ( ci.hasNext() )
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000711 {
Felix Meschbergeraeb8c5c2009-01-14 19:52:32 +0000712 out.write( TOKEN_COMMA );
713 writeSimple( out, ci.next() );
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000714 }
715 out.write( TOKEN_VEC_CLOS );
716 }
717
718
719 private static void writeType( Writer out, Class valueType ) throws IOException
720 {
721 Integer code = ( Integer ) type2Code.get( valueType );
722 if ( code != null )
723 {
724 out.write( ( char ) code.intValue() );
725 }
726 }
727
728
729 private static void writeSimple( Writer out, Object value ) throws IOException
730 {
731 if ( value instanceof Double )
732 {
733 double dVal = ( ( Double ) value ).doubleValue();
734 value = new Long( Double.doubleToRawLongBits( dVal ) );
735 }
736 else if ( value instanceof Float )
737 {
738 float fVal = ( ( Float ) value ).floatValue();
739 value = new Integer( Float.floatToRawIntBits( fVal ) );
740 }
741
742 out.write( TOKEN_VAL_OPEN );
743 writeQuoted( out, String.valueOf( value ) );
744 out.write( TOKEN_VAL_CLOS );
745 }
746
747
748 private static void writeQuoted( Writer out, String simple ) throws IOException
749 {
750 if ( simple == null || simple.length() == 0 )
751 {
752 return;
753 }
754
755 char c = 0;
756 int len = simple.length();
757 for ( int i = 0; i < len; i++ )
758 {
759 c = simple.charAt( i );
760 switch ( c )
761 {
762 case '\\':
763 case TOKEN_VAL_CLOS:
Felix Meschbergere3c0e4b2010-09-08 12:07:47 +0000764 case ' ':
765 case TOKEN_EQ:
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000766 out.write( '\\' );
767 out.write( c );
768 break;
769
770 // well known escapes
771 case '\b':
772 out.write( "\\b" );
773 break;
774 case '\t':
775 out.write( "\\t" );
776 break;
777 case '\n':
778 out.write( "\\n" );
779 break;
780 case '\f':
781 out.write( "\\f" );
782 break;
783 case '\r':
784 out.write( "\\r" );
785 break;
786
787 // other escaping
788 default:
789 if ( c < ' ' )
790 {
791 String t = "000" + Integer.toHexString( c );
792 out.write( "\\u" + t.substring( t.length() - 4 ) );
793 }
794 else
795 {
796 out.write( c );
797 }
798 }
799 }
800 }
801}