PropertiesConfiguration.java

  1. /*
  2.  * Copyright (c) 2007-2025 MetaSolutions AB
  3.  *
  4.  * Licensed under the Apache License, Version 2.0 (the "License");
  5.  * you may not use this file except in compliance with the License.
  6.  * You may obtain a copy of the License at
  7.  *
  8.  *     http://www.apache.org/licenses/LICENSE-2.0
  9.  *
  10.  * Unless required by applicable law or agreed to in writing, software
  11.  * distributed under the License is distributed on an "AS IS" BASIS,
  12.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13.  * See the License for the specific language governing permissions and
  14.  * limitations under the License.
  15.  */

  16. package org.entrystore.repository.config;

  17. import org.apache.commons.logging.Log;
  18. import org.apache.commons.logging.LogFactory;
  19. import org.entrystore.config.Config;
  20. import org.entrystore.config.DurationStyle;

  21. import java.awt.*;
  22. import java.beans.PropertyChangeListener;
  23. import java.beans.PropertyChangeSupport;
  24. import java.io.File;
  25. import java.io.IOException;
  26. import java.io.InputStreamReader;
  27. import java.io.OutputStreamWriter;
  28. import java.net.MalformedURLException;
  29. import java.net.URI;
  30. import java.net.URISyntaxException;
  31. import java.net.URL;
  32. import java.nio.charset.StandardCharsets;
  33. import java.nio.file.Files;
  34. import java.time.Duration;
  35. import java.util.ArrayList;
  36. import java.util.Enumeration;
  37. import java.util.Iterator;
  38. import java.util.List;
  39. import java.util.Properties;

  40. /**
  41.  * Wrapper around Java's Properties.
  42.  * Some methods have been simplified, others just wrapped.<br>
  43.  *
  44.  * <p>
  45.  * See the static methods of the class Configurations for wrappers around the
  46.  * Config interface, e.g., to get synchronized view of the object.
  47.  *
  48.  * <p>
  49.  * If a key maps only to one value, it is done the standard way:<br>
  50.  * <pre>key=value</pre>
  51.  *
  52.  * <p>
  53.  * If a key maps to multiple values, the key is numbered:<br>
  54.  * <pre>key.1=value1</pre>
  55.  * <pre>key.2=value2</pre>
  56.  *
  57.  * @author Hannes Ebner
  58.  * @version $Id$
  59.  * @see org.entrystore.config.Configurations
  60.  * @see org.entrystore.config.Config
  61.  */
  62. public class PropertiesConfiguration implements Config {

  63.     Log log = LogFactory.getLog(PropertiesConfiguration.class);

  64.     /**
  65.      * The main resource in this object. Contains the configuration.
  66.      */
  67.     private final SortedProperties config;

  68.     private final PropertyChangeSupport pcs;

  69.     private final String configName;

  70.     private boolean modified = false;

  71.     /* Constructors */

  72.     /**
  73.      * Initializes the object with an empty Configuration.
  74.      *
  75.      * @param configName
  76.      *            Name of the configuration (appears as comment in the
  77.      *            configuration file).
  78.      */
  79.     public PropertiesConfiguration(String configName) {
  80.         this.configName = configName;
  81.         config = new SortedProperties();
  82.         pcs = new PropertyChangeSupport(this);
  83.     }

  84.     /* Generic helpers */

  85.     /**
  86.      * Sets the modified status of this configuration.
  87.      *
  88.      * @param modified
  89.      *            Status.
  90.      */
  91.     private void setModified(boolean modified) {
  92.         this.modified = modified;
  93.     }

  94.     private void checkFirePropertyChange(String key, Object oldValue, Object newValue) {
  95.         if ((oldValue == null) && (newValue != null)) {
  96.             pcs.firePropertyChange(key, null, newValue);
  97.         } else if ((oldValue != null) && (!oldValue.equals(newValue))) {
  98.             pcs.firePropertyChange(key, oldValue, newValue);
  99.         }
  100.     }

  101.     /*
  102.      * List helpers
  103.      */

  104.     private String numberedKey(String key, int number) {
  105.         return key + "." + number;
  106.     }

  107.     private int getPropertyValueCount(String key) {
  108.         int valueCount = 0;
  109.         if (config.containsKey(key)) {
  110.             valueCount = 1;
  111.         } else {
  112.             while (config.containsKey(numberedKey(key, valueCount + 1))) {
  113.                 valueCount++;
  114.             }
  115.         }
  116.         return valueCount;
  117.     }

  118.     private synchronized void addPropertyValue(String key, Object value) {
  119.         int valueCount = getPropertyValueCount(key);
  120.         if ((valueCount == 1) && config.containsKey(key)) {
  121.             String oldValue = config.getProperty(key);
  122.             config.remove(key);
  123.             config.setProperty(numberedKey(key, 1), oldValue);
  124.             config.setProperty(numberedKey(key, 2), value.toString());
  125.         } else if (valueCount > 1){
  126.             config.setProperty(numberedKey(key, valueCount + 1), value.toString());
  127.         } else if (valueCount == 0) {
  128.             config.setProperty(key, value.toString());
  129.         }
  130.     }

  131.     private void addPropertyValues(String key, List values) {
  132.         addPropertyValues(key, values.iterator());
  133.     }

  134.     private synchronized void addPropertyValues(String key, Iterator it) {
  135.         while (it.hasNext()) {
  136.             addPropertyValue(key, it.next());
  137.         }
  138.     }

  139.     private synchronized List<String> getPropertyValues(String key) {
  140.         int valueCount = getPropertyValueCount(key);
  141.         List<String> result = new ArrayList<>();
  142.         if (valueCount == 1) {
  143.             String value = config.getProperty(key);
  144.             if (value == null) {
  145.                 value = config.getProperty(numberedKey(key, 1));
  146.             }
  147.             if (value != null) {
  148.                 result.add(value);
  149.             }
  150.         } else {
  151.             for (int i = 1; i <= valueCount; i++) {
  152.                 result.add(config.getProperty(numberedKey(key, i)));
  153.             }
  154.         }
  155.         return result;
  156.     }

  157.     private synchronized void clearPropertyValues(String key) {
  158.         int valueCount = getPropertyValueCount(key);
  159.         if (valueCount > 1) {
  160.             for (int i = 1; i <= valueCount; i++) {
  161.                 config.remove(numberedKey(key, i));
  162.             }
  163.         }
  164.         config.remove(key);
  165.     }

  166.     private void setPropertyValues(String key, List values) {
  167.         setPropertyValues(key, values.iterator());
  168.     }

  169.     private synchronized void setPropertyValues(String key, Iterator it) {
  170.         clearPropertyValues(key);
  171.         addPropertyValues(key, it);
  172.     }

  173.     /*
  174.      * Interface implementation
  175.      */

  176.     /* Generic */

  177.     @Override
  178.     public void clear() {
  179.         config.clear();
  180.         setModified(true);
  181.     }

  182.     @Override
  183.     public boolean isEmpty() {
  184.         return config.isEmpty();
  185.     }

  186.     @Override
  187.     public boolean isModified() {
  188.         return modified;
  189.     }

  190.     @Override
  191.     public void load(URL configURL) throws IOException {
  192.         InputStreamReader isr = null;
  193.         try {
  194.             URL escapedURL = new URI(configURL.toString().replaceAll(" ", "%20")).toURL();
  195.             isr = new InputStreamReader(escapedURL.openStream(), StandardCharsets.UTF_8);
  196.             config.load(isr);
  197.         } catch (URISyntaxException e) {
  198.             log.error(e.getMessage());
  199.         } finally {
  200.             if (isr != null) {
  201.                 isr.close();
  202.             }
  203.         }
  204.     }

  205.     @Override
  206.     public void save(URL configURL) throws IOException {
  207.         try {
  208.             String escapedURL = configURL.toString().replaceAll(" ", "%20");
  209.             URI url = new URI(escapedURL);
  210.             File file = new File(url);
  211.             OutputStreamWriter output = new OutputStreamWriter(Files.newOutputStream(file.toPath()), StandardCharsets.UTF_8);
  212.             config.store(output, configName);
  213.             output.close();
  214.         } catch (URISyntaxException e) {
  215.             throw new IOException(e.getMessage());
  216.         }
  217.         setModified(false);
  218.     }

  219.     /* Property Change Listeners */

  220.     @Override
  221.     public void addPropertyChangeListener(PropertyChangeListener listener) {
  222.         pcs.addPropertyChangeListener(listener);
  223.     }

  224.     @Override
  225.     public void addPropertyChangeListener(String key, PropertyChangeListener listener) {
  226.         pcs.addPropertyChangeListener(key, listener);
  227.     }

  228.     @Override
  229.     public void removePropertyChangeListener(PropertyChangeListener listener) {
  230.         pcs.removePropertyChangeListener(listener);
  231.     }

  232.     @Override
  233.     public void removePropertyChangeListener(String key, PropertyChangeListener listener) {
  234.         pcs.removePropertyChangeListener(key, listener);
  235.     }

  236.     /* Properties / Set Values */

  237.     @Override
  238.     public void clearProperty(String key) {
  239.         int valueCount = getPropertyValueCount(key);
  240.         Object oldValue = null;
  241.         if (valueCount == 0) {
  242.             return;
  243.         } else if (valueCount == 1) {
  244.             oldValue = getString(key);
  245.         } else if (valueCount > 1) {
  246.             oldValue = getStringList(key);
  247.         }
  248.         clearPropertyValues(key);
  249.         setModified(true);
  250.         checkFirePropertyChange(key, oldValue, null);
  251.     }

  252.     @Override
  253.     public void addProperty(String key, Object value) {
  254.         addPropertyValue(key, value);
  255.         setModified(true);
  256.         pcs.firePropertyChange(key, null, value);
  257.     }

  258.     @Override
  259.     public void addProperties(String key, List values) {
  260.         addPropertyValues(key, values);
  261.         setModified(true);
  262.         pcs.firePropertyChange(key, null, values);
  263.     }

  264.     @Override
  265.     public void addProperties(String key, Iterator values) {
  266.         addPropertyValues(key, values);
  267.         setModified(true);
  268.         pcs.firePropertyChange(key, null, values);
  269.     }

  270.     @Override
  271.     public void setProperty(String key, Object value) {
  272.         String oldValue;
  273.         oldValue = getString(key);
  274.         config.setProperty(key, value.toString());
  275.         setModified(true);
  276.         checkFirePropertyChange(key, oldValue, value);
  277.     }

  278.     @Override
  279.     public void setProperties(String key, List values) {
  280.         List<String> oldValues = getStringList(key);
  281.         setPropertyValues(key, values);
  282.         setModified(true);
  283.         checkFirePropertyChange(key, oldValues, values);
  284.     }

  285.     @Override
  286.     public void setProperties(String key, Iterator values) {
  287.         List<String> oldValues = getStringList(key);
  288.         setPropertyValues(key, values);
  289.         setModified(true);
  290.         checkFirePropertyChange(key, oldValues, values);
  291.     }

  292.     /* Keys */

  293.     @Override
  294.     public boolean containsKey(String key) {
  295.         return config.containsKey(key);
  296.     }

  297.     @Override
  298.     public List<String> getKeyList() {
  299.         return getKeyList(null);
  300.     }

  301.     @Override
  302.     public List<String> getKeyList(String prefix) {
  303.         Enumeration keyIterator = config.propertyNames();
  304.         ArrayList<String> result = new ArrayList<>();

  305.         while (keyIterator.hasMoreElements()) {
  306.             String next = (String) keyIterator.nextElement();
  307.             if ((prefix != null) && !next.startsWith(prefix)) {
  308.                 continue;
  309.             }
  310.             result.add(next);
  311.         }
  312.         return result;
  313.     }

  314.     /* Get Values */

  315.     @Override
  316.     public String getString(String key) {
  317.         return config.getProperty(key);
  318.     }

  319.     @Override
  320.     public String getString(String key, String defaultValue) {
  321.         return config.getProperty(key, defaultValue);
  322.     }

  323.     @Override
  324.     public List<String> getStringList(String key) {
  325.         return getPropertyValues(key);
  326.     }

  327.     @Override
  328.     public List<String> getStringList(String key, List<String> defaultValues) {
  329.         List<String> result = getPropertyValues(key);
  330.         if (result.isEmpty()) {
  331.             result = defaultValues;
  332.         }
  333.         return result;
  334.     }

  335.     @Override
  336.     public boolean getBoolean(String key) {
  337.         return getBoolean(key, false);
  338.     }

  339.     @Override
  340.     public boolean getBoolean(String key, boolean defaultValue) {
  341.         String strValue = config.getProperty(key);
  342.         if ("on".equalsIgnoreCase(strValue)) {
  343.             return true;
  344.         } else if ("off".equalsIgnoreCase(strValue)) {
  345.             return false;
  346.         }

  347.         if (strValue != null) {
  348.             return Boolean.parseBoolean(strValue);
  349.         } else {
  350.             return defaultValue;
  351.         }
  352.     }

  353.     @Override
  354.     public byte getByte(String key) {
  355.         String strValue = config.getProperty(key);
  356.         byte byteValue = 0;

  357.         if (strValue != null) {
  358.             byteValue = Byte.parseByte(strValue);
  359.         }

  360.         return byteValue;
  361.     }

  362.     @Override
  363.     public byte getByte(String key, byte defaultValue) {
  364.         String strValue = config.getProperty(key);
  365.         byte byteValue;

  366.         if (strValue != null) {
  367.             byteValue = Byte.parseByte(strValue);
  368.         } else {
  369.             byteValue = defaultValue;
  370.         }

  371.         return byteValue;
  372.     }

  373.     @Override
  374.     public double getDouble(String key) {
  375.         String strValue = config.getProperty(key);
  376.         double doubleValue = 0;

  377.         if (strValue != null) {
  378.             doubleValue = Double.parseDouble(strValue);
  379.         }

  380.         return doubleValue;
  381.     }

  382.     @Override
  383.     public double getDouble(String key, double defaultValue) {
  384.         String strValue = config.getProperty(key);
  385.         double doubleValue;

  386.         if (strValue != null) {
  387.             doubleValue = Double.parseDouble(strValue);
  388.         } else {
  389.             doubleValue = defaultValue;
  390.         }

  391.         return doubleValue;
  392.     }

  393.     @Override
  394.     public float getFloat(String key) {
  395.         String strValue = config.getProperty(key);
  396.         float floatValue = 0;

  397.         if (strValue != null) {
  398.             floatValue = Float.parseFloat(strValue);
  399.         }

  400.         return floatValue;
  401.     }

  402.     @Override
  403.     public float getFloat(String key, float defaultValue) {
  404.         String strValue = config.getProperty(key);
  405.         float floatValue;

  406.         if (strValue != null) {
  407.             floatValue = Float.parseFloat(strValue);
  408.         } else {
  409.             floatValue = defaultValue;
  410.         }

  411.         return floatValue;
  412.     }

  413.     @Override
  414.     public int getInt(String key) {
  415.         String strValue = config.getProperty(key);
  416.         int intValue = 0;

  417.         if (strValue != null) {
  418.             intValue = Integer.parseInt(strValue);
  419.         }

  420.         return intValue;
  421.     }

  422.     @Override
  423.     public int getInt(String key, int defaultValue) {
  424.         String strValue = config.getProperty(key);
  425.         int intValue;

  426.         if (strValue != null) {
  427.             intValue = Integer.parseInt(strValue);
  428.         } else {
  429.             intValue = defaultValue;
  430.         }

  431.         return intValue;
  432.     }

  433.     @Override
  434.     public long getLong(String key) {
  435.         String strValue = config.getProperty(key);
  436.         long longValue = 0;

  437.         if (strValue != null) {
  438.             longValue = Long.parseLong(strValue);
  439.         }

  440.         return longValue;
  441.     }

  442.     @Override
  443.     public long getLong(String key, long defaultValue) {
  444.         String strValue = config.getProperty(key);
  445.         long longValue;

  446.         if (strValue != null) {
  447.             longValue = Long.parseLong(strValue);
  448.         } else {
  449.             longValue = defaultValue;
  450.         }

  451.         return longValue;
  452.     }

  453.     @Override
  454.     public short getShort(String key) {
  455.         String strValue = config.getProperty(key);
  456.         short shortValue = 0;

  457.         if (strValue != null) {
  458.             shortValue = Short.parseShort(strValue);
  459.         }

  460.         return shortValue;
  461.     }

  462.     @Override
  463.     public short getShort(String key, short defaultValue) {
  464.         String strValue = config.getProperty(key);
  465.         short shortValue;

  466.         if (strValue != null) {
  467.             shortValue = Short.parseShort(strValue);
  468.         } else {
  469.             shortValue = defaultValue;
  470.         }

  471.         return shortValue;
  472.     }

  473.     @Override
  474.     public URI getURI(String key) {
  475.         try {
  476.             String uri = config.getProperty(key);
  477.             if (uri != null) {
  478.                 return new URI(uri);
  479.             }
  480.         } catch (URISyntaxException ignored) {
  481.         }
  482.         return null;
  483.     }

  484.     @Override
  485.     public URI getURI(String key, URI defaultValue) {
  486.         URI result = getURI(key);
  487.         if (result == null) {
  488.             return defaultValue;
  489.         }
  490.         return result;
  491.     }

  492.     @Override
  493.     public URL getURL(String key) {
  494.         try {
  495.             String uri = config.getProperty(key);
  496.             if (uri != null) {
  497.                 return new URI(uri).toURL();
  498.             }
  499.         } catch (URISyntaxException | MalformedURLException ignored) {
  500.         }
  501.         return null;
  502.     }

  503.     @Override
  504.     public URL getURL(String key, URL defaultValue) {
  505.         URL result = getURL(key);
  506.         if (result == null) {
  507.             return defaultValue;
  508.         }
  509.         return result;
  510.     }

  511.     @Override
  512.     public Color getColor(String key) {
  513.         Color result = null;
  514.         String value = getString(key);

  515.         if (value != null) {
  516.             try {
  517.                 if (!value.startsWith("0x"))
  518.                     result = Color.decode(value);
  519.                 else {
  520.                     int rgb = Long.decode(value).intValue();
  521.                     result = new Color(rgb);
  522.                 }
  523.             } catch (NumberFormatException ignored) {
  524.             }
  525.         }
  526.         return result;
  527.     }

  528.     @Override
  529.     public Color getColor(String key, Color defaultValue) {
  530.         Color result = getColor(key);
  531.         if (result == null) {
  532.             return defaultValue;
  533.         }
  534.         return result;
  535.     }

  536.     @Override
  537.     public Duration getDuration(String key) {
  538.         String durationString = config.getProperty(key);
  539.         if (durationString == null) {
  540.             return null;
  541.         }
  542.         return DurationStyle.detectAndParse(durationString);
  543.     }

  544.     @Override
  545.     public Duration getDuration(String key, Duration defaultValue) {
  546.         String durationString = config.getProperty(key);
  547.         if (durationString == null) {
  548.             return defaultValue;
  549.         }
  550.         return DurationStyle.detectAndParse(durationString);
  551.     }

  552.     @Override
  553.     public Duration getDuration(String key, String defaultValue) {
  554.         String durationString = config.getProperty(key);
  555.         if (durationString == null) {
  556.             return DurationStyle.detectAndParse(defaultValue);
  557.         }
  558.         return DurationStyle.detectAndParse(durationString);
  559.     }

  560.     @Override
  561.     public Duration getDuration(String key, long defaultValue) {
  562.         String durationString = config.getProperty(key);
  563.         if (durationString == null) {
  564.             return Duration.ofMillis(defaultValue);
  565.         }
  566.         return DurationStyle.detectAndParse(durationString);
  567.     }

  568.     @Override
  569.     public Properties getProperties() {
  570.         return this.config;
  571.     }

  572. }