diff --git a/CHANGES.md b/CHANGES.md index 6290eb7cbb..49b5bee58d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -12,6 +12,7 @@ Features * [#1168](https://github.com/java-native-access/jna/pull/1168): Add `c.s.j.p.win32.Kernel32#SetProcessAffinityMask` - [@dbwiddis](https://github.com/dbwiddis). * [#1169](https://github.com/java-native-access/jna/issues/1169): Wait for process in getLinuxLdPaths - [@rdesgroppes](https://github.com/rdesgroppes). * [#1178](https://github.com/java-native-access/jna/pull/1178): Add `c.s.j.p.win32.IPHlpAPI#GetTcpStatistics`, `c.s.j.p.win32.IPHlpAPI#GetUdpStatistics`, `c.s.j.p.win32.IPHlpAPI#GetTcpStatisticsEx` and `c.s.j.p.win32.IPHlpAPI#GetUdpStatisticsEx` - [@dbwiddis](https://github.com/dbwiddis). +* [#1182](https://github.com/java-native-access/jna/pull/1182): Add `toString` to classes extending `c.s.j.ptr.ByReference` - [@dbwiddis](https://github.com/dbwiddis). * [#1191](https://github.com/java-native-access/jna/pull/1191): Add `c.s.j.p.win32.Advapi32Util#getTokenPrimaryGroup` - [@dbwiddis](https://github.com/dbwiddis). Bug Fixes diff --git a/contrib/platform/test/com/sun/jna/platform/ByReferencePlatformToStringTest.java b/contrib/platform/test/com/sun/jna/platform/ByReferencePlatformToStringTest.java new file mode 100644 index 0000000000..5896e9c9ed --- /dev/null +++ b/contrib/platform/test/com/sun/jna/platform/ByReferencePlatformToStringTest.java @@ -0,0 +1,200 @@ +/* Copyright (c) 2020 Daniel Widdis, All Rights Reserved + * + * The contents of this file is dual-licensed under 2 + * alternative Open Source/Free licenses: LGPL 2.1 or later and + * Apache License 2.0. (starting with JNA version 4.0.0). + * + * You can freely decide which license you want to apply to + * the project. + * + * You may obtain a copy of the LGPL License at: + * + * http://www.gnu.org/licenses/licenses.html + * + * A copy is also included in the downloadable source code package + * containing JNA, in file "LGPL2.1". + * + * You may obtain a copy of the Apache License at: + * + * http://www.apache.org/licenses/ + * + * A copy is also included in the downloadable source code package + * containing JNA, in file "AL2.0". + */ +package com.sun.jna.platform; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +import com.sun.jna.Pointer; +import com.sun.jna.platform.unix.X11.AtomByReference; +import com.sun.jna.platform.unix.X11.WindowByReference; +import com.sun.jna.platform.win32.BaseTSD.ULONG_PTR; +import com.sun.jna.platform.win32.BaseTSD.ULONG_PTRByReference; +import com.sun.jna.platform.win32.HighLevelMonitorConfigurationAPI.MC_COLOR_TEMPERATURE; +import com.sun.jna.platform.win32.HighLevelMonitorConfigurationAPI.MC_DISPLAY_TECHNOLOGY_TYPE; +import com.sun.jna.platform.win32.LowLevelMonitorConfigurationAPI.MC_VCP_CODE_TYPE; +import com.sun.jna.platform.win32.OaIdl.DISPID; +import com.sun.jna.platform.win32.OaIdl.DISPIDByReference; +import com.sun.jna.platform.win32.OaIdl.MEMBERID; +import com.sun.jna.platform.win32.OaIdl.MEMBERIDByReference; +import com.sun.jna.platform.win32.OaIdl.VARIANT_BOOL; +import com.sun.jna.platform.win32.OaIdl.VARIANT_BOOLByReference; +import com.sun.jna.platform.win32.OaIdl._VARIANT_BOOLByReference; +import com.sun.jna.platform.win32.WTypes.BSTR; +import com.sun.jna.platform.win32.WTypes.BSTRByReference; +import com.sun.jna.platform.win32.WTypes.VARTYPE; +import com.sun.jna.platform.win32.WTypes.VARTYPEByReference; +import com.sun.jna.platform.win32.WinDef.BOOL; +import com.sun.jna.platform.win32.WinDef.BOOLByReference; +import com.sun.jna.platform.win32.WinDef.CHAR; +import com.sun.jna.platform.win32.WinDef.CHARByReference; +import com.sun.jna.platform.win32.WinDef.DWORD; +import com.sun.jna.platform.win32.WinDef.DWORDByReference; +import com.sun.jna.platform.win32.WinDef.LONG; +import com.sun.jna.platform.win32.WinDef.LONGByReference; +import com.sun.jna.platform.win32.WinDef.LONGLONG; +import com.sun.jna.platform.win32.WinDef.LONGLONGByReference; +import com.sun.jna.platform.win32.WinDef.SCODE; +import com.sun.jna.platform.win32.WinDef.SCODEByReference; +import com.sun.jna.platform.win32.WinDef.UINT; +import com.sun.jna.platform.win32.WinDef.UINTByReference; +import com.sun.jna.platform.win32.WinDef.ULONG; +import com.sun.jna.platform.win32.WinDef.ULONGByReference; +import com.sun.jna.platform.win32.WinDef.ULONGLONG; +import com.sun.jna.platform.win32.WinDef.ULONGLONGByReference; +import com.sun.jna.platform.win32.WinDef.USHORT; +import com.sun.jna.platform.win32.WinDef.USHORTByReference; +import com.sun.jna.platform.win32.WinDef.WORD; +import com.sun.jna.platform.win32.WinDef.WORDByReference; +import com.sun.jna.platform.win32.WinNT.ACL; +import com.sun.jna.platform.win32.WinNT.HANDLE; +import com.sun.jna.platform.win32.WinNT.HANDLEByReference; +import com.sun.jna.platform.win32.WinNT.PACLByReference; +import com.sun.jna.platform.win32.WinNT.PSID; +import com.sun.jna.platform.win32.WinNT.PSIDByReference; +import com.sun.jna.platform.win32.WinReg.HKEY; +import com.sun.jna.platform.win32.WinReg.HKEYByReference; + +public class ByReferencePlatformToStringTest { + @Test + public void testPlatformToStrings() { + BOOLByReference boolbr = new BOOLByReference(new BOOL(true)); + parseAndTest(boolbr.toString(), "BOOL", "true"); + + BSTRByReference bstrbr = new BSTRByReference(new BSTR("bstr")); + parseAndTest(bstrbr.toString(), "BSTR", "bstr"); + + CHARByReference cbr = new CHARByReference(new CHAR(42)); + parseAndTest(cbr.toString(), "CHAR", "42"); + + DISPIDByReference dispidbr = new DISPIDByReference(new DISPID(42)); + parseAndTest(dispidbr.toString(), "DISPID", "42"); + + DWORDByReference dwbr = new DWORDByReference(new DWORD(42)); + parseAndTest(dwbr.toString(), "DWORD", "42"); + + HANDLEByReference handlebr = new HANDLEByReference(new HANDLE(new Pointer(42))); + parseAndTest(handlebr.toString(), "HANDLE", "native"); + + HKEYByReference hkeybr = new HKEYByReference(new HKEY(42)); + parseAndTest(hkeybr.toString(), "HKEY", "native"); + + LONGByReference longbr = new LONGByReference(new LONG(42)); + parseAndTest(longbr.toString(), "LONG", "42"); + + LONGLONGByReference longlongbr = new LONGLONGByReference(new LONGLONG(42)); + parseAndTest(longlongbr.toString(), "LONGLONG", "42"); + + MC_COLOR_TEMPERATURE.ByReference mccbr = new MC_COLOR_TEMPERATURE.ByReference( + MC_COLOR_TEMPERATURE.MC_COLOR_TEMPERATURE_UNKNOWN); + parseAndTest(mccbr.toString(), "MC_COLOR_TEMPERATURE", "MC_COLOR_TEMPERATURE_UNKNOWN"); + + MC_DISPLAY_TECHNOLOGY_TYPE.ByReference mcdbr = new MC_DISPLAY_TECHNOLOGY_TYPE.ByReference( + MC_DISPLAY_TECHNOLOGY_TYPE.MC_SHADOW_MASK_CATHODE_RAY_TUBE); + parseAndTest(mcdbr.toString(), "MC_DISPLAY_TECHNOLOGY_TYPE", "MC_SHADOW_MASK_CATHODE_RAY_TUBE"); + + MC_VCP_CODE_TYPE.ByReference mcvbr = new MC_VCP_CODE_TYPE.ByReference(MC_VCP_CODE_TYPE.MC_MOMENTARY); + parseAndTest(mcvbr.toString(), "MC_VCP_CODE_TYPE", "MC_MOMENTARY"); + + MEMBERIDByReference memberidbr = new MEMBERIDByReference(new MEMBERID(42)); + parseAndTest(memberidbr.toString(), "MEMBERID", "42"); + + PACLByReference paclbr = new PACLByReference(new ACL()); + parseAndTest(paclbr.toString(), "ACL", "WinNT$ACL(native"); + + PSIDByReference psidbr = new PSIDByReference(new PSID()); + parseAndTest(psidbr.toString(), "PSID", "WinNT$PSID(native"); + + SCODEByReference scodebr = new SCODEByReference(new SCODE(42)); + parseAndTest(scodebr.toString(), "SCODE", "42"); + + UINTByReference uibr = new UINTByReference(new UINT(42)); + parseAndTest(uibr.toString(), "UINT", "42"); + + ULONG_PTRByReference ulpbr = new ULONG_PTRByReference(new ULONG_PTR(42)); + parseAndTest(ulpbr.toString(), "ULONG_PTR", "42"); + + ULONGByReference ulbr = new ULONGByReference(new ULONG(42)); + parseAndTest(ulbr.toString(), "ULONG", "42"); + + ULONGLONGByReference ullbr = new ULONGLONGByReference(new ULONGLONG(42)); + parseAndTest(ullbr.toString(), "ULONGLONG", "42"); + + USHORTByReference usbr = new USHORTByReference(new USHORT(42)); + parseAndTest(usbr.toString(), "USHORT", "42"); + + VARIANT_BOOLByReference vboolbr = new VARIANT_BOOLByReference(new VARIANT_BOOL(1)); + parseAndTest(vboolbr.toString(), "VARIANT_BOOL", "1"); + + _VARIANT_BOOLByReference vboolbr2 = new _VARIANT_BOOLByReference(new VARIANT_BOOL(1)); + parseAndTest(vboolbr2.toString(), "VARIANT_BOOL", "1"); + + VARTYPEByReference varbr = new VARTYPEByReference(new VARTYPE(42)); + parseAndTest(varbr.toString(), "VARTYPE", "42"); + + WORDByReference wbr = new WORDByReference(new WORD(42)); + parseAndTest(wbr.toString(), "WORD", "42"); + + // No way to set value on these without native code. Both methods read a random + // NativeLong and return null if 0 or a random hex string otherwise + AtomByReference abr = new AtomByReference(); + String atomStr = abr.toString(); + if (abr.getValue() == null) { + assertTrue(abr.toString().startsWith("null@0x")); + } else { + assertTrue(atomStr.startsWith("Atom@0x")); + assertTrue(atomStr.contains("=0x")); + } + + WindowByReference windowbr = new WindowByReference(); + String windowStr = windowbr.toString(); + if (windowbr.getValue() == null) { + assertTrue(windowStr.startsWith("null@0x")); + } else { + assertTrue(windowStr.startsWith("Window@0x")); + assertTrue(windowStr.contains("=0x")); + } + } + + /** + * Parses a string "foo@0x123=bar" testing equality of fixed parts of the string + * + * @param s + * The string to test + * @param beforeAt + * The string which should match the portion before the first + * {@code @} + * @param afterEquals + * The string which should match the portion after the {@code =} + * sign, before any additional {@code @} + */ + private void parseAndTest(String s, String beforeAt, String afterEquals) { + String[] atSplit = s.split("@"); + assertEquals("Incorrect type prefix", beforeAt, atSplit[0]); + String[] equalsSplit = atSplit[1].split("="); + assertEquals("Incorrect value string", afterEquals, equalsSplit[1]); + } +} diff --git a/src/com/sun/jna/ptr/ByReference.java b/src/com/sun/jna/ptr/ByReference.java index 178eb25587..3b62539135 100644 --- a/src/com/sun/jna/ptr/ByReference.java +++ b/src/com/sun/jna/ptr/ByReference.java @@ -23,22 +23,53 @@ */ package com.sun.jna.ptr; +import java.lang.reflect.Method; + import com.sun.jna.Memory; +import com.sun.jna.Pointer; import com.sun.jna.PointerType; -/** Provides generic "pointer to type" functionality, often used in C - * code to return values to the caller in addition to a function result. +/** + * Provides generic "pointer to type" functionality, often used in C code to + * return values to the caller in addition to a function result. + *

+ * Derived classes must define setValue(<T>) and + * <T> getValue() methods which write to/read from the + * allocated memory. + *

+ * This class derives from PointerType instead of Memory in order to restrict + * the API to only getValue/setValue. *

- * Derived classes should define setValue(<T>) - * and <T> getValue() methods which write to/read from - * memory. - *

This class derives from PointerType instead of Memory in order to - * restrict the API to only getValue/setValue. - *

NOTE: this class would ideally be replaced by a generic. + * NOTE: this class would ideally be replaced by a generic. */ public abstract class ByReference extends PointerType { + /** + * Allocates memory at this pointer, to contain the pointed-to value. + * + * @param dataSize + * The number of bytes to allocate. Must match the byte size of + * T in the derived class + * setValue(<T>) and + * <T> getValue() methods. + */ protected ByReference(int dataSize) { setPointer(new Memory(dataSize)); } + + @Override + public String toString() { + try { + Method getValue = getClass().getMethod("getValue"); + Object value = getValue.invoke(this); + if (value == null) { + return String.format("null@0x%x", Pointer.nativeValue(getPointer())); + } + return String.format("%s@0x%x=%s", value.getClass().getSimpleName(), Pointer.nativeValue(getPointer()), + value); + } catch (Exception ex) { + return String.format("ByReference Contract violated - %s#getValue raised exception: %s", + getClass().getName(), ex.getMessage()); + } + } } diff --git a/src/com/sun/jna/ptr/ByteByReference.java b/src/com/sun/jna/ptr/ByteByReference.java index 1da1abe418..29a8595dcd 100644 --- a/src/com/sun/jna/ptr/ByteByReference.java +++ b/src/com/sun/jna/ptr/ByteByReference.java @@ -23,6 +23,8 @@ */ package com.sun.jna.ptr; +import com.sun.jna.Pointer; + public class ByteByReference extends ByReference { public ByteByReference() { @@ -42,4 +44,8 @@ public byte getValue() { return getPointer().getByte(0); } + @Override + public String toString() { + return String.format("byte@0x%1$x=0x%2$x (%2$d)", Pointer.nativeValue(getPointer()), getValue()); + } } diff --git a/src/com/sun/jna/ptr/DoubleByReference.java b/src/com/sun/jna/ptr/DoubleByReference.java index 09e8ba621d..40d9f8ba16 100644 --- a/src/com/sun/jna/ptr/DoubleByReference.java +++ b/src/com/sun/jna/ptr/DoubleByReference.java @@ -23,6 +23,8 @@ */ package com.sun.jna.ptr; +import com.sun.jna.Pointer; + public class DoubleByReference extends ByReference { public DoubleByReference() { this(0d); @@ -41,4 +43,8 @@ public double getValue() { return getPointer().getDouble(0); } + @Override + public String toString() { + return String.format("double@0x%x=%s", Pointer.nativeValue(getPointer()), getValue()); + } } diff --git a/src/com/sun/jna/ptr/FloatByReference.java b/src/com/sun/jna/ptr/FloatByReference.java index e16f66190c..8bd1d4c974 100644 --- a/src/com/sun/jna/ptr/FloatByReference.java +++ b/src/com/sun/jna/ptr/FloatByReference.java @@ -23,6 +23,8 @@ */ package com.sun.jna.ptr; +import com.sun.jna.Pointer; + public class FloatByReference extends ByReference { public FloatByReference() { this(0f); @@ -41,4 +43,8 @@ public float getValue() { return getPointer().getFloat(0); } + @Override + public String toString() { + return String.format("float@0x%x=%s", Pointer.nativeValue(getPointer()), getValue()); + } } diff --git a/src/com/sun/jna/ptr/IntByReference.java b/src/com/sun/jna/ptr/IntByReference.java index 570368d2cf..1fc6d44b31 100644 --- a/src/com/sun/jna/ptr/IntByReference.java +++ b/src/com/sun/jna/ptr/IntByReference.java @@ -23,6 +23,8 @@ */ package com.sun.jna.ptr; +import com.sun.jna.Pointer; + public class IntByReference extends ByReference { public IntByReference() { @@ -41,4 +43,9 @@ public void setValue(int value) { public int getValue() { return getPointer().getInt(0); } + + @Override + public String toString() { + return String.format("int@0x%1$x=0x%2$x (%2$d)", Pointer.nativeValue(getPointer()), getValue()); + } } diff --git a/src/com/sun/jna/ptr/LongByReference.java b/src/com/sun/jna/ptr/LongByReference.java index f1bce0fcca..750fb8a212 100644 --- a/src/com/sun/jna/ptr/LongByReference.java +++ b/src/com/sun/jna/ptr/LongByReference.java @@ -23,6 +23,8 @@ */ package com.sun.jna.ptr; +import com.sun.jna.Pointer; + public class LongByReference extends ByReference { public LongByReference() { this(0L); @@ -40,4 +42,9 @@ public void setValue(long value) { public long getValue() { return getPointer().getLong(0); } + + @Override + public String toString() { + return String.format("long@0x%1$x=0x%2$x (%2$d)", Pointer.nativeValue(getPointer()), getValue()); + } } diff --git a/src/com/sun/jna/ptr/NativeLongByReference.java b/src/com/sun/jna/ptr/NativeLongByReference.java index 27f6d47ad8..151f338935 100644 --- a/src/com/sun/jna/ptr/NativeLongByReference.java +++ b/src/com/sun/jna/ptr/NativeLongByReference.java @@ -24,6 +24,7 @@ package com.sun.jna.ptr; import com.sun.jna.NativeLong; +import com.sun.jna.Pointer; public class NativeLongByReference extends ByReference { public NativeLongByReference() { @@ -42,4 +43,16 @@ public void setValue(NativeLong value) { public NativeLong getValue() { return getPointer().getNativeLong(0); } + + @Override + public String toString() { + // Can't mix types with ternary operator + if (NativeLong.SIZE > 4) { + return String.format("NativeLong@0x1$%x=0x%2$x (%2$d)", Pointer.nativeValue(getPointer()), + getValue().longValue()); + } else { + return String.format("NativeLong@0x1$%x=0x%2$x (%2$d)", Pointer.nativeValue(getPointer()), + getValue().intValue()); + } + } } diff --git a/src/com/sun/jna/ptr/ShortByReference.java b/src/com/sun/jna/ptr/ShortByReference.java index 52a9562cde..ded63cf0d9 100644 --- a/src/com/sun/jna/ptr/ShortByReference.java +++ b/src/com/sun/jna/ptr/ShortByReference.java @@ -23,6 +23,8 @@ */ package com.sun.jna.ptr; +import com.sun.jna.Pointer; + public class ShortByReference extends ByReference { public ShortByReference() { @@ -42,4 +44,8 @@ public short getValue() { return getPointer().getShort(0); } + @Override + public String toString() { + return String.format("short@0x%1$x=0x%2$x (%2$d)", Pointer.nativeValue(getPointer()), getValue()); + } } diff --git a/test/com/sun/jna/ByReferenceToStringTest.java b/test/com/sun/jna/ByReferenceToStringTest.java new file mode 100644 index 0000000000..bd0ac05321 --- /dev/null +++ b/test/com/sun/jna/ByReferenceToStringTest.java @@ -0,0 +1,100 @@ +/* Copyright (c) 2020 Daniel Widdis, All Rights Reserved + * + * The contents of this file is dual-licensed under 2 + * alternative Open Source/Free licenses: LGPL 2.1 or later and + * Apache License 2.0. (starting with JNA version 4.0.0). + * + * You can freely decide which license you want to apply to + * the project. + * + * You may obtain a copy of the LGPL License at: + * + * http://www.gnu.org/licenses/licenses.html + * + * A copy is also included in the downloadable source code package + * containing JNA, in file "LGPL2.1". + * + * You may obtain a copy of the Apache License at: + * + * http://www.apache.org/licenses/ + * + * A copy is also included in the downloadable source code package + * containing JNA, in file "AL2.0". + */ +package com.sun.jna; + +import com.sun.jna.ptr.ByteByReference; +import com.sun.jna.ptr.DoubleByReference; +import com.sun.jna.ptr.FloatByReference; +import com.sun.jna.ptr.IntByReference; +import com.sun.jna.ptr.LongByReference; +import com.sun.jna.ptr.NativeLongByReference; +import com.sun.jna.ptr.PointerByReference; +import com.sun.jna.ptr.ShortByReference; + +import junit.framework.TestCase; + +public class ByReferenceToStringTest extends TestCase { + + public void testToStrings() { + LongByReference lbr = new LongByReference(42L); + parseAndTest(lbr.toString(), "long", "0x2a (42)"); + lbr = new LongByReference(-42L); + parseAndTest(lbr.toString(), "long", "0xffffffffffffffd6 (-42)"); + + IntByReference ibr = new IntByReference(42); + parseAndTest(ibr.toString(), "int", "0x2a (42)"); + ibr = new IntByReference(-42); + parseAndTest(ibr.toString(), "int", "0xffffffd6 (-42)"); + + ShortByReference sbr = new ShortByReference((short) 42); + parseAndTest(sbr.toString(), "short", "0x2a (42)"); + sbr = new ShortByReference((short) -42); + parseAndTest(sbr.toString(), "short", "0xffd6 (-42)"); + + ByteByReference bbr = new ByteByReference((byte) 42); + parseAndTest(bbr.toString(), "byte", "0x2a (42)"); + bbr = new ByteByReference((byte) -42); + parseAndTest(bbr.toString(), "byte", "0xd6 (-42)"); + + FloatByReference fbr = new FloatByReference(42f); + parseAndTest(fbr.toString(), "float", "42.0"); + fbr = new FloatByReference(-42f); + parseAndTest(fbr.toString(), "float", "-42.0"); + + DoubleByReference dbr = new DoubleByReference(42d); + parseAndTest(dbr.toString(), "double", "42.0"); + dbr = new DoubleByReference(-42d); + parseAndTest(dbr.toString(), "double", "-42.0"); + + NativeLongByReference nlbr = new NativeLongByReference(new NativeLong(42L)); + parseAndTest(nlbr.toString(), "NativeLong", "0x2a (42)"); + nlbr = new NativeLongByReference(new NativeLong(-42L)); + parseAndTest(nlbr.toString(), "NativeLong", + NativeLong.SIZE > 4 ? "0xffffffffffffffd6 (-42)" : "0xffffffd6 (-42)"); + + PointerByReference pbr = new PointerByReference(Pointer.NULL); + assertTrue(pbr.toString().startsWith("null@0x")); + pbr = new PointerByReference(new Pointer(42)); + parseAndTest(pbr.toString(), "Pointer", "native"); + } + + /** + * Parses a string "foo@0x123=bar" testing equality of fixed parts of the string + * + * @param s + * The string to test + * @param beforeAt + * The string which should match the portion before the first + * {@code @} + * @param afterEquals + * The string which should match the portion after the {@code =} + * sign, before any additional {@code @} + */ + private void parseAndTest(String s, String beforeAt, String afterEquals) { + String[] atSplit = s.split("@"); + assertEquals("Incorrect type prefix", beforeAt, atSplit[0]); + String[] equalsSplit = atSplit[1].split("="); + assertEquals("Incorrect value string", afterEquals, equalsSplit[1]); + } +}