package com.cyberfox.util.config;// -*- Java -*-
/*
* Copyright (c) 2000-2007, CyberFOX Software, Inc. All Rights Reserved.
*
* Developed by mrs (Morgan Schweers)
*/
//
// History:
// mrs: 06-July-2003 12:47 - Added default return value for display configurations.
// mrs: 28-June-2000 18:37 - Added a 'queryConfiguration' function with a default.
// mrs: 24-March-2000 05:25 - Really remove the JNews specific stuff and make it a very generic configuration file.
// Administrator: 11-October-1999 21:17 - Removed most JNews specific variables...
// mrs: 24-July-1999 00:02 - Added display information saving/loading.
// mrs: 23-July-1999 10:23 - Mostly comment changes. Added a few more configurable items.
// mrs: 23-July-1999 00:17 - Fixed up so it's an entirely static class. Used
// purely as a central repository for all cfg info.
// mrs: 22-July-1999 23:57 - First version. Contains the configuration
// information, theoretically loaded once on startup.
import java.io.*;
import java.net.MalformedURLException;
import java.util.*;
import java.net.URL;
public class JConfig {
private static String sVersion=null;
// Only one property instance. This class is never 'new'ed,
// it's purely used to keep track of important config info.
protected static Properties soleProperty = new Properties();
protected static Properties displayProperty = null;
protected static Properties mAuxProps = null;
protected static Properties mTempProps = null;
protected static String _configFileName = null;
protected static String baseName = null;
// All the available JConfig.<information> fields. They should be
// set to 'sane' defaults initially, under the theory that the
// program may never actually do a loadconfig. This is ONLY true
// for now, since the server is absolutely necessary. -- note
public static int screenx, screeny, height, width;
// Interesting point: If I make this a function, and take an Object, I
// could xlate the Object passed in to a class name string, and instrument
// debugging messages on a class-by-class basis by using:
//
// if(JConfig.debugging(this)) ...
//
public static volatile boolean debugging = true;
// A vector of ConfigListener classes who (once they've registered) will be told
// when a configuration change is made.
private static List<ConfigListener> _listeners = new LinkedList<>();
// Were there any configuration changes since the last updateComplete()?
private static boolean _anyUpdates = false;
private static LoggerInterface mLogger = new NullLogger();
// A core loader which loads from an InputStream. Used so that we can
// load config files from a resource in a JAR file.
public static void load(InputStream inConfigFile) {
try {
if(inConfigFile != null) {
soleProperty.load(inConfigFile);
inConfigFile.close();
}
} catch(IOException e) {
JConfig.log().handleException("Fatal error loading config file.", e);
System.exit(1);
}
handleConfigLoading();
}
public interface ConfigListener {
void updateConfiguration();
}
// Eventually it might be possible to check the class, and
// instrument debugging on a per-class basis.
public static boolean debugging() {
return debugging;
}
public static void registerListener(ConfigListener jcl) {
_listeners.add(jcl);
}
public static void killAll(String prefix) {
Set ks = soleProperty.keySet();
Iterator it = ks.iterator();
while(it.hasNext()) {
String key = (String)it.next();
if(key.startsWith(prefix)) it.remove();
}
}
public static void kill(String key) {
Set ks;
if(key.startsWith("temp.")) {
if(mTempProps == null) return;
ks = mTempProps.keySet();
} else {
ks = soleProperty.keySet();
}
Iterator it = ks.iterator();
while(it.hasNext()) {
String stepKey = (String)it.next();
if(stepKey.equals(key)) it.remove();
}
}
/**
* @brief Identify what OS the program is running on.
*
*
* @return - A string containing windows, linux, or something else
* identifying the OS.
*/
public static String getOS() {
String rawOSName = System.getProperty("os.name");
int spaceIndex = rawOSName.indexOf(' ');
String osName;
if (spaceIndex == -1) {
osName = rawOSName;
} else {
osName = rawOSName.substring(0, spaceIndex);
}
return osName;
}
public static void updateComplete() {
setDebugging(queryConfiguration("debugging", "false").equals("true"));
if(_anyUpdates) {
for (ConfigListener jcl : _listeners) {
jcl.updateConfiguration();
}
}
_anyUpdates = false;
}
private static void handleConfigLoading() {
passwordUnfixup_b64(soleProperty);
if (soleProperty.containsKey("debugging")) {
String debugFlag = soleProperty.getProperty("debugging");
if (debugFlag.equalsIgnoreCase("true"))
setDebugging(true);
else if (debugFlag.equalsIgnoreCase("false"))
setDebugging(false);
else {
JConfig.log().logMessage("debugging flag is bad, only 'true' or 'false' allowed. Presuming true.");
setDebugging(true);
}
}
}
// configFile can be null, in which case we use defaults.
// Not such a hot idea with the server. -- note
public static void load(String configFile) {
_configFileName = configFile;
if(configFile != null) {
try {
FileInputStream fis = new FileInputStream(configFile);
load(fis);
fis.close();
} catch (FileNotFoundException e) {
JConfig.log().handleException("Property file " + configFile + " not found. Retaining default settings!\n", e);
} catch (IOException e) {
JConfig.log().handleException("Failed to close property file!\n", e);
}
}
}
private static void saveArbitrary(String cfgName, Properties arbProps) {
try {
FileOutputStream fos = new FileOutputStream(cfgName);
arbProps.store(fos, "Configuration information. Do not modify while running.");
fos.close();
} catch(IOException e) {
// D'oh. It failed to write the display information...
JConfig.log().handleException("Failed to write configuration: " + cfgName, e);
}
}
private static Properties loadArbitrary(InputStream inCfgStream) {
Properties slopsProps;
try {
slopsProps = new Properties();
slopsProps.load(inCfgStream);
inCfgStream.close();
} catch(IOException e) {
JConfig.log().handleException("Failed to load arbitrary stream configuration.", e);
slopsProps = null;
}
return slopsProps;
}
public static Properties loadArbitrary(String cfgName) {
File checkExistence = new File(cfgName);
if(checkExistence.exists()) {
try {
FileInputStream fis = new FileInputStream(cfgName);
return(loadArbitrary(fis));
} catch(IOException e) {
// What do we do? -- hackhack
JConfig.log().handleException("Failed to load configuration " + cfgName, e);
}
}
return(null);
}
public static void saveDisplayConfig(String dispFile, Properties displayProps, Properties auxProps) {
try {
File fd = new File(dispFile);
if(fd.canWrite() || !fd.exists()) {
FileOutputStream fos = new FileOutputStream(fd);
displayProps.store(fos, "Display information. Do not modify while running.");
if(auxProps != null) {
auxProps.store(fos, "Column header information. Do not modify while running.");
}
if(mAuxProps != null) {
mAuxProps.store(fos, "Search display information. Do not modify while running.");
}
fos.close();
} else {
JConfig.log().logMessage("Failed to write to the display configuration; no write permissions to " + dispFile);
}
} catch(IOException e) {
// D'oh. It failed to write the display information...
JConfig.log().handleException("Failed to write display configuration.", e);
}
}
private static void passwordFixup(Properties _inProps) {
Properties encoded = new Properties();
List<String> removedKeys = new ArrayList<>();
for (Object o : _inProps.keySet()) {
String key = o.toString();
String lcKey = key.toLowerCase();
if (lcKey.contains("password") && !lcKey.contains("_b64")) {
String val = _inProps.getProperty(key);
removedKeys.add(key);
encoded.setProperty(key + "_b64", Base64.encodeString(val, false));
}
}
for (String key : removedKeys) {
_inProps.remove(key);
}
_inProps.putAll(encoded);
}
private static void passwordUnfixup_b64(Properties _inProps) {
Properties decoded = new Properties();
List<String> removedKeys = new ArrayList<>();
for (Object o : _inProps.keySet()) {
String key = o.toString();
String lcKey = key.toLowerCase();
if (lcKey.contains("_b64")) {
int b64_start = lcKey.indexOf("_b64");
String val = _inProps.getProperty(key);
removedKeys.add(key);
key = key.substring(0, b64_start) + key.substring(b64_start + 4);
try {
decoded.setProperty(key, Base64.decodeToString(val));
} catch (Exception e) {
JConfig.log().handleException("Couldn't decode the password!", e);
}
}
}
for(String key : removedKeys) {
_inProps.remove(key);
}
_inProps.putAll(decoded);
}
public static void setConfigurationFile(String cfgFile) {
_configFileName = cfgFile;
}
public static void saveConfiguration() {
saveConfiguration(_configFileName);
}
protected static void setBaseName(String newBaseName) {
baseName = newBaseName;
}
public static void saveConfiguration(String outFile) {
_configFileName = outFile;
passwordFixup(soleProperty);
if (_configFileName != null) {
saveArbitrary(_configFileName, soleProperty);
JConfig.log().logDebug("Saving to: " + _configFileName);
} else {
saveArbitrary(baseName, soleProperty);
JConfig.log().logDebug("Just saving to: " + baseName + "!");
}
passwordUnfixup_b64(soleProperty);
}
public static InputStream bestSource(ClassLoader urlCL, String inConfig) {
File realConfig = new File(inConfig);
InputStream configStream = null;
if(realConfig.exists()) {
try {
configStream = new FileInputStream(inConfig);
} catch(FileNotFoundException ignore) {
JConfig.log().logMessage(inConfig + " deleted between existence check and loading!");
}
} else {
// Just use the files name as the index in the class loader.
configStream = urlCL.getResourceAsStream(realConfig.getName());
}
return configStream;
}
public static void loadDisplayConfig(String dispFile, ClassLoader urlCL, int screenwidth, int screenheight) {
Properties displayProps = new Properties();
InputStream dispIS = getExternalWithFallback(urlCL, dispFile);
// Preset to zero, because we check this later.
height = 0;
width = 0;
boolean setOwnProps = false;
if(dispIS != null) {
try {
displayProps.load(dispIS);
// We could save/load Font, Locale, Background Color too... -- note
screenx = Integer.parseInt(displayProps.getProperty("screenx", "0"));
screeny = Integer.parseInt(displayProps.getProperty("screeny", "0"));
// If somehow screenx / y have become negative or outside the
// screen, we need to reset the program position.
if(screenx < 0 || screeny < 0 ||
screenx > screenwidth || screeny > screenheight) {
setOwnProps = true;
}
height = Integer.parseInt(displayProps.getProperty("height", "0"));
width = Integer.parseInt(displayProps.getProperty("width", "0"));
// If either is invalid, reset it via later code.
if(height <= 0 || width <= 0) {
height = 0;
width = 0;
setOwnProps = true;
}
} catch (IOException e) {
JConfig.log().handleException("Error loading display properties.", e);
setOwnProps = true;
}
} else {
setOwnProps = true;
}
if(setOwnProps) {
// Plonk it on center of screen
if(height == 0 || width == 0) {
height = screenheight / 2;
width = (screenwidth < 1010) ? screenwidth / 2 : 1010;
}
screenx = (screenwidth - width) / 2;
screeny = (screenheight - height) / 2;
displayProps.setProperty("screenx", Integer.toString(screenx));
displayProps.setProperty("screeny", Integer.toString(screeny));
displayProps.setProperty("height", Integer.toString(height));
displayProps.setProperty("width", Integer.toString(width));
}
displayProperty = displayProps;
}
private static InputStream getExternalWithFallback(ClassLoader urlCL, String dispFile) {
boolean fileLoadFailed = false;
File checkExistence = new File(dispFile);
InputStream dispIS = null;
if(checkExistence.exists()) {
try {
dispIS = new FileInputStream(dispFile);
} catch(FileNotFoundException e) {
JConfig.log().handleException(dispFile + " deleted between existence check and loading!", e);
fileLoadFailed = true;
}
} else {
fileLoadFailed = true;
}
if(fileLoadFailed) {
dispIS = urlCL.getResourceAsStream("/resources/display.cfg");
}
return dispIS;
}
private static boolean validResource(URL path) {
if (path == null) return false;
InputStream is = JConfig.class.getClassLoader().getResourceAsStream(path.toString());
if (is != null) {
try {
is.close();
} catch (IOException ignored) {
// We don't actually care here.
}
return true;
}
return false;
}
public static URL getResource(String path) {
URL rval = JConfig.class.getClassLoader().getResource(path);
if((rval == null || !validResource(rval)) && path.charAt(0) == '/') {
rval = JConfig.class.getClassLoader().getResource(path.substring(1));
}
return rval;
}
public static void setVersion(String version) {
sVersion = version;
}
public static void setDebugging(boolean doDebug) {
// You CANNOT turn off debugging in a pre-release now.
debugging = (sVersion != null && sVersion.matches(".*(pre|alpha|beta).*")) || doDebug;
}
public static void setDisplayConfiguration(String key, String value) {
displayProperty.setProperty(key, value);
}
public static void setAuxConfiguration(String key, String value) {
if(mAuxProps == null) mAuxProps = new Properties();
mAuxProps.setProperty(key, value);
}
public static String queryAuxConfiguration(String key, String inDefault) {
if(mAuxProps == null) return inDefault;
return mAuxProps.getProperty(key, inDefault);
}
public static String queryDisplayProperty(String query) {
return displayProperty.getProperty(query, null);
}
public static Properties multiMatchDisplay(String query) {
Properties p = new Properties();
for(Object key : displayProperty.keySet()) {
if(((String)key).startsWith(query)) {
p.setProperty((String)key, displayProperty.getProperty((String)key));
}
}
return p;
}
public static String queryDisplayProperty(String query, String inDefault) {
String retVal = queryDisplayProperty(query);
if(retVal == null) return inDefault;
return retVal;
}
public static void setConfiguration(String key, String value) {
if(key.startsWith("temp.")) {
if(mTempProps == null) mTempProps = new Properties();
mTempProps.setProperty(key, value);
} else {
_anyUpdates = true;
soleProperty.setProperty(key, value);
}
}
public static String queryConfiguration(String query, String inDefault) {
String retVal = queryConfiguration(query);
if(retVal == null) return inDefault;
return retVal;
}
public static String queryConfiguration(String query) {
if(soleProperty.getProperty("config.logging", "false").equals("true")) {
System.out.println("Query: " + query);
}
if(query.startsWith("temp.")) {
if(mTempProps == null) return null;
return mTempProps.getProperty(query, null);
}
return soleProperty.getProperty(query, null);
}
public static List<String> getAllKeys() {
List<String> keyList = new ArrayList<>();
for (Object name : soleProperty.keySet()) {
keyList.add(name.toString());
}
Collections.sort(keyList);
return keyList;
}
public static List<String> getMatching(String prefix) {
Set keySet = soleProperty.keySet();
List<String> results = null;
int prefixLen = prefix.length();
for (Object aKeySet : keySet) {
String s = (String) aKeySet;
if (s.startsWith(prefix)) {
if (results == null) results = new ArrayList<>();
results.add(s.substring(prefixLen));
}
}
return results;
}
public static void killDisplay(String key) {
displayProperty.remove(key);
}
public static void addAllToDisplay(Properties replace) {
displayProperty.putAll(replace);
}
public static void setLogger(LoggerInterface logger) {
mLogger = logger;
}
public static LoggerInterface log() {
return mLogger;
}
public static void increment(String key) {
long count;
try {
count = Integer.parseInt(queryConfiguration(key, "0"));
} catch (NumberFormatException e) {
count = 0;
}
count++;
setConfiguration(key, Long.toString(count));
}
private static String sHomeDirectory = null;
public static void setHomeDirectory(String homeDirectory) {
sHomeDirectory = homeDirectory;
}
public static String getHomeDirectory() {
return sHomeDirectory;
}
private static Set sTimers = new HashSet();
public static void registerTimer(Object o) {
sTimers.add(o);
}
public static Set getTimers() {
return sTimers;
}
public static URL getURL(String url) throws MalformedURLException {
log().logDebug("Parsing URL " + url);
return new URL(url);
}
}