{"id":815,"date":"2010-11-30T21:17:20","date_gmt":"2010-12-01T05:17:20","guid":{"rendered":"http:\/\/technofovea.com\/blog\/?p=815"},"modified":"2010-11-30T22:09:27","modified_gmt":"2010-12-01T06:09:27","slug":"friendly-enums-in-java-with-jna","status":"publish","type":"post","link":"http:\/\/technofovea.com\/blog\/archives\/815","title":{"rendered":"Friendly Enums in Java with JNA"},"content":{"rendered":"<p>Well, it&#8217;s a slow week so I think I might as well share some old code to apologize for not making new code. In <a href=\"\/blog\/projects\/jhllib\">jHllib<\/a>, which allows Java programs to inter-operate with a certain C\/C++ DLL, I found myself needing to refer to certain integer values that were defined in C. All the documentation and examples for <a href=\"http:\/\/jna.dev.java.net\/\">JNA<\/a> (the Java<->native bridging platform) tell you to use non-type-safe integer constants in your program, but this just didn&#8217;t sit well with me.<\/p>\n<p><p>After some experimentation, I found a way around to use Java&#8217;s type-safe enums with JNA. First I&#8217;ll show you what the final product looks like, and then look at the guts that make it possible.<\/p>\n<p><!--more--><\/p>\n<h3>The Function<\/h3>\n<p>Here&#8217;s an example function call where I wanted to use type-safe enums. The original method in C code looks like this:<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\nHLStreamType hlStreamGetType(const HLStream *pStream);\r\n<\/pre>\n<p>And now in the Java code:<\/p>\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\r\n\/\/ Slightly modified from released code to remove spurious changes\r\npublic StreamType hlStreamGetType(HlStream streamObj);\r\n<\/pre>\n<h3>The Values<\/h3>\n<p>As defined in C:<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\ntypedef enum\r\n{\r\n        HL_STREAM_NONE = 0,\r\n        HL_STREAM_FILE,\r\n        HL_STREAM_GCF,\r\n        HL_STREAM_MAPPING,\r\n        HL_STREAM_MEMORY,\r\n        HL_STREAM_PROC,\r\n        HL_STREAM_NULL\r\n} HLStreamType;\r\n<\/pre>\n<p>And again in Java. You might consider it bad-practice to make the enum values order-dependent, but it just follows the C version that way.<\/p>\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\r\nimport com.technofovea.hllib.JnaEnum;\r\npublic enum StreamType implements JnaEnum&lt;StreamType&gt; {\r\n        \r\n        NONE,\r\n        FILE,\r\n        GCF,\r\n        MAPPING,\r\n        MEMORY,\r\n        PROC,\r\n        NULL;\r\n        \r\n    private static int start = 0;\r\n\r\n    public int getIntValue() {\r\n        return this.ordinal() + start;\r\n    }\r\n\r\n    public StreamType getForValue(int i) {\r\n        for (StreamType o : this.values()) {\r\n            if (o.getIntValue() == i) {\r\n                return o;\r\n            }\r\n        }\r\n        return null;\r\n    }\r\n}\r\n<\/pre>\n<p>As you can see, the only strange bit is this &#8220;JnaEnum<?>&#8221; interface and some extra methods it requires. It&#8217;s actually very simple:<\/p>\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\r\npublic interface JnaEnum&lt;T&gt; {\r\n    public int getIntValue();\r\n    public T getForValue(int i);\r\n}\r\n<\/pre>\n<p> We&#8217;ll see what uses the interface in just a bit.<\/p>\n<h3>The Wiring<\/h3>\n<p>The secret is something JNA calls a TypeConverter. It&#8217;s a mechanism you can use to inject custom behavior to how JNA converts types behind the scenes. Before we dive into the actual <em>conversion<\/em> step, I want to go over how it is wired together for context.<\/p>\n<p>First, we change the initialization of JNA to include out TypeMapper. Yes, Mapper, not Converter. A TypeMapper provides access to multiple TypeConverters, one of which being our EnumConverter.<\/p>\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\r\nMap&lt;String, Object&gt; options = new HashMap&lt;String, Object&gt;();\r\noptions.put(Library.OPTION_TYPE_MAPPER, new HlTypeMapper());\r\nFullLibrary instance = (FullLibrary) Native.loadLibrary(&quot;hllib&quot;, FullLibrary.class, options);\r\n<\/pre>\n<p>Next, we need to define the values we want to get converted and what classes are responsible for doing so.<\/p>\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\r\nclass HlTypeMapper extends DefaultTypeMapper {\r\n\r\n    HlTypeMapper() {\r\n        \r\n        \/\/ The EnumConverter is set to fire when instances of \r\n        \/\/ our interface, JnaEnum, are seen.\r\n        addTypeConverter(JnaEnum.class, new EnumConverter());\r\n        \r\n        \/\/ Remember HlStream from the example several steps back?\r\n        \/\/ I left this line in to show it uses a similar technique.\r\n        addTypeConverter(HlStream.class, new StreamConverter());\r\n    }\r\n}\r\n<\/pre>\n<h3>The secret sauce<\/h3>\n<p>This last class, the EnumConverter, is where most of the hard work happens. It takes the <em>kind<\/em> of enum that the Java code is giving\/expecting, and converts values between enums and integers as appropriate.<\/p>\n<p>In order to do this, it relies on individual enums themselves to do some of<br \/>\nthe logic, through the JnaEnum<T> interface we defined earlier.<\/p>\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\r\nclass EnumConverter implements TypeConverter {\r\n\r\n    private static final Logger logger = LoggerFactory.getLogger(EnumConverter.class);\r\n\r\n    public Object fromNative(Object input, FromNativeContext context) {\r\n        Integer i = (Integer) input;\r\n        Class targetClass = context.getTargetType();\r\n        if (!JnaEnum.class.isAssignableFrom(targetClass)) {\r\n            return null;\r\n        }\r\n        Object[] enums = targetClass.getEnumConstants();\r\n        if (enums.length == 0) {\r\n            logger.error(&quot;Could not convert desired enum type (), no valid values are defined.&quot;,targetClass.getName());\r\n            return null;\r\n        }\r\n        \/\/ In order to avoid nasty reflective junk and to avoid needing\r\n        \/\/ to know about every subclass of JnaEnum, we retrieve the first\r\n        \/\/ element of the enum and make IT do the conversion for us.\r\n        \r\n        JnaEnum instance = (JnaEnum) enums[0];\r\n        return instance.getForValue(i);\r\n\r\n    }\r\n\r\n    public Object toNative(Object input, ToNativeContext context) {\r\n        JnaEnum j = (JnaEnum) input;\r\n        return new Integer(j.getIntValue());\r\n    }\r\n\r\n    public Class nativeType() {\r\n        return Integer.class;\r\n    }\r\n}\r\n<\/pre>\n<h3>Conclusion<\/h3>\n<p>So that&#8217;s how you get type-safe enums in Java with JNA. Note that there is a weakness in this system: If the other end ever sends back a number your enum does not expect, things will break and you&#8217;ll get a null value back instead of an enum object. A constant-integer system would likely also break, but at least you&#8217;d have the actual number on-hand for error messages and things.<\/p>\n<p>On the positive side, any native code withs lots of enumerated values is <strong>much<\/strong> easier to manage: You don&#8217;t need to deal with an &#8220;integer soup&#8221; and and IDEs with auto-complete can intelligently suggest what values a function may take or return.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Well, it&#8217;s a slow week so I think I might as well share some old code to apologize for not making new code. In jHllib, which allows Java programs to inter-operate with a certain C\/C++ DLL, I found myself needing to refer to certain integer values that were defined in C. All the documentation and [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[4],"tags":[17,7],"_links":{"self":[{"href":"http:\/\/technofovea.com\/blog\/wp-json\/wp\/v2\/posts\/815"}],"collection":[{"href":"http:\/\/technofovea.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/technofovea.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/technofovea.com\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"http:\/\/technofovea.com\/blog\/wp-json\/wp\/v2\/comments?post=815"}],"version-history":[{"count":27,"href":"http:\/\/technofovea.com\/blog\/wp-json\/wp\/v2\/posts\/815\/revisions"}],"predecessor-version":[{"id":842,"href":"http:\/\/technofovea.com\/blog\/wp-json\/wp\/v2\/posts\/815\/revisions\/842"}],"wp:attachment":[{"href":"http:\/\/technofovea.com\/blog\/wp-json\/wp\/v2\/media?parent=815"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/technofovea.com\/blog\/wp-json\/wp\/v2\/categories?post=815"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/technofovea.com\/blog\/wp-json\/wp\/v2\/tags?post=815"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}