org.apache.felix.cm.file.ConfigurationHandler Maven / Gradle / Ivy
Show all versions of org.apache.felix.configadmin Show documentation
/*
* 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.Arrays;
import java.util.BitSet;
import java.util.Collection;
import java.util.Collections;
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 ConfigurationHandler
class implements configuration reading
* form a java.io.InputStream
and writing to a
* java.io.OutputStream
on behalf of the
* {@link FilePersistenceManager} class.
*
*
* 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 .
*
*/
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 = '"'; // '}';
protected static final int TOKEN_COMMENT = '#';
// 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 String INDENT = " ";
protected static final String COLLECTION_LINE_BREAK = " \\\r\n";
protected static final Map> code2Type;
protected static final Map, Integer> type2Code;
// set of valid characters for "symblic-name"
private static final BitSet NAME_CHARS;
private static final BitSet TOKEN_CHARS;
static
{
type2Code = new HashMap, Integer>();
// 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(final Map.Entry, Integer> entry : type2Code.entrySet())
{
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 Dictionary
to the
* given OutputStream
.
*
* This method writes at the current location in the stream and does not
* close the output stream.
*
* @param out
* The OutputStream
to write the configuration data
* to.
* @param properties
* The Dictionary
to write.
* @throws IOException
* If an error occurs writing to the output stream.
*/
@SuppressWarnings("rawtypes")
public static void write( OutputStream out, Dictionary properties ) throws IOException
{
BufferedWriter bw = new BufferedWriter( new OutputStreamWriter( out, ENCODING ) );
for ( Enumeration ce = orderedKeys(properties); 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();
}
/**
* Generates an Enumeration
for the given
* Dictionary
where the keys of the Dictionary
* are provided in sorted order.
*
* @param properties
* The Dictionary
that keys are sorted.
* @return An Enumeration
that provides the keys of
* properties in an ordered manner.
*/
@SuppressWarnings("rawtypes")
private static Enumeration orderedKeys(Dictionary properties) {
String[] keyArray = new String[properties.size()];
int i = 0;
for ( Enumeration ce = properties.keys(); ce.hasMoreElements(); )
{
keyArray[i] = ( String ) ce.nextElement();
i++;
}
Arrays.sort(keyArray);
return Collections.enumeration( Arrays.asList( keyArray ) );
}
/**
* Reads configuration data from the given InputStream
and
* returns a new Dictionary
object containing the data.
*
* This method reads from the current location in the stream up to the end of
* the stream but does not close the stream at the end.
*
* @param ins
* The InputStream
from which to read the
* configuration data.
* @return A Dictionary
object containing the configuration
* data. This object may be empty if the stream contains no
* configuration data.
* @throws IOException
* If an error occurs reading from the stream. This exception
* is also thrown if a syntax error is encountered.
*/
@SuppressWarnings("rawtypes")
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;
Dictionary configuration = new Hashtable();
token = 0;
while ( nextToken( pr, true ) == TOKEN_NAME )
{
String key = tokenValue;
// expect equal sign
if ( nextToken( pr, false ) != 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