Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added GetEnvironmentStrings to 'com.sun.jna.platform.win32.Kernel32' #434

Merged
merged 1 commit into from
Apr 29, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ Features
* [#428](https://github.com/twall/jna/pull/428): Added Wincon.h related functions and definitions to `com.sun.jna.platform.win32.Kernel32` - [@lgoldstein](https://github.com/lgoldstein).
* [#431](https://github.com/twall/jna/pull/431): Added named pipe API support to `com.sun.jna.platform.win32.Kernel32` - [@lgoldstein](https://github.com/lgoldstein).
* [#432](https://github.com/twall/jna/pull/432): Added SetLocalTime definition to 'com.sun.jna.platform.win32.Kernel32' - [@lgoldstein](https://github.com/lgoldstein).
* [#434](https://github.com/twall/jna/pull/434): Added GetEnvironmentStrings to 'com.sun.jna.platform.win32.Kernel32' - [@lgoldstein](https://github.com/lgoldstein).

Bug Fixes
---------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1758,13 +1758,14 @@ public EnumKey(HKEY hKey, int dwIndex) {
* @return A environment block
*/
public static String getEnvironmentBlock(Map<String, String> environment) {
StringBuilder out = new StringBuilder();
StringBuilder out = new StringBuilder(environment.size() * 32 /* some guess about average name=value length*/);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally I think this is premature optimization, since I cannot think of a reason why to call this in a tight loop.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In hindsight I am inclined to agree (although not 100%...) - however, if it is not a critical issue for you, let's accept this for now and I will take notice in the future.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not critical and I will take it, but I think you should really consider amending it.

for (Entry<String, String> entry : environment.entrySet()) {
if (entry.getValue() != null) {
out.append(entry.getKey() + "=" + entry.getValue() + "\0");
String key=entry.getKey(), value=entry.getValue();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Space/tab? Make sure there're spaces around = please, so String key = entry.getKey(), value = entry.getValue();

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has been the style of changes I submitted in the past and no one remarked about it. I have taken notice and will make an effort to have future submissions adhere to this style. As far as this submission goes, I would ask that you accept this change as-is, since back-formatting all the code I wrote with the spaces around the assignment operator would be tedious (I know Eclipse has a formatting option but I am afraid that other formatting issues will appear that may not be the way you like it - unless you can send me the exact configuration file for the Eclipse style).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I overlooked this before, it's not that important, but it looks sloppy to future developers - I'll take it, but I would appreciate if you could look into auto-formatting this and others (I don't have the exact Eclipse configuration, sorry), and maybe run something automatic for a future commit. We would like to make JNA look like it was written by 1 person.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duly noted (though I disagree with looks sloppy...) - I do agree with make JNA look like it was written by 1 person and will adhere to this code style in the future.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😄

if (value != null) {
out.append(key).append("=").append(value).append('\0');
}
}
return out.toString() + "\0";
return out.append('\0').toString();
}

/**
Expand Down
28 changes: 28 additions & 0 deletions contrib/platform/src/com/sun/jna/platform/win32/Kernel32.java
Original file line number Diff line number Diff line change
Expand Up @@ -2210,6 +2210,34 @@ boolean GetDiskFreeSpaceEx(String lpDirectoryName,
*/
int GetEnvironmentVariable(String lpName, char[] lpBuffer, int nSize);

/**
* <P>Retrieves the environment variables for the current process. The
* block of variables format is as follows:</P></BR>
* <code>
* Var1=Value1\0
* Var2=Value2\0
* Var3=Value3\0
* ...
* VarN=ValueN\0\0
* </code>
* @return If the function succeeds, the return value is a {@link Pointer}.
* to the environment block of the current process. If fails, then
* {@code null} is returned. When the data is no longer needed the memory
* block must be released using {@link #FreeEnvironmentStrings(Pointer)}
* @see <A HREF="https://msdn.microsoft.com/en-us/library/windows/desktop/ms683187(v=vs.85).aspx">GetEnvironmentStrings</A> documentation
*/
Pointer GetEnvironmentStrings();

/**
* @param lpszEnvironmentBlock A pointer to a block of environment strings
* obtained by calling the {@link #GetEnvironmentStrings()} function
* @return {@code true} if successful, {@code false} otherwise.
* To get extended error information, call {@link #GetLastError()}.
* @see <A HREF="https://msdn.microsoft.com/en-us/library/windows/desktop/ms683151(v=vs.85).aspx">FreeEnvironmentStrings</A>
* documentation
*/
boolean FreeEnvironmentStrings(Pointer lpszEnvironmentBlock);

/**
* Returns the locale identifier for the system locale.
*
Expand Down
174 changes: 174 additions & 0 deletions contrib/platform/src/com/sun/jna/platform/win32/Kernel32Util.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,16 @@

import java.io.File;
import java.io.FileNotFoundException;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import com.sun.jna.LastErrorException;
import com.sun.jna.Memory;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.platform.win32.WinNT.HANDLE;
import com.sun.jna.platform.win32.WinNT.HANDLEByReference;
import com.sun.jna.platform.win32.WinNT.HRESULT;
Expand Down Expand Up @@ -252,6 +256,176 @@ public static String getEnvironmentVariable(String name) {
return Native.toString(buffer);
}

/**
* Uses the {@link Kernel32#GetEnvironmentStrings()} to retrieve and
* parse the current process environment
* @return The current process environment as a {@link Map}.
* @throws LastErrorException if failed to get or free the environment
* data block
* @see #getEnvironmentVariables(Pointer, long)
*/
public static Map<String,String> getEnvironmentVariables() {
Pointer lpszEnvironmentBlock=Kernel32.INSTANCE.GetEnvironmentStrings();
if (lpszEnvironmentBlock == null) {
throw new LastErrorException(Kernel32.INSTANCE.GetLastError());
}

try {
return getEnvironmentVariables(lpszEnvironmentBlock, 0L);
} finally {
if (!Kernel32.INSTANCE.FreeEnvironmentStrings(lpszEnvironmentBlock)) {
throw new LastErrorException(Kernel32.INSTANCE.GetLastError());
}
}
}

/**
* @param lpszEnvironmentBlock The environment block as received from the
* <A HREF="https://msdn.microsoft.com/en-us/library/windows/desktop/ms683187(v=vs.85).aspx">GetEnvironmentStrings</A>
* function
* @param offset Offset within the block to parse the data
* @return A {@link Map} of the parsed <code>name=value</code> pairs.
* <B>Note:</B> if the environment block is {@code null} then {@code null}
* is returned instead of an empty map since we want to distinguish
* between the case that the data block is {@code null} and when there are
* no environment variables (as unlikely as it may be)
*/
public static Map<String,String> getEnvironmentVariables(Pointer lpszEnvironmentBlock, long offset) {
if (lpszEnvironmentBlock == null) {
return null;
}

Map<String,String> vars=new TreeMap<String,String>();
boolean asWideChars=isWideCharEnvironmentStringBlock(lpszEnvironmentBlock, offset);
long stepFactor=asWideChars ? 2L : 1L;
for (long curOffset=offset; ; ) {
String nvp=readEnvironmentStringBlockEntry(lpszEnvironmentBlock, curOffset, asWideChars);
int len=nvp.length();
if (len == 0) { // found the ending '\0'
break;
}

int pos=nvp.indexOf('=');
if (pos < 0) {
throw new IllegalArgumentException("Missing variable value separator in " + nvp);
}

String name=nvp.substring(0, pos), value=nvp.substring(pos + 1);
vars.put(name, value);

curOffset += (len + 1 /* skip the ending '\0' */) * stepFactor;
}

return vars;
}

/**
* @param lpszEnvironmentBlock The environment block as received from the
* <A HREF="https://msdn.microsoft.com/en-us/library/windows/desktop/ms683187(v=vs.85).aspx">GetEnvironmentStrings</A>
* function
* @param offset Offset within the block to look for the entry
* @param asWideChars If {@code true} then the block contains {@code wchar_t}
* instead of &quot;plain old&quot; {@code char}s
* @return A {@link String} containing the <code>name=value</code> pair or
* empty if reached end of block
* @see #isWideCharEnvironmentStringBlock(Pointer)
* @see #findEnvironmentStringBlockEntryEnd(Pointer, long, boolean)
*/
public static String readEnvironmentStringBlockEntry(Pointer lpszEnvironmentBlock, long offset, boolean asWideChars) {
long endOffset=findEnvironmentStringBlockEntryEnd(lpszEnvironmentBlock, offset, asWideChars);
int dataLen=(int) (endOffset - offset);
if (dataLen == 0) {
return "";
}

int charsLen=asWideChars ? (dataLen / 2) : dataLen;
char[] chars=new char[charsLen];
long curOffset=offset, stepSize=asWideChars ? 2L : 1L;
ByteOrder byteOrder=ByteOrder.nativeOrder();
for (int index=0; index < chars.length; index++, curOffset += stepSize) {
byte b=lpszEnvironmentBlock.getByte(curOffset);
if (asWideChars) {
byte x=lpszEnvironmentBlock.getByte(curOffset + 1L);
if (ByteOrder.LITTLE_ENDIAN.equals(byteOrder)) {
chars[index] = (char) (((x << Byte.SIZE) & 0xFF00) | (b & 0x00FF));
} else { // unlikely, but handle it
chars[index] = (char) (((b << Byte.SIZE) & 0xFF00) | (x & 0x00FF));
}
} else {
chars[index] = (char) (b & 0x00FF);
}
}

return new String(chars);
}

/**
* @param lpszEnvironmentBlock The environment block as received from the
* <A HREF="https://msdn.microsoft.com/en-us/library/windows/desktop/ms683187(v=vs.85).aspx">GetEnvironmentStrings</A>
* function
* @param offset Offset within the block to look for the entry
* @param asWideChars If {@code true} then the block contains {@code wchar_t}
* instead of &quot;plain old&quot; {@code char}s
* @return The offset of the <U>first</U> {@code '\0'} in the data block
* starting at the specified offset - can be the start offset itself if empty
* string.
* @see #isWideCharEnvironmentStringBlock(Pointer)
*/
public static long findEnvironmentStringBlockEntryEnd(Pointer lpszEnvironmentBlock, long offset, boolean asWideChars) {
for (long curOffset=offset, stepSize=asWideChars ? 2L : 1L; ; curOffset += stepSize) {
byte b=lpszEnvironmentBlock.getByte(curOffset);
if (b == 0) {
return curOffset;
}
}
}

/**
* <P>Attempts to determine whether the data block uses {@code wchar_t}
* instead of &quot;plain old&quot; {@code char}s. It does that by reading
* 2 bytes from the specified offset - the character value and its charset
* indicator - and examining them as follows:</P></BR>
* <UL>
* <LI>
* If the charset indicator is non-zero then it is assumed to be
* a &quot;plain old&quot; {@code char}s data block. <B>Note:</B>
* the assumption is that the environment variable <U>name</U> (at
* least) is ASCII.
* </LI>
*
* <LI>
* Otherwise (i.e., zero charset indicator), it is assumed to be
* a {@code wchar_t}
* </LI>
* </UL>
* <B>Note:</B> the code takes into account the {@link ByteOrder} even though
* only {@link ByteOrder#LITTLE_ENDIAN} is the likely one
* @param lpszEnvironmentBlock The environment block as received from the
* <A HREF="https://msdn.microsoft.com/en-us/library/windows/desktop/ms683187(v=vs.85).aspx">GetEnvironmentStrings</A>
* function
* @return {@code true} if the block contains {@code wchar_t} instead of
* &quot;plain old&quot; {@code char}s
*/
public static boolean isWideCharEnvironmentStringBlock(Pointer lpszEnvironmentBlock, long offset) {
byte b0=lpszEnvironmentBlock.getByte(offset);
byte b1=lpszEnvironmentBlock.getByte(offset + 1L);
ByteOrder byteOrder=ByteOrder.nativeOrder();
if (ByteOrder.LITTLE_ENDIAN.equals(byteOrder)) {
return isWideCharEnvironmentStringBlock(b1);
} else {
return isWideCharEnvironmentStringBlock(b0);
}
}

private static boolean isWideCharEnvironmentStringBlock(byte charsetIndicator) {
// assume wchar_t for environment variables represents ASCII letters
if (charsetIndicator != 0) {
return false;
} else {
return true;
}
}

/**
* Retrieves an integer associated with a key in the specified section of an
* initialization file.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
/**
* @author lgoldstein
*/
public class WinconTest extends AbstractWin32TestSupport {
public class Kernel32ConsoleTest extends AbstractWin32TestSupport {
private static final Wincon INSTANCE=Kernel32.INSTANCE;

@Test
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/* Copyright (c) 2007 Timothy Wall, All Rights Reserved
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*/
package com.sun.jna.platform.win32;

import java.util.Map;
import java.util.Random;

import org.junit.Test;

import com.sun.jna.Native;

/**
* @author lgoldstein
*/
public class Kernel32EnvironmentVarsTest extends AbstractWin32TestSupport {
public Kernel32EnvironmentVarsTest() {
super();
}

@Test
public void testKernelUtilGetEnvironmentStrings() {
Map<String,String> vars=Kernel32Util.getEnvironmentVariables();
for (Map.Entry<String,String> entry : vars.entrySet()) {
String name=entry.getKey(), expected=entry.getValue();
char[] data=new char[expected.length() + 1];
int size=Kernel32.INSTANCE.GetEnvironmentVariable(name, data, data.length);
assertEquals("Mismatched retrieved length for " + name, data.length - 1 /* w/o the '\0' */, size);

String actual=Native.toString(data);
assertEquals("Mismatched retrieved value for " + name, expected, actual);
}
}

@Test
public void testKernelSetAndGetEnvironmentVariable() {
String name=getCurrentTestName(), expected="42";
assertCallSucceeded("SetEnvironmentVariable", Kernel32.INSTANCE.SetEnvironmentVariable(name, expected));

try {
int size = Kernel32.INSTANCE.GetEnvironmentVariable(name, null, 0);
assertEquals("Mismatched required buffer size", expected.length() + 1, size);

char[] data = new char[size];
assertEquals("Mismatched retrieved variable data length", size - 1, Kernel32.INSTANCE.GetEnvironmentVariable(name, data, size));

String actual=Native.toString(data);
assertEquals("Mismatched retrieved variable value", expected, actual);
} finally {
assertCallSucceeded("Clean up variable", Kernel32.INSTANCE.SetEnvironmentVariable(name, null));
}
}

@Test
public void testKernelUtilGetEnvironmentVariable() {
String name=getCurrentTestName(), expected=Integer.toString(new Random().nextInt());
assertCallSucceeded("SetEnvironmentVariable", Kernel32.INSTANCE.SetEnvironmentVariable(name, expected));
try {
assertEquals("Mismatched retrieved value", expected, Kernel32Util.getEnvironmentVariable(name));
} finally {
assertCallSucceeded("Clean up variable", Kernel32.INSTANCE.SetEnvironmentVariable(name, null));
}
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would have split this into Kernel32EnvironmentTest and Kernel32UtilEnvironmentTest, but I'll accept it as is.

Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@
/**
* @author lgoldstein
*/
public class NamedPipeTest extends AbstractWin32TestSupport {
public NamedPipeTest() {
public class Kernel32NamedPipeTest extends AbstractWin32TestSupport {
public Kernel32NamedPipeTest() {
super();
}

Expand Down
15 changes: 0 additions & 15 deletions contrib/platform/test/com/sun/jna/platform/win32/Kernel32Test.java
Original file line number Diff line number Diff line change
Expand Up @@ -539,21 +539,6 @@ public void testCreateProcess() {
assertTrue(processInformation.dwProcessId.longValue() > 0);
}

public void testGetEnvironmentVariable() {
assertTrue(Kernel32.INSTANCE.SetEnvironmentVariable("jna-getenvironment-test", "42"));
int size = Kernel32.INSTANCE.GetEnvironmentVariable("jna-getenvironment-test", null, 0);
assertTrue(size == 3);
char[] data = new char[size];
assertEquals(size - 1, Kernel32.INSTANCE.GetEnvironmentVariable("jna-getenvironment-test", data, size));
assertEquals(size - 1, Native.toString(data).length());
}

public void testSetEnvironmentVariable() {
int value = new Random().nextInt();
Kernel32.INSTANCE.SetEnvironmentVariable("jna-setenvironment-test", Integer.toString(value));
assertEquals(Integer.toString(value), Kernel32Util.getEnvironmentVariable("jna-setenvironment-test"));
}

public void testGetSetFileTime() throws IOException {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are still functions in Kernel32, so they need tests by themselves, why did you remove them?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I see you moved them into EnvironmentVarsTest.java, that makes it very hard to find, please stick to the convention of Kernel32.java -> Kernel32Test.java and Kernel32Util.java -> Kernel32UtilTest.java (and I would be ok with Kernel32Util_EnvironmentVarsTest.java).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kernel32 is very big - to have a single class test all of the API seems too much - that is why in my past submissions (as in this one) I try to group tests related to a specific topic into a separate class (see NamedPipeTest) - as its the case for this submission - see EnvironmentVarsTest.. Regarding your remark, please note that I did not remove it (I would never remove a test unless some other test covers it) - I moved it to the EnvironmentVarsTest which groups the test for "excercising" the environment variables API(s)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have to insist, don't take it personally :)

Being very big hasn't been a problem. Being impossible to find or know what's tested and what is not has been a problem, we used to have "theme-based" tests and it created a huge mess where people were reimplementing tests twice that did the same thing in two different places.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this mean that you want NamedPipeTest and WinconTest to be moved there as well ?
(and I don't take it personally... :-))

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do, although the PR will now contain more than what its name implies, so don't hold it against me... :-)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you commit to PRing ^^^ I will merge this as is.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’m ok with breaking it up by functional group, e.g. Kernel32XXXTest.java (Kernel32EnvironmentVarsTest.java in this case). Just keep the prefix so it’s easy to see what belongs together.

On Apr 29, 2015, at 6:59 AM, Lyor Goldstein notifications@github.com wrote:

In contrib/platform/test/com/sun/jna/platform/win32/Kernel32Test.java:

@@ -539,21 +539,6 @@ public void testCreateProcess() {
assertTrue(processInformation.dwProcessId.longValue() > 0);
}

  • public void testGetEnvironmentVariable() {

Kernel32 is very big - to have a single class test all of the API seems too much - that is why in my past submissions (as in this one) I try to group tests related to a specific topic into a separate class (see NamedPipeTest) - as its the case for this submission - see EnvironmentVarsTest.. Regarding your remark, please note that I did not remove it (I would never remove a test unless some other test covers it) - I moved it to the EnvironmentVarsTest which groups the test for "excercising" the environment variables API(s)


Reply to this email directly or view it on GitHub.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On "don't hold it against me" I can recommend an excellent book.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will let you know in a few minutes when the change is made and pushed

File tmp = File.createTempFile("testGetSetFileTime", "jna");
tmp.deleteOnExit();
Expand Down