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;
   }
}