/* This code is part of Freenet. It is distributed under the GNU General * Public License, version 2 (or at your option any later version). See * http://www.gnu.org/ for further details of the GPL. */ package freenet.config; import java.util.LinkedHashMap; import java.util.Map; import java.util.Map.Entry; import freenet.support.LogThresholdCallback; import freenet.support.Logger; import freenet.support.SimpleFieldSet; import freenet.support.Logger.LogLevel; import freenet.support.api.BooleanCallback; import freenet.support.api.IntCallback; import freenet.support.api.LongCallback; import freenet.support.api.ShortCallback; import freenet.support.api.StringArrCallback; import freenet.support.api.StringCallback; /** * A specific configuration block. */ public class SubConfig implements Comparable<SubConfig> { private final LinkedHashMap<String, Option<?>> map; public final Config config; final String prefix; private boolean hasInitialized; private static volatile boolean logMINOR; static { Logger.registerLogThresholdCallback(new LogThresholdCallback(){ @Override public void shouldUpdate(){ logMINOR = Logger.shouldLog(LogLevel.MINOR, this); } }); } /** * @deprecated Use {@link Config#createSubConfig(String)} instead */ @Deprecated public SubConfig(String prefix, Config config) { this.config = config; this.prefix = prefix; map = new LinkedHashMap<String, Option<?>>(); hasInitialized = false; config.register(this); } /** * Return all the options registered. Each includes its name. * Used by e.g. webconfig. */ public synchronized Option<?>[] getOptions() { return map.values().toArray(new Option[map.size()]); } public synchronized Option<?> getOption(String option) { return map.get(option); } public void register(Option<?> o) { synchronized(this) { if(o.name.indexOf(SimpleFieldSet.MULTI_LEVEL_CHAR) != -1) throw new IllegalArgumentException("Option names must not contain "+SimpleFieldSet.MULTI_LEVEL_CHAR); if(map.containsKey(o.name)) throw new IllegalArgumentException("Already registered: "+o.name+" on "+this); map.put(o.name, o); } config.onRegister(this, o); } public void register(String optionName, int defaultValue, int sortOrder, boolean expert, boolean forceWrite, String shortDesc, String longDesc, IntCallback cb, boolean isSize) { if(cb == null) cb = new NullIntCallback(); register(new IntOption(this, optionName, defaultValue, sortOrder, expert, forceWrite, shortDesc, longDesc, cb, isSize)); } public void register(String optionName, long defaultValue, int sortOrder, boolean expert, boolean forceWrite, String shortDesc, String longDesc, LongCallback cb, boolean isSize) { if(cb == null) cb = new NullLongCallback(); register(new LongOption(this, optionName, defaultValue, sortOrder, expert, forceWrite, shortDesc, longDesc, cb, isSize)); } /** * Registers a bandwidth option. * @see BandwidthOption */ public void register(String optionName, int defaultValue, int sortOrder, boolean expert, boolean forceWrite, String shortDesc, String longDesc, IntCallback cb) { if(cb == null) cb = new NullIntCallback(); register(new BandwidthOption(this, optionName, defaultValue, sortOrder, expert, forceWrite, shortDesc, longDesc, cb)); } public void register(String optionName, String defaultValueString, int sortOrder, boolean expert, boolean forceWrite, String shortDesc, String longDesc, IntCallback cb, boolean isSize) { if(cb == null) cb = new NullIntCallback(); register(new IntOption(this, optionName, defaultValueString, sortOrder, expert, forceWrite, shortDesc, longDesc, cb, isSize)); } public void register(String optionName, String defaultValueString, int sortOrder, boolean expert, boolean forceWrite, String shortDesc, String longDesc, LongCallback cb, boolean isSize) { if(cb == null) cb = new NullLongCallback(); register(new LongOption(this, optionName, defaultValueString, sortOrder, expert, forceWrite, shortDesc, longDesc, cb, isSize)); } /** * Registers a bandwidth option. * @see BandwidthOption */ public void register(String optionName, String defaultValueString, int sortOrder, boolean expert, boolean forceWrite, String shortDesc, String longDesc, IntCallback cb) { if(cb == null) cb = new NullIntCallback(); register(new BandwidthOption(this, optionName, defaultValueString, sortOrder, expert, forceWrite, shortDesc, longDesc, cb)); } public void register(String optionName, boolean defaultValue, int sortOrder, boolean expert, boolean forceWrite, String shortDesc, String longDesc, BooleanCallback cb) { if(cb == null) cb = new NullBooleanCallback(); register(new BooleanOption(this, optionName, defaultValue, sortOrder, expert, forceWrite, shortDesc, longDesc, cb)); } public void register(String optionName, String defaultValue, int sortOrder, boolean expert, boolean forceWrite, String shortDesc, String longDesc, StringCallback cb) { if(cb == null) cb = new NullStringCallback(); register(new StringOption(this, optionName, defaultValue, sortOrder, expert, forceWrite, shortDesc, longDesc, cb)); } public void register(String optionName, short defaultValue, int sortOrder, boolean expert, boolean forceWrite, String shortDesc, String longDesc, ShortCallback cb, boolean isSize) { if(cb == null) cb = new NullShortCallback(); register(new ShortOption(this, optionName, defaultValue, sortOrder, expert, forceWrite, shortDesc, longDesc, cb, isSize)); } public void register(String optionName, String[] defaultValue, int sortOrder, boolean expert, boolean forceWrite, String shortDesc, String longDesc, StringArrCallback cb) { register(new StringArrOption(this, optionName, defaultValue, sortOrder, expert, forceWrite, shortDesc, longDesc, cb)); } public int getInt(String optionName) { IntOption o; synchronized(this) { o = (IntOption) map.get(optionName); } return o.getValue(); } public long getLong(String optionName) { LongOption o; synchronized(this) { o = (LongOption) map.get(optionName); } return o.getValue(); } public boolean getBoolean(String optionName) { BooleanOption o; synchronized(this) { o = (BooleanOption) map.get(optionName); } return o.getValue(); } public String getString(String optionName) { StringOption o; synchronized(this) { o = (StringOption) map.get(optionName); } return o.getValue().trim(); } public String[] getStringArr(String optionName) { StringArrOption o; synchronized(this) { o = (StringArrOption) map.get(optionName); } return o.getValue(); } public short getShort(String optionName) { ShortOption o; synchronized(this) { o = (ShortOption) map.get(optionName); } return o.getValue(); } public Option<?> removeOption(String optionName) { synchronized(this) { return map.remove(optionName); } } /** * Has the object we are attached to finished initialization? */ public boolean hasFinishedInitialization() { return hasInitialized; } /** * Called when the object we are attached to has finished init. * After this point, the callbacks are authoritative for values of * config variables, and will be called when values are changed by * the user. */ public void finishedInitialization() { hasInitialized = true; if(logMINOR) Logger.minor(this, "Finished initialization on "+this+" ("+prefix+')'); } /** * Set options from a SimpleFieldSet. Once we process an option, we must remove it. */ public void setOptions(SimpleFieldSet sfs) { for(Entry<String, Option<?>> entry: map.entrySet()) { String key = entry.getKey(); Option<?> o = entry.getValue(); String val = sfs.get(key); if(val != null) { try { o.setValue(val); } catch (InvalidConfigValueException e) { String msg = "Invalid config value: "+prefix+SimpleFieldSet.MULTI_LEVEL_CHAR+key+" = "+val+" : error: "+e; Logger.error(this, msg, e); System.err.println(msg); // might be about logging? } catch (NodeNeedRestartException e) { // Impossible String msg = "Impossible: " + prefix + SimpleFieldSet.MULTI_LEVEL_CHAR + key + " = " + val + " : error: " + e; Logger.error(this, msg, e); } } } } public SimpleFieldSet exportFieldSet() { return exportFieldSet(false); } public SimpleFieldSet exportFieldSet(boolean withDefaults) { return exportFieldSet(Config.RequestType.CURRENT_SETTINGS, withDefaults); } public SimpleFieldSet exportFieldSet(Config.RequestType configRequestType, boolean withDefaults) { SimpleFieldSet fs = new SimpleFieldSet(true); @SuppressWarnings("unchecked") Map.Entry<String, Option<?>>[] entries = new Map.Entry[map.size()]; // FIXME is any locking at all necessary here? After it has finished init, it's constant... synchronized(this) { entries = map.entrySet().toArray(entries); } if(logMINOR) Logger.minor(this, "Prefix="+prefix); for(Map.Entry<String, Option<?>> entry: entries) { String key = entry.getKey(); Option<?> o = entry.getValue(); if(logMINOR) Logger.minor(this, "Key="+key+" value="+o.getValueString()+" default="+o.isDefault()); if (configRequestType == Config.RequestType.CURRENT_SETTINGS && (!withDefaults) && o.isDefault() && (!o.forceWrite)) { if(logMINOR) Logger.minor(this, "Skipping "+key+" - "+o.isDefault()); continue; } switch (configRequestType) { case CURRENT_SETTINGS: fs.putSingle(key, o.getValueString()); break; case DEFAULT_SETTINGS: fs.putSingle(key, o.getDefault()); break; case SORT_ORDER: fs.put(key, o.getSortOrder()); break; case EXPERT_FLAG: fs.put(key, o.isExpert()); break; case FORCE_WRITE_FLAG: fs.put(key, o.isForcedWrite()); break; case SHORT_DESCRIPTION: fs.putSingle(key, o.getLocalisedShortDesc()); break; case LONG_DESCRIPTION: fs.putSingle(key, o.getLocalisedLongDesc()); break; case DATA_TYPE: fs.putSingle(key, o.getDataTypeStr()); break; default: Logger.error(this, "Unknown config request type value: "+configRequestType); break; } if(logMINOR) Logger.minor(this, "Key="+prefix+'.'+key+" value="+o.getValueString()); } return fs; } /** * Force an option to be updated even if it hasn't changed. * * @throws InvalidConfigValueException * @throws NodeNeedRestartException */ public void forceUpdate(String optionName) throws InvalidConfigValueException, NodeNeedRestartException { Option<?> o = map.get(optionName); o.forceUpdate(); } public void set(String name, String value) throws InvalidConfigValueException, NodeNeedRestartException { Option<?> o = map.get(name); o.setValue(value); } public void set(String name, boolean value) throws InvalidConfigValueException, NodeNeedRestartException { BooleanOption o = (BooleanOption) map.get(name); o.set(value); } /** * If the option's value is equal to the provided old default, then set it to the * new default. Used to deal with changes to important options where this is not * handled automatically because the option's value is written to the .ini. * @param name The name of the option. * @param value The value of the option. */ public void fixOldDefault(String name, String value) { Option<?> o = map.get(name); if(o.getValueString().equals(value)) o.setDefault(); } /** * If the option's value matches the provided old default regex, then set it to the * new default. Used to deal with changes to important options where this is not * handled automatically because the option's value is written to the .ini. * @param name The name of the option. * @param value The value of the option. */ public void fixOldDefaultRegex(String name, String value) { Option<?> o = map.get(name); if(o.getValueString().matches(value)) o.setDefault(); } public String getPrefix(){ return prefix; } @Override public int compareTo(SubConfig second) { if (this.getPrefix().compareTo(second.getPrefix()) > 0) return 1; else return -1; } public String getRawOption(String name) { if(config instanceof PersistentConfig) { PersistentConfig pc = (PersistentConfig) config; if(pc.finishedInit) throw new IllegalStateException("getRawOption("+name+") on "+this+" but persistent config has been finishedInit() already!"); SimpleFieldSet fs = pc.origConfigFileContents; if(fs == null) return null; return fs.get(prefix + SimpleFieldSet.MULTI_LEVEL_CHAR + name); } else return null; } }