001/* 002 * Copyright 2006 - 2013 003 * Stefan Balev <stefan.balev@graphstream-project.org> 004 * Julien Baudry <julien.baudry@graphstream-project.org> 005 * Antoine Dutot <antoine.dutot@graphstream-project.org> 006 * Yoann Pigné <yoann.pigne@graphstream-project.org> 007 * Guilhelm Savin <guilhelm.savin@graphstream-project.org> 008 * 009 * This file is part of GraphStream <http://graphstream-project.org>. 010 * 011 * GraphStream is a library whose purpose is to handle static or dynamic 012 * graph, create them from scratch, file or any source and display them. 013 * 014 * This program is free software distributed under the terms of two licenses, the 015 * CeCILL-C license that fits European law, and the GNU Lesser General Public 016 * License. You can use, modify and/ or redistribute the software under the terms 017 * of the CeCILL-C license as circulated by CEA, CNRS and INRIA at the following 018 * URL <http://www.cecill.info> or under the terms of the GNU LGPL as published by 019 * the Free Software Foundation, either version 3 of the License, or (at your 020 * option) any later version. 021 * 022 * This program is distributed in the hope that it will be useful, but WITHOUT ANY 023 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 024 * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. 025 * 026 * You should have received a copy of the GNU Lesser General Public License 027 * along with this program. If not, see <http://www.gnu.org/licenses/>. 028 * 029 * The fact that you are presently reading this means that you have had 030 * knowledge of the CeCILL-C and LGPL licenses and that you accept their terms. 031 */ 032package org.graphstream.util; 033 034import java.io.BufferedReader; 035import java.io.BufferedWriter; 036import java.io.FileNotFoundException; 037import java.io.FileReader; 038import java.io.FileWriter; 039import java.io.IOException; 040import java.io.PrintStream; 041import java.lang.reflect.InvocationTargetException; 042import java.lang.reflect.Method; 043import java.util.Arrays; 044import java.util.Collection; 045import java.util.HashSet; 046import java.util.Hashtable; 047import java.util.Set; 048 049/** 050 * Representation of a set of parameters. 051 * 052 * <p> 053 * The environment class mimics the environment variables available in any shell 054 * using a hash map of keys/values, the key being the variables names, excepted 055 * here they are called parameters. 056 * </p> 057 * 058 * <p> 059 * In addition, this class provides facilities to: 060 * <ul> 061 * <li>Read a parameter file and set the parameters from this file;</li> 062 * <li>Write a parameter file from the parameter of this environment;</li> 063 * <li>Parse the command line and get parameters from it;</li> 064 * <li>Take a class as argument and set all its fields having the same name as 065 * parameters in this class;</li> 066 * </ul> 067 * </p> 068 * 069 * <p> 070 * As in any shell, most of the time, the environment is global and accessible 071 * from any part of the system. Here a singleton instance of this class is 072 * created and accessible from anywhere in the JVM using the 073 * {@link #getGlobalEnvironment()} method (indeed the singleton instance is 074 * created at its first access). However, it is still possible to create a 075 * private instance of this class for use in a specific part of a program. 076 * </p> 077 * 078 * <p> 079 * To read a file of parameters, simply call the 080 * {@link #readParameterFile(String)} method. In the same way, to write a set of 081 * parameters to a file, call the {@link #writeParameterFile(String)} method. 082 * The format of the parameter file is given in the description of these 083 * methods. 084 * </p> 085 * 086 * <p> 087 * To read parameters from he command line, call the 088 * {@link #readCommandLine(String[])} or 089 * {@link #readCommandLine(String[], Collection)} methods. These methods expect 090 * a format for the command line that is described in there respective 091 * documentations. 092 * </p> 093 * 094 * <p> 095 * It is also possible to setup automatically the fields of an arbitrary object, 096 * provided these fields have name that match parameters in this environment. To 097 * do this call the {@link #initializeFieldsOf(Object)} method passing the 098 * object to initialise as argument. The object to setup must provide methods of 099 * the form "setThing(Type)" where "Thing" or "thing" is the name of the field 100 * to set and "Type" is one of "int", "long", "float", "double", "String" and 101 * "boolean". For the boolean type, the accepted values meaning true are "true", 102 * "on", "1", and "yes", all other value are considered as false. 103 * </p> 104 * 105 * TODO: how (or when) does the default configuration file is read? 106 * TODO: how to handle parameters that cannot be setup in the {@link #initializeFieldsOf(Object)}? 107 * 108 * @author Frédéric Guinand 109 * @author Yoann Pigné 110 * @author Antoine Dutot 111 * @version 1.0 (jdk 1.5) 112 */ 113public class Environment implements Cloneable 114{ 115 // ---------- Attributes ----------- 116 117 /** 118 * Name of the configuration file. Default is "config" 119 */ 120 protected String configFileName = "config"; 121 122 /** 123 * Has the configuration file been read yet?. 124 */ 125 protected boolean configFileRead = false; 126 127 /** 128 * Set of parameters. This is a hash table and not a hashmap since several 129 * thread may access this class at once. 130 */ 131 protected Hashtable<String, String> parameters = new Hashtable<String, String>(); 132 133 /** 134 * When locked the environment parameters value still can be changed but it 135 * is no more possible to add new parameters. 136 */ 137 protected boolean locked; 138 139 // --------- Static attributes --------- 140 141 /** 142 * Global environment for the whole JVM. This global environment is 143 * available <b>and editable</b> from everywhere. It is create as soon as 144 * the {@link #getGlobalEnvironment()} static method is called if this field 145 * was not yet initialized by any other mean. 146 * @see #getGlobalEnvironment() 147 */ 148 public static Environment GLOBAL_ENV; 149 150 // --------- Static methods ----------- 151 152 /** 153 * Access to the global shared environment for the whole JVM. This method 154 * allows to access a shared environment, that can be read and written from 155 * anywhere. 156 * @return A singleton instance of the global environment. 157 */ 158 public static Environment getGlobalEnvironment() 159 { 160 if( GLOBAL_ENV == null ) 161 GLOBAL_ENV = new Environment(); 162 163 return GLOBAL_ENV; 164 } 165 166 // --------- Methods ------------- 167 168 /** 169 * Is the environment locked?. 170 * @return True if the environment is locked. 171 * @see #lockEnvironment(boolean) 172 */ 173 public boolean isLocked() 174 { 175 return locked; 176 } 177 178 /** 179 * Access to a parameter in the environment. 180 * @param parameter The parameter name. 181 * @return The parameter value (empty string if not set). 182 */ 183 public String getParameter( String parameter ) 184 { 185 String p = parameters.get( parameter ); 186 187 return ( p == null ) ? "" : p; 188 } 189 190 /** 191 * True if the given paramter exist. 192 * @param parameter The parameter name. 193 * @return True if the given paramter name points to a value. 194 */ 195 public boolean hasParameter( String parameter ) 196 { 197 return( parameters.get( parameter ) != null ); 198 } 199 200 /** 201 * Check a parameter expected to be of boolean type. This method returns 202 * "true" if the parameter exists and has a value that is "1", "true", 203 * "on" or "yes" (with any possible combination of upper or lower-case 204 * letters). For any other values of the parameter or if the parameter does 205 * not exist in the environment, "false" is returned. 206 * @param parameter The parameter name. 207 * @return True if the parameter value means "true", false for any other 208 * value or if the parameter does not exist. 209 * @see #getBooleanParameteri(String) 210 */ 211 public boolean getBooleanParameter( String parameter ) 212 { 213 int val = getBooleanParameteri( parameter ); 214 215 return( val == 1 ); 216 } 217 218 /** 219 * Check a parameter expected to be of boolean type. This method returns the 220 * value 1 if the parameter has value "1", "true", "on", "yes" (the case 221 * does not matter). Else it returns 0. To account the case of non-existing 222 * parameters, this method returns -1 if the given parameter does not 223 * exist. 224 * @param parameter The parameter name. 225 * @return 1 if the parameter value means "true", 0 if it has any other 226 * value, or -1 if it does not exist. 227 * @see #getBooleanParameter(String) 228 */ 229 public int getBooleanParameteri( String parameter ) 230 { 231 String p = parameters.get( parameter ); 232 233 if( p != null ) 234 { 235 p = p.toLowerCase(); 236 237 if( p.equals( "1" ) ) return 1; 238 if( p.equals( "true" ) ) return 1; 239 if( p.equals( "on" ) ) return 1; 240 if( p.equals( "yes" ) ) return 1; 241 242 return 0; 243 } 244 245 return -1; 246 } 247 248 /** 249 * Get the value of a parameter that is expected to be a number. If the 250 * parameter does not exist or is not a number, 0 is returned. 251 * @param parameter The parameter name. 252 * @return The numeric value of the parameter. 0 if the parameter does 253 * not exist or is not a number. 254 */ 255 public double getNumberParameter( String parameter ) 256 { 257 String p = parameters.get( parameter ); 258 259 if( p != null ) 260 { 261 try 262 { 263 return Double.parseDouble( p ); 264 } 265 catch( NumberFormatException e ) 266 { 267 return 0; 268 } 269 } 270 271 return 0; 272 } 273 274 /** 275 * Returns the number of parameters found in the configuration file. 276 * @return The number of parameters found in the configuration file. 277 */ 278 public int getParameterCount() 279 { 280 return parameters.size(); 281 } 282 283 /** 284 * Set of all parameter names. 285 * @return A set of all the names identifying parameters in this 286 * environment. 287 */ 288 public Set<String> getParametersKeySet() 289 { 290 return parameters.keySet(); 291 } 292 293 /** 294 * Generate a new Environment object with a deep copy of the elements this 295 * object. 296 * @return An Environment object identical to this one 297 */ 298 @Override 299 public Environment clone() 300 { 301 Environment e = new Environment(); 302 e.configFileName = configFileName; 303 e.configFileRead = configFileRead; 304 e.locked = locked; 305 for( String key: parameters.keySet() ) 306 { 307 e.parameters.put( key, parameters.get( key ) ); 308 } 309 return e; 310 } 311 312 /** 313 * Set the value of a parameter. If the parameter already exists its old 314 * value is overwritten. This works only if the environment is not locked. 315 * @param parameter The parameter name. 316 * @param value The new parameter value. 317 * @see #isLocked() 318 * @see #lockEnvironment(boolean) 319 */ 320 public void setParameter( String parameter, String value ) 321 { 322 if( !locked ) 323 { 324 parameters.put( parameter, value ); 325 } 326 else 327 { 328 if( parameters.get( parameter ) != null ) 329 parameters.put( parameter, value ); 330 } 331 } 332 333 /** 334 * Disallow the addition of new parameters. The already declared parameters 335 * are still modifiable, but no new parameter can be added. 336 * @param on If true the environment is locked. 337 */ 338 public void lockEnvironment( boolean on ) 339 { 340 locked = on; 341 } 342 343 /** 344 * Initialize all the fields of the given object whose name correspond to 345 * parameters of this environment. This works only if the object to 346 * initialize provides methods that begins by "set". For example if the 347 * object provides a method named "setThing(int value)", and if there is a 348 * parameter named "thing" in this environment and its value is convertible 349 * to an integer, then the method "setThing()" will be invoked on the object 350 * with the correct value. 351 * @see #initializeFieldsOf(Object, String[]) 352 * @see #initializeFieldsOf(Object, Collection) 353 * @param object The object to initialize. 354 */ 355 public void initializeFieldsOf( Object object ) 356 { 357 Method[] methods = object.getClass().getMethods(); 358 359 for( Method method: methods ) 360 { 361 if( method.getName().startsWith( "set" ) ) 362 { 363 Class<?> types[] = method.getParameterTypes(); 364 365 if( types.length == 1 ) 366 { 367 String name = method.getName().substring( 3, 4 ) 368 .toLowerCase() 369 + method.getName().substring( 4 ); 370 String value = parameters.get( name ); 371 372 if( value != null ) 373 { 374 invokeSetMethod( object, method, types, name, value ); 375 } 376 } 377 } 378 } 379 } 380 381 /** 382 * Initialize all the fields of the given object that both appear in the 383 * given field list and whose name correspond to parameters of this 384 * environment. See the {@link #initializeFieldsOf(Object)} method 385 * description. 386 * @see #initializeFieldsOf(Object) 387 * @see #initializeFieldsOf(Object, Collection) 388 * @param object The object to initialize. 389 * @param fieldList The name of the fields to initialize in the object. 390 */ 391 public void initializeFieldsOf( Object object, String... fieldList ) 392 { 393 Method[] methods = object.getClass().getMethods(); 394 HashSet<String> names = new HashSet<String>(); 395 396 for( String s: fieldList ) 397 names.add( s ); 398 399 for( Method method: methods ) 400 { 401 if( method.getName().startsWith( "set" ) ) 402 { 403 Class<?> types[] = method.getParameterTypes(); 404 405 if( types.length == 1 ) 406 { 407 String name = method.getName().substring( 3, 4 ) 408 .toLowerCase() 409 + method.getName().substring( 4 ); 410 411 if( names.contains( name ) ) 412 { 413 String value = parameters.get( name ); 414 415 if( value != null ) 416 { 417 invokeSetMethod( object, method, types, name, value ); 418 } 419 } 420 } 421 } 422 } 423 } 424 425 /** 426 * Initialize all the fields of the given object that both appear in the 427 * given field list and whose name correspond to parameters of this 428 * environment. See the {@link #initializeFieldsOf(Object)} method 429 * description. 430 * @see #initializeFieldsOf(Object) 431 * @see #initializeFieldsOf(Object, String[]) 432 * @param object The object to initialize. 433 * @param fieldList The name of the fields to initialize in the object. 434 */ 435 protected void initializeFieldsOf( Object object, 436 Collection<String> fieldList ) 437 { 438 Method[] methods = object.getClass().getMethods(); 439 440 for( Method method: methods ) 441 { 442 if( method.getName().startsWith( "set" ) ) 443 { 444 Class<?> types[] = method.getParameterTypes(); 445 446 if( types.length == 1 ) 447 { 448 String name = method.getName().substring( 3 ).toLowerCase(); 449 450 if( fieldList.contains( name ) ) 451 { 452 String value = parameters.get( name ); 453 454 if( value != null ) 455 { 456 invokeSetMethod( object, method, types, name, value ); 457 } 458 } 459 } 460 } 461 } 462 } 463 464 protected void invokeSetMethod( Object object, Method method, 465 Class<?> types[], String name, String value ) 466 { 467 try 468 { 469 // XXX a way to avoid this overlong and repetitive 470 // list of setters ? 471 472 if( types[0] == Long.TYPE ) 473 { 474 try 475 { 476 long val = Long.parseLong( value ); 477 method.invoke( object, new Long( val ) ); 478 } 479 catch( NumberFormatException e ) 480 { 481 Logger 482 .getGlobalLogger() 483 .log( 484 Logger.LogLevel.WARN, 485 this.getClass().getName(), 486 "cannot set '%s' to the value '%s', values is not a long%n", 487 method.toString(), value ); 488 } 489 } 490 else if( types[0] == Integer.TYPE ) 491 { 492 try 493 { 494 int val = (int) Double.parseDouble( value ); 495 method.invoke( object, new Integer( val ) ); 496 } 497 catch( NumberFormatException e ) 498 { 499 Logger 500 .getGlobalLogger() 501 .log( 502 Logger.LogLevel.WARN, 503 this.getClass().getName(), 504 "cannot set '%s' to the value '%s', values is not a int%n", 505 method.toString(), value ); 506 } 507 } 508 else if( types[0] == Double.TYPE ) 509 { 510 try 511 { 512 double val = Double.parseDouble( value ); 513 method.invoke( object, new Double( val ) ); 514 } 515 catch( NumberFormatException e ) 516 { 517 Logger 518 .getGlobalLogger() 519 .log( 520 Logger.LogLevel.WARN, 521 this.getClass().getName(), 522 "cannot set '%s' to the value '%s', values is not a double%n", 523 method.toString(), value ); 524 } 525 } 526 else if( types[0] == Float.TYPE ) 527 { 528 try 529 { 530 float val = Float.parseFloat( value ); 531 method.invoke( object, new Float( val ) ); 532 } 533 catch( NumberFormatException e ) 534 { 535 Logger 536 .getGlobalLogger() 537 .log( 538 Logger.LogLevel.WARN, 539 this.getClass().getName(), 540 "cannot set '%s' to the value '%s', values is not a float%n", 541 method.toString(), value ); 542 } 543 } 544 else if( types[0] == Boolean.TYPE ) 545 { 546 try 547 { 548 boolean val = false; 549 value = value.toLowerCase(); 550 551 if( value.equals( "1" ) || value.equals( "true" ) 552 || value.equals( "yes" ) || value.equals( "on" ) ) 553 val = true; 554 555 method.invoke( object, new Boolean( val ) ); 556 } 557 catch( NumberFormatException e ) 558 { 559 Logger 560 .getGlobalLogger() 561 .log( 562 Logger.LogLevel.WARN, 563 this.getClass().getName(), 564 "cannot set '%s' to the value '%s', values is not a boolean%n", 565 method.toString(), value ); 566 } 567 } 568 else if( types[0] == String.class ) 569 { 570 method.invoke( object, value ); 571 } 572 else 573 { 574 Logger.getGlobalLogger().log( Logger.LogLevel.WARN, 575 this.getClass().getName(), 576 "cannot match parameter '%s' and the method '%s'%n", 577 value, method.toString() ); 578 } 579 } 580 catch( InvocationTargetException ite ) 581 { 582 Logger 583 .getGlobalLogger() 584 .log( 585 Logger.LogLevel.WARN, 586 this.getClass().getName(), 587 "cannot invoke method '%s' : invocation targer error : %s%n", 588 method.toString(), ite.getMessage() ); 589 } 590 catch( IllegalAccessException iae ) 591 { 592 Logger.getGlobalLogger().log( Logger.LogLevel.WARN, 593 this.getClass().getName(), 594 "cannot invoke method '%s' : illegal access error : %s%n", 595 method.toString(), iae.getMessage() ); 596 } 597 } 598 599 /** 600 * Print all parameters to the given stream. 601 * @param out The output stream to use. 602 */ 603 public void printParameters( PrintStream out ) 604 { 605 out.println( toString() ); 606 } 607 608 /** 609 * Print all parameters the stdout. 610 */ 611 public void printParameters() 612 { 613 printParameters( System.out ); 614 } 615 616 @Override 617 public String toString() 618 { 619 return parameters.toString(); 620 } 621 622 /** 623 * Read the parameters from the given command line array. See the more 624 * complete {@link #readCommandLine(String[], Collection)} method. 625 * @param args The command line. 626 */ 627 public void readCommandLine( String[] args ) 628 { 629 readCommandLine( args, null ); 630 } 631 632 /** 633 * Read the parameters from the given command line array. The expected 634 * format of this array is the following: 635 * <ul> 636 * <li>a word beginning by a "-" is the parameter name (for example 637 * "-param");</li> 638 * <li>if this word is immediately followed by a "=" and another word, this 639 * word is considered as its string value (for example "-param=aValue");</li> 640 * <li>If the parameter name is not followed by "=", it is considered a 641 * boolean option and its value is set to the string "true" (to set this to 642 * false simply give the string "-param=false");</li> 643 * <li>If a word is found on the command line without any preceding "-" but 644 * is followed by a "=" and by another word, then it is considered as a 645 * key,value brace</li> 646 * <li>If a word is found on the command line without any preceding "-" and 647 * is not followed by any "=", the it is considered to be a filename for a 648 * configuration file. The method will try to open this file for reading. A 649 * configuration file is composed of lines. Each line is composed of a brace 650 * key/value separated by a "=". If a line starts with a "#", then it is 651 * considered as a comment. Finally if no format is recognized the line is 652 * inserted to the <code>trashcan</code>.</li> 653 * </ul> 654 * @param args The command line. 655 * @param trashcan Will be filled by the set of unparsed strings (can be 656 * null if these strings can be ignored). 657 */ 658 public void readCommandLine( String[] args, Collection<String> trashcan ) 659 { 660 for( String arg: args ) 661 { 662 boolean startsWithMinus = arg.startsWith( "-" ); 663 int equalPos = arg.indexOf( '=' ); 664 String value = "true"; 665 if( equalPos >= 0 ) 666 { 667 value = arg.substring( equalPos + 1 ); 668 if( startsWithMinus ) 669 { 670 arg = arg.substring( 1, equalPos ); 671 } 672 else 673 { 674 arg = arg.substring( 0, equalPos ); 675 } 676 parameters.put( arg, value ); 677 } 678 else 679 { 680 if( startsWithMinus ) 681 { 682 arg = arg.substring( 1 ); 683 parameters.put( arg, value ); 684 } 685 else 686 { 687 readConfigFile( arg, trashcan ); 688 } 689 } 690 } 691 } 692 693 /** 694 * Internal method that reads a configuration file. 695 */ 696 protected void readConfigFile( String filename, Collection<String> trashcan ) 697 { 698 BufferedReader br; 699 int count = 0; 700 try 701 { 702 br = new BufferedReader( new FileReader( filename ) ); 703 String str; 704 while( ( str = br.readLine() ) != null ) 705 { 706 count++; 707 if( str.length() > 0 && !str.substring( 0, 1 ).equals( "#" ) ) 708 { 709 String[] val = str.split( "=" ); 710 if( val.length != 2 ) 711 { 712 if( val.length == 1 ) 713 { 714 parameters.put( val[0].trim(), "true" ); 715 } 716 else 717 { 718 System.err 719 .printf( 720 "Something is wrong with the configuration file \"%s\"near line %d :\n %s", 721 filename, count, str ); 722 if( trashcan != null ) 723 { 724 trashcan.add( str ); 725 } 726 } 727 } 728 else 729 { 730 String s0 = val[0].trim(); 731 String s1 = val[1].trim(); 732 parameters.put( s0, s1 ); 733 } 734 } 735 } 736 737 } 738 catch( FileNotFoundException fnfe ) 739 { 740 System.err.printf( 741 "Tried to open \"%s\" as a config file: file not found.%n", 742 filename ); 743 if( trashcan != null ) 744 { 745 trashcan.add( filename ); 746 } 747 } 748 catch( IOException ioe ) 749 { 750 ioe.printStackTrace(); 751 System.exit( 0 ); 752 } 753 } 754 755 /** 756 * Save the curent parameters to a file. 757 * @param fileName Name of the file to save the config in. 758 * @throws IOException For any output error on the given file name. 759 */ 760 public void writeParameterFile( String fileName ) throws IOException 761 { 762 BufferedWriter bw = new BufferedWriter( new FileWriter( fileName ) ); 763 Set<String> ks = parameters.keySet(); 764 765 for( String key: ks ) 766 { 767 bw.write( key + " = " + parameters.get( key ) ); 768 bw.newLine(); 769 // System.out.println( key + " = " + parameters.get( key ) ); 770 } 771 772 bw.close(); 773 } 774 775 /** 776 * Read the default configuration file. Once this file has been correctly 777 * parsed, the {@link #configFileRead} boolean is set to true. 778 * @see #configFileName 779 */ 780 protected void readConfigurationFile() 781 { 782 try 783 { 784 readParameterFile( configFileName ); 785 configFileRead = true; 786 } 787 catch( IOException ioe ) 788 { 789 System.err.printf( "%-5s : %s : %s\n", "Warning", "Environment", 790 "Something wrong while reading the configuration file" ); 791 } 792 } 793 794 /** 795 * Read a parameter file. The format of this file is as follows: 796 * <ul> 797 * <li>Each line contains a parameter setting or a comment;</li> 798 * <li>Lines beginning by a "#" are considered comments (be careful, a "#" 799 * in the middle of a line <b>is not</b> a comment);</li> 800 * <li>parameters settings are of the form "name=value", spaces are 801 * allowed, but space before and after the parameter name of value will be 802 * stripped.</li> 803 * </ul> 804 * @param fileName Name of the parameter file to read. 805 * @throws IOException For any error with the given parameter file name. 806 */ 807 public void readParameterFile( String fileName ) throws IOException 808 { 809 BufferedReader br; 810 int count = 0; 811 812 br = new BufferedReader( new FileReader( fileName ) ); 813 814 String str; 815 816 while( ( str = br.readLine() ) != null ) 817 { 818 count++; 819 820 if( str.length() > 0 && !str.startsWith( "#" ) ) 821 { 822 String[] val = str.split( "=" ); 823 824 if( val.length != 2 ) 825 { 826 System.err.printf( "%-5s : %s : %s\n", "Warn", 827 "Environment", 828 "Something is wrong in your configuration file near line " 829 + count + " : \n" + Arrays.toString( val ) ); 830 } 831 else 832 { 833 String s0 = val[0].trim(); 834 String s1 = val[1].trim(); 835 836 setParameter( s0, s1 ); 837 } 838 } 839 } 840 841 br.close(); 842 } 843}