package net.varkhan.base.management.util; import net.varkhan.base.management.config.*; import java.io.BufferedReader; import java.io.IOException; import java.io.Reader; import java.io.Writer; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.*; /** * <b></b>. * <p/> * * @author varkhan * @date 9/20/14 * @time 1:24 PM */ public class Configurations { /** Private constructor to enforce static method collection status */ protected Configurations() { } /********************************************************************************** ** Globally accessible config and contexts **/ /** * The system environment, properties and settings as a Configuration. * <p/> * The contexts names for each subsets are, respectively: * <li><em>environment</em></li> * <li><em>properties</em></li> * * @return a settable configuration containing all system parameters */ public static CompoundConfiguration<SettableConfiguration.Context> sysconf() { return systemConfiguration; } public static final String SYS_CTX_ENVIRONMENT="environment"; public static final String SYS_CTX_PROPERTIES="properties"; /** The system environment, properties and settings as a settable configuration. */ protected static final CompoundConfiguration<SettableConfiguration.Context> systemConfiguration; /** The system environment variables, as a settable context */ protected static final SettableConfiguration.Context systemEnvironment; /** The system properties, as a settable context */ protected static final Properties systemProperties; static { SettableConfiguration.Context env; try { env=new Environment(SYS_CTX_ENVIRONMENT); } catch(Exception e) { try { env=new Environment2(SYS_CTX_ENVIRONMENT); } catch(Exception e2) { env=new MapContext(SYS_CTX_ENVIRONMENT, new HashMap<String,Object>()); } } systemEnvironment= env; systemProperties=new Properties(SYS_CTX_PROPERTIES); systemConfiguration=new CompoundConfiguration<SettableConfiguration.Context>( systemEnvironment, systemProperties ); } /********************************************************************************** ** System environment context magic **/ /** * <b>A modifiable, settable implementation of the Environment context</b>. * <p/> */ protected static class Environment implements SettableConfiguration.Context { protected final String ctx; protected final Method varConv; protected final Method valConv; protected final Map<String,String> envMapUm; protected final Map<Object,Object> envMapCi; protected final Map<Object,Object> envMap; @SuppressWarnings("unchecked") protected Environment(String name) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException, IllegalAccessException { ctx=name; envMapUm=System.getenv(); Class<?> peClass=Class.forName("java.lang.ProcessEnvironment"); varConv=Class.forName("java.lang.ProcessEnvironment$Variable").getMethod("valueOf", String.class); varConv.setAccessible(true); valConv=Class.forName("java.lang.ProcessEnvironment$Value").getMethod("valueOf", String.class); valConv.setAccessible(true); Field varsField; try { varsField=peClass.getDeclaredField("theEnvironment"); varsField.setAccessible(true); } catch(NoSuchFieldException e) { varsField=null; } envMap=varsField==null ? null : (Map<Object,Object>) varsField.get(null); Field varsFieldCi; try { varsFieldCi=peClass.getDeclaredField("theCaseInsensitiveEnvironment"); varsFieldCi.setAccessible(true); } catch(NoSuchFieldException e) { varsFieldCi=null; } envMapCi=varsFieldCi==null?null:(Map<Object,Object>) varsFieldCi.get(null); if(varsField==null&& varsFieldCi==null) throw new ClassNotFoundException("java.lang.ProcessEnvironment"); } @Override public String name() { return ctx; } @Override public synchronized boolean has(String key) { return java.lang.System.getenv().containsKey(key); } @Override public synchronized String get(String var) { return java.lang.System.getenv().get(var); } @Override public synchronized Map<String, String> get() { return java.lang.System.getenv(); } @Override public Iterator<Configuration.Entry> iterator() { return new Iterator<Configuration.Entry>() { Iterator<Map.Entry<Object,Object>> it = envMap.entrySet().iterator(); @Override public boolean hasNext() { return it.hasNext(); } @Override public Configuration.Entry next() { final Map.Entry<Object,Object> e; synchronized(Environment.this) { e = it.next(); } return new Configuration.Entry() { @Override public String ctx() { return name(); } @Override public String key() { synchronized(Environment.this) { return e.getKey().toString(); } } @Override public Object value() { synchronized(Environment.this) { return e.getValue(); } } }; } @Override public void remove() { it.remove(); } }; } @Override public synchronized boolean add(String var, Object val) { if(var==null || val==null) return false; try { Object k=varConv.invoke(null, var); Object v=valConv.invoke(null, val); if(envMap!=null) envMap.put(k, v); if(envMapCi!=null) envMapCi.put(k, v); return true; } catch(Exception ex) { return false; } } @Override public synchronized boolean add(Configuration.Entry cfg) { return add(cfg.key(),cfg.value()); } @Override public synchronized boolean add(Map<String, ?> env) { try { for(Map.Entry<String,?> e: env.entrySet()) { Object var=varConv.invoke(null, e.getKey()); Object v=e.getValue(); if(v==null) { if(envMap!=null) envMap.remove(var); if(envMapCi!=null) envMapCi.remove(var); } else { Object val=valConv.invoke(null, v.toString()); if(envMap!=null) envMap.put(var, val); if(envMapCi!=null) envMapCi.put(var, val); } } return true; } catch(Exception ex) { return false; } } @Override public synchronized boolean del(String var) { if(var==null) return false; try { Object k=varConv.invoke(null, var); boolean m = false; if(envMap!=null) m |= null!=envMapCi.remove(k); if(envMapCi!=null) m |= null!=envMapCi.remove(k); return m; } catch(Exception ex) { return false; } } } /** * <b>A fallback implementation of Environment where the implementation doesn't have java.lang.ProcessEnvironment</b>. * <p/> */ protected static class Environment2 implements SettableConfiguration.Context { protected final String ctx; protected final Field varsField; @SuppressWarnings("unchecked") protected Environment2(String name) throws ClassNotFoundException, NoSuchFieldException { ctx=name; varsField=Class.forName("java.util.Collections$UnmodifiableMap").getField("m"); varsField.setAccessible(true); } @Override public String name() { return ctx; } @Override public synchronized boolean has(String key) { try { Map<String,String> envMapUm=java.lang.System.getenv(); @SuppressWarnings("unchecked") Map<String,String> envMap=(Map<String,String>) varsField.get(envMapUm); return envMap.containsKey(key); } catch(Exception ex) { return false; } } public synchronized String get(String var) { return java.lang.System.getenv(var); } public synchronized Map<String,String> get() { return java.lang.System.getenv(); } @Override public Iterator<Configuration.Entry> iterator() { try { Map<String,String> envMapUm=java.lang.System.getenv(); @SuppressWarnings("unchecked") final Map<String,String> envMap=(Map<String,String>) varsField.get(envMapUm); return new Iterator<Configuration.Entry>() { Iterator<Map.Entry<String,String>> it=envMap.entrySet().iterator(); @Override public boolean hasNext() { return it.hasNext(); } @Override public Configuration.Entry next() { final Map.Entry<String,String> e=it.next(); return new Configuration.Entry() { @Override public String ctx() { return name(); } @Override public String key() { return e.getKey(); } @Override public Object value() { return e.getValue(); } }; } @Override public void remove() { it.remove(); } }; } catch(Exception ex) { return null; } } @Override public synchronized boolean add(String var, Object val) { if(var==null||val==null) return false; try { Map<String,String> envMapUm=java.lang.System.getenv(); @SuppressWarnings("unchecked") final Map<String,String> envMap=(Map<String,String>) varsField.get(envMapUm); envMap.put(var, val.toString()); return true; } catch(Exception ex) { return false; } } @Override public boolean add(Configuration.Entry cfg) { return add(cfg.key(), cfg.value()); } @Override public synchronized boolean del(String var) { if(var==null) return false; try { Map<String,String> envMapUm=java.lang.System.getenv(); @SuppressWarnings("unchecked") final Map<String,String> envMap=(Map<String,String>) varsField.get(envMapUm); return null!=envMap.remove(var); } catch(Exception ex) { return false; } } public synchronized boolean add(Map<String,?> env) { try { Map<String,String> envMapUm=java.lang.System.getenv(); @SuppressWarnings("unchecked") final Map<String,String> envMap=(Map<String,String>) varsField.get(envMapUm); for(Map.Entry<String,?> e : env.entrySet()) { String var=e.getKey(); Object val=e.getValue(); if(val==null) envMap.remove(var); else envMap.put(var, val.toString()); } return true; } catch(Exception ex) { return false; } } } /********************************************************************************** ** System properties context wrapper **/ /** * <b>A wrapper implementation of the system properties</b>. * <p/> */ protected static class Properties implements SettableConfiguration.Context { protected final String ctx; public Properties(String ctx) { this.ctx=ctx;} @Override public String name() { return ctx; } @Override public boolean has(String key) { return java.lang.System.getProperties().containsKey(key); } @Override public String get(String name) { return java.lang.System.getProperty(name); } @Override @SuppressWarnings("unchecked") public Map<String,String> get() { return (Hashtable) java.lang.System.getProperties(); } @Override public Iterator<Configuration.Entry> iterator() { return new Iterator<Configuration.Entry>() { Iterator<Map.Entry<Object,Object>> it = java.lang.System.getProperties().entrySet().iterator(); @Override public boolean hasNext() { return it.hasNext(); } @Override public Configuration.Entry next() { final Map.Entry<Object,Object> e = it.next(); return new Configuration.Entry() { @Override public String ctx() { return name(); } @Override public String key() { return e.getKey().toString(); } @Override public Object value() { return e.getKey(); } }; } @Override public void remove() { it.remove(); } }; } public static String set(String name, String prop) { return java.lang.System.setProperty(name, prop); } public static boolean set(Map<String,String> props) { java.util.Properties p=java.lang.System.getProperties(); p.putAll(props); java.lang.System.setProperties(p); return true; } @Override public boolean add(String key, Object val) { if(key==null||val==null) return false; java.lang.System.setProperty(key, val.toString()); return true; } @Override public boolean add(Configuration.Entry cfg) { return add(cfg.key(),cfg.value()); } @Override public boolean add(Map<String,?> cfgs) { java.util.Properties p=java.lang.System.getProperties(); p.putAll(cfgs); java.lang.System.setProperties(p); return true; } @Override public boolean del(String key) { java.lang.System.clearProperty(key); return true; } } /********************************************************************************** ** Generic properties loader **/ /** * Load configuration mappings from a character stream. * <p/> * Configuration streams are line-oriented. Each line can contain one of: * <li><em>a comment</em>: a hash sign '#', * followed by any number of characters, * and a newline character</li> * <li><em>a context</em>: a opening square bracket sign ']', * the context name (any number of characters, except for closing square brackets and newlines), * a closing square bracket ']', * any number of non-newline characters (ignored) * and a newline character</li> * <li><em>a configuration mapping</em>: a configuration key * (any number of non-newline characters, where '\', '=' and '#' are escaped by a '\' character), * followed by an '=' sign, * a configuration value (where '\' and newline characters are escaped by a '\' character) * and a newline character</li> * <p/> * A physical line equals a logical line, except for configuration mappings * where physical lines can be merged by escaping with a '\' (backslash) the * final newline character of each physical line. * <p/> * Each configuration mapping is set within the context defined by the last * seen context line. If no context line is specified, the default context * will be used. A context name equal to the empty string or a single '*' * character will switch back to the default context. * <p/> * An example configuration file: * <pre> * # [] implicit default context * # Set default context properties * com.example.property1=value1 * * [internal] the rest of this line is ignored * com.example.property1=internal_value1 * * [validation] * com.example.property1=validation_value1 * com.example.property2=validation_value2_line1\ * validation_value2_line2\ * validation_value2_line3\ * * [*] (equivalent to []) * # Override default context values * com.example.property1=override_value1 * </pre> * * * @param cfg the configuration to load mappings into * @param rdr the reader to read mappings from * @return the number of configuration mapping reads (including duplicates) * @throws IOException if an error occurred while reading from the stream */ public static int loadConfig(SettableConfiguration cfg, Reader rdr) throws IOException { BufferedReader lrd = (rdr instanceof BufferedReader)?(BufferedReader)rdr:new BufferedReader(rdr); StringBuilder buf = new StringBuilder(); // Start with null (default) context String ctx=null; String line; int count = 0; while((line=lrd.readLine())!=null) { // Skip whitespace at the beginning int p=0; char c = '\0'; while(p<line.length()) { c = line.charAt(p); if(c!=' '&&c!='\t'&&c!='\f') break; p ++; } // Empty/blank line if(p>=line.length() || c=='\n') continue; if(c=='#') continue; else if(c=='[') { p ++; int q = line.indexOf(']',p); // Err on the safe side: non-terminated context if(q<0) throw new IOException("Malformed context switch at \""+line+"\""); // A context switch of [] or [*] resets to the default context if(q==p || (q==p+1 && line.charAt(p)=='*')) ctx = null; else ctx = line.substring(p,q); } else { boolean esc = false; int q=p; buf.setLength(0); while(q<line.length()) { c = line.charAt(q); if(c=='=' && !esc) break; if(!esc && c=='\\') esc = true; else { esc = false; buf.append(c); } q ++; } // Err on the safe side: malformed config definition if(esc||c!='=') throw new IOException("Malformed config definition at \""+line+"\""); String key = buf.toString(); String val; p = ++q; if(p>=line.length()) { val = ""; } // Handle multi-line values (partial lines terminated by a lone \ character) else if(line.charAt(line.length()-1)=='\\') { buf.setLength(0); while(line!=null) { esc=false; while(q<line.length()) { c=line.charAt(q++); if(!esc && c=='\\') esc = true; else { esc = false; buf.append(c); } } if(esc) { buf.append('\n'); line=lrd.readLine(); q = 0; } else { break; } } val = buf.toString(); } else val = line.substring(p); cfg.add(ctx,key,val); count++; } } return count; } /** * Save configuration mappings to a character stream. * <p/> * See {@link #loadConfig(SettableConfiguration, Reader) loadConfig} * for a description of the format. * * @param cfg the configuration * @param wrt the writer to write mappings to * @return the number of configuration mappings written * @throws IOException if an error occurred while writing to the stream * @see #loadConfig(SettableConfiguration, Reader) */ public static int saveConfig(Configuration cfg, Writer wrt) throws IOException { int count=0; for(String ctx: cfg.contexts()) { wrt.append('[').append(ctx).append(']').append('\n'); for(Configuration.Entry var: cfg.context(ctx)) { if(var.key().startsWith("#")) { if(var.value()!=null) wrt.append('\\'); } wrt.append(var.key().replace("\\", "\\\\").replace("=", "\\=")).append('='); if(var.value()!=null) wrt.append(var.value().toString().replace("\\","\\\\").replace("\n", "\\\n")); wrt.append('\n'); count++; } wrt.append('\n'); } return count; } }