Sunday, October 31, 2010

Useful abuse of API

Can abuse of API be useful? Sometimes it can be!


Word "abuse" is defined as the improper usage or treatment for a bad purpose
    (from Wikipedia)

    Have you ever used a knife as a screw driver? A screw driver is better but if it is not available a knife can help too.

    How can this principle be applied for programming?

    Implementation of Command pattern

    Who has never implemented command patternIt is simple. Define interface Command with one no-arguments method execute, create as many implementations as you need and use it. But JDK already contains 2 command like interfaces: java.util.concurrent.Callable and java.lang.Runnable, so why not to use them? Such usage has a positive side effect. You can execute this command in separate thread or in thread pool without any modification or wrapping.


    Implementation of Filter pattern

    Sometimes we  have to implement filter pattern. Interfaces FileFilter and FilenameFilter already exist in JDK but they deal with files. This fact does not allow their reuse for other purposes. But there is other interface java.lang.Comparable<T>. This interface is already parametrized and declares method

    int compareTo(T obj)

    It is not expected to be used for filter implementation. Here is the quotation from JDK javadoc:

    "This interface imposes a total ordering on the objects of each class that implements it. This ordering is referred to as the class's natural ordering, and the class's compareTo method is referred to as its natural comparison method."

    But what is the difference between methods boolean accept(T) and int compareTo(T)? Only semantics and result type. I suggest to use this interface to implement external filter. 0 means true, non zero value means false. Let's assume that we have collection of strings and would like to filter only 5 character long elements.

    This is the implementation of Comparable interface:

    
    public class StringLengthFilter implements Comparable<Integer> {
        private String str; 
     
        public StringLengthFilter(String str) {
            this.str = str;
        }
    
        @Override
        public int compareTo(Integer len) {
            return str.length() - len;
        }
        
        public String getString() {
            return str;
        }
    }
    

    The following code snippet counts number of 5 characters long strings in collection:
    
    int count = 0;
    for (String s : src) {
        if (new StringLengthFilter(s).compareTo(5) == 0) {
            count++;
        }
    }
    



    Passing primitive wrappers by reference

    All objects are passed by reference in java. Only primitives and their wrappers are passed by value.
    Argument that is passed to method by reference may be changed by this method. For example:

    Collections.sort(list);

    sorts the given list "in place", i.e. the method sort() changes object passed by reference. We cannot do the same with primitives.

    Value of param is not changed after the following method call:
    
    int param = 1;
    foo(param); 
    // param here is still 1 
    //.............
    void foo(int p) {
        p *= 2; 
    }
    

    Passing immutable argument to method by reference may be implemented using AtomicInteger:
    
    AtomicInteger arg = new AtomicInteger(5);
    foo(arg);
    System.out.println(arg.get()); 
     
    This code snippet will print 10. The good side effect of this method is thread safety.


    Discover available implementations

    Assume that we have interface and several implementations packaged in different JARs that can be available in application classpath. Sometimes application should know all available implementations. For example to choose the best one automatically or to allow the user to choose one from drop down list.
    We can use java.class.path and path.separator to find the application classes, then read classes as resources to perform self discovery and locate available implementations dynamically. The following code snippet looks for all available implementations of BSFEngine interface.
    
        private static Map<String, Boolean> getEngines() throws Exception {
            Map<String, Boolean> result = new HashMap<String, Boolean>();
            String[] pathElements = System.getProperty("java.class.path").split(System.getProperty("path.separator"));
            for (String pathElement : pathElements) {
                File resource = new File(pathElement);
                if (!resource.isFile()) {
                    continue;
                }
                JarFile jar = new JarFile(resource);
                for (Enumeration<JarEntry> e = jar.entries(); e.hasMoreElements();) {
                    JarEntry entry = e.nextElement();
                    if(entry.isDirectory()) {
                        continue;
                    }
                    if(!entry.getName().endsWith("Engine.class")) {
                        continue;
                    }
                    String className = entry.getName().replaceFirst("\\.class$", "").replace('/', '.');
                    try {
                        if(BSFEngine.class.getName().equals(className)) {
                            continue;
                        }
                        Class<?> clazz = Class.forName(className);
                        if(BSFEngine.class.isAssignableFrom(clazz) && !clazz.equals(BSFEngine.class)) {
                            result.put(className, true);
                        }
                    } catch (NoClassDefFoundError ex) {
                        // ignore...
                        result.put(className, false);
                    }
                }
            }
            return result;
        }
            
    
    The following method call
    
    System.out.println(getEngines().toString().replaceFirst("\\{", "").replaceFirst("\\}", "").replace(", ", "\n"));
    

    produces the following output:
    
    org.apache.bsf.engines.xslt.XSLTEngine=true
    org.apache.bsf.engines.jacl.JaclEngine=false
    org.apache.bsf.engines.javascript.JavaScriptEngine=false
    org.apache.bsf.engines.jython.JythonEngine=false
    org.apache.bsf.engines.netrexx.NetRexxEngine=true 

    So, we have discovered all implementations of BSFEngine interface available in the application classpath.


    Use AbstractCollection.toString() for debugging

    Method toString() typically creates human readable text representation of the object. It is not recommended to base any logic on toString() format. But there is no rule without an exception.

    Debugger is a great tool that allows viewing of variable values. Unfortunately sometimes it is not powerful enough. Let's assume that you have the linked list or set that contains 10 thousand objects. You toggle breakpoint and want to check whether specific value is on the list. Good luck to find this object :(.

    To save time I typically use the following technique. I create the following watch expression:
    
    list.toString().substring(1).replaceFirst("]$", "").repalce(" , ", "\n") 

    This code produces multi-line string. Each line contains string representation of the list element. This method works fine for relatively small lists. If the quantity of elements is high I put this expression into System.out.println(), so the list is printed on STDOUT where I can perform textual search.



    Copy Map to Properties

    Since Java 5 is released I prefer to use parametrized collections. Although class java.util.Properties is a part of java collection framework and is typically used to store strings it is not Map<String, String> but Map<Object, Object> for backwards compatibility. So, we cannot copy elements from Map<String, String> to Properties using putAll() that expects in this case Map<Object, Object>. Sometimes I have to copy content of my parametrized collection to properties (for example to use its store() method). The regular solution is to create loop that iterates over all entries of source Map and puts them to Properties object. But sometimes we can save a couple of code lines using AbstractMap.toString() and Properties.load().

    Here is an example. I defined map where both keys and values are Integers:

    Map<Integer, Integer> imap = new HashMap<Integer, Integer>();
    imap.put(1, 10);
    imap.put(2, 20); 

    Now I am copying this map to properties using the following code:
    
    Properties props = new Properties();
    props.load(new StringReader(imap.toString().substring(1).replaceFirst("}$", "").replace(", ", "\n")));
    



    Socket based singleton application

    Typically we use sockets for process-to-process communication. In most cases the processes run on different computers. Usually the processes are different too: one of them is server, other is client. This example shows how application uses socket connection to communicate with other instance of itself that runs on the same machine.

    Typical example of singleton application is MS Windows Task manager. You can run only one instance of this application. If you try to run the second instance the already existing one is activated even if its window was minimized before. I have implemented class SingletonApplication. It tries to connect to predefined port. Connection succeeds if the other instance of application is already running, so my class calls System.exit(1):
    
    new Socket().connect(new InetSocketAddress(port)); 
    System.exit(1);
    

    If connection fails we assume that this is the first instance of the application and open server socket that is listening to connections from other instances.
    
    ServerSocket serverSocket = new ServerSocket(port);
    while(true) {
        serverSocket.accept(); // blocked until client connects
        activateWindow();
    }
    

    Implementation of method activateWindow() brings us to the next abuse:


    Portable window activation

    The goal is to bring existing window on top and select (activate) it. Java focus system is sophisticated and not simple. There are a lot of methods that provide focus control.

    Component.requestFocus works for component that is displayable, visible etc.
    Window.toFront brings the window to front and may make it the focused window if this window is visible. What really happens (at least on Windows) is that the button which represents the window on toolbar starts blinking but the window itself is not focused.
    Window.setAlwaysOnTop(boolean) sets whether this window should always be above other windows. It does not make this window active.

    I spent a lot of time but failed to find a good portable way to put window on top of others and make it active. Finally I found the following trick. First I call window.setAlwaysOnTop(true). This brings window on top of others but does not make it active. Then I move mouse to the window header and simulate left mouse click using java.awt.Robot. Then I move mouse back. It is ugly trick but it works:

    
    mainWindow.setAlwaysOnTop(true);
    Robot robot = new Robot();
    robot.mouseMove(mainWindow.getX() + 40, mainWindow.getY() + 10);
    robot.mousePress(InputEvent.BUTTON1_MASK);
    robot.mouseRelease(InputEvent.BUTTON1_MASK);
    

    I have implemented generic class SingletonApplication that makes each application to behave like Task manager by adding only one line to the beginning of main(String[]) method:

    new SingletonApplication(12345).start();

    Where 12345 is a TCP port that will be used by our application.

    Full source code of SingletonApplication is available here.


    Conclusions

    Typically abuse is bad. But sometimes improper usage of existing mechanism, API or technique may be very useful. "Useful" abuse may save time and produce well designed working code.


    Acknowledgments

    I would like to thank my former manager Avshi Avital that commented one of my design solutions as "elegant abuse." Although it was several years ago and I do not remember details of that abuse, the sentence gave me an idea for this article.

      No comments:

      Post a Comment