Class ConfigPropUtilities

java.lang.Object
org.bzdev.util.ConfigPropUtilities

public class ConfigPropUtilities extends Object
Support class for processing ConfigPropertyEditor files. These files use the format described by Properties.load(java.io.Reader). The values for keys that start with
  • ebase64. are encrypted using either GPG or a symmetric cipherand then encoded as printable ASCII characters using the basic Base-64 encoding using the alphabet specified in Table 1 of RFC 4648. When a symmetric cipher is used, the Base-64 encoding is prefaced with the string "===".
  • base64. are encoded as printable ASCII characters using the basic Base-64 encoding using the alphabet specified in Table 1 of RFC 4648.
For all other keys, the sequence $(KEY) is replaced with property value for the key KEY. This replacement occurs recursively, with the recursion terminating when the value does not specify a replacement or KEY starts with "base64." or "ebase64.".

The motivation is that sometimes values are repeated and it is both tedious and error-prone to replace each instance when there is a change. For example


 foregroundColor = white
 backgroundColor = rbg(10,10,20)
 headingColor = $(foregroundColor)
 textColor = $(foregroundColor)
 errorColor = red
 
allows one to switch from "dark mode" by simply changing two values.

Substitution loops are not supported:


 foregroundColor = $(backgroundColor)
 backgroundColor = $(foregroundColor)
 headingColor = $(foregroundColor)
 textColor = $(foregroundColor)
 errorColor = red
 
will fail because the recursion will not terminate. This class does not test for this error, but the class ConfigPropertyEditor does check and will not allow a file containing this error to be written.

Finally, this class duplicates some of the functionality provided by ConfigPropertyEditor. The reason for the duplication is that

  • the amount of code is small.
  • the JAR file containing ConfigPropertyEditor is large enough that it would be wasteful to require its module when the only functionality needed is that provided by ConfigPropUtilities.
  • Field Details

    • EMPTY_CHAR_ARRAY

      public static final char[] EMPTY_CHAR_ARRAY
  • Constructor Details

    • ConfigPropUtilities

      public ConfigPropUtilities()
  • Method Details

    • newInstance

      public static Properties newInstance(File f) throws IOException
      Create a new instance of Properties, loading its keys and values from a file.
      Parameters:
      f - the file from which to load properties
      Returns:
      a new instance of Properties
      Throws:
      IOException - if an IO error occurred
    • newInstance

      public static Properties newInstance(InputStream is) throws IOException
      Create a new instance of Properties, loading its keys and values from an input stream. The stream is not automatically closed.
      Parameters:
      is - the input stream from which to load properties
      Returns:
      a new instance of Properties
      Throws:
      IOException - if an IO error occurred
    • newInstance

      public static Properties newInstance(File f, String mediaType) throws IOException
      Create a new instance of Properties, loading its keys and values from a file, and checking the file's media type.

      Media types are encoded in the first line of the file, which is expected to be

      
       #(!M.T MEDIATYPE)
       
      where MEDIATYPE is the media (or MIME type) as defined in RFC 2045 and subsequent RFCs.
      Parameters:
      f - the file from which to load properties
      mediaType - the expected media type for the file
      Returns:
      a new instance of Properties
      Throws:
      IOException - if an IO error occurred
    • newInstance

      public static Properties newInstance(InputStream is, String mediaType) throws IOException
      Create a new instance of Properties, loading its keys and values from an input sream, and checking the stream's media type.

      Media types are encoded in the first line of the file, which is expected to be

      
       #(!M.T MEDIATYPE)
       
      where MEDIATYPE is the media (or MIME type) as defined in RFC 2045 and subsequent RFCs. The mediatype is converted to lower case for testing. The stream is not automatically closed.
      Parameters:
      is - the input stream from which to load properties
      mediaType - the expected media type for the file
      Returns:
      a new instance of Properties
      Throws:
      IOException - if an IO error occurred
    • newInstance

      public static Properties newInstance(String b64data, String mediaType) throws IOException
      Create a new Properties object given Base-64 encoded data.
      Parameters:
      b64data - a string containing Base64-encoded GZIP data. Media types are encoded in the first line of the file, which is expected to be
      
       #(!M.T MEDIATYPE)
       
      where MEDIATYPE is the media (or MIME type) as defined in RFC 2045 and subsequent RFCs. The mediatype is converted to lower case for testing.
      mediaType - the expected media type for the file
      Returns:
      a new instance of Properties
      Throws:
      IOException - if the media type does not match that of the Base-64 encoded representation
    • newInstance

      public static Properties newInstance(byte[] data, String mediaType, boolean gzipped) throws IOException
      Create a new Properties object given a byte array that represents an instance of Properties. Media types are encoded in the first line of the file, which is expected to be
      
       #(!M.T MEDIATYPE)
       
      where MEDIATYPE is the media (or MIME type) as defined in RFC 2045 and subsequent RFCs. The mediatype is converted to lower case for testing.
      Parameters:
      data - the byte representation
      mediaType - the expected media (MIME) type
      gzipped - true if GZIP compression is used; false otherwise
      Returns:
      a new instance of Properties
      Throws:
      IOException
    • getPassphrase

      public static char[] getPassphrase(Supplier<char[]> supplier)
      Get the passphrase. The default supplier obtains the passphrase from the system console.
      Parameters:
      supplier - a Supplier that will provide the passphrase; null for a default
      Returns:
      the passphrase
    • getDecryptedProperty

      public static char[] getDecryptedProperty(Properties props, String key, char[] passphrase) throws GeneralSecurityException
      Get the value, decrypted if necessary, stored in an instance of Properties under a given key.
      Parameters:
      props - the properties
      key - the key
      passphrase - the GPG passphrase for decryption.
      Returns:
      the decrypted value
      Throws:
      GeneralSecurityException - if decryption failed
    • getDecryptedProperty

      public static char[] getDecryptedProperty(Properties props, String key, char[] passphrase, String gpgdir) throws GeneralSecurityException
      Get the value, decrypted if necessary, stored in an instance of Properties under a given key and a GPG home directory. The GPG home directory is the argument for the GPG --homedir command-line option.

      When gpgdir is non-null, a GPG TOFU (Trust On First Use) trust model is used.

      Parameters:
      props - the properties
      key - the key
      passphrase - the GPG passphrase for decryption
      gpgdir - the GPG 'home' directory to use; null for the default
      Returns:
      the decrypted value
      Throws:
      GeneralSecurityException - if decryption failed
    • getCyclicKeys

      public static Set<String> getCyclicKeys()
      Get keys associated with a cycle. This method will return the keys that appear in a cycle immediately after getProperty(Properties,String) throws an exception.

      This method is intended for debugging, not general use.

      Returns:
      a set of keys
    • getProperty

      public static String getProperty(Properties props, String key) throws IllegalStateException, IllegalArgumentException
      Get the value stored in an instance of Properties under a given key. Values whose keys start with "base64." are decoded using a Base-64 decoder. Otherwise, the sequence "$(KEY)", where KEY is some key, is replaced with the value stored for KEY in the value provided for the given key.

      If an error occurs due to a key recursively referencing itself, the method getCyclicKeys() can be used to help find the cycle.

      Parameters:
      props - the instance of Properties storing key-value pairs.
      key - the key
      Returns:
      the value for the given key; null if the key does not exist
      Throws:
      IllegalStateException - if a key is part of a cyclic reference
      IllegalArgumentException
    • setProperty

      public static void setProperty(Properties props, String key, String value) throws IllegalArgumentException
      Set a property for an instance of Properties. When decoded, each '$$' will be replaced with a single '$' and substrings of the form "$(KEY)" will be replaced with the value for the specified KEY.
      Parameters:
      props - the instance of Properties
      key - the property key
      value - the property value
      Throws:
      IllegalArgumentException - if the key starts with "ebase64."
      See Also:
    • setProperty

      public static void setProperty(Properties props, String key, String value, boolean literal) throws IllegalArgumentException
      Set a property for an instance of Properties. When the argument literal is false, a '$' must be escaped by replacing it with the pair "$$", and substrings of the form "$(KEY)" are replaced with the value of KEY. A check for circularity is not performed at this point. Each "$" in the value will be replaced with "$$" because of ConfigPropertyEditor conventions, unless the key starts with "base64.", in which case the value will be Base-64 encoded.
      Parameters:
      props - the instance of Properties
      key - the property key
      value - the property value
      literal - true if the value is a literal string; false if variable substitution is allowed
      Throws:
      IllegalArgumentException - if the key starts with "ebase64."
      See Also:
    • setProperty

      public static void setProperty(Properties props, String key, String value, char[] password) throws IllegalArgumentException
      Set a property for an instance of Properties when symmetric encryption is used.

      Note: When a ConfigPropertyEditor is used, either all encrypted entries should use either symmetric encryption or public key encryption, but these should not be mixed. All encrypted entries should use the same password or passphrase.

      Parameters:
      props - the instance of Properties
      key - the property key
      value - the property value
      password - the password for symmetric encryption
      Throws:
      IllegalArgumentException - if the password is null or empty, or if the key does not start with "ebase64."
      See Also:
    • setProperty

      public static void setProperty(Properties props, String key, char[] value, char[] password) throws IllegalArgumentException
      Set a property for an instance of Properties when symmetric encryption is used and the value being encrypted is an array of char.

      Note: When a ConfigPropertyEditor is used, either all encrypted entries should use either symmetric encryption or public key encryption, but these should not be mixed. All encrypted entries should use the same password or passphrase.

      Parameters:
      props - the instance of Properties
      key - the property key
      value - the property value
      password - the password for symmetric encryption
      Throws:
      IllegalArgumentException - if the password is null or empty, or if the key does not start with "ebase64."
      See Also:
    • setProperty

      public static void setProperty(Properties props, String key, String value, String gpgdir, String[] recipients) throws IllegalArgumentException
      Set a property for an instance of Properties when GPG encryption is used. The recipient's list must contain strings that GPG can use to look up a public key.

      Note: When a ConfigPropertyEditor is used, either all encrypted entries should use either symmetric encryption or public key encryption, but these should not be mixed. All encrypted entries should use the same password or passphrase.

      When gpgdir is non-null, a GPG TOFU (Trust On First Use) trust model is used.

      Parameters:
      props - the instance of Properties
      key - the property key
      value - the property value
      gpgdir - The GPG home directory used to store public an private keys; null for the default
      recipients - the recipients
      Throws:
      IllegalArgumentException - if there are no recipients or if the key does not start with "ebase64"
      See Also:
    • setProperty

      public static void setProperty(Properties props, String key, char[] value, String gpgdir, String[] recipients) throws IllegalArgumentException
      Set a property for an instance of Properties when GPG encryption is used and the value being encrypted is an array of char. The recipient's list must contain strings that GPG can use to look up a public key.

      Note: When a ConfigPropertyEditor is used, either all encrypted entries should use either symmetric encryption or public key encryption, but these should not be mixed. All encrypted entries should use the same password or passphrase.

      When gpgdir is non-null, a GPG TOFU (Trust On First Use) trust model is used.

      Parameters:
      props - the instance of Properties
      key - the property key
      value - the property value
      gpgdir - The GPG home directory used to store public an private keys; null for the default
      recipients - the recipients
      Throws:
      IllegalArgumentException - if there are no recipients or if the key does not start with "ebase64"
      See Also:
    • store

      public static void store(Properties props, File file, String mediaType) throws IOException
      Store a properties file given an output file, using the ConfigPropertyEditor format. This method uses Properties.store(Writer,String) with a writer that uses the UTF-8 character set with CRLF as an end-of-line delimiter.
      Parameters:
      props - the properties object
      file - the file
      mediaType - the media (or MIME) type
      Throws:
      IOException - if an IO error occurred
    • store

      public static void store(Properties props, OutputStream os, String mediaType) throws IOException
      Store a properties file given an output stream, using the ConfigPropertyEditor format. This method uses Properties.store(Writer,String) with a writer that uses the UTF-8 character set with CRLF as an end-of-line delimiter.
      Parameters:
      props - the properties object
      os - the output stream
      mediaType - the media (or MIME) type
      Throws:
      IOException - if an IO error occurred
    • store

      public static String store(Properties props, String mediaType)
      Store a properties file given an output stream, using the ConfigPropertyEditor format. The string is produced by in effect first creating a text file using the UTF-8 charset and with a CRLF sequence terminating each line, compressing that file, and then base-64 encoding the compressed file. The text-file format is that produced by Properties.store(Writer,String). This byte array is finally base-64 encoded and turned into a string using the UTF-8 character set. The properties file will start with a comment "#(!M.T " MEDIATYPE)" where MEDIATYPE is the media type in lower case.
      Parameters:
      props - the properties object
      mediaType - the media (or MIME) type
      Returns:
      a string representation of a Properties object
    • storeBytes

      public static byte[] storeBytes(Properties props, String mediaType, boolean gzip)
      Store a properties file as an array of bytes. The byte array is produced by in effect first creating a text file using the UTF-8 charset and with a CRLF sequence terminating each line, optionally compressing that file. The text-file format is that produced by Properties.store(Writer,String). The properties file will start with a comment "#(!M.T " MEDIATYPE)" where MEDIATYPE is the media type in lower case.
      Parameters:
      props - the properties object
      mediaType - the media (or MIME) type
      gzip - true if GZIP compression is used; false otherwise
      Returns:
      a byte array storing an instance of Properties.
    • encodeRecipients

      public static String encodeRecipients(List<String> recipients)
      Encode a list of GPG recipients. The recipients first turned into a series of UTF-8 encoded bytes, separated by a new-line character, and the resulting sequence of bytes is Base64 encoded.
      Parameters:
      recipients - the recipients
      Returns:
      a string encoding the recipients; null if the argument is null
    • encodeRecipients

      public static String encodeRecipients(String[] recipients)
      Encode an array of GPG recipients. The recipients first turned into a series of UTF-8 encoded bytes, separated by a new-line character, and the resulting sequence of bytes is Base64 encoded.
      Parameters:
      recipients - the recipients
      Returns:
      a string encoding the recipients; null if the argument is null
    • decodeRecipients

      public static String[] decodeRecipients(String recipients)
      Decode a string representing GPG recipients Encoded recipients are first Base-64 decoded and the resulting string is then split at new-line characters to create the array.
      Parameters:
      recipients - the encoded recipients
      Returns:
      an array each element of which is a recipient; an empty array if there are no recipients; null if the argument is null empty array if there are no recipients