Personalized Event Listeners in Java

Although the Observer pattern is implemented natively in Java, sometimes we need to make an event management that suits better our needs when using event listeners.

Some context

The problem of event handling is very simple: We have an object that will be changing its state. Without touching its code, we should be able to “hook” to other objects that are pending status changes and act accordingly. This “hook” must be turned on and off dynamically at runtime.

To implement it we will use a static object, a class and an interface. The static object will be responsible for ensuring the relationships between observers and observables. It will also notify relevant changes to the objects concerned. To pass information during an event we use that class. The interfaces will make the distinction between objects observed and observable objects and any other objects on the application.

A real example

But the best way to see how event listeners works is through an example:

The instances of the class will be those that carry MyCustomEvent information to an observable object from the observed object when an event occurs. This class should contain all the necessary information on that event. For example, if the event was a mouse click, this class should carry data such as coordinates on the screen or the number of clicks. Specifically, our event class contains only the original object that triggered the event (the observable object).

import java.util.EventObject;
import java.util.LinkedList;
import java.util.List;

public class MyCustomEvent extends EventObject {

  private static final long serialVersionUID = 7383182229898306240L;

  private final MyCustomListener source;

  public MyCustomEvent (MyCustomListener OriginalSource) {
    super (OriginalSource);
    this.source = OriginalSource;
  }

 MyCustomListener public getSource () {
    this.source return;
  }
}

The static object that keeps the relations between observers and observables is MyCustomEventHandler. It is very important to use synchronization methods to avoid concurrency issues in multithreaded applications:

import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class MyCustomEventHandler {
  private static final Log log = LogFactory.getLog(MyCustomEventHandler.class);

  private static Map <MyCustomListener, Set > = new LinkedHashMap observable <MyCustomListener, Set > ();

  public static void fire (MyCustomEvent event) {
    log.trace ("fire (" + event + ")");
    try {
      Observers = getRemarks September (source);
      if (Observers! = null) {
        for (final MyCustomListener pl: Observers) {
          try {
            pl.fire (event);
          } catch (Throwable t) {
            log.error (t, t);
          }
        }
      }
    } catch (Throwable t) {
      log.error (t, t);
    }
  } 

  private static synchronized September getRemarks (MyCustomListener source) {
    observables.get return (source);
  }

/ **
* Register to watch to alert you when observable change
*
* @ Param observer
* @ Param observable
* /
  public static synchronized void register (MyCustomListener observer, MyCustomListener observable) {
    Observers getRemarks September = (observable);
    if (null == observers) {
      observers = new HashSet();
    }
    observers.add(observer);
    observables.put(observable, Observers);
  }

/ **
* Deregister not become observers to alert you when observable
* Modify
*
* @ Param observer
* @ Param observable
* /
  public static synchronized void deregister (MyCustomListener observer, MyCustomListener observable) {
    Observers getRemarks September = (observable);
    if (null == Observers) {
      Observers = new HashSet ();
    }
    observers.remove (observer);
    observables.put (observable, Observers);
  }
}

Both objects (observable objects as observers) must implement the interface MyCustomListener. This interface requires implementing the function fire(MyCustomEvent event) . In the observable, this function will be responsible for calling MyCustomEventHandler. For observers, this function will be called when the observed object launches an event.

Actually, it would be convenient to separate the interface into two: one for observers and another for observables. If an object wants to be both observer and observed, it may have problems with this system. But for simplicity for the example we have chosen this implementation.

import java.util.EventListener;

public interface extends EventListener {MyCustomListener
  public void fire (MyCustomEvent event);
}

If we have different types of events (similar to how the MouseListener), all we need to do is to expand MyCustomListener interface with all the functionality we want to have:

import java.util.EventListener;

public interface MyCustomListener extends EventListener {
  public void fireObjectRecycled (MyCustomEvent event);
  public void fireObjectRefreshed (MyCustomEvent event);
  public void fireObjectUpdated (MyCustomEvent event);
}

So, we might use different classes derived from MyCustomEvent for each of these features.

We have to modify the static object MyCustomEventHandler to launch the different events.

References: thread which discusses this type of implementation of Event Handler

How does Memory work on Java

One of the major advantages of Java since its first version was that developers didn’t have to worry about memory, as Java itself was able to keep it clean and free memory automatically. But any good Java developer should know the basics on how Java handles memory to be prevent memory leaks and bottlenecks.

To begin with, Java divides memory into two distinct segments:

  • Heap: instances, variables, …
  • Non-Heap/Perm: code, metadata,…

As the first step to optimize memory in Java, we should focus on the Heap, as it is what we can “control”. The Heap is divided in two generations depending on their lifetime:

  • Young Generation
  • Old Generation

Usually the Young generation is composed of local variables and temporary objects. While the older generation contains structures that are needed during the execution like configurations.

The younger generation is divided into two:

  • Eden: This is where objects are created initially
  • Survivor: It’s like the limbo through which we pass from the Young to the Old generation.

The Garbage Collector

The Garbage Collector is the system making sure the memory is clean. It performs two types of periodic tasks:

  • Minor Collection: Reviews quickly the younger generation.
  • Major Collection: Reviews all the memory, mainly the older generation.

The garbage collector runs at the same time as the normal program execution. Each execution involves a small pause (usually milliseconds) in all the threads that are running at that time. While your application memory remains healthy, the Garbage Collector will limit its actions to minor collections, to not interfere with the flow of the application.

Different memory strategies

There are several implementations of the garbage collector, being the most common the Serial Collector. This implementation, as well as being the simplest, uses only one processor. If you’re using a more powerful machine with multiple processors and a good amount of physical memory, you can activate the Parallel Collector , which uses multiple CPUs at the same time. This improves the way in which the garbage collector works. It can also parallelize the flow of normal execution of the application.

For proper operation and cleaning of memory, we should have little short-lived temporary objects better than long, durable objects. The small temporary objects will stay in the Eden, so they will be collected much earlier and much faster.

Also, having unused objects in memory, although they do not disrupt the execution of the program, will slow the execution of the garbage collection. Because you have to process them over and over again to check if they can be deleted.

At some point it may seem tempting to force a Garbage Collector implementation calling System.gc(). However, this will force a major collection asynchronously, breaking up all the heuristics of the Garbage Collector and stopping your application while it lasts. It is so discouraged that there is an option in the virtual machine to disable these calls: -XX:+DisableExplicitGC

References

To help the task of garbage collection, there are three types of references when defining objects:

  • Weak : It does not prevent the GC to clean it.
  • Soft : The GC respects a little and removes the instance only if memory is needed. Useful for caching, but can be misleading.
  • Phantom: Always returns null. The link doesn’t really point to the object. Can be used to clear instances before taking the object that binds it.

For example we can use WeakHashMap, which works as a HashMap, but using weak references. So, if the key contains an object which is only referenced in the map, it is no longer considered useful and is removed.

FastJTable

Swing JTables of Java by default does not handle frequent updates and a huge number of columns and rows. I created a lighted version of JTable called FastJTable based on the code of the Java Christmas Tree.

This implementation is pretty faster when handling huge amounts of data. Consider taking a look at how the memory works if this is your use case.

/**
* Based on Sun's CTTable (Christmas Tree):
* https://java.sun.com/products/jfc/tsc/articles/ChristmasTree/
*
* @author marias
*/

import java.awt.Component;
import java.awt.Container;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.Rectangle;

import javax.swing.CellRendererPane;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JTable;
import javax.swing.JViewport;
import javax.swing.plaf.basic.BasicTableUI;
import javax.swing.table.TableModel;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
* Based on CTTable from Christmas Tree:
* https://java.sun.com/products/jfc/tsc/articles/ChristmasTree/
*
* @author marias marias@emergya.es
*/
public class FastJTable extends JTable {
private static final Log log = LogFactory.getLog(FastJTable.class);
private static final long serialVersionUID = -3218140266706898440L;

private JScrollPane scrollPane;

public FastJTable(TableModel model) {
super(model);
}

public void updateUI() {
super.updateUI();
setUI(new FastTableUI(this));
}

private static class FastTableUI extends BasicTableUI {

public FastTableUI(FastJTable table) {
super();
installUI(table);
}

@Override
public void installUI(JComponent c) {
// Overriden to install our own CellRendererPane
super.installUI(c);
c.remove(rendererPane);
rendererPane = new FastCellRendererPane();
c.add(rendererPane);
}
}

/**
* FastCellRendererPane overrides paintComponent to NOT clone the Graphics
* passed in and NOT validate the Component passed in. This will NOT work if
* the painting code of the Component clobbers the graphics (scales it,
* installs a Paint on it...) and will NOT work if the Component needs to be
* validated before being painted.
*/
private static class FastCellRendererPane extends CellRendererPane {
private static final long serialVersionUID = 4811773663334451913L;
private JViewport viewport;

public FastCellRendererPane() {
super();
}

// Can be ignored, we don't exist in the containment hierarchy.
public void repaint() {
}

@Override
public void paintComponent(Graphics g, Component c, Container p, int x,
int y, int w, int h, boolean shouldValidate) {
try {
if (c == null || !isVisible(new Rectangle(x, y, w, h))) {
log.trace("No lo pintamos (" + c + ")");
return;
}

// if (p != null) {
// Color oldColor = g.getColor();
// g.setColor(p.getBackground());
// g.fillRect(x, y, w, h);
// g.setColor(oldColor);
// }

if (c.getParent() != this) {
this.add(c);
}

c.setBounds(x, y, w, h);

// As we are only interested in using a JLabel as the renderer,
// which does nothing in validate we can override this to do
// nothing, if you need to support components that can do
// layout,
// this will need to be commented out, or conditionally
// validate.
if (!(c instanceof JLabel))
c.validate();

// JComponent jc = (c instanceof JComponent) ? (JComponent) c
// : null;
// jc.setDoubleBuffered(true);

// Don't create a new Graphics, reset the clip and translate
// the origin.
Rectangle clip = g.getClipBounds(c.getBounds());
g.clipRect(x, y, w, h);
g.translate(x, y);
c.paint(g);
g.translate(-x, -y);
g.setClip(clip.x, clip.y, clip.width, clip.height);
c.setBounds(-w, -h, 0, 0);
} catch (Throwable t) {
log.error("Error al pintar el componente de la tabla ", t);
}
}

/**
* We only paint the visible parts of the JTable.
* @param rectangle visible
* @return if it has to be painted on screen
*/
public boolean isVisible(Rectangle rectangle) {
if (viewport == null)
return true;

			Rectangle visRect = viewport.getViewRect();
			int xmin = ((Double) rectangle.getMinX()).intValue();
			int ymin = ((Double) rectangle.getMinY()).intValue();
			int xmax = ((Double) rectangle.getMaxX()).intValue();
			int ymax = ((Double) rectangle.getMaxY()).intValue();
			return (visRect.contains(new Point(xmin, ymin))
					|| visRect.contains(new Point(xmax, ymin))
					|| visRect.contains(new Point(xmin, ymax)) || visRect
					.contains(new Point(xmax, ymax)));
		}
	}
}
en_GBEnglish (UK)