interrogating your code – dipping your toes into java’s reflection

Perhaps the coolest programming language ability that I was never allowed to use was reflection in Java. Why? Well, thats another story.

What exactly is reflection? Well, reflection is a set of low level methods that can be used to inspect code while the program that is doing the inspecting is running.

This puts the program in a very unique situation where it can interrogate objects that it is working with. Thus the program can then make decisions based on what information it finds on the class that it is inspecting.

This makes it possible to inspect an object to see if it contains a certain method. This allows your program to make the decision which method to use if multiple valid methods exist. One, perhaps somewhat contrived, example of this would be that your program could check and see if the new 2.0 version of your method existed.

Method method = null;
try {
    method = financialCalcObj.getClass().getMethod("calcBreakEvenV2", null);
}
catch (Exceptione ex)
{
    // not really an error, use standard method
}
try {
    if (method == null)
        method = financialCalcObj.getClass().getMethod("calcBreakEvenV1", null);
}
catch (Exceptione ex)
{
    // wow, thats bad
    System.exit(1);
}
method.invoke(financialCalcObj, null);

If this doesn’t seem pretty amazing just remember that you cannot do the following unless you already have all methods defined at compile time.  (which does defeat the idea of trying to future proof your code)

try {
    financialCalcObj.calcBreakEvenV2();
}
catch (Exception ex)
{
    financialCalcObj.calcBreakEvenV1();
}

The difference between the two sets of code is that in the reflection example was compiled before version 2 of this method existed.  We just knew that it would show up eventually.  This could be extended to either use the highest version that existed or to use the version of a method based on a command line argument.

No magic just a bit of forethought required

A program that uses reflection in this manner might not make much of a difference if the same set of class files or jar files are used each time.  It does allow some flexibility but if the code base never really changes it is not necessary to go to these extremes just to change how the program behaves at runtime.

The program that I had been envisioning  was going to use this technology to dynamically support multiple language text.  The dynamic part was the program would convert numbers into text of the desired language.  The program would run every few minutes so it would be possible to exchange jar files between runs, however I think there is an even better solution.

ie.

    numberToTextGerman
    numberToTextEnglish
    numberToTextFrench

Instead of continuously replacing the language jar – each time with more languages I would take a slightly different route.  I would structure the program in such a way that each new language with its methods would exist in its own jar file.

The actual program itself would retrieve the data which needs to be processed and this would include the form language.  Most of the form text might be read from a database table or a possibly a file but the conversion between numbers and their textual descriptions would need to be some sort of program.

Because each language would exist in its own jar file and because the program would be receiving the target language in the input data, it would be possible to support new target languages whenever they were ready.

Simply copy the new jar file to the program directory and change the list of supported languages in the application.  The program would do the rest.  Well, the script or batch file that calls the program does need to ensure that it will include the new jar file.

I simply create the classpath from the list of jar files available and the script which runs the program would be called periodically from the crontab or other scheduler.  So the simple act of adding the jar file to the programs library directory automatically adds support to the program for this new language each time the script is run.

Reflection classes and methods

The Java API has the Method class which can be used to retrieve and inspect methods.  It is possible to query the name, which parameters types it expects, the return type and of course the ability to call the method.

class Method

  • String getName()
    Returns the name of the method.
  • Class[] getParameterTypes()
    Returns an array of Class objects which represent the parameters, in the order of declaration.
  • Class getReturnType()
    Returns a Class object that represents the return type of the method.
  • Object invoke(Object obj, Object... args)
    Invokes the underlying method represented by this Method object with the specified parameters.

class Field

The Method class allows you to interrogate a method to see exactly what parameters it is expecting and so forth but it is also possible to perform similar queries but on the class level for defined variables.

Once you have retrieved your variable from the class it is it can be used to then query information about the fields.  It is possible to retrieve the name, the type or even the value.

  • String getName()
    Returns the name of the field.
  • Class getType()
    Returns the type of the variable.
  • Object get(Object obj)
    Returns a Class object that represents the return type of the method.

 

Multilingual support example

For this example, it is expected that every language support class must have a certain core set of methods.  The easiest way to ensure that this happens is to create an interface of those methods.

To demonstrate this rather interesting idea using reflection, I am not going to have complete language functionality just enough to demonstrate how it works.  The language classes will each have to support two numberToText methods.

package com.acmesoft.support;

public interface languageInterface {

	public String numberToText(long value);
	public String numberToText(double value);
}

The actual reflection magic happens in the Numbers class. This class essentially is a small dispatcher by looking up the method based on the language that is passed in.

package com.acmesoft.support;

import java.lang.reflect.Method;

public class Numbers 
{
	public static String number2TextClass = "Numbers";
	public static String number2TextMethod = "numberToText";

	public String numberToText(String language, Integer val) throws Exception 
	{
		String outputtext = "";
		
		try{
			String searchObject = "com.acmesoft.support." + language ;
			Class cls = Class.forName(searchObject);
			Object obj = cls.newInstance();
			Method method = null;

			/* for methods with other parameters 
			//no paramater
			Class noparams[] = {};

			//String parameter
			Class[] paramString = new Class[1];	
			paramString[0] = String.class;

			//Double parameter
			Class[] paramDouble = new Class[1];	
			paramDouble[0] = Double.TYPE;
			*/

			//Long parameter
			Class[] paramLong = new Class[1];	
			paramLong[0] = Long.TYPE;
			
			// lets find out what the number is in words
			method = cls.getDeclaredMethod(number2TextMethod, paramLong);
			outputtext = (String)method.invoke(obj, val);
		}
		catch(Exception ex)
		{
			/*
			ex.printStackTrace();
			System.out.println(ex.toString());
			System.out.println(ex.getLocalizedMessage());
			*/
			throw ex;
		}
		
		return outputtext;
	}
}

Lines 15-17 actually lookup the class that we will need for this method.  This will be the actual language class (ie English).  Lines 34-35 prepare the type of parameter that will be used by this method as well as performing the lookup of the method with the getDeclareMethod call.  Lines 38-39 simply calls this method with our parameter.

The actual language implementations are actually rather sparse.  Having the code to convert 73626.82 into either either German or English although interesting doesn’t actually reinforce anything related to reflection so it has been simplified.

package com.acmesoft.support;

public class en implements languageInterface 
{
	public String numberToText(long val)
	{
		String retval = "";
		
		switch ((int)val)
		{
		case 1:
			retval = "one";
			break;
			
		case 2:
			retval = "two";
			break;
			
		case 3:
			retval = "three";
			break;
			
		default:
			retval = "unknown";
		}
		return retval;
	}
	
	public String numberToText(double val)
	{
		return "not yet";
	}
	
	public void testPgm(String[] args)
	{
		int val = 2;
		String valText = "";
		
		if (args.length != 0)
			val = Integer.parseInt(args[0]);
		
		try 
		{
			valText = numberToText(val);
		} 
		catch (Exception e) 
		{
			e.printStackTrace();
		}
		
		System.out.println(val + " is equal to " + valText);

	}
	public static void main(String[] args)
	{
		en pgm = new en();
		pgm.testPgm(args);
		return ;
	}
}

There is nothing in this class “en” sample code that you won’t be able to recreate in perhaps in the first few days of introduction to Java.  It is only a few lines of reflection code in our Number class that provides the flexibility.

#!/bin/bash

CP=base/final/internationalBase.jar:german/final/internationalDE.jar:english/final/internationalEN.jar:.

java -cp $CP com.acmesoft.support.TestPgm  1 en de fr
echo " "
java -cp $CP com.acmesoft.support.TestPgm  2 en de xx
echo " "
java -cp $CP com.acmesoft.support.TestPgm  3 en de

testrun

Download complete source for this language example

The darker side of reflection

The ability to get either the value or method directly is much more powerful than it appears at the first glance.  It is actually possible to retrieve values from private variables or call private methods that you normally wouldn’t have access to.

The neat thing about this is you only need to add one additional method call to enable the actual access to the value or method.

For Private methods

Method method = yourObject.getClass().getDeclaredMethod(yourMethod, argumentTypes);
method.setAccessible(true);
return method.invoke(yourObject, yourParameters);

For private fields

Field field = yourObject.getClass().getDeclaredField(yourField);
field.setAccessible(true);
field.set(yourObject, value);

Ignoring access permissions example

package payroll;

import java.lang.reflect.*;

public class Interrogate {

    public void showIncome(PayrollDept myobject)
    {
        Field privateIncomeField = null;
        
        try {
            privateIncomeField = myobject.getClass().getDeclaredField("income");
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        }
        double income = 0;
        try {
            privateIncomeField.setAccessible(true);
            income = privateIncomeField.getDouble(myobject);
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        System.out.println("Income " + income);
    }
    
    public void hackIncome(PayrollDept myobject)
    {
        Field privateIncomeField = null;
        
        try {
            privateIncomeField = myobject.getClass().getDeclaredField("income");
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        }
        double income = 0;
        try {
            privateIncomeField.setAccessible(true);
            privateIncomeField.set(myobject, 1000000.0);
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

    }
    
    public Interrogate()
    {
        PayrollDept myobject = new PayrollDept();
        
        System.out.println("Name   " + myobject.firstName + " "+ myobject.lastName + ", " + myobject.title);
        System.out.println("Eyes   " + myobject.eyeColor);
        System.out.println("Height " + myobject.height);
        System.out.println("Weight " + myobject.weight);

        showIncome(myobject);
        hackIncome(myobject);
        showIncome(myobject);
        
        Method increaseIncomeMethod = null;
        Class[] paramDouble = new Class[1];
        paramDouble[0] = Double.TYPE;
        try {
            increaseIncomeMethod = myobject.getClass().getDeclaredMethod("setIncome", paramDouble);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        }
        try {
            increaseIncomeMethod.setAccessible(true);
            increaseIncomeMethod.invoke(myobject,2000000.0);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    
        System.out.println("Income " + myobject.verifyIncome());
    }
    public static void main(String[] args) 
    {
        new Interrogate();
    }
}
package payroll;

public class PayrollDept extends Object
{
    public String title ;
    public int height;
    public String eyeColor;
    public double weight;
    public String firstName;
    public String lastName;
    private double income ;
    
    public PayrollDept()
    {
        title = "CEO";
        height = 204;
        eyeColor = "hazel";
        weight = 90;
        income = 1000;
        firstName = "Max";
        lastName = "Musterman";
    }
    
    private void setIncome(double val)
    {
        income = val;
    }
    private double getIncome()
    {
        return income;
    }

    public double verifyIncome()
    {
        return income;
    }
}
#!/bin/bash

CP=final/darkside.jar

java -cp $CP payroll.Interrogate

darksidetest

Download complete source for this “darkside” example

This entry was posted in programming and tagged . Bookmark the permalink.