| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.felix.cm.file; |
| |
| |
| import java.io.BufferedReader; |
| import java.io.BufferedWriter; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.InputStreamReader; |
| import java.io.OutputStream; |
| import java.io.OutputStreamWriter; |
| import java.io.PushbackReader; |
| import java.io.Writer; |
| import java.lang.reflect.Array; |
| import java.util.ArrayList; |
| import java.util.BitSet; |
| import java.util.Collection; |
| import java.util.Dictionary; |
| import java.util.Enumeration; |
| import java.util.HashMap; |
| import java.util.Hashtable; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| |
| |
| /** |
| * The <code>ConfigurationHandler</code> class implements configuration reading |
| * form a <code>java.io.InputStream</code> and writing to a |
| * <code>java.io.OutputStream</code> on behalf of the |
| * {@link FilePersistenceManager} class. |
| * |
| * <pre> |
| * cfg = prop "=" value . |
| * prop = symbolic-name . // 1.4.2 of OSGi Core Specification |
| * symbolic-name = token { "." token } . |
| * token = { [ 0..9 ] | [ a..z ] | [ A..Z ] | '_' | '-' } . |
| * value = [ type ] ( "[" values "]" | "(" values ")" | simple ) . |
| * values = simple { "," simple } . |
| * simple = """ stringsimple """ . |
| * type = // 1-char type code . |
| * stringsimple = // quoted string representation of the value . |
| * </pre> |
| */ |
| public class ConfigurationHandler |
| { |
| protected static final String ENCODING = "UTF-8"; |
| |
| protected static final int TOKEN_NAME = 'N'; |
| protected static final int TOKEN_EQ = '='; |
| protected static final int TOKEN_ARR_OPEN = '['; |
| protected static final int TOKEN_ARR_CLOS = ']'; |
| protected static final int TOKEN_VEC_OPEN = '('; |
| protected static final int TOKEN_VEC_CLOS = ')'; |
| protected static final int TOKEN_COMMA = ','; |
| protected static final int TOKEN_VAL_OPEN = '"'; // '{'; |
| protected static final int TOKEN_VAL_CLOS = '"'; // '}'; |
| |
| // simple types (string & primitive wrappers) |
| protected static final int TOKEN_SIMPLE_STRING = 'T'; |
| protected static final int TOKEN_SIMPLE_INTEGER = 'I'; |
| protected static final int TOKEN_SIMPLE_LONG = 'L'; |
| protected static final int TOKEN_SIMPLE_FLOAT = 'F'; |
| protected static final int TOKEN_SIMPLE_DOUBLE = 'D'; |
| protected static final int TOKEN_SIMPLE_BYTE = 'X'; |
| protected static final int TOKEN_SIMPLE_SHORT = 'S'; |
| protected static final int TOKEN_SIMPLE_CHARACTER = 'C'; |
| protected static final int TOKEN_SIMPLE_BOOLEAN = 'B'; |
| |
| // primitives |
| protected static final int TOKEN_PRIMITIVE_INT = 'i'; |
| protected static final int TOKEN_PRIMITIVE_LONG = 'l'; |
| protected static final int TOKEN_PRIMITIVE_FLOAT = 'f'; |
| protected static final int TOKEN_PRIMITIVE_DOUBLE = 'd'; |
| protected static final int TOKEN_PRIMITIVE_BYTE = 'x'; |
| protected static final int TOKEN_PRIMITIVE_SHORT = 's'; |
| protected static final int TOKEN_PRIMITIVE_CHAR = 'c'; |
| protected static final int TOKEN_PRIMITIVE_BOOLEAN = 'b'; |
| |
| protected static final String CRLF = "\r\n"; |
| |
| protected static final Map type2Code; |
| protected static final Map code2Type; |
| |
| // set of valid characters for "symblic-name" |
| private static final BitSet NAME_CHARS; |
| private static final BitSet TOKEN_CHARS; |
| |
| static |
| { |
| type2Code = new HashMap(); |
| |
| // simple (exclusive String whose type code is not written) |
| type2Code.put( Integer.class, new Integer( TOKEN_SIMPLE_INTEGER ) ); |
| type2Code.put( Long.class, new Integer( TOKEN_SIMPLE_LONG ) ); |
| type2Code.put( Float.class, new Integer( TOKEN_SIMPLE_FLOAT ) ); |
| type2Code.put( Double.class, new Integer( TOKEN_SIMPLE_DOUBLE ) ); |
| type2Code.put( Byte.class, new Integer( TOKEN_SIMPLE_BYTE ) ); |
| type2Code.put( Short.class, new Integer( TOKEN_SIMPLE_SHORT ) ); |
| type2Code.put( Character.class, new Integer( TOKEN_SIMPLE_CHARACTER ) ); |
| type2Code.put( Boolean.class, new Integer( TOKEN_SIMPLE_BOOLEAN ) ); |
| |
| // primitives |
| type2Code.put( Integer.TYPE, new Integer( TOKEN_PRIMITIVE_INT ) ); |
| type2Code.put( Long.TYPE, new Integer( TOKEN_PRIMITIVE_LONG ) ); |
| type2Code.put( Float.TYPE, new Integer( TOKEN_PRIMITIVE_FLOAT ) ); |
| type2Code.put( Double.TYPE, new Integer( TOKEN_PRIMITIVE_DOUBLE ) ); |
| type2Code.put( Byte.TYPE, new Integer( TOKEN_PRIMITIVE_BYTE ) ); |
| type2Code.put( Short.TYPE, new Integer( TOKEN_PRIMITIVE_SHORT ) ); |
| type2Code.put( Character.TYPE, new Integer( TOKEN_PRIMITIVE_CHAR ) ); |
| type2Code.put( Boolean.TYPE, new Integer( TOKEN_PRIMITIVE_BOOLEAN ) ); |
| |
| // reverse map to map type codes to classes, string class mapping |
| // to be added manually, as the string type code is not written and |
| // hence not included in the type2Code map |
| code2Type = new HashMap(); |
| for ( Iterator ti = type2Code.entrySet().iterator(); ti.hasNext(); ) |
| { |
| Map.Entry entry = ( Map.Entry ) ti.next(); |
| code2Type.put( entry.getValue(), entry.getKey() ); |
| } |
| code2Type.put( new Integer( TOKEN_SIMPLE_STRING ), String.class ); |
| |
| NAME_CHARS = new BitSet(); |
| for ( int i = '0'; i <= '9'; i++ ) |
| NAME_CHARS.set( i ); |
| for ( int i = 'a'; i <= 'z'; i++ ) |
| NAME_CHARS.set( i ); |
| for ( int i = 'A'; i <= 'Z'; i++ ) |
| NAME_CHARS.set( i ); |
| NAME_CHARS.set( '_' ); |
| NAME_CHARS.set( '-' ); |
| NAME_CHARS.set( '.' ); |
| NAME_CHARS.set( '\\' ); |
| |
| TOKEN_CHARS = new BitSet(); |
| TOKEN_CHARS.set( TOKEN_EQ ); |
| TOKEN_CHARS.set( TOKEN_ARR_OPEN ); |
| TOKEN_CHARS.set( TOKEN_ARR_CLOS ); |
| TOKEN_CHARS.set( TOKEN_VEC_OPEN ); |
| TOKEN_CHARS.set( TOKEN_VEC_CLOS ); |
| TOKEN_CHARS.set( TOKEN_COMMA ); |
| TOKEN_CHARS.set( TOKEN_VAL_OPEN ); |
| TOKEN_CHARS.set( TOKEN_VAL_CLOS ); |
| TOKEN_CHARS.set( TOKEN_SIMPLE_STRING ); |
| TOKEN_CHARS.set( TOKEN_SIMPLE_INTEGER ); |
| TOKEN_CHARS.set( TOKEN_SIMPLE_LONG ); |
| TOKEN_CHARS.set( TOKEN_SIMPLE_FLOAT ); |
| TOKEN_CHARS.set( TOKEN_SIMPLE_DOUBLE ); |
| TOKEN_CHARS.set( TOKEN_SIMPLE_BYTE ); |
| TOKEN_CHARS.set( TOKEN_SIMPLE_SHORT ); |
| TOKEN_CHARS.set( TOKEN_SIMPLE_CHARACTER ); |
| TOKEN_CHARS.set( TOKEN_SIMPLE_BOOLEAN ); |
| |
| // primitives |
| TOKEN_CHARS.set( TOKEN_PRIMITIVE_INT ); |
| TOKEN_CHARS.set( TOKEN_PRIMITIVE_LONG ); |
| TOKEN_CHARS.set( TOKEN_PRIMITIVE_FLOAT ); |
| TOKEN_CHARS.set( TOKEN_PRIMITIVE_DOUBLE ); |
| TOKEN_CHARS.set( TOKEN_PRIMITIVE_BYTE ); |
| TOKEN_CHARS.set( TOKEN_PRIMITIVE_SHORT ); |
| TOKEN_CHARS.set( TOKEN_PRIMITIVE_CHAR ); |
| TOKEN_CHARS.set( TOKEN_PRIMITIVE_BOOLEAN ); |
| } |
| |
| |
| /** |
| * Writes the configuration data from the <code>Dictionary</code> to the |
| * given <code>OutputStream</code>. |
| * <p> |
| * This method writes at the current location in the stream and does not |
| * close the outputstream. |
| * |
| * @param out |
| * The <code>OutputStream</code> to write the configurtion data |
| * to. |
| * @param properties |
| * The <code>Dictionary</code> to write. |
| * @throws IOException |
| * If an error occurrs writing to the output stream. |
| */ |
| public static void write( OutputStream out, Dictionary properties ) throws IOException |
| { |
| BufferedWriter bw = new BufferedWriter( new OutputStreamWriter( out, ENCODING ) ); |
| |
| for ( Enumeration ce = properties.keys(); ce.hasMoreElements(); ) |
| { |
| String key = ( String ) ce.nextElement(); |
| |
| // cfg = prop "=" value "." . |
| writeQuoted( bw, key ); |
| bw.write( TOKEN_EQ ); |
| writeValue( bw, properties.get( key ) ); |
| bw.write( CRLF ); |
| } |
| |
| bw.flush(); |
| } |
| |
| |
| /** |
| * Reads configuration data from the given <code>InputStream</code> and |
| * returns a new <code>Dictionary</code> object containing the data. |
| * <p> |
| * This method reads from the current location in the stream upto the end of |
| * the stream but does not close the stream at the end. |
| * |
| * @param ins |
| * The <code>InputStream</code> from which to read the |
| * configuration data. |
| * @return A <code>Dictionary</code> object containing the configuration |
| * data. This object may be empty if the stream contains no |
| * configuration data. |
| * @throws IOException |
| * If an error occurrs reading from the stream. This exception |
| * is also thrown if a syntax error is encountered. |
| */ |
| public static Dictionary read( InputStream ins ) throws IOException |
| { |
| return new ConfigurationHandler().readInternal( ins ); |
| } |
| |
| |
| // private constructor, this class is not to be instantiated from the |
| // outside |
| private ConfigurationHandler() |
| { |
| } |
| |
| // ---------- Configuration Input Implementation --------------------------- |
| |
| private int token; |
| private String tokenValue; |
| private int line; |
| private int pos; |
| |
| |
| private Dictionary readInternal( InputStream ins ) throws IOException |
| { |
| BufferedReader br = new BufferedReader( new InputStreamReader( ins, ENCODING ) ); |
| PushbackReader pr = new PushbackReader( br, 1 ); |
| |
| token = 0; |
| tokenValue = null; |
| line = 0; |
| pos = 0; |
| |
| Hashtable configuration = new Hashtable(); |
| token = 0; |
| while ( nextToken( pr ) == TOKEN_NAME ) |
| { |
| String key = tokenValue; |
| |
| // expect equal sign |
| if ( nextToken( pr ) != TOKEN_EQ ) |
| { |
| throw readFailure( token, TOKEN_EQ ); |
| } |
| |
| // expect the token value |
| Object value = readValue( pr ); |
| if ( value != null ) |
| { |
| configuration.put( key, value ); |
| } |
| } |
| |
| return configuration; |
| } |
| |
| |
| /** |
| * value = type ( "[" values "]" | "(" values ")" | simple ) . values = |
| * value { "," value } . simple = "{" stringsimple "}" . type = // 1-char |
| * type code . stringsimple = // quoted string representation of the value . |
| * |
| * @param pr |
| * @return |
| * @throws IOException |
| */ |
| private Object readValue( PushbackReader pr ) throws IOException |
| { |
| // read (optional) type code |
| int type = read( pr ); |
| |
| // read value kind code if type code is not a value kinde code |
| int code; |
| if ( code2Type.containsKey( new Integer( type ) ) ) |
| { |
| code = read( pr ); |
| } |
| else |
| { |
| code = type; |
| type = TOKEN_SIMPLE_STRING; |
| } |
| |
| switch ( code ) |
| { |
| case TOKEN_ARR_OPEN: |
| return readArray( type, pr ); |
| |
| case TOKEN_VEC_OPEN: |
| return readCollection( type, pr ); |
| |
| case TOKEN_VAL_OPEN: |
| Object value = readSimple( type, pr ); |
| ensureNext( pr, TOKEN_VAL_CLOS ); |
| return value; |
| |
| default: |
| return null; |
| } |
| } |
| |
| |
| private Object readArray( int typeCode, PushbackReader pr ) throws IOException |
| { |
| List list = new ArrayList(); |
| for ( ;; ) |
| { |
| if ( !checkNext( pr, TOKEN_VAL_OPEN ) ) |
| { |
| return null; |
| } |
| |
| Object value = readSimple( typeCode, pr ); |
| if ( value == null ) |
| { |
| // abort due to error |
| return null; |
| } |
| |
| ensureNext( pr, TOKEN_VAL_CLOS ); |
| |
| list.add( value ); |
| |
| int c = read( pr ); |
| if ( c == TOKEN_ARR_CLOS ) |
| { |
| Class type = ( Class ) code2Type.get( new Integer( typeCode ) ); |
| Object array = Array.newInstance( type, list.size() ); |
| for ( int i = 0; i < list.size(); i++ ) |
| { |
| Array.set( array, i, list.get( i ) ); |
| } |
| return array; |
| } |
| else if ( c < 0 ) |
| { |
| return null; |
| } |
| else if ( c != TOKEN_COMMA ) |
| { |
| return null; |
| } |
| } |
| } |
| |
| |
| private Collection readCollection( int typeCode, PushbackReader pr ) throws IOException |
| { |
| Collection collection = new ArrayList(); |
| for ( ;; ) |
| { |
| if ( !checkNext( pr, TOKEN_VAL_OPEN ) ) |
| { |
| return null; |
| } |
| |
| Object value = readSimple( typeCode, pr ); |
| if ( value == null ) |
| { |
| // abort due to error |
| return null; |
| } |
| |
| ensureNext( pr, TOKEN_VAL_CLOS ); |
| |
| collection.add( value ); |
| |
| int c = read( pr ); |
| if ( c == TOKEN_VEC_CLOS ) |
| { |
| return collection; |
| } |
| else if ( c < 0 ) |
| { |
| return null; |
| } |
| else if ( c != TOKEN_COMMA ) |
| { |
| return null; |
| } |
| } |
| } |
| |
| |
| private Object readSimple( int code, PushbackReader pr ) throws IOException |
| { |
| switch ( code ) |
| { |
| case -1: |
| return null; |
| |
| case TOKEN_SIMPLE_STRING: |
| return readQuoted( pr ); |
| |
| // Simple/Primitive, only use wrapper classes |
| case TOKEN_SIMPLE_INTEGER: |
| case TOKEN_PRIMITIVE_INT: |
| return Integer.valueOf( readQuoted( pr ) ); |
| |
| case TOKEN_SIMPLE_LONG: |
| case TOKEN_PRIMITIVE_LONG: |
| return Long.valueOf( readQuoted( pr ) ); |
| |
| case TOKEN_SIMPLE_FLOAT: |
| case TOKEN_PRIMITIVE_FLOAT: |
| int fBits = Integer.parseInt( readQuoted( pr ) ); |
| return new Float( Float.intBitsToFloat( fBits ) ); |
| |
| case TOKEN_SIMPLE_DOUBLE: |
| case TOKEN_PRIMITIVE_DOUBLE: |
| long dBits = Long.parseLong( readQuoted( pr ) ); |
| return new Double( Double.longBitsToDouble( dBits ) ); |
| |
| case TOKEN_SIMPLE_BYTE: |
| case TOKEN_PRIMITIVE_BYTE: |
| return Byte.valueOf( readQuoted( pr ) ); |
| |
| case TOKEN_SIMPLE_SHORT: |
| case TOKEN_PRIMITIVE_SHORT: |
| return Short.valueOf( readQuoted( pr ) ); |
| |
| case TOKEN_SIMPLE_CHARACTER: |
| case TOKEN_PRIMITIVE_CHAR: |
| String cString = readQuoted( pr ); |
| if ( cString != null && cString.length() > 0 ) |
| { |
| return new Character( cString.charAt( 0 ) ); |
| } |
| return null; |
| |
| case TOKEN_SIMPLE_BOOLEAN: |
| case TOKEN_PRIMITIVE_BOOLEAN: |
| return Boolean.valueOf( readQuoted( pr ) ); |
| |
| // unknown type code |
| default: |
| return null; |
| } |
| } |
| |
| |
| private void ensureNext( PushbackReader pr, int expected ) throws IOException |
| { |
| int next = read( pr ); |
| if ( next != expected ) |
| { |
| readFailure( next, expected ); |
| } |
| } |
| |
| |
| private boolean checkNext( PushbackReader pr, int expected ) throws IOException |
| { |
| int next = read( pr ); |
| if ( next < 0 ) |
| { |
| return false; |
| } |
| |
| if ( next == expected ) |
| { |
| return true; |
| } |
| |
| return false; |
| } |
| |
| |
| private String readQuoted( PushbackReader pr ) throws IOException |
| { |
| StringBuffer buf = new StringBuffer(); |
| for ( ;; ) |
| { |
| int c = read( pr ); |
| switch ( c ) |
| { |
| // escaped character |
| case '\\': |
| c = read( pr ); |
| switch ( c ) |
| { |
| // well known escapes |
| case 'b': |
| buf.append( '\b' ); |
| break; |
| case 't': |
| buf.append( '\t' ); |
| break; |
| case 'n': |
| buf.append( '\n' ); |
| break; |
| case 'f': |
| buf.append( '\f' ); |
| break; |
| case 'r': |
| buf.append( '\r' ); |
| break; |
| case 'u':// need 4 characters ! |
| char[] cbuf = new char[4]; |
| if ( read( pr, cbuf ) == 4 ) |
| { |
| c = Integer.parseInt( new String( cbuf ), 16 ); |
| buf.append( ( char ) c ); |
| } |
| break; |
| |
| // just an escaped character, unescape |
| default: |
| buf.append( ( char ) c ); |
| } |
| break; |
| |
| // eof |
| case -1: // fall through |
| |
| // separator token |
| case TOKEN_EQ: |
| case TOKEN_VAL_CLOS: |
| pr.unread( c ); |
| return buf.toString(); |
| |
| // no escaping |
| default: |
| buf.append( ( char ) c ); |
| } |
| } |
| } |
| |
| |
| private int nextToken( PushbackReader pr ) throws IOException |
| { |
| int c = ignorableWhiteSpace( pr ); |
| |
| // immediately return EOF |
| if ( c < 0 ) |
| { |
| return ( token = c ); |
| } |
| |
| // check whether there is a name |
| if ( NAME_CHARS.get( c ) || !TOKEN_CHARS.get( c ) ) |
| { |
| // read the property name |
| pr.unread( c ); |
| tokenValue = readQuoted( pr ); |
| return ( token = TOKEN_NAME ); |
| } |
| |
| // check another token |
| if ( TOKEN_CHARS.get( c ) ) |
| { |
| return ( token = c ); |
| } |
| |
| // unexpected character -> so what ?? |
| return ( token = -1 ); |
| } |
| |
| |
| private int ignorableWhiteSpace( PushbackReader pr ) throws IOException |
| { |
| int c = read( pr ); |
| while ( c >= 0 && Character.isWhitespace( ( char ) c ) ) |
| { |
| c = read( pr ); |
| } |
| return c; |
| } |
| |
| |
| private int read( PushbackReader pr ) throws IOException |
| { |
| int c = pr.read(); |
| if ( c == '\r' ) |
| { |
| int c1 = pr.read(); |
| if ( c1 != '\n' ) |
| { |
| pr.unread( c1 ); |
| } |
| c = '\n'; |
| } |
| |
| if ( c == '\n' ) |
| { |
| line++; |
| pos = 0; |
| } |
| else |
| { |
| pos++; |
| } |
| |
| return c; |
| } |
| |
| |
| private int read( PushbackReader pr, char[] buf ) throws IOException |
| { |
| for ( int i = 0; i < buf.length; i++ ) |
| { |
| int c = read( pr ); |
| if ( c >= 0 ) |
| { |
| buf[i] = ( char ) c; |
| } |
| else |
| { |
| return i; |
| } |
| } |
| |
| return buf.length; |
| } |
| |
| |
| private IOException readFailure( int current, int expected ) |
| { |
| return new IOException( "Unexpected token " + current + "; expected: " + expected + " (line=" + line + ", pos=" |
| + pos + ")" ); |
| } |
| |
| |
| // ---------- Configuration Output Implementation -------------------------- |
| |
| private static void writeValue( Writer out, Object value ) throws IOException |
| { |
| Class clazz = value.getClass(); |
| if ( clazz.isArray() ) |
| { |
| writeArray( out, value ); |
| } |
| else if ( value instanceof Collection ) |
| { |
| writeCollection( out, ( Collection ) value ); |
| } |
| else |
| { |
| writeType( out, clazz ); |
| writeSimple( out, value ); |
| } |
| } |
| |
| |
| private static void writeArray( Writer out, Object arrayValue ) throws IOException |
| { |
| int size = Array.getLength( arrayValue ); |
| if ( size == 0 ) |
| { |
| return; |
| } |
| |
| writeType( out, arrayValue.getClass().getComponentType() ); |
| out.write( TOKEN_ARR_OPEN ); |
| for ( int i = 0; i < size; i++ ) |
| { |
| if ( i > 0 ) |
| out.write( TOKEN_COMMA ); |
| writeSimple( out, Array.get( arrayValue, i ) ); |
| } |
| out.write( TOKEN_ARR_CLOS ); |
| } |
| |
| |
| private static void writeCollection( Writer out, Collection collection ) throws IOException |
| { |
| if ( collection.isEmpty() ) |
| { |
| return; |
| } |
| |
| Iterator ci = collection.iterator(); |
| Object firstElement = ci.next(); |
| |
| writeType( out, firstElement.getClass() ); |
| out.write( TOKEN_VEC_OPEN ); |
| writeSimple( out, firstElement ); |
| |
| while ( ci.hasNext() ) |
| { |
| out.write( TOKEN_COMMA ); |
| writeSimple( out, ci.next() ); |
| } |
| out.write( TOKEN_VEC_CLOS ); |
| } |
| |
| |
| private static void writeType( Writer out, Class valueType ) throws IOException |
| { |
| Integer code = ( Integer ) type2Code.get( valueType ); |
| if ( code != null ) |
| { |
| out.write( ( char ) code.intValue() ); |
| } |
| } |
| |
| |
| private static void writeSimple( Writer out, Object value ) throws IOException |
| { |
| if ( value instanceof Double ) |
| { |
| double dVal = ( ( Double ) value ).doubleValue(); |
| value = new Long( Double.doubleToRawLongBits( dVal ) ); |
| } |
| else if ( value instanceof Float ) |
| { |
| float fVal = ( ( Float ) value ).floatValue(); |
| value = new Integer( Float.floatToRawIntBits( fVal ) ); |
| } |
| |
| out.write( TOKEN_VAL_OPEN ); |
| writeQuoted( out, String.valueOf( value ) ); |
| out.write( TOKEN_VAL_CLOS ); |
| } |
| |
| |
| private static void writeQuoted( Writer out, String simple ) throws IOException |
| { |
| if ( simple == null || simple.length() == 0 ) |
| { |
| return; |
| } |
| |
| char c = 0; |
| int len = simple.length(); |
| for ( int i = 0; i < len; i++ ) |
| { |
| c = simple.charAt( i ); |
| switch ( c ) |
| { |
| case '\\': |
| case TOKEN_VAL_CLOS: |
| case ' ': |
| case TOKEN_EQ: |
| out.write( '\\' ); |
| out.write( c ); |
| break; |
| |
| // well known escapes |
| case '\b': |
| out.write( "\\b" ); |
| break; |
| case '\t': |
| out.write( "\\t" ); |
| break; |
| case '\n': |
| out.write( "\\n" ); |
| break; |
| case '\f': |
| out.write( "\\f" ); |
| break; |
| case '\r': |
| out.write( "\\r" ); |
| break; |
| |
| // other escaping |
| default: |
| if ( c < ' ' ) |
| { |
| String t = "000" + Integer.toHexString( c ); |
| out.write( "\\u" + t.substring( t.length() - 4 ) ); |
| } |
| else |
| { |
| out.write( c ); |
| } |
| } |
| } |
| } |
| } |