views:

612

answers:

6

I would like to use the java.text.Normalizer class from Java 1.6 to do Unicode normalization, but my code has to be able to run on Java 1.5.

I don't mind if the code running on 1.5 doesn't do normalization, but I don't want it to give NoClassDefFoundErrors or ClassNotFoundExceptions when it runs.

What's the best way to achieve this?

+9  A: 
public interface NfcNormalizer
{
  public String normalize(String str);
}

public class IdentityNfcNormalizer implements NfcNormalizer
{
  public String normalize(String str)
  {
    return str;
  }
}

public class JDK16NfcNormalizer implements NfcNormalizer
{
  public String normalize(String str)
  {
    return Normalizer.normalize(str, Normalizer.Form.NFC);
  }
}

In your client code:

NfcNormalizer normalizer;
try
{
  normalizer = Class.forName("JDK16NfcNormalizer").newInstance();
}
catch(Exception e)
{
  normalizer = new IdentityNfcNormalizer();
}
Andrew Duffy
+13  A: 

The usual way of doing this is via reflection, i.e. don't refer directly to the class in question, but invoke it programmatically. This allows you to catch exceptions gracefully if the code in question doesn't exist, and either ignore it, or try something else. Reflection throws ClassNotFoundException, which is a nice normal exception, rather than NoClassDefFoundError, which is a bit scarier.

In the case of java.text.Normalizer, this should be pretty easy, since it's just a couple of static methods, and easy to invoke via reflection.

skaffman
@simonn: See http://stackoverflow.com/questions/1279910 for code that probably does what you want.
Grant Wagner
+3  A: 

I don't mind if the code running on 1.5 doesn't do normalization, but I don't want it to give NoClassDefFoundErrors or ClassNotFoundExceptions when it runs.

If you want to avoid reflection, you can actually catch those Errors.

This way, you can compile against the shiny new classes with a Java6 compiler, and it will still work (as in "not do anything, but also not crash") on Java5.

You can also combine the two approaches, and check if the class exists using reflection, and if it does continue to call it in a non-reflective way. This is what Andrew's solution is doing.

If you also need to compile on Java5, then you need to go reflection all the way.

Thilo
+1  A: 

I have had this same need, since we have code that needs to run on all versions of Java from Java 1.2, but some code needs to take advantage of newer API's if they are available.

After various permutations using reflection to obtain Method objects and invoking them dynamically, I have settled on a wrapper style approach as best, in general (although under some circumstances, just storing the reflected Method as a static and invoking it is better - it depends).

Following is an example "System Utility" class which exposes certain newer API's for Java 5 when running an earlier version - the same principles hold for Java 6 in earlier JVMs. This example uses a Singleton, but could easily instantiate multiple objects if the underlying API needed that.

There are two classes:

  • SysUtil
  • SysUtil_J5

The latter is the one used if the run-time JVM is Java 5 or later. Otherwise fallback methods which are compatible in contract are used from the default implementation in SysUtil which utilizes only Java 4 or earlier APIs. Each class is compiled with the specific version's compiler, so that there is no accidental usage of a Java 5+ API in the Java 4 class:

SysUtil (compiled with the Java 4 compiler)

import java.io.*;
import java.util.*;

/**
 * Masks direct use of select system methods to allow transparent use of facilities only
 * available in Java 5+ JVM.
 *
 * Threading Design : [ ] Single Threaded  [x] Threadsafe  [ ] Immutable  [ ] Isolated
 */

public class SysUtil
extends Object
{

/** Package protected to allow subclass SysUtil_J5 to invoke it. */
SysUtil() {
    super();
    }

// *****************************************************************************
// INSTANCE METHODS - SUBCLASS OVERRIDE REQUIRED
// *****************************************************************************

/** Package protected to allow subclass SysUtil_J5 to override it. */
int availableProcessors() {
    return 1;
    }

/** Package protected to allow subclass SysUtil_J5 to override it. */
long milliTime() {
    return System.currentTimeMillis();
    }

/** Package protected to allow subclass SysUtil_J5 to override it. */
long nanoTime() {
    return (System.currentTimeMillis()*1000000L);
    }

// *****************************************************************************
// STATIC PROPERTIES
// *****************************************************************************

static private final SysUtil            INSTANCE;
static {
    SysUtil                             instance=null;

    try                  { instance=(SysUtil)Class.forName("SysUtil_J5").newInstance(); } // can't use new SysUtil_J5() - compiler reports "class file has wrong version 49.0, should be 47.0"
    catch(Throwable thr) { instance=new SysUtil();                                                                    }
    INSTANCE=instance;
    }

// *****************************************************************************
// STATIC METHODS
// *****************************************************************************

/**
 * Returns the number of processors available to the Java virtual machine.
 * <p>
 * This value may change during a particular invocation of the virtual machine. Applications that are sensitive to the
 * number of available processors should therefore occasionally poll this property and adjust their resource usage
 * appropriately.
 */
static public int getAvailableProcessors() {
    return INSTANCE.availableProcessors();
    }

/**
 * Returns the current time in milliseconds.
 * <p>
 * Note that while the unit of time of the return value is a millisecond, the granularity of the value depends on the
 * underlying operating system and may be larger. For example, many operating systems measure time in units of tens of
 * milliseconds.
 * <p>
 * See the description of the class Date for a discussion of slight discrepancies that may arise between "computer time"
 * and coordinated universal time (UTC).
 * <p>
 * @return         The difference, measured in milliseconds, between the current time and midnight, January 1, 1970 UTC.
 */
static public long getMilliTime() {
    return INSTANCE.milliTime();
    }

/**
 * Returns the current value of the most precise available system timer, in nanoseconds.
 * <p>
 * This method can only be used to measure elapsed time and is not related to any other notion of system or wall-clock
 * time. The value returned represents nanoseconds since some fixed but arbitrary time (perhaps in the future, so values
 * may be negative). This method provides nanosecond precision, but not necessarily nanosecond accuracy. No guarantees
 * are made about how frequently values change. Differences in successive calls that span greater than approximately 292
 * years (263 nanoseconds) will not accurately compute elapsed time due to numerical overflow.
 * <p>
 * For example, to measure how long some code takes to execute:
 * <p><pre>
 *    long startTime = SysUtil.getNanoTime();
 *    // ... the code being measured ...
 *    long estimatedTime = SysUtil.getNanoTime() - startTime;
 * </pre>
 * <p>
 * @return          The current value of the system timer, in nanoseconds.
 */
static public long getNanoTime() {
    return INSTANCE.nanoTime();
    }

} // END PUBLIC CLASS

SysUtil_J5 (compiled with the Java 5 compiler)

import java.util.*;

class SysUtil_J5
extends SysUtil
{

private final Runtime                   runtime;

SysUtil_J5() {
    super();

    runtime=Runtime.getRuntime();
    }

// *****************************************************************************
// INSTANCE METHODS
// *****************************************************************************

int availableProcessors() {
    return runtime.availableProcessors();
    }

long milliTime() {
    return System.currentTimeMillis();
    }

long nanoTime() {
    return System.nanoTime();
    }

} // END PUBLIC CLASS
Software Monkey
+1  A: 

Check/use/modify class info.olteanu.utils.TextNormalizer in Phramer project (http://sourceforge.net/projects/phramer/ , www.phramer.org ) - the code is BSD licensed.

That code can be compiled in Java 5 and runs both in Java 5 or Java 6 (or future Java versions). Also, it can be compiled in Java 6 and be run in Java 5 (if it was compiled with the proper "-target", for bytecode compatibility) or Java 6 or any other future version.

IMHO this fully solves your problem - you are free to compile on any Java 5+ platform, and you are able to get the functionality desired (normalization) on any Java 5+ platform (*)

(*) The SUN Java 5 solution for normalization will most likely not be present on all Java 5 implementations, so in the worst case scenario you will end up getting a ClassNotFoundException when you call getNormalizationStringFilter() method.

Marian
A: 
 String str = "éèà";
 try {
  Class c = Class.forName("java.text.Normalizer");
  Class f = Class.forName("java.text.Normalizer$Form");
  Field ff = f.getField("NFD");
  Method m = c.getDeclaredMethod("normalize", new Class[]{java.lang.CharSequence.class,f});
  temp = (String) m.invoke(null, new Object[]{str,ff.get(null)});
 } catch (Throwable e) {
  System.err.println("Unsupported Normalisation method (jvm <1.6)");
 }
 System.out.println(temp+" should produce [eea]");
MrPatol