Word "abuse" is defined as the improper usage or treatment for a bad purpose
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 pattern? It 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 methodint 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