/** * Copyright (c) 2003-2009, Xith3D Project Group all rights reserved. * * Portions based on the Java3D interface, Copyright by Sun Microsystems. * Many thanks to the developers of Java3D and Sun Microsystems for their * innovation and design. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the 'Xith3D Project Group' nor the names of its * contributors may be used to endorse or promote products derived from this * software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) A * RISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE */ package org.xith3d.utility.config; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Properties; import javax.swing.filechooser.FileSystemView; import org.xith3d.Xith3D; import org.xith3d.utility.logging.X3DLog; /** * @author Mathias Henze (aka cylab) */ public final class Configuration { private static class TargetSpec { final Class< ? > targetClass; final String targetProperty; final String camelCaseTargetProperty; final String description; TargetSpec( Class< ? > targetClass, String targetProperty, String description ) { this.targetClass = targetClass; this.targetProperty = targetProperty; this.description = description; String camelCaseName = targetProperty; camelCaseName = camelCaseName.substring( 0, 1 ).toUpperCase() + camelCaseName.substring( 1 ); this.camelCaseTargetProperty = camelCaseName; } } private final HashMap< String, TargetSpec > mapping = new HashMap< String, TargetSpec >(); private Properties currentConfig = new Properties(); public final Properties getCurrentConfig() { return ( currentConfig ); } public void register( String propertyKey, Class< ? > targetClass, String targetProperty, String description ) { mapping.put( propertyKey, new TargetSpec( targetClass, targetProperty, description ) ); } public void register( String propertyKey, Class< ? > targetClass, String targetProperty ) { register( propertyKey, targetClass, targetProperty, null ); } public void register( Class< ? > targetClass, String targetProperty, String description ) { final String propertyKey = targetClass.getSimpleName() + "." + targetProperty; register( propertyKey, targetClass, targetProperty, description ); } public void register( Class< ? > targetClass, String targetProperty ) { final String propertyKey = targetClass.getSimpleName() + "." + targetProperty; register( propertyKey, targetClass, targetProperty, null ); } public void register( Class< ? > targetClass ) { Method[] methods = targetClass.getDeclaredMethods(); for ( int i = 0; i < methods.length; i++ ) { Method method = methods[ i ]; Configurable c = method.getAnnotation( Configurable.class ); if ( c != null ) { String targetProperty = method.getName(); int skip = ( targetProperty.indexOf( "is" ) == 0 ) ? 2 : 3; targetProperty = targetProperty.substring( skip, skip + 1 ).toLowerCase() + targetProperty.substring( skip + 1 ); String[] vs = c.value(); if ( ( vs != null ) & ( vs.length > 0 ) ) { register( vs[ 0 ], targetClass, targetProperty, ( ( vs.length > 1 ) ? vs[ 1 ] : null ) ); } else { register( targetClass, targetProperty ); } } } Field[] fields = targetClass.getDeclaredFields(); for ( int i = 0; i < methods.length; i++ ) { Field field = fields[ i ]; Configurable c = field.getAnnotation( Configurable.class ); if ( c != null ) { String targetProperty = field.getName(); String[] vs = c.value(); if ( ( vs != null ) & ( vs.length > 0 ) ) { register( vs[ 0 ], targetClass, targetProperty, ( ( vs.length > 1 ) ? vs[ 1 ] : null ) ); } else { register( targetClass, targetProperty ); } } } } /** * Searches <filename> in a subdir under "/org/xith3d/settings/" in the current classpath. * * @param subdir * @param filename */ private void loadFromClassPath( String subdir, String filename ) { InputStream stream = Xith3D.class.getResourceAsStream( "settings/" + subdir + "/" + filename ); if ( stream != null ) { try { currentConfig.load( stream ); } catch ( IOException ex ) { // ignore! } finally { if ( stream != null ) try { stream.close(); } catch ( Exception ignore ) {} } } } /** * Searches <filename> in a subdir under the current user's home. * * @param subdir * @param filename */ private void loadFromHomeDirectory( String subdir, String filename ) { String home = FileSystemView.getFileSystemView().getHomeDirectory().getAbsolutePath(); File file = new File( home + "/" + subdir, filename ); InputStream stream = null; if ( file.exists() ) { try { stream = new FileInputStream( file ); currentConfig = new Properties( currentConfig ); currentConfig.load( stream ); } catch ( IOException ex ) { // ignore! } finally { if ( stream != null ) try { stream.close(); } catch ( Exception ignore ) {} } } } /** * Searches <filename> in a subdir under "My Documents" (Windows)... * * @param subdir * @param filename */ private void loadFromMyDocuments( String subdir, String filename ) { final FileSystemView filesystem = FileSystemView.getFileSystemView(); if ( !filesystem.getHomeDirectory().equals( filesystem.getDefaultDirectory() ) ) { File file = new File( filesystem.getDefaultDirectory() + "/" + subdir, filename ); InputStream stream = null; if ( file.exists() ) { try { stream = new FileInputStream( file ); currentConfig = new Properties( currentConfig ); currentConfig.load( stream ); } catch ( IOException ex ) { // ignore! } finally { if ( stream != null ) try { stream.close(); } catch ( Exception ignore ) {} } } } } /** * Searches <filename> in the current working directory. * * @param subdir * @param filename */ @SuppressWarnings("unused") private void loadFromCurrentWorkingDirectory( String subdir, String filename ) { File file = new File( System.getProperty( "user.dir" ), filename ); InputStream stream = null; if ( file.exists() ) { try { stream = new FileInputStream( file ); currentConfig = new Properties( currentConfig ); currentConfig.load( stream ); } catch ( IOException ex ) { // ignore! } finally { if ( stream != null ) try { stream.close(); } catch ( Exception ignore ) {} } } } private Method findSetter( TargetSpec spec ) { Method[] methods = spec.targetClass.getDeclaredMethods(); for ( int i = 0; i < methods.length; i++ ) { Method method = methods[ i ]; Class< ? >[] params = method.getParameterTypes(); if ( ( params != null ) && ( params.length == 1 ) && method.getName().equals( "set" + spec.camelCaseTargetProperty ) ) { return ( method ); } } return ( null ); } /** * Iterates over the loaded configuration and tries to set static properties * or fields on the registered classes. * * @param config */ private void loadSettings( Properties config ) { for ( Object key: config.keySet() ) { final String name = (String)key; final String value = config.getProperty( name ); final TargetSpec spec = mapping.get( name ); if ( spec == null ) continue; final Method setter = findSetter( spec ); try { // use a setter, if available if ( setter != null ) { final Class< ? > type = setter.getParameterTypes()[ 0 ]; if ( type.isAssignableFrom( byte.class ) ) { setter.invoke( null, Byte.parseByte( value ) ); } else if ( type.isAssignableFrom( short.class ) ) { setter.invoke( null, Short.parseShort( value ) ); } else if ( type.isAssignableFrom( int.class ) ) { setter.invoke( null, Integer.parseInt( value ) ); } else if ( type.isAssignableFrom( long.class ) ) { setter.invoke( null, Long.parseLong( value ) ); } else if ( type.isAssignableFrom( float.class ) ) { setter.invoke( null, Float.parseFloat( value ) ); } else if ( type.isAssignableFrom( double.class ) ) { setter.invoke( null, Double.parseDouble( value ) ); } else if ( type.isAssignableFrom( boolean.class ) ) { setter.invoke( null, Boolean.parseBoolean( value ) ); } else if ( type.isAssignableFrom( String.class ) ) { setter.invoke( null, value ); } } // else try a field else { final Field field = spec.targetClass.getField( spec.targetProperty ); final Class< ? > type = field.getType(); if ( type.isAssignableFrom( byte.class ) ) { field.set( null, Byte.parseByte( value ) ); } else if ( type.isAssignableFrom( short.class ) ) { field.set( null, Short.parseShort( value ) ); } else if ( type.isAssignableFrom( int.class ) ) { field.set( null, Integer.parseInt( value ) ); } else if ( type.isAssignableFrom( long.class ) ) { field.set( null, Long.parseLong( value ) ); } else if ( type.isAssignableFrom( float.class ) ) { field.set( null, Float.parseFloat( value ) ); } else if ( type.isAssignableFrom( double.class ) ) { field.set( null, Double.parseDouble( value ) ); } else if ( type.isAssignableFrom( boolean.class ) ) { field.set( null, Boolean.parseBoolean( value ) ); } else if ( type.isAssignableFrom( String.class ) ) { field.set( null, value ); } } } catch ( Throwable t ) { X3DLog.print( t ); } } } /** * Inspects different points in the claspath and the filesystem for the * requested file resource and loads/merges the config settings in case. * * @param subdir * @param filename */ public void load( String subdir, String filename ) { currentConfig.clear(); loadFromClassPath( subdir, filename ); loadFromHomeDirectory( subdir, filename ); loadFromMyDocuments( subdir, filename ); //loadFromCurrentWorkingDirectory( subdir, filename ); loadSettings( currentConfig ); } }