Coverage Report - org.crosswire.common.config.Config
 
Classes in this File Line Coverage Branch Coverage Complexity
Config
0%
0/140
0%
0/62
2.346
 
 1  
 /**
 2  
  * Distribution License:
 3  
  * JSword is free software; you can redistribute it and/or modify it under
 4  
  * the terms of the GNU Lesser General Public License, version 2.1 or later
 5  
  * as published by the Free Software Foundation. This program is distributed
 6  
  * in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
 7  
  * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 8  
  * See the GNU Lesser General Public License for more details.
 9  
  *
 10  
  * The License is available on the internet at:
 11  
  *       http://www.gnu.org/copyleft/lgpl.html
 12  
  * or by writing to:
 13  
  *      Free Software Foundation, Inc.
 14  
  *      59 Temple Place - Suite 330
 15  
  *      Boston, MA 02111-1307, USA
 16  
  *
 17  
  * © CrossWire Bible Society, 2005 - 2016
 18  
  *
 19  
  */
 20  
 package org.crosswire.common.config;
 21  
 
 22  
 import java.beans.PropertyChangeEvent;
 23  
 import java.beans.PropertyChangeListener;
 24  
 import java.beans.PropertyChangeSupport;
 25  
 import java.io.IOException;
 26  
 import java.net.URI;
 27  
 import java.util.ArrayList;
 28  
 import java.util.Iterator;
 29  
 import java.util.List;
 30  
 import java.util.ResourceBundle;
 31  
 import java.util.concurrent.CopyOnWriteArrayList;
 32  
 
 33  
 import org.crosswire.common.util.LucidException;
 34  
 import org.crosswire.common.util.NetUtil;
 35  
 import org.crosswire.common.util.PropertyMap;
 36  
 import org.crosswire.common.util.Reporter;
 37  
 import org.crosswire.jsword.JSOtherMsg;
 38  
 import org.jdom2.Document;
 39  
 import org.jdom2.Element;
 40  
 import org.slf4j.Logger;
 41  
 import org.slf4j.LoggerFactory;
 42  
 
 43  
 /**
 44  
  * Config is the core part of the configuration system; it is simply a
 45  
  * Collection of <code>Choice</code>s.
 46  
  * 
 47  
  * Config does the following things:
 48  
  * <ul>
 49  
  * <li>Provides a GUI independent API with which to create GUIs</li>
 50  
  * <li>Stores a local store of settings</li>
 51  
  * <li>Allows updates to the local store</li>
 52  
  * </ul>
 53  
  * 
 54  
  * 
 55  
  * Config does not attempt to make permanent copies of the config data because
 56  
  * different applications may wish to store the data in different ways. Possible storage
 57  
  * mechanisms include:
 58  
  * <ul>
 59  
  * <li>Properties Files</li>
 60  
  * <li>Resource Objects (J2SE 1.4)</li>
 61  
  * <li>Network Sockets (see Remote)</li>
 62  
  * </ul>
 63  
  * 
 64  
  * The Config class stored the current Choices, and moves the data between the
 65  
  * various places that it is stored. There are 4 storage areas:
 66  
  * <ul>
 67  
  * <li><b>Permanent:</b> This can be local file, a URI, or a remote server Data
 68  
  * is stored here between invocations of the program.
 69  
  * <li><b>Application:</b> This is the actual working copy of the data.
 70  
  * <li><b>Screen:</b> This copy of the data is shown on screen whist a Config
 71  
  * dialog box is showing.
 72  
  * <li><b>Local:</b> This is required so that we can tell which bits of data
 73  
  * have been changed in the screen data, and so that we can load data from disk
 74  
  * to screen without involving the app.
 75  
  * </ul>
 76  
  * 
 77  
  * @see gnu.lgpl.License The GNU Lesser General Public License for details.
 78  
  * @author Joe Walker
 79  
  */
 80  0
 public class Config implements Iterable<Choice> {
 81  
     /**
 82  
      * Config ctor
 83  
      * 
 84  
      * @param title
 85  
      *            The name for dialog boxes and properties files
 86  
      */
 87  0
     public Config(String title) {
 88  0
         this.title = title;
 89  0
         keys = new ArrayList<String>();
 90  0
         models = new ArrayList<Choice>();
 91  0
         local = new PropertyMap();
 92  0
         listeners = new CopyOnWriteArrayList<ConfigListener>();
 93  0
     }
 94  
 
 95  
     /**
 96  
      * The name for the dialog boxes and properties files.
 97  
      * 
 98  
      * @return the title for this config
 99  
      */
 100  
     public String getTitle() {
 101  0
         return title;
 102  
     }
 103  
 
 104  
     /**
 105  
      * Add a key/model pairing
 106  
      * 
 107  
      * @param model
 108  
      *            The Choice model to map to its key
 109  
      */
 110  
     public void add(Choice model) {
 111  0
         String key = model.getKey();
 112  
         // log.debug("Adding key={}", key);
 113  
 
 114  0
         keys.add(key);
 115  0
         models.add(model);
 116  
 
 117  0
         String value = model.getString();
 118  0
         if (value == null) {
 119  0
             value = "";
 120  0
             LOGGER.info("key={} had a null value", key);
 121  
         }
 122  
 
 123  0
         local.put(key, value);
 124  
 
 125  0
         fireChoiceAdded(key, model);
 126  0
     }
 127  
 
 128  
     /**
 129  
      * Add the set of configuration options specified in the xml file.
 130  
      * 
 131  
      * @param xmlconfig
 132  
      *            The JDOM document to read.
 133  
      * @param configResources
 134  
      *            contains the user level text for this config
 135  
      */
 136  
     public void add(Document xmlconfig, ResourceBundle configResources) {
 137  
         // We are going to assume a DTD has validated the config file and
 138  
         // just assume that everything is laid out properly.
 139  0
         Element root = xmlconfig.getRootElement();
 140  0
         Iterator<?> iter = root.getChildren().iterator();
 141  0
         while (iter.hasNext()) {
 142  0
             Element element = (Element) iter.next();
 143  0
             String key = element.getAttributeValue("key");
 144  
 
 145  0
             Exception ex = null;
 146  
             try {
 147  0
                 Choice choice = ChoiceFactory.getChoice(element, configResources);
 148  0
                 if (!choice.isIgnored()) {
 149  0
                     add(choice);
 150  
                 }
 151  0
             } catch (StartupException e) {
 152  0
                 ex = e;
 153  0
             } catch (ClassNotFoundException e) {
 154  0
                 ex = e;
 155  0
             } catch (IllegalAccessException e) {
 156  0
                 ex = e;
 157  0
             } catch (InstantiationException e) {
 158  0
                 ex = e;
 159  0
             }
 160  
 
 161  0
             if (ex != null) {
 162  0
                 LOGGER.warn("Error creating config element, key={}", key, ex);
 163  
             }
 164  0
         }
 165  0
     }
 166  
 
 167  
     /**
 168  
      * Remove a key/model pairing
 169  
      * 
 170  
      * @param key
 171  
      *            The name to kill
 172  
      */
 173  
     public void remove(String key) {
 174  0
         Choice model = getChoice(key);
 175  0
         keys.remove(key);
 176  0
         models.remove(model);
 177  
 
 178  
         // Leave the pair in local?
 179  
         // local.put(key, value);
 180  
 
 181  0
         fireChoiceRemoved(key, model);
 182  0
     }
 183  
 
 184  
     /**
 185  
      * The set of Choice that we are controlling
 186  
      * 
 187  
      * @return An iterator over the choices
 188  
      */
 189  
     public Iterator<Choice> iterator() {
 190  0
         return models.iterator();
 191  
     }
 192  
 
 193  
     /**
 194  
      * Get the Choice for a given key
 195  
      * 
 196  
      * @param key the key for the choice
 197  
      * @return the requested choice
 198  
      */
 199  
     public Choice getChoice(String key) {
 200  0
         int index = keys.indexOf(key);
 201  0
         if (index == -1) {
 202  0
             return null;
 203  
         }
 204  
 
 205  0
         return models.get(index);
 206  
     }
 207  
 
 208  
     /**
 209  
      * The number of Choices
 210  
      * 
 211  
      * @return The number of Choices
 212  
      */
 213  
     public int size() {
 214  0
         return keys.size();
 215  
     }
 216  
 
 217  
     /**
 218  
      * Set a configuration Choice (by name) to a new value. This method is only
 219  
      * of use to classes displaying config information.
 220  
      * 
 221  
      * @param name the key for the choice
 222  
      * @param value the value for the choice
 223  
      */
 224  
     public void setLocal(String name, String value) {
 225  0
         assert name != null;
 226  0
         assert value != null;
 227  
 
 228  0
         local.put(name, value);
 229  0
     }
 230  
 
 231  
     /**
 232  
      * Get a configuration Choice (by name). This method is only of use to
 233  
      * classes displaying config information.
 234  
      * 
 235  
      * @param name the key for the choice
 236  
      * @return the value for the choice.
 237  
      */
 238  
     public String getLocal(String name) {
 239  0
         return local.get(name);
 240  
     }
 241  
 
 242  
     /**
 243  
      * Take the data in the application and copy it to the local storage area.
 244  
      */
 245  
     public void applicationToLocal() {
 246  0
         for (String key : keys) {
 247  0
             Choice model = getChoice(key);
 248  0
             String value = model.getString();
 249  0
             local.put(key, value);
 250  0
         }
 251  0
     }
 252  
 
 253  
     /**
 254  
      * Take the data in the local storage area and copy it to the application.
 255  
      */
 256  
     public void localToApplication() {
 257  0
         for (String key : keys) {
 258  0
             Choice choice = getChoice(key);
 259  
 
 260  0
             String oldValue = choice.getString(); // never returns null
 261  0
             String newValue = local.get(key);
 262  
 
 263  
             // The new value shouldn't really be blank - obviously this
 264  
             // choice has just been added, substitute the default.
 265  0
             if ((newValue == null) || (newValue.length() == 0)) {
 266  0
                 if ((oldValue == null) || (oldValue.length() == 0)) {
 267  0
                     continue;
 268  
                 }
 269  0
                 local.put(key, oldValue);
 270  0
                 newValue = oldValue;
 271  
             }
 272  
 
 273  
             // If a value has not changed, we only call setString()
 274  
             // if force==true or if a higher priority choice has
 275  
             // changed.
 276  0
             if (!newValue.equals(oldValue)) {
 277  0
                 LOGGER.info("Setting {}={} (was {})", key, newValue, oldValue);
 278  
                 try {
 279  0
                     choice.setString(newValue);
 280  0
                     if (changeListeners != null) {
 281  0
                         changeListeners.firePropertyChange(new PropertyChangeEvent(choice, choice.getKey(), oldValue, newValue));
 282  
                     }
 283  0
                 } catch (LucidException ex) {
 284  0
                     LOGGER.warn("Failure setting {}={}", key, newValue, ex);
 285  0
                     Reporter.informUser(this, new ConfigException(JSOtherMsg.lookupText("Failed to set option: {0}", choice.getFullPath()), ex));
 286  0
                 }
 287  
             }
 288  0
         }
 289  0
     }
 290  
 
 291  
     /**
 292  
      * Take the data stored permanently and copy it to the local storage area,
 293  
      * using the specified stream.
 294  
      * 
 295  
      * @param prop the set of properties to save
 296  
      */
 297  
     public void setProperties(PropertyMap prop) {
 298  0
         for (String key : prop.keySet()) {
 299  0
             String value = prop.get(key);
 300  
 
 301  0
             Choice model = getChoice(key);
 302  
             // Only if a value was stored and it should be stored then we use
 303  
             // it.
 304  0
             if (value != null && model != null && model.isSaveable()) {
 305  0
                 local.put(key, value);
 306  
             }
 307  0
         }
 308  0
     }
 309  
 
 310  
     /**
 311  
      * Take the data in the local storage area and store it permanently.
 312  
      * 
 313  
      * @return the collection of properties
 314  
      */
 315  
     public PropertyMap getProperties() {
 316  0
         PropertyMap prop  = new PropertyMap();
 317  
 
 318  0
         for (String key : keys) {
 319  0
             String value = local.get(key);
 320  
 
 321  0
             Choice model = getChoice(key);
 322  0
             if (model.isSaveable()) {
 323  0
                 prop.put(key, value);
 324  
             } else {
 325  0
                 prop.remove(key);
 326  
             }
 327  0
         }
 328  
 
 329  0
         return prop;
 330  
     }
 331  
 
 332  
     /**
 333  
      * Take the data stored permanently and copy it to the local storage area,
 334  
      * using the configured storage area
 335  
      * 
 336  
      * @param uri the location of the permanent storage
 337  
      * @throws IOException if there was a problem getting the permanent config info
 338  
      */
 339  
     public void permanentToLocal(URI uri) throws IOException {
 340  0
         setProperties(NetUtil.loadProperties(uri));
 341  0
     }
 342  
 
 343  
     /**
 344  
      * Take the data in the local storage area and store it permanently, using
 345  
      * the configured storage area.
 346  
      * 
 347  
      * @param uri the location of the permanent storage
 348  
      * @throws IOException if there was a problem storing the permanent config info
 349  
      */
 350  
     public void localToPermanent(URI uri) throws IOException {
 351  0
         NetUtil.storeProperties(getProperties(), uri, title);
 352  0
     }
 353  
 
 354  
     /**
 355  
      * What is the Path of this key
 356  
      * 
 357  
      * @param key the key of the property
 358  
      * @return the path of the key
 359  
      */
 360  
     public static String getPath(String key) {
 361  0
         int lastDot = key.lastIndexOf('.');
 362  0
         if (lastDot == -1) {
 363  0
             throw new IllegalArgumentException("key=" + key + " does not contain a dot.");
 364  
         }
 365  
 
 366  0
         return key.substring(0, lastDot);
 367  
     }
 368  
 
 369  
     /**
 370  
      * What is the last part of the Path of this key.
 371  
      * 
 372  
      * @param key the key of the property
 373  
      * @return the part of the path after the last dot, '.'
 374  
      */
 375  
     public static String getLeaf(String key) {
 376  0
         int lastDot = key.lastIndexOf('.');
 377  0
         if (lastDot == -1) {
 378  0
             throw new IllegalArgumentException("key=" + key + " does not contain a dot.");
 379  
         }
 380  
 
 381  0
         return key.substring(lastDot + 1);
 382  
     }
 383  
 
 384  
     /**
 385  
      * Add a PropertyChangeListener to the listener list. The listener is
 386  
      * registered for all properties.
 387  
      * 
 388  
      * @param listener
 389  
      *            The PropertyChangeListener to be added
 390  
      */
 391  
     public void addPropertyChangeListener(PropertyChangeListener listener) {
 392  0
         if (changeListeners == null) {
 393  0
             changeListeners = new PropertyChangeSupport(this);
 394  
         }
 395  0
         changeListeners.addPropertyChangeListener(listener);
 396  0
     }
 397  
 
 398  
     /**
 399  
      * Add a PropertyChangeListener for a specific property. The listener will
 400  
      * be invoked only when a call on firePropertyChange names that specific
 401  
      * property.
 402  
      * 
 403  
      * @param propertyName
 404  
      *            The name of the property to listen on.
 405  
      * @param listener
 406  
      *            The PropertyChangeListener to be added
 407  
      */
 408  
 
 409  
     public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
 410  0
         if (changeListeners == null) {
 411  0
             changeListeners = new PropertyChangeSupport(this);
 412  
         }
 413  0
         changeListeners.addPropertyChangeListener(propertyName, listener);
 414  0
     }
 415  
 
 416  
     /**
 417  
      * Remove a PropertyChangeListener from the listener list. This removes a
 418  
      * PropertyChangeListener that was registered for all properties.
 419  
      * 
 420  
      * @param listener
 421  
      *            The PropertyChangeListener to be removed
 422  
      */
 423  
     public void removePropertyChangeListener(PropertyChangeListener listener) {
 424  0
         if (changeListeners != null) {
 425  0
             changeListeners.removePropertyChangeListener(listener);
 426  
         }
 427  0
     }
 428  
 
 429  
     /**
 430  
      * Remove a PropertyChangeListener for a specific property.
 431  
      * 
 432  
      * @param propertyName
 433  
      *            The name of the property that was listened on.
 434  
      * @param listener
 435  
      *            The PropertyChangeListener to be removed
 436  
      */
 437  
     public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) {
 438  0
         if (changeListeners != null) {
 439  0
             changeListeners.removePropertyChangeListener(propertyName, listener);
 440  
         }
 441  0
     }
 442  
 
 443  
     /**
 444  
      * Add an Exception listener to the list of things wanting to know whenever
 445  
      * we capture an Exception.
 446  
      * 
 447  
      * @param li
 448  
      *            The ConfigListener to be added
 449  
      */
 450  
     public void addConfigListener(ConfigListener li) {
 451  0
         listeners.add(li);
 452  0
     }
 453  
 
 454  
     /**
 455  
      * Remove an Exception listener from the list of things wanting to know
 456  
      * whenever we capture an Exception
 457  
      * 
 458  
      * @param li
 459  
      *            The ConfigListener to be removed
 460  
      */
 461  
     public void removeConfigListener(ConfigListener li) {
 462  0
         listeners.remove(li);
 463  0
     }
 464  
 
 465  
     /**
 466  
      * A Choice got added.
 467  
      * 
 468  
      * @param key the key of the choice that has been added
 469  
      * @param model the choice that was added
 470  
      */
 471  
    protected void fireChoiceAdded(String key, Choice model) {
 472  0
         ConfigEvent ev = new ConfigEvent(this, key, model);
 473  0
         for (ConfigListener listener : listeners) {
 474  0
             listener.choiceAdded(ev);
 475  
         }
 476  0
     }
 477  
 
 478  
     /**
 479  
      * A Choice got removed.
 480  
      * 
 481  
      * @param key the key of the choice that has been removed
 482  
      * @param model the choice that was removed
 483  
      */
 484  
     protected void fireChoiceRemoved(String key, Choice model) {
 485  0
         ConfigEvent ev = new ConfigEvent(this, key, model);
 486  0
         for (ConfigListener listener : listeners) {
 487  0
             listener.choiceRemoved(ev);
 488  
         }
 489  0
     }
 490  
 
 491  
     /**
 492  
      * The name for dialog boxes and properties files
 493  
      */
 494  
     protected String title;
 495  
 
 496  
     /**
 497  
      * The array that stores the keys
 498  
      */
 499  0
     protected List<String> keys = new ArrayList<String>();
 500  
 
 501  
     /**
 502  
      * The array that stores the models
 503  
      */
 504  0
     protected List<Choice> models = new ArrayList<Choice>();
 505  
 
 506  
     /**
 507  
      * The set of local values
 508  
      */
 509  
     protected PropertyMap local;
 510  
 
 511  
     /**
 512  
      * The set of property change listeners.
 513  
      */
 514  
     protected PropertyChangeSupport changeListeners;
 515  
 
 516  
     /**
 517  
      * The list of listeners
 518  
      */
 519  
     protected List<ConfigListener> listeners;
 520  
 
 521  
     /**
 522  
      * The log stream
 523  
      */
 524  0
     private static final Logger LOGGER = LoggerFactory.getLogger(Config.class);
 525  
 }