# Registry.java

    /*
    ** Module   : Registry.java
    ** Abstract : Progress 4GL compatible Winodws registry related methods
    **
    ** Copyright (c) 2013-2024, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- ---------------------------------Description----------------------------------
    ** 001 EVL 20130604 Created initial version.
    ** 002 EVL 20140117 The native library call is performing now in dedicated place and only once.
    **                  See the ClientCore class.
    ** 003 EVL 20140327 The default constructor needs the session parameter to get properties from
    **                  server side.
    ** 004 CA  20140702 Added getEnvironmentName API.
    ** 005 MAG 20140908 Added getEnvironmentType API. Added return value to load() method.
    ** 006 CA  20180523 Registry env name is case insensitive.
    ** 007 GBB 20240826 Removing ServerPropertiesInspector.
    */
    /*
    ** This program is free software: you can redistribute it and/or modify
    ** it under the terms of the GNU Affero General Public License as
    ** published by the Free Software Foundation, either version 3 of the
    ** License, or (at your option) any later version.
    **
    ** This program is distributed in the hope that it will be useful,
    ** but WITHOUT ANY WARRANTY; without even the implied warranty of
    ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    ** GNU Affero General Public License for more details.
    **
    ** You may find a copy of the GNU Affero GPL version 3 at the following
    ** location: https://www.gnu.org/licenses/agpl-3.0.en.html
    **
    ** Additional terms under GNU Affero GPL version 3 section 7:
    **
    **   Under Section 7 of the GNU Affero GPL version 3, the following additional
    **   terms apply to the works covered under the License.  These additional terms
    **   are non-permissive additional terms allowed under Section 7 of the GNU
    **   Affero GPL version 3 and may not be removed by you.
    **
    **   0. Attribution Requirement.
    **
    **     You must preserve all legal notices or author attributions in the covered
    **     work or Appropriate Legal Notices displayed by works containing the covered
    **     work.  You may not remove from the covered work any author or developer
    **     credit already included within the covered work.
    **
    **   1. No License To Use Trademarks.
    **
    **     This license does not grant any license or rights to use the trademarks
    **     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
    **     of Golden Code Development Corporation. You are not authorized to use the
    **     name Golden Code, FWD, or the names of any author or contributor, for
    **     publicity purposes without written authorization.
    **
    **   2. No Misrepresentation of Affiliation.
    **
    **     You may not represent yourself as Golden Code Development Corporation or FWD.
    **
    **     You may not represent yourself for publicity purposes as associated with
    **     Golden Code Development Corporation, FWD, or any author or contributor to
    **     the covered work, without written authorization.
    **
    **   3. No Misrepresentation of Source or Origin.
    **
    **     You may not represent the covered work as solely your work.  All modified
    **     versions of the covered work must be marked in a reasonable way to make it
    **     clear that the modified work is not originating from Golden Code Development
    **     Corporation or FWD.  All modified versions must contain the notices of
    **     attribution required in this license.
    */

    package com.goldencode.p2j.util;

    import com.goldencode.p2j.net.*;

    /**
     * A class implementing the Windows registry based environment methods.  Key values are stored in
     * registry under appropriate keys in registry subsection.  This is the default approach if none
     * new environment is not loaded and used by LOAD and USE Progress statements.
     * <p>
     * Some terms need to be clarified.  Unlike handling the INI file the 4GL sections are stored as
     * registry subkeys, while the keys are stored as registry value names.  Current investigation
     * show the 4GL base option has no effect in registry handling, everything is happening under
     * HKEY_CURRENT_USER registry base.  Also the documented behavior for getting the subkeys in
     * format 'section1@key1,section2@key2,..,sectionN@keyN' does not work.  Another format for
     * getting the list of the keys is implemented: 'section1,section2,..,sectionN'.
     *
     * @author    EVL
     */
    public class Registry
    implements EnvironmentReader
    {
       /** Windows registry key-subkey names separator. */
       public static final String NAME_SEPARATOR = "\\";

       /** Windows default hive name. */
       public static final String HKEY_DEFAULT_BASE = "HKEY_CURRENT_USER";

       /** Windows default key name. */
       public static final String KEY_DEFAULT = "";

       /**
        * Native entry point to get key value from registry.
        *
        * @param base
        *        The name of the root for the environment location.  It is currently ignored because
        *        the only root is working is HKEY_CURRENT_USER.
        * @param section
        *        The name of the section in the current environment registry entry to look the key.
        * @param key
        *        The name of the key(or value name in a terms of the windows registry) whose value
        *        is requested.
        *
        * @return The string representing the value of the given environment key.
        */
       private native static String getKeyVal(String base, String section, String key);

       /**
        * Getting the comma separated list of subkeys for the given environment name.
        *
        * @param base
        *        The name of the root for the environment location.  It is currently ignored because
        *        the only root is working is HKEY_CURRENT_USER.
        * @param name
        *        The name of the current environment registry entry to get the section list.
        *
        * @return The string representing the comma separated list of the sections for a given
        *         environment entry.
        */
       private native static String getSubKeys(String base, String name);

       /**
        * Getting the comma separated list of keys(value name sin a terms of the windows registry)
        * for given subkey section.
        *
        * @param base
        *        The name of the root for the environment location.  It is currently ignored because
        *        the only root is working is HKEY_CURRENT_USER.
        * @param section
        *        The name of the section in the current environment registry entry to get the value
        *        names list.
        *
        * @return The string representing the comma separated list of the keys in a given section.
        */
       private native static String getValueNames(String base, String section);

       /**
        * Native entry point to store new key value to registry.
        *
        * @param base
        *        The name of the root for the environment location.  It is currently ignored because
        *        the only root is working is HKEY_CURRENT_USER.
        * @param section
        *        The name of the section in the current environment registry entry to change the key.
        * @param key
        *        The name of the key(or value name in a terms of the windows registry) to modify.
        * @param value
        *        The new value to be stored for the key.
        *
        * @return <code>true</code> in case of success, <code>false</code> otherwise.
        */
       private native static boolean setKeyVal(String base, String section, String key, String value);

       /**
        * Deletes name and it's value from subkey 'section'.
        *
        * @param base
        *        The name of the root for the environment location.  It is currently ignored because
        *        the only root is working is HKEY_CURRENT_USER.
        * @param section
        *        The name of the section in the current environment registry entry to remove the key.
        * @param key
        *        The name of the key(or value name in a terms of the windows registry) to remove.
        *
        * @return <code>true</code> in case of success, <code>false</code> otherwise.
        */
       private native static boolean removeValueName(String base, String section, String key);

       /**
        * Deletes subkey section and all content.
        *
        * @param base
        *        The name of the root for the environment location.  It is currently ignored because
        *        the only root is working is HKEY_CURRENT_USER.
        * @param section
        *        The name of the section to remove from the current environment registry entry.
        *
        * @return <code>true</code> in case of success, <code>false</code> otherwise.
        */
       private native static boolean removeSubKey(String base, String section);

       /** The complete key path with subkeys to store the key values without base key name. */
       private String registryKeyName = null;

       /** The name of the registry base to store the keys. */
       private String baseKeyName = null;

       /** The flag indicating whether we need to create new registry sections replacing existed. */
       private boolean isNew;

       /**
        * Default constructor creating the internals based on the expected registry keys for currently
        * installed Progress simulation.
        *
        * @param    envVersion
        *           The current version string of the Progress compatible environment.
        */
       public Registry(String envVersion)
       {
          registryKeyName = "SOFTWARE"+NAME_SEPARATOR+"PSC"+NAME_SEPARATOR+"PROGRESS"+NAME_SEPARATOR+envVersion;
          isNew = false;
          baseKeyName = HKEY_DEFAULT_BASE;
       }

       /**
        * Construct an instance of the environment with Windows registry as backend.
        *
        * @param env
        *        The environment to create or load. Can be the registry key.
        * @param flagNew
        *        If <code>true</code> - the new file will be created overwriting the possibly
        *        existed one.
        * @param baseKey
        *        When the parameter is specified the serching is performed under the particular base
        *        key value.
        */
       public Registry(String env, boolean flagNew, String baseKey)
       {
          registryKeyName = env;
          isNew = flagNew;
          if (baseKey != null)
          {
             baseKeyName = baseKey;
          }
          else
          {
             baseKeyName = HKEY_DEFAULT_BASE;
          }
       }

       /**
        * Creates application defaults implementing Progress LOAD statement.
        *
        * @return  <code>true</code> if loaded false otherwise
        */
       public boolean load()
       {
          return true;
       }

       /**
        * Adds, modifies and deletes keys or sections in current environment.  The real work it
        * performs is controlled by the input parameters.  In a simplest case when all three inputs
        * have the real not null and not empty string the method change the registry key for the
        * current environment.
        * <p>
        * The other possible cases are("" means the parameter is null or empty string if the
        * difference is not important):
        * section == "", key != "", value != "" causes adding directly under current environment
        *                                       value name 'key' with given 'value'.
        * section == "", key != "", value == "" removing value name 'key' directly under current
        *                                       environment with its 'value'
        * section != "", key != "", value == "" deletes name and it's value from subkey 'section'.
        * section != "", key == "", value == "" deletes subkey 'section' with all content.
        * section != "", key == null, value != "" sets new value for DEFAULT value name for subkey
        *                                         'section'.
        * section != "", key == null, value == "" removes DEFAULT value name setting 'value' as unset
        *                                         for subkey 'section'
        *
        * @param section
        *        The name of the section containing the key to modify.
        * @param key
        *        The name of the key to modify or default key if not specified.
        * @param value
        *        The new value of the key under modification.
        */
       public void setKeyValue(String section, String key, String value)
       {
          // Prepare initial parameters for JNI call
          String sectionInitial = null;
          String keyInitial = null;
          String valueInitial = null;

          if (section == null || section.isEmpty()) // Section is empty or null
          {
             if (key != null && !key.isEmpty()) // Key is not empty
             {
                if (value != null && !value.isEmpty()) // Value is not empty
                {
                   // Adding name-value directly under current environment
                   sectionInitial = registryKeyName;
                   keyInitial = key;
                   valueInitial = value;
                   // Call C native code here
                   setKeyVal(baseKeyName, sectionInitial, keyInitial, valueInitial);
                }
                else  // Value is empty or null
                {
                   // Deletes name and it's value from current environment (no subsections)
                   sectionInitial = registryKeyName;
                   keyInitial = key;
                   // Call C native code here
                   removeValueName(baseKeyName, sectionInitial, keyInitial);
                }
             }
             else // Key is empty or null
             {
                // Other 2 invalid cases - probably we have to generate error here
             }
          }
          else
          {
             if (key == null) // Key is null, working with DEFAULT key for given section
             {
                if (value == null || value.isEmpty()) // Value is empty or null
                {
                   // Deletes subkey section and all content
                   sectionInitial = registryKeyName + NAME_SEPARATOR + section;
                   keyInitial = KEY_DEFAULT;
                   // Call C native code here
                   removeValueName(baseKeyName, sectionInitial, keyInitial);
                }
                else // Value is not empty
                {
                   // Sets the DEFAULT value name for subkey section
                   sectionInitial = registryKeyName + NAME_SEPARATOR + section;
                   keyInitial = KEY_DEFAULT;
                   valueInitial = value;
                   // Call C native code here
                   setKeyVal(baseKeyName, sectionInitial, keyInitial, valueInitial);
                }
             }
             else if (key.isEmpty()) // Key is empty
             {
                if (value == null || value.isEmpty()) // Value is empty or null
                {
                   // Deletes subkey section and all content
                   sectionInitial = registryKeyName + NAME_SEPARATOR + section;
                   // Call C native code here
                   removeSubKey(baseKeyName, sectionInitial);
                }
             }
             else // Key is not empty
             {
                if (value == null || value.isEmpty()) // Value is empty
                {
                   // Deletes name and it's value from subkey 'section'
                   sectionInitial = registryKeyName + NAME_SEPARATOR + section;
                   keyInitial = key;
                   // Call C native code here
                   removeValueName(baseKeyName, sectionInitial, keyInitial);
                }
                else // Value is not empty
                {
                   // Usual work, just set the new value
                   sectionInitial = registryKeyName + NAME_SEPARATOR + section;
                   keyInitial = key;
                   valueInitial = value;
                   // Call C native code here
                   setKeyVal(baseKeyName, sectionInitial, keyInitial, valueInitial);
                }
             }
          }
       }

       /**
        * Getting the key value or key or section lists from the current environment.  The real work
        * it performs is controlled by the input parameters.  In a simplest case when all two inputs
        * have the real not null and not empty string the method gets the value of the given key.
        * <p>
        * The other possible cases are("" means the parameter is null or empty string if the
        * difference is not important):
        * section == "", key != "" gets the value for given value name 'key' under current
        *                          environment.  There is no 'section' for this key.
        * section == "", key == "" gets the comma separated list of all subkeys for the current
        *                          environment.
        * section != "", key == empty gets the comma separated list of value names for subkey
        *                             'section'.  Empty here means not null string with 0 length.
        * section != "", key == null gets the DEFAULT value for the given subkey 'section'.
        *
        * @param section
        *        The name of the section containing the key to get.
        * @param key
        *        The name of the key to get or default key if not specified.
        *
        * @return The value of the key.
        */
       public String getKeyValue(String section, String key)
       {
          // Prepare initial parameters for JNI call
          String sectionInitial = null;
          String keyInitial = null;
          @SuppressWarnings("unused")
          String valueInitial = null;
          // What we return from C native code
          String result = null;

          if (section == null || section.isEmpty()) // Section is empty or null
          {
             if (key == null || key.isEmpty()) // Key is empty or null
             {
                // Getting the comma separated list of subkey@value_name pair
                sectionInitial = registryKeyName;
                result = getSubKeys(baseKeyName, sectionInitial);
             }
             else // Key is not empty
             {
                // Getting the value of the key under current environment
                sectionInitial = registryKeyName;
                keyInitial = key;
                // Call C native code here
                result = getKeyVal(baseKeyName, sectionInitial, keyInitial);
             }
          }
          else // Section is not empty
          {
             if (key == null) // Key is null
             {
                // Getting the default value name for given subkey section
                sectionInitial = registryKeyName + NAME_SEPARATOR + section;
                keyInitial = KEY_DEFAULT;
                // Call C native code here
                result = getKeyVal(baseKeyName, sectionInitial, keyInitial);
             }
             else if (key.isEmpty()) // Key is empty but not null, special case
             {
                // Getting the comma separated list of value_names for given subkey section
                sectionInitial = registryKeyName + NAME_SEPARATOR + section;
                result = getValueNames(baseKeyName, sectionInitial);
             }
             else // Section and key are not empty
             {
                // Usual provessing, get the key value
                sectionInitial = registryKeyName + NAME_SEPARATOR + section;
                keyInitial = key;
                // Call C native code here
                result = getKeyVal(baseKeyName, sectionInitial, keyInitial);
             }
          }

          return result;
       }

       /**
        * Unloads specifies environment from the current one.
        */
       public void unload()
       {
          return;
       }

       /**
        * Get the environment's name.
        *
        * @return   See above.
        */
       @Override
       public String getEnvironmentName()
       {
          return registryKeyName.toLowerCase();
       }

       /**
        * Get the environment's type.
        *
        * @return   See above.
        */
       @Override
       public String getEnvironmentType()
       {
          return TYPE_REG;
       }
    }