JNA: Change String encoding for only one external native library - java

We have here an JAVA application which loads and use a lots of external librarys. The default encoding of the operating system (Windows) is "windows-1252" (or "cp-1252"). But there is one external library which want all String (incoming and outgoing) in "utf-8". How can I do that? How can I change the String encoding type for only one JNA library?

The normal JNA pattern is this:
public interface DemoLibrary extends Library {
DemoLibrary INSTANCE = Native.load("demoLibrary", DemoLibrary.class);
// abstract method declarations as interface to native library
}
However, Native#load is overloaded multiple times to support customizing the bindings. The relevant overload is: Native#load(String, Class, Map<String,?>). The third argument can be used to pass options to the native library loader. The options can be found in the com.sun.jna.Library Interface.
The relevant option here is Library.OPTION_STRING_ENCODING. That option is passed to the NativeLibrary instance loaded and will be used as the default encoding for this class.
The sample above becomes then
public interface DemoLibrary extends Library {
DemoLibrary INSTANCE = Native.load("demoLibrary", DemoLibrary.class,
Collections.singletonMap(Library.OPTION_STRING_ENCODING, "UTF-8"));
}
If you need to customize more (typemapper, calling convention), you'll need to create the option map for example in a static initializer block.

Matthias Bläsing's answer is a much better solution to this specific use case. Please read it first if you only need character encoding.
My original answer is below and is more general to a wider range of applications.
An easy way to handle it is to not directly map String fields/args at all. Just send and receive byte arrays from the library, and create a helper function to do the translation between Strings and the byte arrays. As you've pointed out, you can write those bytes to an allocated Memory block and pass the pointer.
If you want a more permanent solution to do the same thing behind the scenes, you can use a TypeMapper for that particular library.
The W32APITypeMapper is a good reference, with the stringConverter variable showing you how in unicode it maps String to the wide string WString (UTF16).
Create your own UTF8TypeMapper (or similar) and use Java's character set/encoding functions to translate your strings to a sequence of UTF-8 bytes.
This is untested, but should be close to what you need. You could do a bit more abstraction to create a new UTF8String type that handles the details.
public class UTF8TypeMapper extends DefaultTypeMapper {
public UTF8TypeMapper() {
TypeConverter stringConverter = new TypeConverter() {
#Override
public Object toNative(Object value, ToNativeContext context) {
if (value == null)
return null;
String str = (String) value;
byte[] bytes = str.getBytes(StandardCharsets.UTF_8);
// Allocate an extra byte for null terminator
Memory m = new Memory(bytes.length + 1);
// write the string's bytes
m.write(0, bytes, 0, bytes.length);
// write the terminating null
m.setByte((long) bytes.length, (byte) 0);
return m;
}
#Override
public Object fromNative(Object value, FromNativeContext context) {
if (value == null)
return null;
Pointer p = (Pointer) value;
// handles the null terminator
return p.getString(0, StandardCharsets.UTF_8.name());
}
#Override
public Class<?> nativeType() {
return Pointer.class;
}
};
addTypeConverter(String.class, stringConverter);
}
}
Then, add the type mapper to the options when loading the library:
private static final Map<String, ?> UTF8_OPTIONS =
Collections.singletonMap(Library.OPTION_TYPE_MAPPER, new UTF8TypeMapper());
TheUTF8Lib INSTANCE = Native.load("TheUTF8Lib", TheUTF8Lib.class, UTF8_OPTIONS);

The only way I have found is to use the Function class of JNA (see at https://java-native-access.github.io/jna/5.2.0/javadoc/com/sun/jna/Function.html ) like this:
public void setIp(String ip) {
Function fSetIp = Function.getFunction("myLib", "setIp", Function.C_CONVENTION, "utf-8");
Object[] args = {ip};
fSetIp.invoke(args);
}
But I must implementing this for each function I want to call. Not sure if there is a better/easier way. If so: Please answer my question.

Related

How to use Data Type Renderers list for byte[] in IntelliJ

So I have all these byte[]'s, and their encoding could be one of many different formats, depending on where I'm currently debugging. I'd like to be able to toss together a list of projections for the byte array. I'm using IntelliJ's Data Type Renderers view, applying the renderer to type byte[], and I'm primarily concerned with the List of Expressions box.
So I've seen how you can render the node with the expression new String(this), however that doesn't work in the list of expressions view below. Eventually I want to do expressions like new String(this, "UTF16") (or do conversions to hex or base64 or w/e) but this doesn't appear to be a byte[] in the expressions list box - in fact, when I try to typecast like (byte[])this the result says Inconvertible types; cannot cast '_Dummy_.__Array__≤T≥' to 'byte[]' (same behavior for java.lang.Byte[]). It's really strange behavior, that in one place it's a byte[] and in another it's some opaque internal type.
What does work is simply displaying the fields - i.e., an expression like this.length works as expected. Also, an expression like this simply rerenders the node, claiming its type is byte[] and its object id is the same as the original's id.
I answered a similar question here. The message about being unable to cast _Dummy_.__Array__<T> to byte[] sounds like an IntelliJ error where it can't determine the name of the class in the call stack. Perhaps adding DTRs for other "forms" of byte[] will help. I've included examples of three DTRs below -- byte[], Byte[] and ArrayList.
The test and the helper method (written in Groovy, so make sure it's on your classpath or rewrite in pure Java):
#Test
void testShouldHandleDTR() {
// Arrange
byte[] primitiveArray = "90abcdef".bytes
Byte[] objectArray = "90abcdef".bytes
List<Byte> objectList = "90abcdef".bytes
final String EXPECTED_STRING = Hex.encodeHexString(primitiveArray)
logger.info("Expected hex string: ${EXPECTED_STRING}")
// Fully qualified for DTR dialog
String primitiveDTR = "org.bouncycastle.util.encoders.Hex.toHexString(this);"
String objectArrayDTR = "org.example.ThisTest.encodeObjectArrayToHex(this);"
String objectListDTR = "org.example.ThisTest.encodeObjectArrayToHex(this.toArray());"
def types = [primitiveArray, objectArray, objectList]
def expressions = [(primitiveArray.class.name): primitiveDTR,
(objectArray.class.name): objectArrayDTR,
(objectList.class.name): objectListDTR]
// Act
types.each { it ->
logger.info("Contents: ${it}")
logger.info("Type: ${it.class.name}")
String result = Eval.x(it, expressions[it.class.name].replaceAll(/this/, "x"))
logger.info("Result: ${result}")
// Assert
assert result == EXPECTED_STRING
}
}
public static String encodeObjectArrayToHex(Object[] objectArray) {
byte[] primitiveArray = objectArray.collect { it as byte }
Hex.encodeHexString(primitiveArray)
}
For each DTR you want to define, just copy the exact String defined above into the When rendering a node > Use following expression field. I'd recommend putting the utility method into a source class on your classpath as opposed to a test, as on every build you will have to re-import the test class in the DTR dialog because the target/ gets cleaned.

How to create an object in a utility class based on if statement in Java? (Or based on a particular string)

I would have a string that is parsed into an array, as shown here:
class Example extends ParentClass {
private String[] array;
public static Example parseString(String lineToParse) {
array = lineToParse.split("\");
}
public ObjectType1() { // arguments: String, String, String
}
public ObjectType2() { // arguments: String, String, String, double, double
}
}
What I'm wondering is could I do this?
if (array[0].equals("Test")) {
public ObjectType1()
}
Or is there a better way to do this?
I want to create various objects with different arguments each, and the first argument (array[0]) will be applicable to each object, so I was wondering if I could create objects within an if statement like this, or a switch (not sure if that would work either).
I believe a factory method would be useful for you, one that returns instances of classes according to the parameter received:
// ObjectType1, ObjectType2, ObjectType3 inherit from ObjectType
static ObjectType getInstance(String[] array) {
if (array[0].equals("Test"))
return new ObjectType1(array);
else if (array[0].equals("Test2"))
return new ObjectType2(array);
else
return new ObjectType3(array);
}
For the record, actually you can define a class inside a method, this is valid code in Java ... of course, that's hardly a good thing to do:
// ObjectType1, ObjectType2 inherit from ObjectType
public ObjectType example(String[] array) {
if (array[0].equals("Test")) {
class ObjectType1 {
ObjectType1(String[] array) {
}
}
return new ObjectType1(array);
}
else {
class ObjectType2 {
ObjectType2(String[] array) {
}
}
return new ObjectType2(array);
}
}
"Creating" an object means "instantiating it", with new:
ObjectType1 foo = new ObjectType1(...);
You can do that anywhere it's legal to instantiate a class, including in an if statement.
You cannot define classes in arbitrary locations, however.
If you just want to call a method (which should start with a lower-case letter if you want Java developers to understand what you're trying to do), you can call it from anywhere, including inside if statements.
This sounds like you may want to use a [static factory method][1].
[1]: http://en.m.wikipedia.org/wiki/Factory_method_pattern
I guess that you want to dynamically create objects based on a configuration file?
There are lots of ways to achieve this. One simple way is to use reflection to create the objects. Then you do not need any if/switch statements, and if you want to create a new type of object your code does not need to be changed.
Here are some examples for using reflection: Reflection API Code Samples

Java method invocation parser?

I need to parse certain method invocation calls including the whole signature out of some Java classes, e.g.
public class MyClass {
public void myMthod() {
// ... some code here
result = someInstance.someOtherMethod(param1, param2);
// ... some other code here
}
}
As a result I would like to get something like:
serviceName = someInstance
methodName = someOtherMethod
arguments = {
argument = java.lang.String,
argument = boolean
}
result = java.lang.Long
What would be the fastest way to achieve this?
I was thinking about using a RegEx parser. The problem there is there are several occurence patterns, e.g.
a)
result = someInstance.someOtherMethod(getSomething(), param);
b)
result =
getSomeInstance().someOtherMethod(param);
c)
result = getSomeInstance()
.someOtherMethod(
getSomethingElse(), null, param);
Any help would be really appreciated! Thanks!
Don't use regex! Use tools that understand java.
Use either:
A source code parser (e.g. javaparser)
Byte code analysis (e.g. ASM)
An aspect (AspectJ)
In both source parser and ASM, you will write a visitor that scans for method invocations.
For javaparser: Read this page, extend VoidVisitorAdapter and override
public void visit(MethodCallExpr n, A arg)
Sample Code:
public static void main(final String[] args) throws Exception{
parseCompilationUnit(new File("src/main/java/foo/bar/Phleem.java"));
}
public static void parseCompilationUnit(final File sourceFile)
throws ParseException, IOException{
final CompilationUnit cu = JavaParser.parse(sourceFile);
cu.accept(new VoidVisitorAdapter<Void>(){
#Override
public void visit(final MethodCallExpr n, final Void arg){
System.out.println(n);
super.visit(n, arg);
}
}, null);
}
The problem here is that you only have the object names, not the object types, so you will also have to keep a local Map of variable / field to type and that's where things get messy. Perhaps ASM is the easier choice, after all.
For ASM: read this tutorial page to get started
Realistically you can't parse a typical programming langauge like Java with regular expressions; you need a full parser. In your case, you need a parser that is capable of parsing arbitrary substrings (in this case, function call nonterminals) of the langauge, and those are much harder to find.
You also didn't say how you intended to find (and delimit) the calls of interest, before you decided to call them. You are likely to need Java symbol table data in order to just pick the method names you want; oherwise,
how will you know that "someService" resolves the actual service class that you care about, and not just some arbitrary class that happens to have what appears to be the right name?

java: How can I do dynamic casting of a variable from one type to another?

I would like to do dynamic casting for a Java variable, the casting type is stored in a different variable.
This is the regular casting:
String a = (String) 5;
This is what I want:
String theType = 'String';
String a = (theType) 5;
Is this possible, and if so how? Thanks!
Update
I'm trying to populate a class with a HashMap that I received.
This is the constructor:
public ConnectParams(HashMap<String,Object> obj) {
for (Map.Entry<String, Object> entry : obj.entrySet()) {
try {
Field f = this.getClass().getField(entry.getKey());
f.set(this, entry.getValue()); /* <= CASTING PROBLEM */
} catch (NoSuchFieldException ex) {
log.error("did not find field '" + entry.getKey() + '"');
} catch (IllegalAccessException ex) {
log.error(ex.getMessage());
}
}
}
The problem here is that some of the class' variables are of type Double, and if the number 3 is received it sees it as Integer and I have type problem.
Yes it is possible using Reflection
Object something = "something";
String theType = "java.lang.String";
Class<?> theClass = Class.forName(theType);
Object obj = theClass.cast(something);
but that doesn't make much sense since the resulting object must be saved in a variable of Object type. If you need the variable be of a given class, you can just cast to that class.
If you want to obtain a given class, Number for example:
Object something = new Integer(123);
String theType = "java.lang.Number";
Class<? extends Number> theClass = Class.forName(theType).asSubclass(Number.class);
Number obj = theClass.cast(something);
but there is still no point doing it so, you could just cast to Number.
Casting of an object does NOT change anything; it is just the way the compiler treats it.
The only reason to do something like that is to check if the object is an instance of the given class or of any subclass of it, but that would be better done using instanceof or Class.isInstance().
Update
according your last update the real problem is that you have an Integer in your HashMap that should be assigned to a Double. What you can do in this case, is check the type of the field and use the xxxValue() methods of Number
...
Field f = this.getClass().getField(entry.getKey());
Object value = entry.getValue();
if (Integer.class.isAssignableFrom(f.getType())) {
value = Integer.valueOf(((Number) entry.getValue()).intValue());
} else if (Double.class.isAssignableFrom(f.getType())) {
value = Double.valueOf(((Number) entry.getValue()).doubleValue());
} // other cases as needed (Long, Float, ...)
f.set(this, value);
...
(not sure if I like the idea of having the wrong type in the Map)
You'll need to write sort of ObjectConverter for this. This is doable if you have both the object which you want to convert and you know the target class to which you'd like to convert to. In this particular case you can get the target class by Field#getDeclaringClass().
You can find here an example of such an ObjectConverter. It should give you the base idea. If you want more conversion possibilities, just add more methods to it with the desired argument and return type.
Regarding your update, the only way to solve this in Java is to write code that covers all cases with lots of if and else and instanceof expressions. What you attempt to do looks as if are used to program with dynamic languages. In static languages, what you attempt to do is almost impossible and one would probably choose a totally different approach for what you attempt to do. Static languages are just not as flexible as dynamic ones :)
Good examples of Java best practice are the answer by BalusC (ie ObjectConverter) and the answer by Andreas_D (ie Adapter) below.
That does not make sense, in
String a = (theType) 5;
the type of a is statically bound to be String so it does not make any sense to have a dynamic cast to this static type.
PS: The first line of your example could be written as Class<String> stringClass = String.class; but still, you cannot use stringClass to cast variables.
You can do this using the Class.cast() method, which dynamically casts the supplied parameter to the type of the class instance you have. To get the class instance of a particular field, you use the getType() method on the field in question. I've given an example below, but note that it omits all error handling and shouldn't be used unmodified.
public class Test {
public String var1;
public Integer var2;
}
public class Main {
public static void main(String[] args) throws Exception {
Map<String, Object> map = new HashMap<String, Object>();
map.put("var1", "test");
map.put("var2", 1);
Test t = new Test();
for (Map.Entry<String, Object> entry : map.entrySet()) {
Field f = Test.class.getField(entry.getKey());
f.set(t, f.getType().cast(entry.getValue()));
}
System.out.println(t.var1);
System.out.println(t.var2);
}
}
You can write a simple castMethod like the one below.
private <T> T castObject(Class<T> clazz, Object object) {
return (T) object;
}
In your method you should be using it like
public ConnectParams(HashMap<String,Object> object) {
for (Map.Entry<String, Object> entry : object.entrySet()) {
try {
Field f = this.getClass().getField(entry.getKey());
f.set(this, castObject(entry.getValue().getClass(), entry.getValue()); /* <= CASTING PROBLEM */
} catch (NoSuchFieldException ex) {
log.error("did not find field '" + entry.getKey() + '"');
} catch (IllegalAccessException ex) {
log.error(ex.getMessage());
}
}
}
It works and there's even a common pattern for your approach: the Adapter pattern. But of course, (1) it does not work for casting java primitives to objects and (2) the class has to be adaptable (usually by implementing a custom interface).
With this pattern you could do something like:
Wolf bigBadWolf = new Wolf();
Sheep sheep = (Sheep) bigBadWolf.getAdapter(Sheep.class);
and the getAdapter method in Wolf class:
public Object getAdapter(Class clazz) {
if (clazz.equals(Sheep.class)) {
// return a Sheep implementation
return getWolfDressedAsSheep(this);
}
if (clazz.equals(String.class)) {
// return a String
return this.getName();
}
return null; // not adaptable
}
For you special idea - that is impossible. You can't use a String value for casting.
Your problem is not the lack of "dynamic casting". Casting Integer to Double isn't possible at all. You seem to want to give Java an object of one type, a field of a possibly incompatible type, and have it somehow automatically figure out how to convert between the types.
This kind of thing is anathema to a strongly typed language like Java, and IMO for very good reasons.
What are you actually trying to do? All that use of reflection looks pretty fishy.
Don't do this. Just have a properly parameterized constructor instead. The set and types of the connection parameters are fixed anyway, so there is no point in doing this all dynamically.
For what it is worth, most scripting languages (like Perl) and non-static compile-time languages (like Pick) support automatic run-time dynamic String to (relatively arbitrary) object conversions. This CAN be accomplished in Java as well without losing type-safety and the good stuff statically-typed languages provide WITHOUT the nasty side-effects of some of the other languages that do evil things with dynamic casting. A Perl example that does some questionable math:
print ++($foo = '99'); # prints '100'
print ++($foo = 'a0'); # prints 'a1'
In Java, this is better accomplished (IMHO) by using a method I call "cross-casting".
With cross-casting, reflection is used in a lazy-loaded cache of constructors and methods that are dynamically discovered via the following static method:
Object fromString (String value, Class targetClass)
Unfortunately, no built-in Java methods such as Class.cast() will do this for String to BigDecimal or String to Integer or any other conversion where there is no supporting class hierarchy. For my part, the point is to provide a fully dynamic way to achieve this - for which I don't think the prior reference is the right approach - having to code every conversion. Simply put, the implementation is just to cast-from-string if it is legal/possible.
So the solution is simple reflection looking for public Members of either:
STRING_CLASS_ARRAY = (new Class[] {String.class});
a) Member member = targetClass.getMethod(method.getName(),STRING_CLASS_ARRAY);
b) Member member = targetClass.getConstructor(STRING_CLASS_ARRAY);
You will find that all of the primitives (Integer, Long, etc) and all of the basics (BigInteger, BigDecimal, etc) and even java.regex.Pattern are all covered via this approach. I have used this with significant success on production projects where there are a huge amount of arbitrary String value inputs where some more strict checking was needed. In this approach, if there is no method or when the method is invoked an exception is thrown (because it is an illegal value such as a non-numeric input to a BigDecimal or illegal RegEx for a Pattern), that provides the checking specific to the target class inherent logic.
There are some downsides to this:
1) You need to understand reflection well (this is a little complicated and not for novices).
2) Some of the Java classes and indeed 3rd-party libraries are (surprise) not coded properly. That is, there are methods that take a single string argument as input and return an instance of the target class but it isn't what you think... Consider the Integer class:
static Integer getInteger(String nm)
Determines the integer value of the system property with the specified name.
The above method really has nothing to do with Integers as objects wrapping primitives ints.
Reflection will find this as a possible candidate for creating an Integer from a String incorrectly versus the decode, valueof and constructor Members - which are all suitable for most arbitrary String conversions where you really don't have control over your input data but just want to know if it is possible an Integer.
To remedy the above, looking for methods that throw Exceptions is a good start because invalid input values that create instances of such objects should throw an Exception. Unfortunately, implementations vary as to whether the Exceptions are declared as checked or not. Integer.valueOf(String) throws a checked NumberFormatException for example, but Pattern.compile() exceptions are not found during reflection lookups. Again, not a failing of this dynamic "cross-casting" approach I think so much as a very non-standard implementation for exception declarations in object creation methods.
If anyone would like more details on how the above was implemented, let me know but I think this solution is much more flexible/extensible and with less code without losing the good parts of type-safety. Of course it is always best to "know thy data" but as many of us find, we are sometimes only recipients of unmanaged content and have to do the best we can to use it properly.
Cheers.
So, this is an old post, however I think I can contribute something to it.
You can always do something like this:
package com.dyna.test;
import java.io.File;
import java.lang.reflect.Constructor;
public class DynamicClass{
#SuppressWarnings("unchecked")
public Object castDynamicClass(String className, String value){
Class<?> dynamicClass;
try
{
//We get the actual .class object associated with the specified name
dynamicClass = Class.forName(className);
/* We get the constructor that received only
a String as a parameter, since the value to be used is a String, but we could
easily change this to be "dynamic" as well, getting the Constructor signature from
the same datasource we get the values from */
Constructor<?> cons =
(Constructor<?>) dynamicClass.getConstructor(new Class<?>[]{String.class});
/*We generate our object, without knowing until runtime
what type it will be, and we place it in an Object as
any Java object extends the Object class) */
Object object = (Object) cons.newInstance(new Object[]{value});
return object;
}
catch (Exception e)
{
e.printStackTrace();
}
return null;
}
public static void main(String[] args)
{
DynamicClass dynaClass = new DynamicClass();
/*
We specify the type of class that should be used to represent
the value "3.0", in this case a Double. Both these parameters
you can get from a file, or a network stream for example. */
System.out.println(dynaClass.castDynamicClass("java.lang.Double", "3.0"));
/*
We specify a different value and type, and it will work as
expected, printing 3.0 in the above case and the test path in the one below, as the Double.toString() and
File.toString() would do. */
System.out.println(dynaClass.castDynamicClass("java.io.File", "C:\\testpath"));
}
Of course, this is not really dynamic casting, as in other languages (Python for example), because java is a statically typed lang. However, this can solve some fringe cases where you actually need to load some data in different ways, depending on some identifier. Also, the part where you get a constructor with a String parameter could be probably made more flexible, by having that parameter passed from the same data source. I.e. from a file, you get the constructor signature you want to use, and the list of values to be used, that way you pair up, say, the first parameter is a String, with the first object, casting it as a String, next object is an Integer, etc, but somehwere along the execution of your program, you get now a File object first, then a Double, etc.
In this way, you can account for those cases, and make a somewhat "dynamic" casting on-the-fly.
Hope this helps anyone as this keeps turning up in Google searches.
Try this for Dynamic Casting. It will work!!!
String something = "1234";
String theType = "java.lang.Integer";
Class<?> theClass = Class.forName(theType);
Constructor<?> cons = theClass.getConstructor(String.class);
Object ob = cons.newInstance(something);
System.out.println(ob.equals(1234));
I recently felt like I had to do this too, but then found another way which possibly makes my code look neater, and uses better OOP.
I have many sibling classes that each implement a certain method doSomething(). In order to access that method, I would have to have an instance of that class first, but I created a superclass for all my sibling classes and now I can access the method from the superclass.
Below I show two ways alternative ways to "dynamic casting".
// Method 1.
mFragment = getFragmentManager().findFragmentByTag(MyHelper.getName(mUnitNum));
switch (mUnitNum) {
case 0:
((MyFragment0) mFragment).sortNames(sortOptionNum);
break;
case 1:
((MyFragment1) mFragment).sortNames(sortOptionNum);
break;
case 2:
((MyFragment2) mFragment).sortNames(sortOptionNum);
break;
}
and my currently used method,
// Method 2.
mSuperFragment = (MySuperFragment) getFragmentManager().findFragmentByTag(MyHelper.getName(mUnitNum));
mSuperFragment.sortNames(sortOptionNum);
Just thought I would post something that I found quite useful and could be possible for someone who experiences similar needs.
The following method was a method I wrote for my JavaFX application to avoid having to cast and also avoid writing if object x instance of object b statements every time the controller was returned.
public <U> Optional<U> getController(Class<U> castKlazz){
try {
return Optional.of(fxmlLoader.<U>getController());
}catch (Exception e){
e.printStackTrace();
}
return Optional.empty();
}
The method declaration for obtaining the controller was
public <T> T getController()
By using type U passed into my method via the class object, it could be forwarded to the method get controller to tell it what type of object to return. An optional object is returned in case the wrong class is supplied and an exception occurs in which case an empty optional will be returned which we can check for.
This is what the final call to the method looked like (if present of the optional object returned takes a Consumer
getController(LoadController.class).ifPresent(controller->controller.onNotifyComplete());

Reading/writing binary structures: how to simplify this code?

I'm writing a network app, which sends and receives a lot of different kinds of binary packets, and I'm trying to make adding new kinds of packets to my app as easy as possible.
For now, I created a Packet class, and I create subclasses of it for each different kind of packet. However, it isn't as clean as it seems; I've ended up with code like this:
static class ItemDesc extends Packet {
public final int item_id;
public final int desc_type;
public final String filename;
public final String buf;
public ItemDesc(Type t, int item_id, int desc_type, String filename, String buf) {
super(t); // sets type for use in packet header
this.item_id = item_id;
this.desc_type = desc_type;
this.filename = filename;
this.buf = buf;
}
public ItemDesc(InputStream i) throws IOException {
super(i); // reads packet header and sets this.input
item_id = input.readInt();
desc_type = input.readByte();
filename = input.readStringWithLength();
buf = input.readStringWithLength();
}
public void writeTo(OutputStream o) throws IOException {
MyOutputStream dataOutput = new MyOutputStream();
dataOutput.writeInt(item_id);
dataOutput.writeByte(desc_type);
dataOutput.writeStringWithLength(filename);
dataOutput.writeStringWithLength(buf);
super.write(dataOutput.toByteArray(), o);
}
}
What bothers me about this approach is the code repetition - I'm repeating the packet structure four times. I'd be glad to avoid this, but I can't see a reasonable way to simplify it.
If I was writing in Python I would create a dictionary of all possible field types, and then define new packet types like this:
ItemDesc = [('item_id', 'int'), ('desc_type', 'byte'), ...]
I suppose that I could do something similar in any functional language. However, I can't see a way to take this approach to Java.
(Maybe I'm just too pedantic, or I got used to functional programming and writing code that writes code, so I could avoid any repetition :))
Thank you in advance for any suggestions.
I agree with #silky that your current code is a good solution. A bit of repetitious (though not duplicated) code is not a bad thing, IMO.
If you wanted a more python-like solution, you could:
Replace the member attributes of ItemDesc with some kind of order-preserving map structure, do the serialization using a common writeTo method that iterates over the map. You also need to add getters for each attribute, and replace all uses of the existing fields.
Replace the member attributes with a Properties object and use Properties serialization instead of binary writes.
Write a common writeTo method that uses Java reflection to access the member attributes and their types and serialize them.
But in all 3 cases, the code will be slower, more complicated and potentially more fragile than the current "ugly" code. I wouldn't do this.
Seem okay to me. You may just want to abstract some of the 'general' parts of the packet up the inheritance chain, so you don't need to read them, but it makes sense to be repeating the format like you are, because you've got a case for reading in raw from the constructor, reading from a stream, and writing. I see nothing wrong with it.
I am not sure you can do this in java- but maybe you could reuse one of the ctors:
public ItemDesc(InputStream i) throws IOException {
super(i); // reads packet header and sets this.input
this(input.readInt(), input.readByte(), input.readStringWithLength(), input.readStringWithLength());
}
Were 'this' means a call to this classes ctor, whtever the syntax might be.

Categories