reasonable handling of ScriptException thrown by JSR223 Rhino - java

I'm starting to run into the dirty little secrets of what is an otherwise very useful JSR223 scripting environment.
I'm using the builtin version of Rhino shipped with Java 6 SE, accessing it through JSR223's ScriptingEngine et al.
When I get an exception caused by a Java object I've exported into the Javascript environment, it is a ScriptingException that wraps a sun.org.mozilla.javascript.internal.WrappedException that wraps my real exception (e.g. UnsupportedOperationException or whatever)
The ScriptingException returns null for getFileName() and -1 for getLineNumber().
But when I look at the message and at the debugger, the WrappedException has the correct filename and line number, it's just not publishing it via the ScriptingException's getter methods.
Great. Now what do I do? I don't know how I'm going to use sun.org.mozilla.javascript.internal.wrappedException which isn't a public class anyway.

Argh. Java 6's Rhino does the same thing (doesn't publish the file name / line number / etc via ScriptingException's methods) with sun.org.mozilla.javascript.internal.EvaluatorException and who knows how many other exceptions.
The only reasonable way I can think of to handle this is to use reflection. Here's my solution.
void handleScriptingException(ScriptingException se)
{
final Throwable t1 = se.getCause();
String lineSource = null;
String filename = null;
Integer lineNumber = null;
if (hasGetterMethod(t1, "sourceName"))
{
lineNumber = getProperty(t1, "lineNumber", Integer.class);
filename = getProperty(t1, "sourceName", String.class);
lineSource = getProperty(t1, "lineSource", String.class);
}
else
{
filename = se.getFileName();
lineNumber = se.getLineNumber();
}
/* do something with this info */
}
static private Method getGetterMethod(Object object, String propertyName)
{
String methodName = "get"+getBeanSuffix(propertyName);
try {
Class<?> cl = object.getClass();
return cl.getMethod(methodName);
}
catch (NoSuchMethodException e) {
return null;
/* gulp */
}
}
static private String getBeanSuffix(String propertyName) {
return propertyName.substring(0,1).toUpperCase()
+propertyName.substring(1);
}
static private boolean hasGetterMethod(Object object, String propertyName)
{
return getGetterMethod(object, propertyName) != null;
}
static private <T> T getProperty(Object object, String propertyName,
Class<T> cl) {
try {
Object result = getGetterMethod(object, propertyName).invoke(object);
return cl.cast(result);
}
catch (Exception e) {
e.printStackTrace();
}
return null;
}

Related

jUnit Testing: Trying to write a test where I can input empty string to the function and I want to assert that the return value is null

Trying to write a test where I can input empty string to the function and I want to assert that the return value is null.
Attached is my code snippet I am using:
public String getUserInputNess() {
String inputLine = null;
try {
BufferedReader is = new BufferedReader(new InputStreamReader(System.in));
inputLine = is.readLine();
if (inputLine.length() == 0)
return null;
} catch (IOException e) {
System.out.println("IOException: " + e);
}
return inputLine.toLowerCase();
}
And below is my Unit test setup:
private void provideInput(String data) {
testIn = new ByteArrayInputStream(data.getBytes());
System.setIn(testIn);
}
private String getOutput() {
return testOut.toString();
}
#After
public void restoreSystemInputOutput() {
System.setIn(systemIn);
System.setOut(systemOut);
}
#Test
public void testGetUserInput() {
/*
Testing the getUserInput method by passing a user input and checking
if it is returned
*/
final String testString = "";
provideInput(testString);
GameHelper game = new GameHelper();
String output = game.getUserInput("");
assertNull(output);
}
Thanks for your help and time in advance
The problem here is that static access hides dependencies.
Your code under test (cut) uses System.in as a dependency. The proper way to deal with that would be to inject this dependency into your cut. The way I suggest is to do this via a constructor parameter. The constructor then assigns it to a final member variable.
If you do so you can at test time pass a test double into your cut instead of the real dependency.

How to modify the input parameter of void method from the mocked method with mockito answer

I have a class A as
Class A{
private static final String ANON_DIR = "/webapps/worldlingo/data/anonymizer/";
private static final String NO_ANON = "noanonymize";
public String first(String text, String srclang, Map dictTokens) {
Set<String> noAnonymize = new HashSet<String>();
second(noAnonymize,ANON_DIR + NO_ANON, "tmpLang","name");
String value;
if(noAnonymize.contains("test")){
value = "test1";
}
else {
value = "test";
}
return value;
}
where ANON_DIR and NO_ANON is static final value. This class has function first and function second .The first function has a calling method in it which calls second function. The second function is void function which takes static fields as parameter.
Second function is just the file read function with the path provided as
public void second (Set<String> hashSet, String path, String lang , String type) {
FileReader fr = null;
BufferedReader br = null;
try {
fr = new FileReader(path);
br = new BufferedReader(fr);
String Line;
while ((Line = br.readLine()) != null) {
hashSet.add(Line);
}
} catch (IOException e) {
log.error("Anonymizer: Unable to load file.", e);
} finally {
try {
if (fr != null) {
fr.close();
}
if (br != null) {
br.close();
}
} catch (IOException e) {
log.error("Anonymizer : An error occured while closing a resource.", e);
}
}
}
}
Now I am trying to test the function first using mockito. I am trying update the passed first argument (list parameter) i.e noAnonymize in second(noAnonymize,ANON_DIR + NO_ANON, "tmpLang","name");
public void testfirst() throws Exception {
Anonymizer mock = PowerMockito.mock(Anonymizer.class);
doAnswer(new Answer() {
public Object answer(InvocationOnMock invocation) {
List<String> args = invocation.getArgumentAt(0,List.class);
args.add("a");
args.add("b");
return null; // void method, so return null
}
}).when(mock).readNoAnonymizeFile(Mockito.anySet(),Mockito.anyString(),Mockito.anyString(),Mockito.anyString());
Method anonymizeNames = anon.getClass().getDeclaredMethod("anonymizeNames_test", String.class, String.class, Map.class);
String srcLang = "MSFT_EN";
Map mapTokens = new HashMap();
String result = (String) anonymizeNames.invoke(anon,"I am David",srcLang,mapTokens);
}
PROBLEM:
I am not able to mock the void second method to update list with value a and b. How can I have the mockto test case to update parameter in void method.
When unit testing a class, you test it through its public methods. If you can't test the class sufficiently through its public methods, it needs re-factored.
In this case, you're trying to unit test a private method for an edge case that doesn't exist. Why even provide the constant as a parameter? Why not reference it directly in the private method and save passing an argument? Instead, you could write:
fr = new FileReader(ANON_DIR + NO_ANON);
EDIT
After Laxmi and I had a discussion we came up with a solution using constructor based dependency injection and changing the void method to return Set<String>. This let us test in isolation and mock easily.

Get int, float, boolean and string from Properties

I have int, float, boolean and string from Properties file. Everything has loaded in Properties. Currently, I am parsing values as I know expected value for particular key.
Boolean.parseBoolean("false");
Integer.parseInt("3")
What is better way of setting these constants values, If I don't know what could be primitive value datatype for a key.
public class Messages {
Properties appProperties = null;
FileInputStream file = null;
public void initialization() throws Exception {
appProperties = new Properties();
try {
loadPropertiesFile();
} catch (Exception e) {
throw new Exception(e.getMessage(), e);
}
}
public void loadPropertiesFile() throws IOException {
String path = "./cfg/message.properties";
file = new FileInputStream(path);
appProperties.load(file);
file.close();
}
}
Properties File.
messassge.properties
SSO_URL = https://example.com/connect/token
SSO_API_USERNAME = test
SSO_API_PASSWORD = Uo88YmMpKUp
SSO_API_SCOPE = intraday_api
SSO_IS_PROXY_ENABLED = false
SSO_MAX_RETRY_COUNT = 3
SSO_FLOAT_VALUE = 3.0
Constant.java
public class Constants {
public static String SSO_URL = null;
public static String SSO_API_USERNAME = null;
public static String SSO_API_PASSWORD = null;
public static String SSO_API_SCOPE = null;
public static boolean SSO_IS_PROXY_ENABLED = false;
public static int SSO_MAX_RETRY_COUNT = 0;
public static float SSO_FLOAT_VALUE = 0;
}
If you have a class of configuration values, like your Constants class, and you want to load all values from a configuration (properties) file, you can create a little helper class and use reflection:
public class ConfigLoader {
public static void load(Class<?> configClass, String file) {
try {
Properties props = new Properties();
try (FileInputStream propStream = new FileInputStream(file)) {
props.load(propStream);
}
for (Field field : configClass.getDeclaredFields())
if (Modifier.isStatic(field.getModifiers()))
field.set(null, getValue(props, field.getName(), field.getType()));
} catch (Exception e) {
throw new RuntimeException("Error loading configuration: " + e, e);
}
}
private static Object getValue(Properties props, String name, Class<?> type) {
String value = props.getProperty(name);
if (value == null)
throw new IllegalArgumentException("Missing configuration value: " + name);
if (type == String.class)
return value;
if (type == boolean.class)
return Boolean.parseBoolean(value);
if (type == int.class)
return Integer.parseInt(value);
if (type == float.class)
return Float.parseFloat(value);
throw new IllegalArgumentException("Unknown configuration value type: " + type.getName());
}
}
Then you call it like this:
ConfigLoader.load(Constants.class, "/path/to/constants.properties");
You can extend the code to handle more types. You can also change it to ignore missing properties, instead of failing like it does now, such that assignments in the field declaration will remain unchanged, i.e. be the default.
If you know the type of constant, you can use Apache Commons Collections.
For example, you can use some utilities method based on type of your constant.
booelan SSO_IS_PROXY_ENABLED = MapUtils.getBooleanValue(appProperties, "SSO_IS_PROXY_ENABLED", false);
String SSO_URL = MapUtils.getString(appProperties, "SSO_URL", "https://example.com/connect/token");
You can even use default values to avoid errors.
Dambros is right, every thing you store inside a Properties file is as a String value.
You can track your different primitive data types after retrieving properties value as below like ref. -
Java Properties File: How to Read config.properties Values in Java?
package crunchify.com.tutorial;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
import java.util.Properties;
/**
* #author Crunchify.com
*
*/
public class CrunchifyGetPropertyValues {
String result = "";
InputStream inputStream;
public String getPropValues() throws IOException {
try {
Properties prop = new Properties();
String propFileName = "config.properties";
inputStream = getClass().getClassLoader().getResourceAsStream(propFileName);
if (inputStream != null) {
prop.load(inputStream);
} else {
throw new FileNotFoundException("property file '" + propFileName + "' not found in the classpath");
}
Date time = new Date(System.currentTimeMillis());
// get the property value and print it out
String user = prop.getProperty("user");
String company1 = prop.getProperty("company1");
String company2 = prop.getProperty("company2");
String company3 = prop.getProperty("company3");
result = "Company List = " + company1 + ", " + company2 + ", " + company3;
System.out.println(result + "\nProgram Ran on " + time + " by user=" + user);
} catch (Exception e) {
System.out.println("Exception: " + e);
} finally {
inputStream.close();
}
return result;
}
}
and later convert to primitive -
How to convert String to primitive type value?
I suggest you to track your data types value by putting the key values inside String type switch statement and later retrieve the related data type value by using key name cases.
String type switch case is possible after Java 7.
Not entirely sure whether I exactly understand the problem but a possibility could be to include the type of the property value in the (String) value. So for example the properties you showed would become something like:
SSO_URL = URL:https://example.com/connect/token
SSO_API_USERNAME = STRING:test
SSO_API_PASSWORD = STRING:Uo88YmMpKUp
SSO_API_SCOPE = STRING:intraday_api
SSO_IS_PROXY_ENABLED = BOOLEAN:false
SSO_MAX_RETRY_COUNT = INTEGER:3
SSO_FLOAT_VALUE = FLOAT:3.0
During the parsing of the property values you first determine the type of the property by looking at the part before : and use the part after for the actual parsing.
private static Object getValue(Properties props, String name) {
String propertyValue = props.getProperty(name);
if (propertyValue == null) {
throw new IllegalArgumentException("Missing configuration value: " + name);
} else {
String[] parts = string.split(":");
switch(parts[0]) {
case "STRING":
return parts[1];
case "BOOLEAN":
return Boolean.parseBoolean(parts[1]);
....
default:
throw new IllegalArgumentException("Unknown configuration value type: " + parts[0]);
}
}
}
Follow the dropwizard configuration pattern where you define your constants using YAML instead of Properties and use Jackson to deserialize it into your Class. Other than type safety, dropwizard's configuration pattern goes one step further by allowing Hibernate Validator annotations to validate that the values fall into your expected ranges.
For dropwizard's example...
http://www.dropwizard.io/0.9.2/docs/getting-started.html#creating-a-configuration-class
For more information about the technology involved...
github.com/FasterXML/jackson-dataformat-yaml
hibernate.org/validator/
Spring Boot has ready to use and feature reach solution for type-safe configuration properties.
Definitely, use of the Spring just for this task is overkill but Spring has a lot of cool features and this one can attract you to right side ;)
You can define your configurable parameters as 'static' in your class of choice, and from a static init call a method that loads the parameter values from a properties file.
For example:
public class MyAppConfig {
final static String propertiesPath="/home/workspace/MyApp/src/config.properties";
static String strParam;
static boolean boolParam;
static int intParam;
static double dblParam;
static {
// Other static initialization tasks...
loadParams();
}
private static void loadParams(){
Properties prop = new Properties();
try (InputStream propStream=new FileInputStream(propertiesPath)){
// Load parameters from config file
prop.load(propStream);
// Second param is default value in case key-pair is missing
strParam=prop.getProperty("StrParam", "foo");
boolParam=Boolean.parseBoolean(prop.getProperty("boolParam", "false"));
intParam= Integer.parseInt(prop.getProperty("intParam", "1"));
dblParam=Double.parseDouble(prop.getProperty("dblParam", "0.05"));
} catch (IOException e) {
logger.severe(e.getMessage());
e.printStackTrace();
}
}
}
This might help:
props.getProperty("name", Integer.class);

Get callers method (java.lang.reflect.Method)

I would like to get the calling method java.lang.reflect.Method. NOT the name of the method.
Here is an example how to get the callers Class.
// find the callers class
Thread t = Thread.getCurrentThread();
Class<?> klass = Class.forName(t.getStackTrace()[2].getClassName());
// do something with the class (like processing its annotations)
...
It's for testing purpose only!
If it's just for testing, then this may work. It assumes that the class files are accessible via the calling class's ClassLoader and that the class files were compiled with debugging symbols (which I hope they are for testing!). This code relies on the ASM bytecode library.
public static Method getMethod(final StackTraceElement stackTraceElement) throws Exception {
final String stackTraceClassName = stackTraceElement.getClassName();
final String stackTraceMethodName = stackTraceElement.getMethodName();
final int stackTraceLineNumber = stackTraceElement.getLineNumber();
Class<?> stackTraceClass = Class.forName(stackTraceClassName);
// I am only using AtomicReference as a container to dump a String into, feel free to ignore it for now
final AtomicReference<String> methodDescriptorReference = new AtomicReference<String>();
String classFileResourceName = "/" + stackTraceClassName.replaceAll("\\.", "/") + ".class";
InputStream classFileStream = stackTraceClass.getResourceAsStream(classFileResourceName);
if (classFileStream == null) {
throw new RuntimeException("Could not acquire the class file containing for the calling class");
}
try {
ClassReader classReader = new ClassReader(classFileStream);
classReader.accept(
new EmptyVisitor() {
#Override
public MethodVisitor visitMethod(int access, final String name, final String desc, String signature, String[] exceptions) {
if (!name.equals(stackTraceMethodName)) {
return null;
}
return new EmptyVisitor() {
#Override
public void visitLineNumber(int line, Label start) {
if (line == stackTraceLineNumber) {
methodDescriptorReference.set(desc);
}
}
};
}
},
0
);
} finally {
classFileStream.close();
}
String methodDescriptor = methodDescriptorReference.get();
if (methodDescriptor == null) {
throw new RuntimeException("Could not find line " + stackTraceLineNumber);
}
for (Method method : stackTraceClass.getMethods()) {
if (stackTraceMethodName.equals(method.getName()) && methodDescriptor.equals(Type.getMethodDescriptor(method))) {
return method;
}
}
throw new RuntimeException("Could not find the calling method");
}
We can almost get there, here's a method that works in many cases. The problem is: it won't work reliably if there are overloaded methods (multiple methods with the same name). The stack trace does not provide the arguments, unfortunately.
private static Method getCallingMethod() throws ClassNotFoundException{
final Thread t = Thread.currentThread();
final StackTraceElement[] stackTrace = t.getStackTrace();
final StackTraceElement ste = stackTrace[2];
final String methodName = ste.getMethodName();
final String className = ste.getClassName();
Class<?> kls = Class.forName(className);
do{
for(final Method candidate : kls.getDeclaredMethods()){
if(candidate.getName().equals(methodName)){
return candidate;
}
}
kls = kls.getSuperclass();
} while(kls != null);
return null;
}
Test code:
public static void main(final String[] args) throws Exception{
System.out.println(getCallingMethod());
}
Output:
public static void foo.bar.Phleem.main(java.lang.String[]) throws java.lang.Exception
OK, here is a solution using ASM. It works for almost all cases:
private static Method getCallingMethod() throws ClassNotFoundException,
IOException{
final Thread t = Thread.currentThread();
final StackTraceElement[] stackTrace = t.getStackTrace();
final StackTraceElement ste = stackTrace[2];
final String methodName = ste.getMethodName();
final int lineNumber = ste.getLineNumber();
final String className = ste.getClassName();
final Class<?> kls = Class.forName(className);
final ClassReader cr = new ClassReader(className);
final EmptyVisitor empty = new EmptyVisitor();
final AtomicReference<Method> holder = new AtomicReference<Method>();
cr.accept(new ClassAdapter(empty){
#Override
public MethodVisitor visitMethod(
final int access,
final String name,
final String desc,
final String signature,
final String[] exceptions){
return name.equals(methodName) ? new MethodAdapter(empty){
#Override
public void visitLineNumber(final int line,
final Label start){
if(line >= lineNumber && holder.get() == null){
final Type[] argumentTypes =
Type.getArgumentTypes(desc);
final Class<?>[] argumentClasses =
new Class[argumentTypes.length];
try{
for(int i = 0; i < argumentTypes.length; i++){
final Type type = argumentTypes[i];
final String dd = type.getDescriptor();
argumentClasses[i] = getClassFromType(type);
}
holder.set(kls.getDeclaredMethod(methodName,
argumentClasses));
} catch(final ClassNotFoundException e){
throw new IllegalStateException(e);
} catch(final SecurityException e){
throw new IllegalStateException(e);
} catch(final NoSuchMethodException e){
throw new IllegalStateException(e);
}
}
super.visitLineNumber(line, start);
}
private Class<?> getClassFromType(final Type type) throws ClassNotFoundException{
Class<?> javaType;
final String descriptor = type.getDescriptor();
if(type.equals(Type.INT_TYPE)){
javaType = Integer.TYPE;
} else if(type.equals(Type.LONG_TYPE)){
javaType = Long.TYPE;
} else if(type.equals(Type.DOUBLE_TYPE)){
javaType = Double.TYPE;
} else if(type.equals(Type.FLOAT_TYPE)){
javaType = Float.TYPE;
} else if(type.equals(Type.BOOLEAN_TYPE)){
javaType = Boolean.TYPE;
} else if(type.equals(Type.BYTE_TYPE)){
javaType = Byte.TYPE;
} else if(type.equals(Type.CHAR_TYPE)){
javaType = Character.TYPE;
} else if(type.equals(Type.SHORT_TYPE)){
javaType = Short.TYPE;
} else if(descriptor.startsWith("[")){
final Class<?> elementType =
getClassFromType(type.getElementType());
javaType =
Array.newInstance(elementType, 0).getClass();
} else{
javaType = Class.forName(type.getClassName());
}
return javaType;
}
}
: null;
}
},
0);
return holder.get();
}
I'll leave it to you to refactor this into something readable. And it won't work if the signature of the calling method contains primitive arrays or multidimensional arrays. Obviously it only works if the class file contains line numbers.
Argghh, I work for ages and then I see that someone has come up with an almost identical solution!!! Anyway, I'll leave mine, because I developed it independently.
quite easy: just get the corresponding Class object first and then use Class.getMethod(String name,params...)
check here for the javadoc
public class GetMethod {
public static void main(String[] args){
new GetMethod().checkMethod();
}
public void checkMethod(){
Thread t=Thread.currentThread();
StackTraceElement element=t.getStackTrace()[1];
System.out.println(element.getClassName());
System.out.println(element.getMethodName());
try{
Method m=Class.forName(element.getClassName()).getMethod(element.getMethodName(),null);
System.out.println("Method: " + m.getName());
}catch (Exception e) {
e.printStackTrace();
}
}
}
hope that helped....
Here is a modified version of Sean Patrick Floyd's posted method for getting a Java Class from an ASM Type. It fixes a problem with multidimensional arrays and another problem with classes loaded by other classloaders.
public static Class<?> getClassFromType(Class<?> clazz, final Type type) throws ClassNotFoundException{
Class<?> javaType = null;
switch( type.getSort() ) {
case Type.VOID : javaType = Void.TYPE; break;
case Type.BOOLEAN : javaType = Boolean.TYPE; break;
case Type.CHAR : javaType = Character.TYPE; break;
case Type.BYTE : javaType = Byte.TYPE; break;
case Type.SHORT : javaType = Short.TYPE; break;
case Type.INT : javaType = Integer.TYPE; break;
case Type.FLOAT : javaType = Float.TYPE; break;
case Type.LONG : javaType = Long.TYPE; break;
case Type.DOUBLE : javaType = Double.TYPE; break;
case Type.ARRAY : javaType = Array.newInstance( getClassFromType( clazz, type.getElementType()), new int[type.getDimensions()] ).getClass(); break;
case Type.OBJECT : javaType = Class.forName( type.getClassName(), false, clazz.getClassLoader() ); break;
}
if ( javaType != null ) return javaType;
throw new ClassNotFoundException( "Couldn't find class for type " + type );
}

Is there a way for reading/writing serializable objects to a RandomAccesFile?

How can I read/write serializable object instances to a RandomAccessFile in Java? I want to be able to do this the same way you do it in c++ through structs. In java only ObjectInputStreams/ObjectOutputStreamscan can read/Write objects. I am amazed Java does not have something already implemented.
Write the data to a ByteArrayOutputStream, via a ObjectOutputStream and then you can put the byte[] into the random access file. You can do the reverse much the same way.
However, why are you trying to do this? I suspect there are products which do this for you such as caches which can be persisted to disk. In this cause you can have just a Map which you put objects into and the library takes care of the rest.
I can see why you would want to do this to read legacy file formats. In that case, default Java serialization mechanisms are a hindrance, not a help. To a degree, you can read/write struct-like classes using reflection.
Sample code:
public static class MyStruct {
public int foo;
public boolean bar = true;
public final byte[] byteArray = new byte[3];
}
public static void main(String[] args) throws IOException {
LegacyFileHandler handler = new LegacyFileHandler();
MyStruct struct = new MyStruct();
RandomAccessFile file = new RandomAccessFile("foo", "rw");
try {
for (int i = 0; i < 4; i++) {
struct.foo = i;
handler.write(file, struct);
}
struct = readRecord(file, handler, 2);
System.out.println(struct.foo);
} finally {
file.close();
}
}
private static MyStruct readRecord(RandomAccessFile file,
LegacyFileHandler handler, int n) throws IOException {
MyStruct struct = new MyStruct();
long pos = n * handler.sizeOf(struct);
file.seek(pos);
handler.read(file, struct);
return struct;
}
The handler class; can handle primitive types and byte arrays, but nothing else:
public class LegacyFileHandler {
private final Map<Class<?>, Method> readMethods = createReadMethodMap();
private final Map<Class<?>, Method> writeMethods = createWriteMethodMap();
private Map<Class<?>, Method> createReadMethodMap() {
Class<DataInput> clazz = DataInput.class;
Class<?>[] noparams = {};
try {
Map<Class<?>, Method> map = new HashMap<Class<?>, Method>();
map.put(Boolean.TYPE, clazz.getMethod("readBoolean", noparams));
map.put(Byte.TYPE, clazz.getMethod("readByte", noparams));
map.put(Character.TYPE, clazz.getMethod("readChar", noparams));
map.put(Double.TYPE, clazz.getMethod("readDouble", noparams));
map.put(Float.TYPE, clazz.getMethod("readFloat", noparams));
map.put(Integer.TYPE, clazz.getMethod("readInt", noparams));
map.put(Long.TYPE, clazz.getMethod("readLong", noparams));
map.put(Short.TYPE, clazz.getMethod("readShort", noparams));
return map;
} catch (NoSuchMethodException e) {
throw new IllegalStateException(e);
}
}
private Map<Class<?>, Method> createWriteMethodMap() {
Class<DataOutput> clazz = DataOutput.class;
try {
Map<Class<?>, Method> map = new HashMap<Class<?>, Method>();
map.put(Boolean.TYPE, clazz.getMethod("writeBoolean",
new Class[] { Boolean.TYPE }));
map.put(Byte.TYPE, clazz.getMethod("writeByte",
new Class[] { Integer.TYPE }));
map.put(Character.TYPE, clazz.getMethod("writeChar",
new Class[] { Integer.TYPE }));
map.put(Double.TYPE, clazz.getMethod("writeDouble",
new Class[] { Double.TYPE }));
map.put(Float.TYPE, clazz.getMethod("writeFloat",
new Class[] { Float.TYPE }));
map.put(Integer.TYPE, clazz.getMethod("writeInt",
new Class[] { Integer.TYPE }));
map.put(Long.TYPE, clazz.getMethod("writeLong",
new Class[] { Long.TYPE }));
map.put(Short.TYPE, clazz.getMethod("writeShort",
new Class[] { Integer.TYPE }));
return map;
} catch (NoSuchMethodException e) {
throw new IllegalStateException(e);
}
}
public int sizeOf(Object struct) throws IOException {
class ByteCounter extends OutputStream {
int count = 0;
#Override
public void write(int b) throws IOException {
count++;
}
}
ByteCounter counter = new ByteCounter();
DataOutputStream dos = new DataOutputStream(counter);
write(dos, struct);
dos.close();
counter.close();
return counter.count;
}
public void write(DataOutput dataOutput, Object struct) throws IOException {
try {
Class<?> clazz = struct.getClass();
for (Field field : clazz.getFields()) {
Class<?> type = field.getType();
if (type == byte[].class) {
byte[] barray = (byte[]) field.get(struct);
dataOutput.write(barray);
continue;
}
Method method = writeMethods.get(type);
if (method != null) {
method.invoke(dataOutput, field.get(struct));
continue;
}
throw new IllegalArgumentException("Type "
+ struct.getClass().getName()
+ " contains unsupported field type " + type.getName()
+ " (" + field.getName() + ")");
}
} catch (IllegalAccessException e) {
throw new IllegalArgumentException(e);
} catch (InvocationTargetException e) {
throw new IllegalStateException(e);
}
}
public void read(DataInput dataInput, Object struct) throws IOException {
try {
Class<?> clazz = struct.getClass();
Object[] noargs = {};
for (Field field : clazz.getFields()) {
Class<?> type = field.getType();
if (type == byte[].class) {
byte[] barray = (byte[]) field.get(struct);
dataInput.readFully(barray);
continue;
}
Method method = readMethods.get(type);
if (method != null) {
Object value = method.invoke(dataInput, noargs);
field.set(struct, value);
continue;
}
throw new IllegalArgumentException("Type "
+ struct.getClass().getName()
+ " contains unsupported field type " + type.getName()
+ " (" + field.getName() + ")");
}
} catch (IllegalAccessException e) {
throw new IllegalArgumentException(e);
} catch (InvocationTargetException e) {
throw new IllegalStateException(e);
}
}
}
This code has only undergone cursory testing.
There are problems with trying to generalise reading/writing data like this.
Dealing with any object type is tricky as you have to convert them to byte arrays yourself and ensure they remain a fixed size.
The biggest problem will probably be keeping all your strings the correct length - you'll need to know a bit about unicode to understand the relationship between character count and encoded byte size.
Java uses network byte order and doesn't really have unsigned types. If you're reading legacy data, you might need to do some bit manipulation.
If I needed to handle a binary format, I would probably encapsulate the functionality in specialized classes which would be able to write/read their state to/from an I/O source. You'll still have the problems in the above list, but it is a more object-oriented approach.
If I had the freedom to choose the file format, I would go with what waqas suggested and use a database.
There is a difference between serialized Java objects, and C structs you write out with fwrite: serialized java objects may not have a fixed size in bytes.
I am amazed Java does not have
something already implemented.
Sure it does. Newer versions of Java ship with Java DB, an embedded database, which does what you want, and much much more.
(Note that this type of databases are fundamentally random access files which work on data types like strings and numbers instead of bytes). This does have the downside that you have to write some JDBC boilerplate.
And if you don't mind including external dependencies, object databases (like db4o) are quite interesting and require minimal code.

Categories

Resources