Package au.net.causal.projo.prefs.windows

Source Code of au.net.causal.projo.prefs.windows.WindowsRegistryNode

package au.net.causal.projo.prefs.windows;

import java.lang.annotation.Annotation;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.prefs.Preferences;

import org.apache.commons.lang3.StringUtils;

import au.net.causal.projo.prefs.AbstractPreferenceNode;
import au.net.causal.projo.prefs.PreferenceKeyMetadata;
import au.net.causal.projo.prefs.PreferenceNode;
import au.net.causal.projo.prefs.PreferencesException;
import au.net.causal.projo.prefs.ProjoStore;
import au.net.causal.projo.prefs.UnsupportedDataTypeException;

import com.google.common.collect.ImmutableSet;
import com.google.common.reflect.TypeToken;
import com.sun.jna.Memory;
import com.sun.jna.Native;
import com.sun.jna.platform.win32.Advapi32;
import com.sun.jna.platform.win32.Advapi32Util;
import com.sun.jna.platform.win32.W32Errors;
import com.sun.jna.platform.win32.Win32Exception;
import com.sun.jna.platform.win32.WinNT;
import com.sun.jna.platform.win32.WinReg;
import com.sun.jna.platform.win32.WinReg.HKEY;
import com.sun.jna.ptr.IntByReference;

public class WindowsRegistryNode extends AbstractPreferenceNode
{
  private static final TypeToken<List<String>> LIST_OF_STRING_TYPE = new TypeToken<List<String>>(){};
 
  private static final Set<TypeToken<?>> SUPPORTED_DATA_TYPES = ImmutableSet.<TypeToken<?>>of(TypeToken.of(String.class),
                                          TypeToken.of(Integer.class),
                                          TypeToken.of(Long.class),
                                          TypeToken.of(byte[].class),
                                          LIST_OF_STRING_TYPE);
 
  private static final Charset CONVERTER_CHARSET = StandardCharsets.ISO_8859_1; //Windows standard
 
 
 
  private HKEY root;
 
  /**
   * In Windows terminology, a key corresponds to a preference node.  A preference key actually corresponds to a Windows registry value.
   * Terminology can be a bit confusing.
   */
  private String winRegKey;
 
  /**
   * Returns the standard location for the root of application settings.  It is expected that applications will choose an appropriate
   * child node (which {@link ProjoStore} will do by default).
   * <p>
   *
   * Projo chooses <code>HKEY_CURRENT_USER\Software\JavaSoft\projo</code> as the root.  It shouldn't go in the Prefs node which Java uses for its
   * {@link Preferences} since key names are encoded slightly different.  Applications may, however, choose a different node themselves (e.g.
   * Software/&lt;application name&gt;) if they wish by using the {@link #WindowsRegistryNode(HKEY, String)} constructor.
   *
   * @return the root Windows registry node for application settings.
   *
   * @throws PreferencesException if an error occurs reading the Windows registry.
   */
  public static WindowsRegistryNode javaPreferencesRootNode()
  throws PreferencesException
  {
    return(new WindowsRegistryNode(WinReg.HKEY_CURRENT_USER, "Software\\JavaSoft\\projo"));
  }
 
  public WindowsRegistryNode(HKEY root, String winRegKey)
  {
    super(Collections.<Class<? extends Annotation>>emptySet());
   
    if (root == null)
      throw new NullPointerException("root == null");
    if (winRegKey == null)
      throw new NullPointerException("winRegKey == null");
   
    this.root = root;
    this.winRegKey = winRegKey;
  }

  @Override
  protected boolean isDataTypeSupportedImpl(PreferenceKeyMetadata<?> keyType) throws PreferencesException
  {
    return(SUPPORTED_DATA_TYPES.contains(keyType.getDataType()));
  }

  @Override
  protected <T> T getValueImpl(String key, PreferenceKeyMetadata<T> metadata) throws UnsupportedDataTypeException, PreferencesException
  {
    TypeToken<?> dataType = metadata.getDataType();
   
    try
    {
      Object regValue = registryGetValue(root, winRegKey, key);
     
      if (TypeToken.of(String.class).equals(dataType))
        return((T)valueToString(regValue));
      else if (TypeToken.of(Integer.class).equals(dataType))
        return((T)valueToInteger(regValue));
      else if (TypeToken.of(Long.class).equals(dataType))
        return((T)valueToLong(regValue));
      else if (TypeToken.of(byte[].class).equals(dataType))
        return((T)valueToBytes(regValue));
      else if (LIST_OF_STRING_TYPE.equals(dataType))
        return((T)valueToStringList(regValue));
      else
        throw new UnsupportedDataTypeException(metadata.getDataType());
    }
    catch (Win32Exception e)
    {
      throw new PreferencesException(e.getMessage(), e);
    }
  }
 
  private String valueToString(Object regValue)
  {
    if (regValue == null)
      return(null);
    else if (regValue instanceof String[])
      return(StringUtils.join((String[])regValue, System.lineSeparator())); //just like regedit does it
    else if (regValue instanceof byte[])
      return(new String((byte[])regValue, CONVERTER_CHARSET));
    else //All the rest toString() is fine
      return(regValue.toString());
  }
 
  private Integer valueToInteger(Object regValue)
  {
    if (regValue == null)
      return(null);
    else if (regValue instanceof Number)
      return(((Number)regValue).intValue());
    else if (regValue instanceof String[])
    {
      String[] list = (String[])regValue;
      if (list.length == 1)
        return(valueToInteger(list[0]));
      else
        return(null); //Cannot convert if zero length or multiple elements
    }
    else if (regValue instanceof String)
    {
      //Attempt to parse
      try
      {
        return(Integer.parseInt((String)regValue));
      }
      catch (NumberFormatException e)
      {
        //Failed to parse so cannot convert
        return(null);
      }
    }
    else if (regValue instanceof byte[])
    {
      byte[] bValue = (byte[])regValue;
      if (bValue.length == Integer.SIZE)
        return(ByteBuffer.wrap(bValue).order(ByteOrder.LITTLE_ENDIAN).getInt());
      else if (bValue.length == Long.SIZE)
        return((int)ByteBuffer.wrap(bValue).order(ByteOrder.LITTLE_ENDIAN).getLong());
      else
        return(null);
    }
    else
      return(null); //Cannot convert
  }
 
  private Long valueToLong(Object regValue)
  {
    if (regValue == null)
      return(null);
    else if (regValue instanceof Number)
      return(((Number)regValue).longValue());
    else if (regValue instanceof String[])
    {
      String[] list = (String[])regValue;
      if (list.length == 1)
        return(valueToLong(list[0]));
      else
        return(null); //Cannot convert if zero length or multiple elements
    }
    else if (regValue instanceof String)
    {
      //Attempt to parse
      try
      {
        return(Long.parseLong((String)regValue));
      }
      catch (NumberFormatException e)
      {
        //Failed to parse so cannot convert
        return(null);
      }
    }
    else if (regValue instanceof byte[])
    {
      byte[] bValue = (byte[])regValue;
      if (bValue.length == Integer.SIZE)
        return((long)ByteBuffer.wrap(bValue).order(ByteOrder.LITTLE_ENDIAN).getInt());
      else if (bValue.length == Long.SIZE)
        return(ByteBuffer.wrap(bValue).order(ByteOrder.LITTLE_ENDIAN).getLong());
      else
        return(null);
    }
    else
      return(null); //Cannot convert
  }
 
  private byte[] valueToBytes(Object regValue)
  {
    if (regValue == null)
      return(null);
    else if (regValue instanceof byte[])
      return((byte[])regValue);
    else if (regValue instanceof String[])
      return(valueToBytes(valueToString(regValue)));
    else if (regValue instanceof String)
      return(((String)regValue).getBytes(CONVERTER_CHARSET));
    else if (regValue instanceof Integer)
      return(ByteBuffer.allocate(Integer.SIZE / 8).order(ByteOrder.LITTLE_ENDIAN).putInt((Integer)regValue).array());
    else if (regValue instanceof Long)
      return(ByteBuffer.allocate(Long.SIZE / 8).order(ByteOrder.LITTLE_ENDIAN).putLong((Long)regValue).array());
    else
      return(null); //Cannot convert
  }
 
  private List<String> valueToStringList(Object regValue)
  {
    if (regValue == null)
      return(null);
    else if (regValue instanceof String[])
      return(Arrays.asList((String[])regValue));
    else
    {
      String s = valueToString(regValue);
      if (s == null)
        return(null);
      else
        return(Collections.singletonList(s));
    }
  }
 
  @Override
  protected <T> void putValueImpl(String key, T value, PreferenceKeyMetadata<T> metadata) throws UnsupportedDataTypeException, PreferencesException
  {
    TypeToken<T> dataType = metadata.getDataType();
   
    //Null values mean remove the value from the registry
    if (value == null)
    {
      removeValue(key, metadata);
      return;
    }
   
    if (!existsInRegistry())
      RegistryUtils.createKeyPath(root, winRegKey);
   
    try
    {
      if (TypeToken.of(String.class).equals(dataType))
        Advapi32Util.registrySetStringValue(root, winRegKey, key, (String)value);
      else if (TypeToken.of(Integer.class).equals(dataType))
        Advapi32Util.registrySetIntValue(root, winRegKey, key, (Integer)value);
      else if (TypeToken.of(Long.class).equals(dataType))
        Advapi32Util.registrySetLongValue(root, winRegKey, key, (Long)value);
      else if (LIST_OF_STRING_TYPE.equals(dataType))
      {
        @SuppressWarnings("unchecked") //Safe
        List<String> list = (List<String>)value;
        String[] arrayValue = list.toArray(new String[list.size()]);
       
        Advapi32Util.registrySetStringArray(root, winRegKey, key, arrayValue);
      }
      else if (TypeToken.of(byte[].class).equals(dataType))
        Advapi32Util.registrySetBinaryValue(root, winRegKey, key, (byte[])value);
      else
        throw new UnsupportedDataTypeException(metadata.getDataType());
    }
    catch (Win32Exception e)
    {
      throw new PreferencesException(e.getMessage(), e);
    }
  }

  @Override
  protected <T> void removeValueImpl(String key, PreferenceKeyMetadata<T> metadata) throws UnsupportedDataTypeException, PreferencesException
  {
    //No removal needed if the whole registry key does not exist
    if (!existsInRegistry())
      return;
   
    try
    {
      if (Advapi32Util.registryValueExists(root, winRegKey, key))
        Advapi32Util.registryDeleteValue(root, winRegKey, key);
    }
    catch (Win32Exception e)
    {
      throw new PreferencesException(e.getMessage(), e);
    }
  }

  @Override
  public void removeAllValues() throws PreferencesException
  {
    //No removal needed if the whole registry key does not exist
    if (!existsInRegistry())
      return;
   
    for (String keyName : getKeyNames())
    {
      try
      {
        Advapi32Util.registryDeleteValue(root, winRegKey, keyName);
      }
      catch (Win32Exception e)
      {
        throw new PreferencesException(e.getMessage(), e);
      }
    }
  }

  @Override
  public PreferenceNode getChildNode(String name) throws PreferencesException
  {
    String childRegKey = winRegKey + RegistryUtils.NODE_SEPARATOR + name;
    return(new WindowsRegistryNode(root, childRegKey));
  }

  @Override
  public void removeChildNode(String name) throws PreferencesException
  {
    try
    {
      RegistryUtils.deleteKeyWithChildren(root, winRegKey + RegistryUtils.NODE_SEPARATOR + name);
    }
    catch (Win32Exception e)
    {
      throw new PreferencesException(e.getMessage(), e);
    }
  }

  @Override
  public Set<String> getKeyNames() throws PreferencesException
  {
    if (!existsInRegistry())
      return(Collections.emptySet());
   
    try
    {
      return(Advapi32Util.registryGetValues(root, winRegKey).keySet());
    }
    catch (Win32Exception e)
    {
      throw new PreferencesException(e.getMessage(), e);
    }
  }
 
  /**
   * Returns true if this node exists in the Windows registry.
   *
   * @return true if exists, false if not.
   *
   * @throws PreferencesException if an error occurs.
   */
  private boolean existsInRegistry()
  throws PreferencesException
  {
    try
    {
      return(Advapi32Util.registryKeyExists(root, winRegKey));
    }
    catch (Win32Exception e)
    {
      throw new PreferencesException(e.getMessage(), e);
    }
  }

  @Override
  public Set<String> getNodeNames() throws PreferencesException
  {
    if (!existsInRegistry())
      return(Collections.emptySet());
   
    try
    {
      return(ImmutableSet.<String>builder().addAll(Arrays.asList(Advapi32Util.registryGetKeys(root, winRegKey))).build());
    }
    catch (Win32Exception e)
    {
      throw new PreferencesException(e.getMessage(), e);
    }
  }
 
  /**
   * Get a registry value and returns a java object depending on the value
   * type.
   * <p>
   *
   * This was mostly copied from {@link Advapi32Util} class but adds support for REG_MULTI_SZ type which was apparently lacking in the original.
   *
   * @param hkKey
   *            Root key.
   * @param subKey
   *            Registry key path.
   * @param lpValueName
   *            Name of the value to retrieve or null for the default value.
   * @return Object value.
   */
  private static Object registryGetValue(HKEY hkKey, String subKey,
      String lpValueName) {
    Object result = null;
    IntByReference lpType = new IntByReference();
    byte[] lpData = new byte[Advapi32.MAX_VALUE_NAME];
    IntByReference lpcbData = new IntByReference(Advapi32.MAX_VALUE_NAME);

    int rc = Advapi32.INSTANCE.RegGetValue(hkKey, subKey, lpValueName,
        Advapi32.RRF_RT_ANY, lpType, lpData, lpcbData);

    // if lpType == 0 then the value is empty (REG_NONE)!
    if (lpType.getValue() == WinNT.REG_NONE)
      return null;

    if (rc != W32Errors.ERROR_SUCCESS
        && rc != W32Errors.ERROR_INSUFFICIENT_BUFFER) {
      throw new Win32Exception(rc);
    }

    Memory byteData = new Memory(lpcbData.getValue());
    byteData.write(0, lpData, 0, lpcbData.getValue());

    if (lpType.getValue() == WinNT.REG_DWORD) {
      result = new Integer(byteData.getInt(0));
    } else if (lpType.getValue() == WinNT.REG_QWORD) {
      result = new Long(byteData.getLong(0));
    } else if (lpType.getValue() == WinNT.REG_BINARY) {
      result = byteData.getByteArray(0, lpcbData.getValue());
    } else if ((lpType.getValue() == WinNT.REG_SZ)
        || (lpType.getValue() == WinNT.REG_EXPAND_SZ)) {
      result = byteData.getWideString(0);
    } else if ((lpType.getValue() == WinNT.REG_MULTI_SZ)) {
      ArrayList<String> resultList = new ArrayList<>();
      int offset = 0;
      while (offset < byteData.size()) {
        String s = byteData.getWideString(offset);
        offset += s.length() * Native.WCHAR_SIZE;
        offset += Native.WCHAR_SIZE;
        if (s.length() == 0 && offset == byteData.size()) {
          // skip the final NULL
        } else {
          resultList.add(s);
        }
      }
      result = resultList.toArray(new String[resultList.size()]);
    }
   

    return result;
  }
 
  @Override
  public void flush() throws PreferencesException
  {
    //Everything is immediately flushed anyway so nothing to do here
  }
 
  @Override
  public void close() throws PreferencesException
  {
    flush();
  }
}
TOP

Related Classes of au.net.causal.projo.prefs.windows.WindowsRegistryNode

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.