ConfigData.java [src/usda/weru/util] Revision: default  Date:
package usda.weru.util;

import java.io.File;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.logging.Logger;

/**
 * Contains configuration data for WepsUI.
 * 
 * @author wjr
 * @version 2.0
 */
public class ConfigData /*implements PropertyChangeListener*/ {

    static{
        //The reference currency is always the United States Dollar.
//        Currency.setReferenceCurrency(Currency.USD);
    }

    private static final Object LOCK = new Object();    
    public enum AccessLevel {

        Write,
        Read,
        Hidden;

        public static final AccessLevel parse(String name) {
            for (AccessLevel level : AccessLevel.values()) {
                if (level.toString().equalsIgnoreCase(name)) {
                    return level;
                }
            }
            return null;
        }

        public static final AccessLevel higher(AccessLevel a, AccessLevel b) {
            if (a == null && b == null) {
                return null;
            } else if (a == null) {
                return b;
            } else if (b == null) {
                return a;
            } else if (Write.equals(a) || Write.equals(b)) {
                return Write;
            } else if (Read.equals(a) || Read.equals(b)) {
                return Read;
            } else if (Hidden.equals(a) || Hidden.equals(b)) {
                return Hidden;
            } else {
                return null;
            }
        }
        
        public static final AccessLevel lower(AccessLevel a, AccessLevel b) {
            if (a == null && b == null) {
                return null;
            } else if (a == null) {
                return b;
            } else if (b == null) {
                return a;
            } 
            else if (Hidden.equals(a) || Hidden.equals(b)) {
                return Hidden;
            }             
            else if (Read.equals(a) || Read.equals(b)) {
                return Read;
            } 
            else if (Write.equals(a) || Write.equals(b)) {
                return Write;
            } 
            else {
                return null;
            }
        }
    }
    private static final Logger LOGGER = Logger.getLogger(ConfigData.class.getName());
    private static final String XML_CONFIGURATION = "configuration";
    private static final String XML_VERSION = "version";
    private static final String XML_PARAMETER = "parameter";
    private static final String XML_NAME = "name";
    private static final String XML_VALUE = "value";
    private static final String XML_ACCESS = "access";
    private static final String CD = "CD-";
    private static final String RUNTIME_ONLY = "RUNTIME_ONLY";
    @FileConfigurationOption(defaultLocation="images")
    public static final String ReportFileName = CD + "report file name";
    public static final String Units = CD + "measurement";
    public static final String OutputFreq = CD + "output report frequency";
    // Submodel output flags
    public static final String OPHydroDet = CD + "detailed hydro output";
    public static final String OPSoilDet = CD + "detailed soil output";
    public static final String OPManageDet = CD + "detailed manage output";
    public static final String OPCropDet = CD + "detailed crop output";
    public static final String OPDecompDet = CD + "detailed decomp output";
    public static final String OPErosionDet = CD + "detailed erosion output";
    public static final String OPHydroDbg = CD + "debug hydro output";
    public static final String OPSoilDbg = CD + "debug soil output";
    public static final String OPManageDbg = CD + "debug manage output";
    public static final String OPCropDbg = CD + "debug crop output";
    public static final String OPDecompDbg = CD + "debug decomp output";
    public static final String OPErosionDbg = CD + "debug erosion output";

    // Windgen specific
    public static final String WindgenAllowedModes = CD + "windgen.allowedmodes";
    @FileConfigurationOption(defaultLocation="bin")
    public static final String WinExe = CD + "windgen generator";
    public static final String WinCmd = CD + "windgen cmdline";
    @FileConfigurationOption(defaultLocation="db/windgen")
    public static final String WinData = CD + "windgen database";
    @FileConfigurationOption(defaultLocation="db/windgen")
    public static final String WinIndex = CD + "windgen index";
//    @FileConfigurationOption(defaultLocation="db/wind_gen")
//    public static final String NRCSWinShape = CD + "nrcs wind shape file";
//    public static final String NRCSWinFlg = CD + "nrcs wind shape flg";
    @FileConfigurationOption(defaultLocation="bin")
    public static final String WindInterp1EXE = CD + "windgen interpolate 1";
    @FileConfigurationOption(defaultLocation="bin")
    public static final String WindInterp2EXE = CD + "windgen interpolate 2";
    
    @FileConfigurationOption(defaultLocation="db/windgen")
    public static final String WindgenInterpolationBoundaryFile = CD + "windgen.interpolate.boundary";

    // Cligen specific
    public static final String CligenAllowedModes = CD + "cligen.allowedmodes";
    @FileConfigurationOption(defaultLocation="bin")
    public static final String CliExe = CD + "cligen generator";
    public static final String CliCmd = CD + "cligen cmdline";
    @FileConfigurationOption(defaultLocation="db/cligen")
    public static final String CliData = CD + "cligen database";
    @FileConfigurationOption(defaultLocation="db/cligen")
    public static final String CliIndex = CD + "cligen index";
//    @FileConfigurationOption(defaultLocation="db/cligen")
//    public static final String NRCSCliShape = CD + "nrcs cli shape file";
//    public static final String NRCSCliFlg = CD + "nrcs cli shape flg";
    // Weps specfic
    @FileConfigurationOption(defaultLocation="bin")
    public static final String WepsExe = CD + "WEPS exe";
    public static final String WepsCmd = CD + "WEPS exe parameter";
    public static final String CalWepsCmd = CD + "WEPS calibration exe parameter";
    //user specific
    public static final String UserFullName = CD + "user.fullname";


    // Communications specific

    public static final String EmailSender = CD + "SenderEmail";
    
    public static final String UseOutlookFlg = CD + "Outlook Flg";
    public static final String MailHost = CD + "SMTP_host";
    public static final String CommentAddr = CD + "Dest_email";
    public static final String BugsAddr = CD + "Dest_bug_email";
    
    public static final String MantisEmail = CD + "MantisEmail";
    public static final String MantisURL = CD + "Mantis URL";
    public static final String MantisUser = CD + "Mantis user";
    public static final String MantisPassword = CD + "Mantis password";
    public static final String MantisProject = CD + "Mantis project";

    @FileConfigurationOption(defaultLocation="db")
    public static final String ManTemp = CD + "management template";
    @FileConfigurationOption(defaultLocation="db")
    public static final String ManTempSaveAs = CD + "management template save as";
    @FileConfigurationOption(defaultLocation="db")
    public static final String ManSkel = CD + "management skeleton";
    @FileConfigurationOption(defaultLocation="db")
    public static final String CropDB = CD + "cropdb";
    @FileConfigurationOption(defaultLocation=".")
    public static final String MCREW = CD + "MCREW";
    @FileConfigurationOption(defaultLocation="db")
    public static final String ManDB = CD + "manoperdb";
    @FileConfigurationOption(defaultLocation="db")
    public static final String SoilDB = CD + "soil database";
    //public static final String SoilDBSpec	= CD + "soil database spec";
    
    public static final String SoilDBDisp = CD + "soil database display";

    public static final String SoilReadonly= CD+"soil.readonly";

    @FileConfigurationOption(required=false, defaultLocation="db/soil")
    public static final String SoilOrganicFile = CD+"soil.organicfile";
    public static final String SoilTestOrganic = CD+"soil.organictest";
    @MeasureableConfigurationOption(units="mm", alternativeUnits="in")
    public static final String SoilMaxOrganicDepth = CD+"soil.maxorganicdepth";

    @FileConfigurationOption
    public static final String ProjDir = CD + "projects path";
    @FileConfigurationOption(parentOption=MCREW)
    public static final String McrewDataConfigFile = CD + "mcrew data config file name";
    public static final String TimeSteps = CD + "time steps";
    public static final String UseMap = CD + "use map";
    public static final String DispLatLon = CD + "display latitude longitude";
    public static final String DispElevation = CD + "display elevation";
    public static final String DispStCty = CD + "display state county";
    public static final String ReadonlySlope = CD + "readonly region slope";
    public static final String ReadonlyRockFragments = CD + "readonly rock fragments";
    public static final String McrewEdit = CD + "mcrew edit";
    public static final String SoilEstimate = CD + "soil estimate";
    public static final String SoilAverageStratifiedLayers = "CD-soil.averagestratified";
    public static final String SoilSkipOrganicSurfaceLayers = "CD-soil.skiporganic";
    public static final String ShowWeatheFilerCheckboxes = CD + "showweatherfilecheckboxes";
    public static final String WindFlag = CD + "wind flag";

    @MeasureableConfigurationOption(units="km", alternativeUnits="mi")
    public static final String WindRadius = CD + "wind radius";
    public static final String ClimateFlag = CD + "climate flag";

    @MeasureableConfigurationOption(units="km", alternativeUnits="mi")
    public static final String ClimateRadius = CD + "climate radius";
    public static final String SubDailyFlag = CD + "sub-daily flag";
    //public static final String RunTypeDisp = CD + "runtypedisp";
    public static final String DefaultRunMode = CD + "defaultrunmode";
    public static final String NRCSRunLen = CD + "nrcsrunlength";
    public static final String TTInit = CD + "ToolTipInit";
    public static final String TTDismiss = CD + "ToolTipDismiss";    // CurrentProj is a CD entry but its stored value is not used
    @FileConfigurationOption()
    public static final String CurrentProj = CD + "current project";
    public static final String SWEEP = CD + "SWEEP-";
    @FileConfigurationOption(defaultLocation="bin")
    public static final String SWEEPExe = SWEEP + "exe";
    public static final String SWEEPCmd = SWEEP + "cmdLine";
    public static final String SWEEPWorkDir = SWEEP + "work";
    //public static final String FireProperty = "FireProperty " + CD;
    public static final String DataChanged = "data changed " + CD;
    public static final String Dates = "dates";
    public static final String Cycle = "cycle";
    public static final String NRCS = "NRCS";
    //Formats for the display of data
    public static final String FormatOperationDate = CD + "operation date format";
    //Runs Location default
    @FileConfigurationOption(required=false)
    public static final String DefaultRunsLocationTemplate = CD + "default runs location Template";
    public static final String DefaultRunsLocation = RUNTIME_ONLY + CD + "default runs location";
    //Files to purge after running a simulation.
    public static final String PurgeRunFiles = CD + "purge.filefilter";
    //Flag to indicate that .cli & .win files should be deleted from run directory
    public static final String PurgeRunFilesFlg = CD + "purge.flag";
    //Mcrew's skel translation file
    @FileConfigurationOption()
    public static final String SkelTranslationFile = CD + "skel translations file";
    //Don't warn about system locale
    public static final String DoNotWarnAboutSystemLocale = CD + "don't warn about system locale";
    //Don't estimate missing surgo values
    public static final String DoNotEstimateMissingSurgoValues = CD + "do not estimate missing surgo values";
    public static final String OMFractionThreshold = CD + "organic material fraction threshold";
    //Use the system default mailer
    public static final String UseDefaultMailClient = CD + "use default mail client";
    public static final String DoNotReviewWarningsWhenRestoringRun = CD + "do not review warnings when restoring run";
    @FileConfigurationOption(defaultLocation="tables")
    public static final String DetailTableFilterFile = CD + "detail table filter file";
    public static final String SingleProjectMode = CD + "single project mode";
    public static final String HideProjectFileButtons = CD + "hide project file buttons";
    public static final String HideTemplatetFileButtons = CD + "hide template file buttons";
    public static final String DaysToKeepLogs = CD + "logs.daystokeep";
    public static final String ReportsView = CD + "reports.view";
    public static final String ReportsGeneratePDF = CD + "reports.generatepdf";
    public static final String ReportsConfidenceIntervalEnabled = CD + "reports.ci.enabled";
    @FileConfigurationOption(required=false)
    public static final String ReportsDirectory= CD + "reports.directory";
    public static final String ReportsCustomized= CD + "reports.customized";
    public static final String AllowScriptCreation = CD + "script.allow";
    public static final String CompatibilityNRCS = CD + "compatibility.nrcs";
    public static final String BarriersReadonly = CD + "barriers.readonly";
    @FileConfigurationOption()
    public static final String BarriersFile = CD + "barriers.file";

    @FileConfigurationOption()
    public static final String GISData = CD + "gis.data";

    public static final String SiteChooserShowCountry =CD + "sitechooser.showcountry";

    //enable experimental xml format
    public static final String FormatsXML =CD + "formats.xml";

    //fules
    @FileConfigurationOption
    public static final String FuelDatabase = CD + "fuel.database";

    public static final String FuelDefault = CD + "fuel.default";

    private static ConfigData c_default;
    private List<ConfigStorage> c_storage;
    private ConfigStorage c_mainConfig;
    private ConfigStorage c_userConfig;
    private ConfigStorage c_defaultConfig;
    private Map<String, AccessLevel> c_accessLevels;
    public static final String PROP_CHANGED = "changed";
    private boolean c_changed = true;
//    /**
//     * ConfigData throws a property change each time setData is called.
//     * Wrapper classes listen to determine if the the event is relevant to
//     * them.
//     */
//    private PropertyChangeSupport c_changes = new PropertyChangeSupport(this);
//
    private ConfigData() {
//        //TODO: does this really make sense to listen to itself?
//        c_changes.addPropertyChangeListener(this);
//
//
        //default data
        synchronized(LOCK){
            c_storage = new ArrayList<ConfigStorage>();
            //special storage with all the default values
            c_defaultConfig = new DefaultConfigStoage();
            c_defaultConfig.load();
            c_storage.add(c_defaultConfig);
                        
        }

    }
    
    private void fixUp(){
        //if there is no cligen index then use the cligen data and replace the exention with .idx
        String cliIndex = getDataParsed(CliIndex);
        if(cliIndex == null || cliIndex.trim().length() == 0){
            //no cligen index, build from the clidata value
            String data = getDataParsed(CliData);
            if (data != null && data.trim().length() > 0){
                cliIndex = Util.purgeExtensions(data, ".par") + ".idx";
                setData(CliIndex, cliIndex);
            }
        }

        //if there is no wingen index then use the windgen data and replace the exention with .idx
        String winIndex = getDataParsed(WinIndex);
        if(winIndex == null || winIndex.trim().length() == 0){
            //no cligen index, build from the clidata value
            String data = getDataParsed(WinData);
            if (data != null && data.trim().length() > 0){
                winIndex = Util.purgeExtensions(data, ".wdb") + ".idx";
                setData(WinIndex, winIndex);
            }
        }
    }

    private void applyCompatibility(){
        //are we in nrcs compatibility mode?
        if ("1".equals(getData(CompatibilityNRCS))){
            //yes, nrcs install
            setData(DefaultRunMode, ConfigData.NRCS);
            
            //user is not allowed to change the modes.            
            
            setAccessLevel(DefaultRunMode, AccessLevel.lower(getAccessLevel(DefaultRunMode), AccessLevel.Read));
            setAccessLevel(NRCSRunLen, AccessLevel.lower(getAccessLevel(NRCSRunLen), AccessLevel.Read));

            //reports
            setData(ReportsCustomized, "nrcs");
        }
    }

    public static ConfigData getDefault() {
        if (c_default == null) {
            c_default = new ConfigData();
        }
        return c_default;
    }

    public void load(File main, File user) {
        c_storage = new ArrayList<ConfigStorage>();

        //main config
        if (main != null && main.exists()) {
            LOGGER.info("Config File: \"" + main.getAbsolutePath() + "\"");
            synchronized(LOCK){
                c_mainConfig = new ConfigStorage(main, true);
                c_mainConfig.load();
                c_storage.add(c_mainConfig);
            }
        } else {
            LOGGER.warning("Config File: NULL");
            LOGGER.warning("Using a default main configuration.");
        }

        //user's config
        if (user != null) {
            synchronized(LOCK){
                LOGGER.info("User Config File: \"" + user.getAbsolutePath() + "\"");
                c_userConfig = new ConfigStorage(user, false);
                c_userConfig.load();
                c_storage.add(0, c_userConfig);
            }
        } else {
            LOGGER.info("User Config File: NULL");
        }

        //special storage with all the default values
        c_storage.add(c_defaultConfig);
        applyCompatibility();
        fixUp();
    }
    
    public File getMainFile(){
        return c_mainConfig.c_file;
    }
    
    public File getUserFile(){
        return c_userConfig.c_file;
    }

    public AccessLevel getAccessLevel(String propertyName) {
        AccessLevel level = null;
        if (c_accessLevels != null) {
            level = c_accessLevels.get(propertyName);
        }
        return level != null ? level : AccessLevel.Write;
    }
    
    protected void setAccessLevel(String propertyName, AccessLevel level){
        if (c_accessLevels == null) {
            c_accessLevels = new HashMap<String, AccessLevel>();
        }
        c_accessLevels.put(propertyName, level);
    }

    public boolean isChanged() {
        return c_changed;
    }

    /**
     * Gets data from hashtable.
     * 
     * @param propertyName Specifies which datum to retrieve using public static final String parameters.     
     * @return 
     */
    public String getData(String propertyName) {
        synchronized(LOCK){
            for (ConfigStorage storage : c_storage) {
                String value = storage.get(propertyName);
                if (value != null) {
                    return value;
                }
            }
            return null;
        }
    }
    
    public String getDataParsed(String propertyName){
        return Util.parse(getData(propertyName));
    }

    /**
     * Puts data into hashtable.
     * 
     * @param propertyName Specifies which datum to save using public static final String parameters.
     * @param newData Value of datum saved.
     */
    public void setData(String propertyName, String newData) {
        synchronized(LOCK){
            if (c_userConfig == null) {
                //no user config can't set values, this could break things!
                return;
            }
        }

        if (newData != null) {
            newData = newData.trim();
        } else {
            newData = "";
        }

        String oldData = getData(propertyName);
        if (newData.equals(oldData)) {
            //no change, leaving
            return;
        }

        //there is a change, do we have permission
        AccessLevel level = getAccessLevel(propertyName);

        //we only need permission on CD properties, others are not saved to file
        if (!CurrentProj.equals(propertyName) && propertyName.startsWith(CD) && !AccessLevel.Write.equals(level)) {
            //we don't have access level to change this value.
            return;
        }
        
        synchronized(LOCK){
            c_userConfig.set(propertyName, newData);
        }

//        c_changes.firePropertyChange(propertyName, oldData, newData);


        if (propertyName.equals(DefaultRunsLocationTemplate)) {
            updateDefaultRunsLocation();
        }
        if (propertyName.equals(CurrentProj)) {
            updateDefaultRunsLocation();
        }
    }

    private Collection<String> keys() {
        synchronized(LOCK){
            List<String> temp = new LinkedList<String>();
            for (ConfigStorage storage : c_storage) {
                for (String key : storage.keys()) {
                    if (!temp.contains(key)) {
                        temp.add(key);
                    }
                }
            }
            return temp;
        }
    }

//    public void fireAll() {
//        boolean firedDefaultRunLocation = false;
//        for (String key : keys()) {
//            if (DefaultRunsLocation.equals(key)) {
//                firedDefaultRunLocation = true;
//            }
//            String value = getData(key);
//            c_changes.firePropertyChange(key, null, value);
//        }
//
//        if (!firedDefaultRunLocation) {
//            updateDefaultRunsLocation();
//        }
//    }
//
//    public void fireAll(PropertyChangeListener... listeners) {
//        boolean firedDefaultRunLocation = false;
//        for (String key : keys()) {
//            if (DefaultRunsLocation.equals(key)) {
//                firedDefaultRunLocation = true;
//            }
//            String value = getData(key);
//            PropertyChangeEvent event = new PropertyChangeEvent(this,key, null, value);
//            for (PropertyChangeListener listener : listeners) {
//                listener.propertyChange(event);
//            }
//        }
//        if (!firedDefaultRunLocation) {
//            updateDefaultRunsLocation();
//        }
//    }
//
//    public void save() {
//        if (c_userConfig != null) {
//            c_userConfig.save();
//            boolean old = c_changed;
//            c_changed = false;
//            c_changes.firePropertyChange(PROP_CHANGED, old, false);
//        } else {
//            LOGGER.warning("User config is null.  Unable to save configuration.");
//        }
//    }
//
//    /**
//     * 
//     * @param l 
//     */
//    public void addPropertyChangeListener(PropertyChangeListener l) {
//        c_changes.addPropertyChangeListener(l);
//    }
//
//    /**
//     * 
//     * @param l 
//     */
//    public void removePropertyChangeListener(PropertyChangeListener l) {
//        c_changes.removePropertyChangeListener(l);
//    }
//
//    /**
//     * 
//     * @param l 
//     */
//    public void addPropertyChangeListener(String propertyName, PropertyChangeListener l) {
//        c_changes.addPropertyChangeListener(propertyName, l);
//    }
//
//    /**
//     * 
//     * @param l 
//     */
//    public void removePropertyChangeListener(String propertyName, PropertyChangeListener l) {
//        c_changes.removePropertyChangeListener(propertyName, l);
//    }
//
//    /**
//     * 
//     * @param e 
//     */
//    public void propertyChange(PropertyChangeEvent e) {
//        String propertyName = e.getPropertyName();
//        if(propertyName == null){
//            return;
//        }
//        if (!propertyName.startsWith(CD)) {
//            return;
//        }
//        String valstr = (String) e.getNewValue();
//        setData(propertyName, valstr);
//
//        if (propertyName.startsWith(CD)) {
//            boolean old = c_changed;
//            c_changed = true;
//            c_changes.firePropertyChange(PROP_CHANGED, old, true);
//        }
//
//    }
//
//
//
    private void updateDefaultRunsLocation() {
        try {
            String template = getData(DefaultRunsLocationTemplate);
            template = Util.parse(template);
            File file = new File(template);
            String parsedPath = file.getAbsolutePath();

            //Update listeners to the parsed template path
            setData(DefaultRunsLocation, parsedPath);
        } catch (NullPointerException e) {
        }
    }

    private class ConfigStorage {

        protected Map<String, String> c_data;
        private File c_file;
        private boolean c_readAccessLevels;

        protected ConfigStorage() {
        }

        public ConfigStorage(File file, boolean access) {
            this();
            c_file = file;
            c_readAccessLevels = access;
        }

        public Collection<String> keys() {
            synchronized (this) {
                if (c_data == null) {
                    load();
                }
            }
            return c_data.keySet();
        }

        public String get(String propertyName) {
            synchronized (this) {
                if (c_data == null) {
                    load();
                }
            }
            return c_data.get(propertyName);
        }

        public void set(String propertyName, String value) {
            synchronized (this) {
                if (c_data == null) {
                    load();
                }
            }
            c_data.put(propertyName, value);
        }

        protected void load() {
//            try {                
                synchronized (this) {
                    c_data = new HashMap<String, String>();
                }  

                if (!c_file.exists()) {
                    return;
                }

//                SAXBuilder builder = new SAXBuilder();
//                FileInputStream in = new FileInputStream(c_file);
//                Document document = builder.build(in);
//
//                Element root = document.getRootElement();
//                for (Object o : root.getChildren(XML_PARAMETER)) {
//                    Element parameter = (Element) o;
//                    String key = parameter.getChildText(XML_NAME);
//                    String value = parameter.getChildText(XML_VALUE);
//
//                    //UPGRADE PATHS
//                    //ignore the runtypedisp value from the config, this is only in the runfiledata now
//                    if ("CD-runtypedisp".equals(key)){
//                        LOGGER.info("Parameter \"" + key + "\" deprecated  use " + DefaultRunMode);
//                        continue;
//                    }
//                    //END UPGRADE PATHS
//
//                    //read and store the access level
//                    if (c_readAccessLevels) {
//                        //stor the value
//                        synchronized (this) {
//                            c_data.put(key, value != null ? value : "");
//                        }
//
//                        String levelText = parameter.getChildText(XML_ACCESS);
//                        AccessLevel level = AccessLevel.parse(levelText);
//                        setAccessLevel(key, level);
//                        if (level != null) {
//                            setAccessLevel(key, level);
//                        }
//                    } else {
//                        //check access level before storing
//                        if (CurrentProj.equals(key) || AccessLevel.Write.equals(getAccessLevel(key))) {
//                            synchronized (this) {
//                                c_data.put(key, value != null ? value : "");
//                            }
//                        } else {
//                            LOGGER.warning("Parameter \"" + key + "\" ignored from user config due to access restrictions.");
//                        }
//
//                    }
//                }
//                return;
//            } catch (UTFDataFormatException udfe) {
//                LOGGER.error("Unable to load XML configuration file." + c_file.getPath(), udfe);
//                return;
//            } catch (JDOMException jde) {
//                if (jde.getMessage().indexOf("Content is not allowed in prolog") > 0) {
//                    return;
//                }//Assume this is not an xml file.
//
//                LOGGER.error("Unable to load XML configuration file. " + c_file.getPath(), jde);
//                return;
//            } catch (IOException ioe) {
//                LOGGER.error("Unable to load XML configuration file." + c_file.getPath(), ioe);
//                return;
//            }
        }
                

//        protected void save() {
//            DocType type = new DocType(XML_CONFIGURATION);
//            String dtd = "<!ELEMENT configuration (parameter*)>";
//            dtd += "<!ELEMENT parameter (name, value)>";
//            dtd += "<!ELEMENT name (#PCDATA)>";
//            dtd += "<!ELEMENT value (#PCDATA)>";
//            dtd += "<!ATTLIST configuration version CDATA #REQUIRED>";
//            type.setInternalSubset(dtd);
//            Element root = new Element(XML_CONFIGURATION);
//            Document document = new Document(root, type);
//
//            root = document.getRootElement().setAttribute(XML_VERSION, "1.0");
//            
//            
//            for (String key : keys()) {
//                String value = get(key);
//                AccessLevel level = getAccessLevel(key);
//
//                //Special case, we write current project even if it's hidden.
//                if ( !CurrentProj.equals(key) && !AccessLevel.Write.equals(level)) {
//                    //skip ones that we aren't allowed to write
//                    continue;
//                }
//
//                //only save config values
//                if (!key.startsWith(CD)) {
//                    continue;
//                }
//
//                //only save if different from main
//                synchronized(LOCK){
//                    String main = c_mainConfig.get(key);
//                        if (areStringsEqual(main, value)) {
//                            continue;
//                        }
//                }
//                
//
//                Element node = new Element(XML_PARAMETER);
//                root.addContent(node);
//
//                Element nameNode = new Element(XML_NAME);
//                nameNode.setText(key);
//                node.addContent(nameNode);
//
//                Element valueNode = new Element(XML_VALUE);
//                valueNode.setText(value);
//                node.addContent(valueNode);               
//                
//                
//                
//                
//
//            }
//
//            //Write the XML File
//            try {
//                //setup the folder structure if needed.
//                java.io.File parent = c_file.getParentFile();
//                if (parent != null && !parent.exists()) {
//                    if (!parent.mkdirs()) {
//                        if (!parent.exists()) {
//                            throw new IOException("can't create directory");
//                        }
//                    }
//                }
//
//                FileWriter writer = new FileWriter(c_file);
//                XMLOutputter serializer = new XMLOutputter();
//                serializer.setFormat(Format.getPrettyFormat());
//                serializer.output(document, writer);
//                writer.close();
//            } catch (IOException ioe) {
//                LOGGER.error("Error writing config file \"" + c_file.getPath() + "\".", ioe);
//            }
//        }
    }
    //special storage that has all the default values, sits at the bottom of the stack
    private class DefaultConfigStoage extends ConfigStorage {

        @Override
        protected void load() {
            synchronized (this) {
                c_data = new HashMap<String, String>();
            }            

            set(Units, Util.SIUnits);
            set(OutputFreq, "2");
            set(OPHydroDet, "0");
            set(OPSoilDet, "0");
            set(OPManageDet, "0");
            set(OPCropDet, "0");
            set(OPDecompDet, "0");
            set(OPErosionDet, "0");
            set(OPHydroDbg, "0");
            set(OPSoilDbg, "0");
            set(OPManageDbg, "0");
            set(OPCropDbg, "0");
            set(OPDecompDbg, "0");
            set(OPErosionDbg, "0");
            set(WinExe, "");
            set(WinCmd, "");
            set(WinData, "${weps.databases}/db/windgen/wind_gen_his_upper_US.wdb");
            set(WindgenInterpolationBoundaryFile, "${weps.databases}/db/windgen/interpolation_boundary.pol");
            set(CliExe, "");
            set(CliCmd, "");
            set(CliData, "${weps.databases}/db/cligen/US_cligen_stations.par");
            set(WepsExe, "");
            set(WepsCmd, "");
            set(CalWepsCmd, "");
            set(EmailSender, "");
            set(MailHost, "");
            set(CommentAddr, "");
            set(BugsAddr, "");
            set(ManTemp, "");
            set(ManTempSaveAs, "");
            set(ManSkel, "");
            set(CropDB, "");
            set(ManDB, "");
            set(SoilDB, "");
            set(SoilOrganicFile,"${weps.databases}/db/soil/NRCS Generic Soils/Organic Soil.ifc");
            set(SoilMaxOrganicDepth, "101.6");
            set(SoilTestOrganic, "1");
            set(ProjDir, "");
            set(MCREW, "mcrew_cfg");
            set(McrewDataConfigFile, "dataconfig.xml");
            set(TimeSteps, "24");
            set(WindFlag, "0");
            set(ClimateFlag, "0");
            set(SubDailyFlag, "0");
            set(DefaultRunMode, ConfigData.Cycle);
            set(TTInit, "2000");		// set to more reasonable defaults if not
            set(TTDismiss, "2000");		// set in cfg file (or cfg file is missing)
            set(ReadonlySlope, "1");
            set(ReadonlyRockFragments, "1");

            //Formats
            set(FormatOperationDate, "MMM dd, y");

            //Purge run files
            set(PurgeRunFilesFlg, "0");
            set(PurgeRunFiles, "win_gen.win:cli_gen.cli");

            //Default runs location template
            set(DefaultRunsLocationTemplate, "${PROJECT_DIRECTORY}");

            //Mcrew's skel translastion file
            set(SkelTranslationFile, "${weps.databases}/db/RUSLE2_Translation.txt");

            set(DoNotWarnAboutSystemLocale, "0");

            set(DoNotEstimateMissingSurgoValues, "0");

            set(UseDefaultMailClient, "0");

            set(DoNotReviewWarningsWhenRestoringRun, "0");
            set(SoilEstimate, "1");
            set(SoilAverageStratifiedLayers, "0");
            set(OMFractionThreshold, "0.20");

            set(DetailTableFilterFile, "tables/detail_filters.xml");

            set(HideProjectFileButtons, "0");
            set(HideTemplatetFileButtons, "0");
            set(DaysToKeepLogs, "2");
            
            
            //reports 
            set(ReportsView, "run");
            set(ReportsGeneratePDF, "run,management,crop,stir");            
            set(ReportsDirectory, "reports/");
            set(ReportsCustomized, null);
            set(ReportsConfidenceIntervalEnabled, "1");
            
            //bash script 
            set(AllowScriptCreation, "1");
            
            //nrcs install
            set(CompatibilityNRCS, "0");

            //default barriers
            set(BarriersFile, "${weps.databases}/db/barriers/barrier.dat");

            ///barriers readonly, false
            set(BarriersReadonly, "0");

            //show the checkboxes that allow switching between weather files or station lists on a per project basis
            set(ShowWeatheFilerCheckboxes, "1");

            //LocationPanel rewrite
            set(WindgenAllowedModes, "choice,file,interpolated,gis,nearest,nrcs");
            set(CligenAllowedModes, "choice,nearest,gis,file,nrcs");

            setData(SoilAverageStratifiedLayers, "0");
            setData(SoilSkipOrganicSurfaceLayers, "0");
            setData(SiteChooserShowCountry, "0");

            set(GISData, "db/gis");

            set(SoilReadonly, "0");

            //default to not allowing xml format
            set(FormatsXML, "0");

            //fuels
            set(FuelDatabase, "${weps.databases}/db/fuels/fuels.xml");
            set(FuelDefault,  "diesel");

        }

//        @Override
        protected void save() {
            //do nothing
        }
    }

    private static boolean areStringsEqual(String a, String b) {
        if (a == null && b == null) {
            return true;
        } else if (a != null && b != null) {
            return a.equals(b);
        } else {
            return false;
        }
    }

    public boolean getSoilTestOrganic(){
        return "1".equals(getData(SoilTestOrganic));
    }

//    public Measurable<Length> getSoilMaxOrganicDepth(){
//        String valueString = getData(SoilMaxOrganicDepth);
//        try{
//            double depthMiliMeters = Double.valueOf(valueString);
//            return Measure.valueOf(depthMiliMeters, SI.MILLIMETER);        
//        }
//        catch(Exception e){
//            LOGGER.warning("Unable to parse the soil max organic depth. Defaulting to 4 inches." + valueString);
//            return Measure.valueOf(4, NonSI.INCH);
//        }
//    }
//
    public boolean isAverageStratifiedSoilLayers(){
        String value = getData(SoilAverageStratifiedLayers);
        return value != null ? value.trim().equals("1") : false;
    }

        public boolean isSkipOrganicSoilSurfaceLayers(){
        String value = getData(SoilSkipOrganicSurfaceLayers);
        return value != null ? value.trim().equals("1") : false;
    }

//    //TODO: fire changes when the underlying data changes
//    public static final String PROP_ALLOWED_CLIGEN_STATION_MODES = "allowedCligenStationModes";
//    public StationMode[] getAllowedCligenStationModes(){
//        return parseAllowedStationModes(getData(CligenAllowedModes));
//    }
//
//    public static final String PROP_ALLOWED_WINDGEN_STATION_MODES = "allowedWindgenStationModes";
//    public StationMode[] getAllowedWindgenStationModes(){
//        return parseAllowedStationModes(getData(WindgenAllowedModes));
//    }
//
//    public static final String PROP_CLIGEN_SEARCH_RADIUS= "cligenSearchRadius";
//    public Measurable<Length> getCligenSearchRadius(){
//        String valueString = getData(ClimateRadius);
//        try {            
//            double distanceMeters = Double.parseDouble(valueString.trim());
//            return Measure.valueOf(distanceMeters, SI.KILOMETER);
//        } catch (Exception e) {
//            LOGGER.warning("Unable to parse the cligen radius limit. " + valueString);
//            return null;
//        }
//    }
//
    public static final String PROP_CLIGEN_SEARCH_LIMIT= "cligenSearchLimit";
    public int getCligenSearchLimit(){
        return 50;
    }

    public static final String PROP_WINDGEN_SEARCH_RADIUS= "windgenSearchRadius";
//    public Measurable<Length> getWindgenSearchRadius(){
//        String valueString = getData(WindRadius);
//        try {            
//            double distanceMeters = Double.parseDouble(valueString.trim());
//            return Measure.valueOf(distanceMeters, SI.KILOMETER);
//        } catch (Exception e) {
//            LOGGER.warning("Unable to parse the windgen radius limit. " + valueString);
//            return null;
//        }
//    }
//
    public static final String PROP_WINDGEN_SEARCH_LIMIT= "windgenSearchLimit";
    public int getWindgenSearchLimit(){
        return 50;
    }

//    private static final StationMode[] parseAllowedStationModes(String modes){
//        if(modes == null || modes.trim().length()==0){
//            return new StationMode[0];
//        }
//        EnumSet <StationMode> c_allowedModes = null;
//        String[] parts = modes.split(",");
//        for(String part : parts){
//            StationMode mode = StationMode.parse(part.trim());
//            if(mode != null){
//                if(c_allowedModes == null){
//                    c_allowedModes = EnumSet.of(mode);
//                }
//                else{
//                    c_allowedModes.add(mode);
//                }
//            }
//        }
//
//        return c_allowedModes.toArray(new StationMode[c_allowedModes.size()]);
//    }
//
    public void logConfiguration(){
        try{
            StringBuffer buffer = new StringBuffer();
            List<String> sortedKeys = new LinkedList<String> (keys());
            Collections.sort(sortedKeys);
            for(String key : sortedKeys){
                //USER
                String value = c_userConfig.get(key);
                String type = "user";

                //MAIN
                if(value == null){
                    value = c_mainConfig.get(key);
                    type = "main";
                }

                //DEFAULT
                if(value == null){
                    value = c_defaultConfig.get(key);
                    type = "default";
                }

                if(value == null){
                    value = "null";
                    type = null;
                }

                buffer.append("\t" + key + (type != null ? "(" + type + ")" : "")+ "= " + value + "\n");

                String valueParsed = Util.parse(value);
                if(!value.equals(valueParsed)){
                    buffer.append("\t" + key + "(parsed) = " + valueParsed + "\n");
                }


            }

            LOGGER.fine("Current Configuration:\n" + buffer.toString());
        }
        catch(Exception e){
            LOGGER.warning("Unable to log configuration.\n" + e.getMessage());
        }
    }

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.FIELD)
    public static @interface MeasureableConfigurationOption{        
        String units();
        String alternativeUnits();
        boolean isDouble() default true;

    }

    private Map<String, MeasureableConfigurationOption> c_measureableConfigurationOptions;

    private synchronized Map<String, MeasureableConfigurationOption> measureableOptions(){
        if(c_measureableConfigurationOptions == null){
            c_measureableConfigurationOptions = new HashMap<String, MeasureableConfigurationOption>();
             //loop over all the fields in the configdata and record all those with the FileConfigurationOption annotation
            for (Field field : ConfigData.class.getFields()) {
                try {
                    //if final static and annotated
                    if (Modifier.isStatic(field.getModifiers()) && Modifier.isFinal(field.getModifiers()) && field.isAnnotationPresent(MeasureableConfigurationOption.class)) {
                        MeasureableConfigurationOption annotation = field.getAnnotation(MeasureableConfigurationOption.class);
                        String propertyName = field.get(null).toString();
                        c_measureableConfigurationOptions.put(propertyName, annotation);
                    }
                } catch (Exception e) {
                    LOGGER.severe("Initilizing" + e.getMessage());
                }
            }
        }
        return c_measureableConfigurationOptions;
    }

    public MeasureableConfigurationOption getMeasureableConfigurationOption(String propertyName){
        return measureableOptions().get(propertyName);
    }

//    public Unit getMeasureableUnit(String propertyName){
//        MeasureableConfigurationOption option = getMeasureableConfigurationOption(propertyName);
//
//        if(option != null){           
//                Unit units = Unit.valueOf(option.units());                
//                return units;           
//        }
//        return null;
//    }
//
//        public Unit getMeasureableAlternativeUnit(String propertyName){
//        MeasureableConfigurationOption option = getMeasureableConfigurationOption(propertyName);
//
//        if(option != null){
//                Unit units = Unit.valueOf(option.alternativeUnits());
//                return units;
//        }
//        return null;
//    }
//
//
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.FIELD)
    public static @interface FileConfigurationOption{
        boolean required() default true;
        String parentOption() default "";
        String defaultLocation() default "";
    }

    private Map<String, FileConfigurationOption> c_fileConfigurationOptions;

    private synchronized Map<String, FileConfigurationOption> fileOptions(){
        if(c_fileConfigurationOptions == null){
            c_fileConfigurationOptions = new HashMap<String, FileConfigurationOption>();
             //loop over all the fields in the configdata and record all those with the FileConfigurationOption annotation
            for (Field field : ConfigData.class.getFields()) {
                try {
                    //if final static and annotated
                    if (Modifier.isStatic(field.getModifiers()) && Modifier.isFinal(field.getModifiers()) && field.isAnnotationPresent(FileConfigurationOption.class)) {
                        FileConfigurationOption annotation = field.getAnnotation(FileConfigurationOption.class);
                        String propertyName = field.get(null).toString();
                        c_fileConfigurationOptions.put(propertyName, annotation);
                    }
                } catch (Exception e) {
                    LOGGER.severe("Initilizing" + e.getMessage());
                }
            }
        }
        return c_fileConfigurationOptions;
    }
    
    public boolean isFileConfigurationOption(String propertyName){
        return fileOptions().containsKey(propertyName);
    }

    public FileConfigurationOption getFileConfigurationOption(String propertyName){
        return fileOptions().get(propertyName);
    }

    public String[] getFileConfigurationOptions(){
        Collection<String> temp = fileOptions().keySet();
        return temp.toArray(new String[temp.size()]);
    }

    public boolean isFileReferenceErrorInConfigData(){
        boolean failed = false;
        for(Map.Entry<String, FileConfigurationOption> entry : fileOptions().entrySet()){
            //exit on the first error to be as fast as possible
            String propertyName = entry.getKey();
            FileConfigurationOption meta = entry.getValue();
            if(getAccessLevel(propertyName) != AccessLevel.Hidden){
                if(meta.required()){
                                //test if the value exists as a file
                    File parentFile = null;
                    String parentPropertyName = meta.parentOption();
                    if(parentPropertyName != null && parentPropertyName.trim().length() > 0){
                        String parentPath = getDataParsed(parentPropertyName);
                        if(parentPath != null){
                            parentFile = new File(Util.parse(parentPath));
                        }
                    }

                    String path = getDataParsed(propertyName);
                    boolean error = false;
                    if(path != null){
                        File file = new File(path);
                        if(!file.isAbsolute() && parentFile != null){
                             file = new File(parentFile, Util.parse(path));
                        }                        

                        error = !file.exists();
                    }
                   if(error){
                       failed = true;
                       LOGGER.warning("Unable to resolve file reference for : " + propertyName + "=" + getData(propertyName));
                   }
                }
            }
        }
        return failed;
    }

    public boolean isSiteChooserShowCountries(){
        return "1".equals(getData(SiteChooserShowCountry));
    }

    public boolean isPurgeFlag(){
        return "1".equals(getData(PurgeRunFilesFlg));
    }

    public boolean isSoilReadonly(){
        return "1".equals(getData(SoilReadonly));
    }

   public boolean isFormatXMLAllowed(){
        return "1".equals(getData(FormatsXML));
    }
   
   public boolean isReportConfidenceIntervalEnabled(){
        return "1".equals(getData(ReportsConfidenceIntervalEnabled));
    }


    public String[] getPurgeFilenames(){
        String filter = getData(PurgeRunFiles);
        filter = filter != null ? filter.trim() : null;

        if(filter == null || filter.length() == 0){
            return new  String[0];
        }

        String[] parts = filter.split(":");
        for (int i = 0; i < parts.length; i++){
            parts[i] = parts[i].trim();
        }
        return parts;
    }

//    /**
//     * Always returns USD.  Placeholder for future potential support of other
//     * currencies.
//     * @return USD
//     */
//    public Currency getCurrency(){
//        return Currency.USD;
//    }
//    
    public boolean isCompatibilityNRCS(){
        return "1".equals(getData(CompatibilityNRCS));
    }

}