1 /*
   2  * Copyright (c) 2002-2012, the original author or authors.
   3  *
   4  * This software is distributable under the BSD license. See the terms of the
   5  * BSD license in the documentation provided with this software.
   6  *
   7  * http://www.opensource.org/licenses/bsd-license.php
   8  */
   9 package jline.internal;
  10 
  11 import java.io.BufferedInputStream;
  12 import java.io.File;
  13 import java.io.IOException;
  14 import java.io.InputStream;
  15 import java.net.URL;
  16 import java.nio.charset.Charset;
  17 import java.util.Map;
  18 import java.util.Properties;
  19 
  20 import static jline.internal.Preconditions.checkNotNull;
  21 
  22 /**
  23  * Provides access to configuration values.
  24  *
  25  * @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
  26  * @author <a href="mailto:gnodet@gmail.com">Guillaume Nodet</a>
  27  * @since 2.4
  28  */
  29 public class Configuration
  30 {
  31     /**
  32      * System property which can point to a file or URL containing configuration properties to load.
  33      *
  34      * @since 2.7
  35      */
  36     public static final String JLINE_CONFIGURATION = "jline.configuration";
  37 
  38     /**
  39      * Default configuration file name loaded from user's home directory.
  40      */
  41     public static final String JLINE_RC = ".jline.rc";
  42 
  43     private static volatile Properties properties;
  44 
  45     private static Properties initProperties() {
  46         URL url = determineUrl();
  47         Properties props = new Properties();
  48         try {
  49             loadProperties(url, props);
  50         }
  51         catch (IOException e) {
  52             // debug here instead of warn, as this can happen normally if default jline.rc file is missing
  53             Log.debug("Unable to read configuration from: ", url, e);
  54         }
  55         return props;
  56     }
  57 
  58     private static void loadProperties(final URL url, final Properties props) throws IOException {
  59         Log.debug("Loading properties from: ", url);
  60         InputStream input = url.openStream();
  61         try {
  62             props.load(new BufferedInputStream(input));
  63         }
  64         finally {
  65             try {
  66                 input.close();
  67             }
  68             catch (IOException e) {
  69                 // ignore
  70             }
  71         }
  72 
  73         if (Log.DEBUG) {
  74             Log.debug("Loaded properties:");
  75             for (Map.Entry<Object,Object> entry : props.entrySet()) {
  76                 Log.debug("  ", entry.getKey(), "=", entry.getValue());
  77             }
  78         }
  79     }
  80 
  81     private static URL determineUrl() {
  82         // See if user has customized the configuration location via sysprop
  83         String tmp = System.getProperty(JLINE_CONFIGURATION);
  84         if (tmp != null) {
  85             return Urls.create(tmp);
  86         }
  87         else {
  88             // Otherwise try the default
  89             File file = new File(getUserHome(), JLINE_RC);
  90             return Urls.create(file);
  91         }
  92     }
  93 
  94     /**
  95      * @since 2.7
  96      */
  97     public static void reset() {
  98         Log.debug("Resetting");
  99         properties = null;
 100 
 101         // force new properties to load
 102         getProperties();
 103     }
 104 
 105     /**
 106      * @since 2.7
 107      */
 108     public static Properties getProperties() {
 109         // Not sure its worth to guard this with any synchronization, volatile field probably sufficient
 110         if (properties == null) {
 111             properties = initProperties();
 112         }
 113         return properties;
 114     }
 115 
 116     public static String getString(final String name, final String defaultValue) {
 117         checkNotNull(name);
 118 
 119         String value;
 120 
 121         // Check sysprops first, it always wins
 122         value = System.getProperty(name);
 123 
 124         if (value == null) {
 125             // Next try userprops
 126             value = getProperties().getProperty(name);
 127 
 128             if (value == null) {
 129                 // else use the default
 130                 value = defaultValue;
 131             }
 132         }
 133 
 134         return value;
 135     }
 136 
 137     public static String getString(final String name) {
 138         return getString(name, null);
 139     }
 140 
 141     public static boolean getBoolean(final String name, final boolean defaultValue) {
 142         String value = getString(name);
 143         if (value == null) {
 144             return defaultValue;
 145         }
 146         return value.length() == 0
 147             || value.equalsIgnoreCase("1")
 148             || value.equalsIgnoreCase("on")
 149             || value.equalsIgnoreCase("true");
 150     }
 151 
 152     /**
 153      * @since 2.6
 154      */
 155     public static int getInteger(final String name, final int defaultValue) {
 156         String str = getString(name);
 157         if (str == null) {
 158             return defaultValue;
 159         }
 160         return Integer.parseInt(str);
 161     }
 162 
 163     /**
 164      * @since 2.6
 165      */
 166     public static long getLong(final String name, final long defaultValue) {
 167         String str = getString(name);
 168         if (str == null) {
 169             return defaultValue;
 170         }
 171         return Long.parseLong(str);
 172     }
 173 
 174     //
 175     // System property helpers
 176     //
 177 
 178     /**
 179      * @since 2.7
 180      */
 181     public static String getLineSeparator() {
 182         return System.getProperty("line.separator");
 183     }
 184 
 185     public static File getUserHome() {
 186         return new File(System.getProperty("user.home"));
 187     }
 188 
 189     public static String getOsName() {
 190         return System.getProperty("os.name").toLowerCase();
 191     }
 192 
 193     /**
 194      * @since 2.7
 195      */
 196     public static boolean isWindows() {
 197         return getOsName().startsWith("windows");
 198     }
 199 
 200     // FIXME: Sort out use of property access of file.encoding in InputStreamReader, consolidate should configuration access here
 201 
 202     public static String getFileEncoding() {
 203         return System.getProperty("file.encoding");
 204     }
 205 
 206     /**
 207      * Get the default encoding.  Will first look at the LC_CTYPE environment variable, then the input.encoding
 208      * system property, then the default charset according to the JVM.
 209      *
 210      * @return The default encoding to use when none is specified.
 211      */
 212     public static String getEncoding() {
 213         // LC_CTYPE is usually in the form en_US.UTF-8
 214         String envEncoding = extractEncodingFromCtype(System.getenv("LC_CTYPE"));
 215         if (envEncoding != null) {
 216             return envEncoding;
 217         }
 218         return System.getProperty("input.encoding", Charset.defaultCharset().name());
 219     }
 220 
 221     /**
 222      * Parses the LC_CTYPE value to extract the encoding according to the POSIX standard, which says that the LC_CTYPE
 223      * environment variable may be of the format <code>[language[_territory][.codeset][@modifier]]</code>
 224      *
 225      * @param ctype The ctype to parse, may be null
 226      * @return The encoding, if one was present, otherwise null
 227      */
 228     static String extractEncodingFromCtype(String ctype) {
 229         if (ctype != null && ctype.indexOf('.') > 0) {
 230             String encodingAndModifier = ctype.substring(ctype.indexOf('.') + 1);
 231             if (encodingAndModifier.indexOf('@') > 0) {
 232                 return encodingAndModifier.substring(0, encodingAndModifier.indexOf('@'));
 233             } else {
 234                 return encodingAndModifier;
 235             }
 236         }
 237         return null;
 238     }
 239 }