diff --git a/CHANGES.md b/CHANGES.md
index 979ef79270..2881a2ae85 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,6 +1,10 @@
Next Release (4.0.1)
====================
+Features
+--------
+* More win32 initialization file APIs - [@quipsy](https://github.com/quipsy).
+
Release 4.0
===========
diff --git a/contrib/platform/src/com/sun/jna/platform/win32/Kernel32.java b/contrib/platform/src/com/sun/jna/platform/win32/Kernel32.java
index aa3bf9abb4..7cbf58ceb6 100644
--- a/contrib/platform/src/com/sun/jna/platform/win32/Kernel32.java
+++ b/contrib/platform/src/com/sun/jna/platform/win32/Kernel32.java
@@ -1940,4 +1940,65 @@ boolean Process32Next(HANDLE hSnapshot,
*
*/
boolean WritePrivateProfileString(String lpAppName, String lpKeyName, String lpString, String lpFileName);
+
+ /**
+ * Retrieves all the keys and values for the specified section of an initialization file.
+ *
+ *
+ * Each string has the following format: {@code key=string}.
+ *
+ *
+ * This operation is atomic; no updates to the specified initialization file are allowed while the key name and value pairs for the section are being copied
+ * to the buffer pointed to by the {@code lpReturnedString} parameter.
+ *
+ *
+ * @param lpAppName
+ * The name of the section in the initialization file.
+ * @param lpReturnedString
+ * A buffer that receives the key name and value pairs associated with the named section. The buffer is filled with one or more {@code null}
+ * -terminated strings; the last string is followed by a second {@code null} character.
+ * @param nSize
+ * The size of the buffer pointed to by the {@code lpReturnedString} parameter, in characters. The maximum profile section size is 32,767
+ * characters.
+ * @param lpFileName
+ * The name of the initialization file. If this parameter does not contain a full path to the file, the system searches for the file in the
+ * Windows directory.
+ * @return The number of characters copied to the buffer, not including the terminating null character. If the buffer is not large enough to contain all the
+ * key name and value pairs associated with the named section, the return value is equal to {@code nSize} minus two.
+ */
+ DWORD GetPrivateProfileSection(String lpAppName, char[] lpReturnedString, DWORD nSize, String lpFileName);
+
+ /**
+ * Retrieves the names of all sections in an initialization file.
+ *
+ * This operation is atomic; no updates to the initialization file are allowed while the section names are being copied to the buffer.
+ *
+ *
+ * @param lpszReturnBuffer
+ * A pointer to a buffer that receives the section names associated with the named file. The buffer is filled with one or more {@code null}
+ * -terminated strings; the last string is followed by a second {@code null} character.
+ * @param nSize
+ * size of the buffer pointed to by the {@code lpszReturnBuffer} parameter, in characters.
+ * @param lpFileName
+ * The name of the initialization file. If this parameter is {@code NULL}, the function searches the Win.ini file. If this parameter does not
+ * contain a full path to the file, the system searches for the file in the Windows directory.
+ * @return The return value specifies the number of characters copied to the specified buffer, not including the terminating {@code null} character. If the
+ * buffer is not large enough to contain all the section names associated with the specified initialization file, the return value is equal to the
+ * size specified by {@code nSize} minus two.
+ */
+ DWORD GetPrivateProfileSectionNames(char[] lpszReturnBuffer, DWORD nSize, String lpFileName);
+
+ /**
+ * @param lpAppName
+ * The name of the section in which data is written. This section name is typically the name of the calling application.
+ * @param lpString
+ * The new key names and associated values that are to be written to the named section. This string is limited to 65,535 bytes. Must be filled
+ * with zero or many {@code null}-terminated strings of the form {@code key=value}, appended by an additional {@code null} byte to terminate the
+ * list.
+ * @param lpFileName
+ * The name of the initialization file. If this parameter does not contain a full path for the file, the function searches the Windows directory
+ * for the file. If the file does not exist and lpFileName does not contain a full path, the function creates the file in the Windows directory.
+ * @return If the function succeeds, the return value is nonzero. If the function fails, the return value is zero.
+ */
+ boolean WritePrivateProfileSection(String lpAppName, String lpString, String lpFileName);
}
diff --git a/contrib/platform/src/com/sun/jna/platform/win32/Kernel32Util.java b/contrib/platform/src/com/sun/jna/platform/win32/Kernel32Util.java
index 5f66a9114d..07df8531ff 100644
--- a/contrib/platform/src/com/sun/jna/platform/win32/Kernel32Util.java
+++ b/contrib/platform/src/com/sun/jna/platform/win32/Kernel32Util.java
@@ -12,6 +12,8 @@
*/
package com.sun.jna.platform.win32;
+import static java.util.Arrays.asList;
+
import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
@@ -307,6 +309,66 @@ public static final void writePrivateProfileString(final String appName, final S
throw new Win32Exception(Kernel32.INSTANCE.GetLastError());
}
+ /**
+ * Retrieves all the keys and values for the specified section of an initialization file.
+ *
+ *
+ * Each string has the following format: {@code key=string}.
+ *
+ *
+ * This operation is atomic; no updates to the specified initialization file are allowed while this method is executed.
+ *
+ *
+ * @param appName
+ * The name of the section in the initialization file.
+ * @param fileName
+ * The name of the initialization file. If this parameter does not contain a full path to the file, the system searches for the file in the
+ * Windows directory.
+ * @return The key name and value pairs associated with the named section.
+ */
+ public static final List getPrivateProfileSection(final String appName, final String fileName) {
+ final char buffer[] = new char[32768]; // Maximum section size according to MSDN (http://msdn.microsoft.com/en-us/library/windows/desktop/ms724348(v=vs.85).aspx)
+ if (Kernel32.INSTANCE.GetPrivateProfileSection(appName, buffer, new DWORD(buffer.length), fileName).intValue() == 0)
+ throw new Win32Exception(Kernel32.INSTANCE.GetLastError());
+ return asList(Native.toStrings(buffer));
+ }
+
+ /**
+ * Retrieves the names of all sections in an initialization file.
+ *
+ * This operation is atomic; no updates to the initialization file are allowed while this method is executed.
+ *
+ *
+ * @param fileName
+ * The name of the initialization file. If this parameter is {@code NULL}, the function searches the Win.ini file. If this parameter does not
+ * contain a full path to the file, the system searches for the file in the Windows directory.
+ * @return the section names associated with the named file.
+ */
+ public static final List getPrivateProfileSectionNames(final String fileName) {
+ final char buffer[] = new char[65536]; // Maximum INI file size according to MSDN (http://support.microsoft.com/kb/78346)
+ if (Kernel32.INSTANCE.GetPrivateProfileSectionNames(buffer, new DWORD(buffer.length), fileName).intValue() == 0)
+ throw new Win32Exception(Kernel32.INSTANCE.GetLastError());
+ return asList(Native.toStrings(buffer));
+ }
+
+ /**
+ * @param appName
+ * The name of the section in which data is written. This section name is typically the name of the calling application.
+ * @param strings
+ * The new key names and associated values that are to be written to the named section. Each entry must be of the form {@code key=value}.
+ * @param fileName
+ * The name of the initialization file. If this parameter does not contain a full path for the file, the function searches the Windows directory
+ * for the file. If the file does not exist and lpFileName does not contain a full path, the function creates the file in the Windows directory.
+ */
+ public static final void writePrivateProfileSection(final String appName, final List strings, final String fileName) {
+ final StringBuilder buffer = new StringBuilder();
+ for (final String string : strings)
+ buffer.append(string).append('\0');
+ buffer.append('\0');
+ if (!(Kernel32.INSTANCE.WritePrivateProfileSection(appName, buffer.toString(), fileName)))
+ throw new Win32Exception(Kernel32.INSTANCE.GetLastError());
+ }
+
/**
* Convenience method to get the processor information. Takes care of auto-growing the array.
*
diff --git a/contrib/platform/test/com/sun/jna/platform/win32/Kernel32Test.java b/contrib/platform/test/com/sun/jna/platform/win32/Kernel32Test.java
index 2b17055d66..552a0827de 100644
--- a/contrib/platform/test/com/sun/jna/platform/win32/Kernel32Test.java
+++ b/contrib/platform/test/com/sun/jna/platform/win32/Kernel32Test.java
@@ -12,6 +12,13 @@
*/
package com.sun.jna.platform.win32;
+import static com.sun.jna.platform.win32.RegexMatcher.matches;
+import static org.hamcrest.CoreMatchers.anyOf;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.hasItems;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
@@ -22,12 +29,15 @@
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
+import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import java.util.TimeZone;
import junit.framework.TestCase;
+import org.junit.Test;
+
import com.sun.jna.Memory;
import com.sun.jna.Native;
import com.sun.jna.NativeMappedConverter;
@@ -606,4 +616,96 @@ public final void testWritePrivateProfileString() throws IOException {
tmp.delete();
}
}
+
+ @Test
+ public final void testGetPrivateProfileSection() throws IOException {
+ // given
+ final File tmp = File.createTempFile(getName(), "ini");
+ tmp.deleteOnExit();
+ try {
+ final PrintWriter writer = new PrintWriter(new BufferedWriter(new FileWriter(tmp)));
+ try {
+ writer.println("[X]");
+ writer.println("A=1");
+ writer.println("B=X");
+ } finally {
+ writer.close();
+ }
+
+ // when
+ final char[] buffer = new char[9];
+ final DWORD len = Kernel32.INSTANCE.GetPrivateProfileSection("X", buffer, new DWORD(buffer.length), tmp.getCanonicalPath());
+
+ // then
+ assertThat("Wrong length", len, is(new DWORD(7)));
+ assertThat("Wrong content", buffer, is(anyOf(equalTo("A=1\0B=X\0\0".toCharArray()), equalTo("B=X\0A=1\0\0".toCharArray()))));
+ } finally {
+ tmp.delete();
+ }
+ }
+
+ @Test
+ public final void testGetPrivateProfileSectionNames() throws IOException {
+ // given
+ final File tmp = File.createTempFile(getName(), "ini");
+ tmp.deleteOnExit();
+ try {
+ final PrintWriter writer = new PrintWriter(new BufferedWriter(new FileWriter(tmp)));
+ try {
+ writer.println("[S1]");
+ writer.println("[S2]");
+ } finally {
+ writer.close();
+ }
+
+ // when
+ final char[] buffer = new char[7];
+ final DWORD len = Kernel32.INSTANCE.GetPrivateProfileSectionNames(buffer, new DWORD(buffer.length), tmp.getCanonicalPath());
+
+ // then
+ assertThat("Wrong length", len, is(new DWORD(5)));
+ assertThat("Wrong content", buffer, is(anyOf(equalTo("S1\0S2\0\0".toCharArray()), equalTo("S2\0S1\0\0".toCharArray()))));
+ } finally {
+ tmp.delete();
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ @Test
+ public final void testWritePrivateProfileSection() throws IOException {
+ // given
+ final File tmp = File.createTempFile(getName(), "ini");
+ tmp.deleteOnExit();
+ try {
+ final PrintWriter writer = new PrintWriter(new BufferedWriter(new FileWriter(tmp)));
+ try {
+ writer.println("[S1]");
+ writer.println("A=1");
+ writer.println("B=X");
+ } finally {
+ writer.close();
+ }
+
+ // when
+ final boolean result = Kernel32.INSTANCE.WritePrivateProfileSection("S1", "A=3\0E=Z\0\0", tmp.getCanonicalPath());
+
+ // then
+ assertThat("Wrong result", result, is(true));
+ assertThat(readAllLines(tmp), hasItems(is("[S1]"), matches("A\\s*=\\s*3"), matches("E\\s*=\\s*Z")));
+ } finally {
+ tmp.delete();
+ }
+ }
+
+ private static final List readAllLines(final File file) throws IOException {
+ final List lines = new LinkedList();
+ final BufferedReader reader = new BufferedReader(new FileReader(file));
+ try {
+ for (String line = reader.readLine(); line != null; line = reader.readLine())
+ lines.add(line);
+ } finally {
+ reader.close();
+ }
+ return lines;
+ }
}
diff --git a/contrib/platform/test/com/sun/jna/platform/win32/Kernel32UtilTest.java b/contrib/platform/test/com/sun/jna/platform/win32/Kernel32UtilTest.java
index e57a161708..18f1762a26 100644
--- a/contrib/platform/test/com/sun/jna/platform/win32/Kernel32UtilTest.java
+++ b/contrib/platform/test/com/sun/jna/platform/win32/Kernel32UtilTest.java
@@ -12,6 +12,13 @@
*/
package com.sun.jna.platform.win32;
+import static com.sun.jna.platform.win32.RegexMatcher.matches;
+import static java.util.Arrays.asList;
+import static org.hamcrest.CoreMatchers.hasItems;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.junit.Assert.assertThat;
+
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
@@ -19,9 +26,12 @@
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
+import java.util.List;
import junit.framework.TestCase;
+import org.junit.Test;
+
import com.sun.jna.platform.win32.WinNT.LARGE_INTEGER;
import com.sun.jna.platform.win32.WinNT.LOGICAL_PROCESSOR_RELATIONSHIP;
@@ -182,6 +192,98 @@ public final void testWritePrivateProfileString() throws IOException {
reader.close();
}
+ @Test
+ public final void testGetPrivateProfileSection() throws IOException {
+ // given
+ final File tmp = File.createTempFile("testGetPrivateProfileSection"(), "ini");
+ tmp.deleteOnExit();
+ try {
+ final PrintWriter writer = new PrintWriter(new BufferedWriter(new FileWriter(tmp)));
+ try {
+ writer.println("[X]");
+ writer.println("A=1");
+ writer.println("B=X");
+ } finally {
+ writer.close();
+ }
+
+ // when
+ final List section = Kernel32Util.getPrivateProfileSection("X", tmp.getCanonicalPath());
+
+ // then
+ assertThat(section, hasItems("A=1", "B=X"));
+ } finally {
+ tmp.delete();
+ }
+ }
+
+ @Test
+ public final void testGetPrivateProfileSectionNames() throws IOException {
+ // given
+ final File tmp = File.createTempFile("testGetPrivateProfileSectionNames", "ini");
+ tmp.deleteOnExit();
+ try {
+ final PrintWriter writer = new PrintWriter(new BufferedWriter(new FileWriter(tmp)));
+ try {
+ writer.println("[S1]");
+ writer.println("A=1");
+ writer.println("B=X");
+ writer.println("[S2]");
+ writer.println("C=2");
+ writer.println("D=Y");
+ } finally {
+ writer.close();
+ }
+
+ // when
+ final List section = Kernel32Util.getPrivateProfileSectionNames(tmp.getCanonicalPath());
+
+ // then
+ assertThat(section, hasItems("S1", "S2"));
+ } finally {
+ tmp.delete();
+ }
+ }
+
+ @Test
+ public final void testWritePrivateProfileSection() throws IOException {
+ // given
+ final File tmp = File.createTempFile("testWritePrivateProfileSecion", "ini");
+ tmp.deleteOnExit();
+ try {
+ final PrintWriter writer = new PrintWriter(new BufferedWriter(new FileWriter(tmp)));
+ try {
+ writer.println("[S1]");
+ writer.println("A=1");
+ writer.println("B=X");
+ writer.println("[S2]");
+ writer.println("C=2");
+ writer.println("D=Y");
+ } finally {
+ writer.close();
+ }
+
+ // when
+ Kernel32Util.writePrivateProfileSection("S1", asList("A=3", "E=Z"), tmp.getCanonicalPath());
+
+ // then
+ final BufferedReader reader = new BufferedReader(new FileReader(tmp));
+ try {
+ assertThat(reader.readLine(), is("[S1]"));
+ assertThat(reader.readLine(), matches("A\\s*=\\s*3"));
+ assertThat(reader.readLine(), matches("E\\s*=\\s*Z"));
+ assertThat(reader.readLine(), is("[S2]"));
+ assertThat(reader.readLine(), matches("C\\s*=\\s*2"));
+ assertThat(reader.readLine(), matches("D\\s*=\\s*Y"));
+ assertThat(reader.readLine(), is(nullValue()));
+ } finally {
+ reader.close();
+ }
+ } finally {
+ tmp.delete();
+ }
+ }
+
public final void testGetLogicalProcessorInformation() {
WinNT.SYSTEM_LOGICAL_PROCESSOR_INFORMATION[] informationArray = Kernel32Util.getLogicalProcessorInformation();
assertTrue(informationArray.length >= 1); // docs say so
diff --git a/contrib/platform/test/com/sun/jna/platform/win32/RegexMatcher.java b/contrib/platform/test/com/sun/jna/platform/win32/RegexMatcher.java
new file mode 100644
index 0000000000..ad88413c82
--- /dev/null
+++ b/contrib/platform/test/com/sun/jna/platform/win32/RegexMatcher.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2013 Markus Karg, 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 org.hamcrest.Description;
+import org.hamcrest.TypeSafeMatcher;
+
+/**
+ * This Hamcrest matcher asserts that a {@link String} matches a provided regex pattern.
+ *
+ * @author Markus KARG (markus[at]headcrashing[dot]eu)
+ */
+final class RegexMatcher extends TypeSafeMatcher {
+ private final String regex;
+
+ public RegexMatcher(final String regex) {
+ this.regex = regex;
+ }
+
+ @Override
+ public final boolean matchesSafely(final String string) {
+ return string.matches(this.regex);
+ }
+
+ @Override
+ public final void describeTo(final Description description) {
+ description.appendText("matches regex ").appendValue(this.regex);
+ }
+
+ public final static RegexMatcher matches(final String regex) {
+ return new RegexMatcher(regex);
+ }
+}
diff --git a/lib/hamcrest-core-1.3.jar b/lib/hamcrest-core-1.3.jar
new file mode 100644
index 0000000000..9d5fe16e3d
Binary files /dev/null and b/lib/hamcrest-core-1.3.jar differ
diff --git a/lib/junit.jar b/lib/junit.jar
index 674d71e89e..aaf7444849 100755
Binary files a/lib/junit.jar and b/lib/junit.jar differ
diff --git a/src/com/sun/jna/Native.java b/src/com/sun/jna/Native.java
index 7163c84c7a..2bb0acfa96 100644
--- a/src/com/sun/jna/Native.java
+++ b/src/com/sun/jna/Native.java
@@ -344,6 +344,17 @@ public static String toString(char[] buf) {
return s;
}
+ /**
+ * Converts a null-terminated sequence of null-terminated strings into an array of Java {@link String}s.
+ *
+ * @param buffer
+ * Contains a null-terminated sequence of null-terminated strings.
+ * @return {@code buffer}'s content converted to Java {@link String}s.
+ */
+ public static final String[] toStrings(final char[] buffer) {
+ return new String(buffer).split("\0");
+ }
+
/** Map a library interface to the current process, providing
* the explicit interface class.
* Native libraries loaded via this method may be found in
diff --git a/test/com/sun/jna/NativeTest.java b/test/com/sun/jna/NativeTest.java
index a946ca8d90..59cf136c6b 100644
--- a/test/com/sun/jna/NativeTest.java
+++ b/test/com/sun/jna/NativeTest.java
@@ -12,6 +12,9 @@
*/
package com.sun.jna;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
import java.io.File;
import java.util.Arrays;
import java.util.Collections;
@@ -275,6 +278,18 @@ public void testByteArrayToString() {
byte[] buf = { 'a', 'b', 'c', '\0', 'd', 'e' };
assertEquals("Wrong String generated", "abc", Native.toString(buf));
}
+
+ @Test
+ public final void shouldConvertSequenceToStrings() {
+ // given
+ final char[] buffer = "ABC\0DEF\0GHI\0\0".toCharArray();
+
+ // when
+ final String[] strings = /* Native */N.toStrings(buffer);
+
+ // then
+ assertThat(strings, is(new String[] { "ABC", "DEF", "GHI" }));
+ }
public void testToByteArray() {
final String VALUE = getName();