Search This Blog

Monday, July 21, 2008

Java Instrumentation, javaassist and a new hero Mr.Ted Neward

One of the most interesting sessions that I attended at NFJS was by Ted Neward called " The Busy Java Developer's Guide to Hacking (on) the JDK". I found Mr.Neward to be a very engaging speaker. One of the topics he covered during the session was how to agument java byte code dynamically and the use of the java instrumentation API. I have not directly had a chance to play/work with the same. Regarding byte code enhancement, asm does that and I have used libraries that utilize the same.

Mr.Neward introduced me to javassist, a neat tool to perform byte code augmentation at run time. Based of the same, I have below a simple Maven project that attempts to profile classes.

Keeping the scope simple, the example will only profile classes that are part of the package "com.welflex" and beyond. The code will point out when a method is entered and upon exit of the method and will also print out the time taken for the execution of the method.

The strategy used is that the original method is dynamically replaced with a method that the proxy will delegate to. The original method is named differently (with an impl) and the new proxy method takes the place of the original method and proxies to the new method.

The example is based of maven multi-module project. There are two modules, the first being the actual profiler and the second is an example that will be profiled.
.
-- pom.xml
-- profiler-instrumentation
-- pom.xml
`-- src
`-- main
-- java
`-- welflex
`-- instrumentation
`-- ProfilerClassFileTransformer.java
`-- resources
`-- META-INF
`-- MANIFEST.MF
`-- profiler-target
-- pom.xml
`-- src
`-- main
`-- java
`-- com
`-- welflex
`-- profiletarget
-- Application.java
`-- Description.java



To start of, we define an implementation of the ClassFileTransformer called ProfilerClassTranformer whose core method shown below is responsible for replacing the byte code:
private void addProfilingInformation(CtClass clas, CtMethod mold) throws NotFoundException,
CannotCompileException {
// get the method information (throws exception if method with
// given name is not declared directly by this class, returns
// arbitrary choice if more than one with the given name)
String mname = mold.getName();
String longName = mold.getLongName();

// rename old method to synthetic name, then duplicate the
// method with original name for use as interceptor
String nname = mname + "$impl";

mold.setName(nname);
CtMethod mnew = CtNewMethod.copy(mold, mname, clas, null);

// start the body text generation by saving the start time
// to a local variable, then call the timed method; the
// actual code generated needs to depend on whether the
// timed method returns a value
String type = mold.getReturnType().getName();
StringBuffer body = new StringBuffer();
body.append("{\n");
body.append("int currentStackDepth = Thread.currentThread().getStackTrace().length;\n");
body.append("StringBuffer buf = new StringBuffer();");
body.append("for (int zs = 0; zs < currentStackDepth; zs++) {")
.append("buf.append(\" \");\n")
.append("}");

body.append(" System.out.println(buf.toString() + \"-> Enter Method:" + longName + "\");\n");
body.append(" long startTime = System.nanoTime();\n");
body.append("try {\n");

if (!"void".equals(type)) {
body.append(type + " result = ");
}

body.append(nname + "($$);\n");

if (!"void".equals(type)) {
body.append("return result;\n");
}

body.append("} finally {");
// finish body text generation with call to print the timing
// information, and return saved value (if not void)
body.append("long endTime = System.nanoTime();\n");
body.append("long delta = endTime - startTime;\n");

body.append("System.out.println(buf.toString() + \"<- Exit Method:" + longName +
" completed in \" + delta + \" nano secs\");\n");

body.append(" }\n");
body.append("}");

// replace the body of the interceptor method with generated
// code block and add it to class
mnew.setBody(body.toString());
clas.addMethod(mnew);
}
The code utilizes javassist in augmenting the original methods with profiling information. The target jar produced has a references to the javaassist.jar through the MANIFEST.MF (thanks to my boss for reminding Ted Neward of the same).

The above code should be rather simple to follow where the original method is being replaced by the "Aspect" like code. After downloading the project, issue a "mvn install" command to install the modules. I have additionally provided a ZIP file containing the resultant jars HERE.

In order to ensure that the profiler is applied on the code base, the following java command is executed from within a folder that contains the profiler-instrumentation, javaassist and profiler-target jars as shown below:
java -javaagent:./profiler-instrumentation-1.0-SNAPSHOT.jar -classpath .:profiler-target-1.0-SNAPSHOT.jar com.welflex.profiletarget.Application
-> Enter Method:com.welflex.profiletarget.Application.main(java.lang.String[])
-> Enter Method:com.welflex.profiletarget.Description.setDescription(java.lang.String)
<- Exit Method:com.welflex.profiletarget.Description.setDescription(java.lang.String) completed in 10415 nanosecs
-> Enter Method:com.welflex.profiletarget.Application.rest()
<- Exit Method:com.welflex.profiletarget.Application.rest() completed in 1000072374 nano secs
-> Enter Method:com.welflex.profiletarget.Application.rest(long)
<- Exit Method:com.welflex.profiletarget.Application.rest(long) completed in 3000058065 nano secs
-> Enter Method:com.welflex.profiletarget.Application.getDescription()
-> Enter Method:com.welflex.profiletarget.Description.getDescription()
<- Exit Method:com.welflex.profiletarget.Description.getDescription() completed in 5265 nano secs
<- Exit Method:com.welflex.profiletarget.Application.getDescription() completed in 207834 nano secs
Descripion of App:Simple App
<- Exit Method:com.welflex.profiletarget.Application.main(java.lang.String[]) completed in 4037331452 nano secsva

As seen above the key is to specify a "javaagent" via the "-javaagent" argument to the VM. As shown above that although the profiler-target code did not have any profiling information, we were able to instrument the same at run time using javaassist and the Java Instrumentation library. The above execution shows how long each method took to execute.

The MAVEN project for the profiler can be found HERE . I am running the example with JDK1.6.X and Maven 2.0.8.

The code is largely based of the following two fantastic blogs/articles:
Java Programming Dynamics, Part 4 by Dennis Sosnoski and
Add Logging at Class Load Time with Java Instrumentation
by Thorbjørn Ravn Andersen

If you have problems accessing the source or running the example..ping me. Thaks Mr.Neward..I have such a long way to go...the more I learn, there is still so much more to learn (please don't count the unlearn part) ! The "Force is infinite, I am not yet ready for the Jedi Council", I am still a Padwan! But...a Happy Padwan at that :-)))!

Friday, July 18, 2008

Attending a Symposium/Conference for the first time...

8+ years of Software development and this is the first time I am attending some sort of conference. I have previously not done the same as I did not want to pay the $$$ from my pocket. None of my previous employers have ever decided to shell out money for "my" growth :-)...Thankfully Overstock is so much different. They pay for any developer who wishes to attend a conference and infact encourage the same. Nice to be part of a company that invests back in their employees.

Anyway, I attended an interesting talk where I had my former boss and current boss in the same room. My former boss being a presenter and my current boss part of the audience along with me. I must admit, it is a bit intimidating thinking of the following fictional nightmarish conversation that might have ensued between them:

Former Boss: Can't believe you guys hired the moron..!
Current Boss: Yep! Me too, this is definitely not a case where the phrase, 'Your loss is our gain' is applicable.
Former Boss: He had his moments with us, I am curious as to what damage he has done to you?
Current Boss: Let me see...From the time he has landed, we have suffered quite a few disasters....I really have no way of linking the disasters to him, but I am leaning toward bad karma that most likely is following him.."
Former Boss: "I totally understand..ever since he left, our stock prices rose.."
Current Boss: "You know, since he came, our stock price actually rose, so maybe there is some redemption for him..but then again, our stock fell pretty bad today...I feel he is a time bomb..Any chances we can pay you to take him back?"
......
....more stuff...EEEP!

I hope the above is only part of my dreams ;-))....Lol !

Anyway, regarding NFJS..from what I attended not bad, I was not blown away or can say I enjoyed all the talks but one cannot hope to satisfy everyone.

...I think I might be able to do something like this, atleast that is the goal...Just need a good enough topic.."Sadness that there is no originality in me :-("....I need a good counsellor, and puhlease, I don't mean a shrink!

I liked the talk about base Java by Venkat Subramaniam...a very lively speaker, some topics were basic but the presentation was super, I must admit I learned stuff! I liked Ken Sipe's "Hacking, the Dark Arts"..nicely done with lot of examples, tips etc...

Got another day of NFJS tomorrow...can't wait to get there...I hope I can goto Java One one of these days..I just want to sight Gosling or Rod Johnson or Martin Fowler and that will be more memorable than the close encounter of the third kind I had the other day ;-)

Wednesday, July 16, 2008

More on the equals()/hashCode() Hack with Hibernate

The last blog I did not address Object Retrival. A reader of the blog mentioned the same. Prior to Hibernate 3, I beleive that custom collections could not be supported. However, as of Hibernate 3, they are. R.J.Lorimer's blog on how to implement the same is super!

The use of a the attribute "collection-type" when defining the mapping of the Set is the key to the solution.

So moving forward, for Hibernate, we define a custom CollectionType that factories the PropertyListenerHashSet as shown below:

  1 public class HibernatePropListenerHashSetType implements UserCollectionType {
2 public boolean contains(Object collection, Object entity) {
3 Set set = (Set) collection;
4 return set.contains(entity);
5 }
6
7 public Iterator getElementsIterator(Object collection) {
8 return ((Set) collection).iterator();
9 }
10
11 public Object indexOf(Object collection, Object entity) {
12 return null;
13 }
14
15 public Object instantiate(int anticipatedSize) {
16 return new PropertyListenerHashSet();
17 }
18
19 public PersistentCollection instantiate(SessionImplementor session, CollectionPersister persister) throws HibernateException {
20 return new PersistentSet(session);
21 }
22
23 @SuppressWarnings("unchecked") public Object replaceElements(Object original, Object target,
24 CollectionPersister persister, Object owner, Map copyCache, SessionImplementor session) throws HibernateException {
25 Set setA = (Set) original;
26 Set setB = (Set) target;
27 setB.clear();
28 setB.addAll(setA);
29
30 return null;
31 }
32
33 public PersistentCollection wrap(SessionImplementor session, Object collection) {
34 return new PersistentSet(session, (Set) collection);
35 }
36 }
Pretty simple...now the blocker!! Hibernate does not have an annotation that will support the custom collection-type, for example, something like @CollectionType (name=FooCollection.class). This hibernate forum entry describes the situation. It might be supported at a later date.

In the meanwhile, we can still achieve the custom collection type using hbm mappings :-). So reverting to XML mapping, we defined the Organization object's mapping to use our custom collections as follows:

  1 <set name="applications"  inverse="true" cascade="all-delete-orphan"
2 collection-type="com.welflex.collection.HibernatePropListenerHashSetType">
3 <key column="ORG_ID" not-null="true"/>
4 <one-to-many class="com.welflex.model.SmartApplication" />
5 </set>


The unit test has been augmented to test for the same:

  1     try {
2 session = HibernateUtil.getSessionFactory().getCurrentSession();
3 tx = session.beginTransaction();
4 Organization org = (Organization) session.load(Organization.class, orgId);
5 org.addApplication(app);
6 assertTrue(org.getApplications().contains(app));
7
8 // Lets add a new App to the set
9 SmartApplication otherApp = new SmartApplication();
10 otherApp.setName("Foo Bar");
11 org.addApplication(otherApp);
12 assertEquals(2, org.getApplications().size());
13
14 session.update(org);
15 session.flush();
16
17 // Upon Saving if our collection's contains matches up, our code woiked!
18 assertTrue(otherApp.getId() != null);
19 assertTrue("More acid test", org.getApplications().contains(otherApp));
20 tx.commit();
21 }
A Maven example of the above is available HERE.  Enjoy!!!





Tuesday, July 8, 2008

Java - equals(), hashCode(), Object Identity, A HACK solution....

Introduction:
Time to provide a note to myself again. I am using this blog to log stuff that act as notes for me. I hate searching for some stuff on the Internet. There are times when I am involved with re-learning and this is one of those times :-).

Wise men of java say, "When you override the equals() method in java, you must also override hashCode()"..Why is that so? I found an nice blog/article on the same to explain better than I ever can.

In short, whenever an object is added to a HashSet, its hashCode() method is interrogated. The value obtained there is used in a computation to determine in which bucket to place the object in. Many objects could hash to the same bucket. The way an object is found in the HashSet is to first hash to the bucket and then run through the elements in the bucket with a comparison of identity or equals. I hope I am right on this ;-)

What I am hoping to explore is equals and hashCode from a persistence perspective, i.e., when using persistent/persistable entities.

Exploration of Problem:

Let us consider a problem domain for the sake of discussion. Our problem domains involves Organizations and Applications there in. Every Organization has one or more Applications. In our domain, an Application is unique by virtue of its name. The underlying data model will not allow two applications with the same name. Additionally, as an Organization, we choose to use surrogate id's and not natural ids for primary keys. In other words, the name attribute of an Application object will not be the pk but an alternate key. Additionally, the name of a persisted application could be changed over time.




public class Application {
private Integer id;
private String name;

public Application() {}

public Integer getId() { return id; }

public void setId(Integer id) { this.id = id;}

public String getName() { return name; }

public void setName(String name) { this.name = name; }
}


The above implementation does not override equals() or hashCode(). The following are some unit-tests against the above class.




  1  @Test public void testIdentity() throws Exception {
2 HashSet<Application> appSet = new HashSet<Application>();
3
4 Application app = new Application();
5 Application appOther = new Application();
6
7 appSet.add(app);
8
9 assertTrue("Set must contain inserted application", appSet.contains(app));
10 assertFalse("Set must not contain other app as it should be using identity for equals", appSet
11 .contains(appOther));
12 assertFalse("Should not be equal", app.equals(appOther));
13 assertFalse("Should not have the same hashcodes as different objects",
14 app.hashCode() == appOther.hashCode());
15
16 app.setId(new Integer(2));
17 appOther.setId(new Integer(2));
18
19 assertTrue("Object should still be obtainable from the due to identity", appSet.contains(app));
20 assertFalse("Set must not contain other app as it should be using identity for equals", appSet
21 .contains(appOther));
22 }


From the above example, two objects are neither equal nor share the same hashCode even though they appear to be the same, i.e., don't have any properties set. Even if the properties are set, they still appear un-equal as equals() and hashCode() are different.

Lets now look at a similar class where equals() has been overridden using the object's id property but the hashCode() method has not been overridden.




  1   @Override public boolean equals(Object otherApp) {
2 if (!(otherApp instanceof AppWithEqualsImpl)) { return false;
3
4 if (this == otherApp) { return true; }
5
6 if (this.getClass() != otherApp.getClass()) { return false;}
7
8 AppWithEqualsImpl other = (AppWithEqualsImpl) otherApp;
9
10 if (id == null) {
11 if (other.id != null)
12 return false;
13 }
14 else if (!id.equals(other.id))
15 return false;
16
17 return true;
18 }


Some tests with class:



  1   @Test public void testOnlyEqualsImpl() throws Exception {
2 HashSet<AppWithEqualsImpl> appSet = new HashSet<AppWithEqualsImpl>();
3
4 AppWithEqualsImpl app = new AppWithEqualsImpl();
5 AppWithEqualsImpl appOther = new AppWithEqualsImpl();
6
7 appSet.add(app);
8
9 assertTrue("App and other App are equal", app.equals(appOther));
10 assertTrue("App and other App don't have same hashcode", app.hashCode() != appOther.hashCode());
11 assertTrue("Set must contain inserted application", appSet.contains(app));
12 assertFalse(
13 "Set will not contain other instance as although equal, they have different hashCodes",
14 appSet.contains(appOther));
15
16 app.setId(new Integer(10));
17 assertTrue("Set should contain app as equals has changed but not hashcode", appSet
18 .contains(app));
19
20 appOther.setId(new Integer(10));
21 assertFalse(
22 "Set will not contain other instance as although equal in Id, they still have different hashCodes",
23 appSet.contains(appOther));
24 }


The above example demonstrates, that although both the objects are identical, as they have different hash Codes, the "appOther" object will fail the contains() test on the HashSet. The object added to the set is located as it matched both on identity and hashCode. After changing the "app" objects, Id field, it can still be found in the HashSet as the hashCode of the object has not altered and is still hashing to the same bucket. However, the problem to note here is that although "otherApp" is equal to the app in the Set, the Set considers "appOther" as a totally different object, thus breaking set semantics (i.e., duplicates) if "appOther" is inserted.

So we need to implement hashCode so that both "app" and "appOther" hash to the same bucket. Lets take a look at a variant that does exactly that:




  1 @Override public int hashCode() {
2 final int prime = 31;
3 int result = 1;
4 result = prime * result + ((id == null)
5 ? 0
6 : id.hashCode());
7 return result;
8 }
9
10 @Override public boolean equals(Object obj) {
11 if (!(obj instanceof AppWithEqualsHashCodeImpl)) { return false;}
12
13 if (this == obj) { return true;}
14
15 if (this.getClass() != obj.getClass()) { return false;}
16
17 final AppWithEqualsHashCodeImpl other = (AppWithEqualsHashCodeImpl) obj;
18 if (id == null) {
19 if (other.id != null)
20 return false;
21 }
22 else if (!id.equals(other.id))
23 return false;
24
25 return true;
26 }


A few tests based of the above class:




  1 @Test public void testEqualsAndHashCodeImpl() {
2 HashSet<AppWithEqualsHashCodeImpl> appSet = new HashSet<AppWithEqualsHashCodeImpl>();
3
4 AppWithEqualsHashCodeImpl app = new AppWithEqualsHashCodeImpl();
5 AppWithEqualsHashCodeImpl appOther = new AppWithEqualsHashCodeImpl();
6
7 appSet.add(app);
8
9 assertTrue("Set must contain inserted application", appSet.contains(app));
10 assertTrue("Set must return a match for contains of appOther as equals/hashCode are same now",
11 appSet.contains(appOther));
12
13 app.setId(new Integer(10));
14
15 assertEquals(app, appSet.iterator().next());
16 assertTrue(app.hashCode() == appSet.iterator().next().hashCode());
17
18 // Note the below
19 assertFalse(
20 "Set contains() should return false when checked for inserted app as hashcode has now changed."
21 + "Contains will check agaisnt a new bucket based of the new hashcode.", appSet
22 .contains(app)); // This means adding app back to the collection will have two elements.
23
24 appSet.add(app);
25 assertEquals(2, appSet.size());
26 }


In the above tests, as the object override's equal and hashCode, when "appOther" is considered the same object by the Set, thus preserving Set semantics. All good, however, look at the lines from 13-25 where we change a property that is participating in the hashCode. When the original added object is checked agaisnt the collection to see if the collection contains it, the collection reports back as false. Whatever is happening???

The problem is that as we changed a property of the object that participates in the hashCode calculation, we have effectively changed the hashCode of the object. When contains() is invoked, it tries to locate the object using the new hashCode. However, the object is present in the Set based of the old hash code and therefore cannot be located. What a pain? The first question we ask is can't we say appSet.rehash() so that the inserted object's hashCode is re-invoked and placed in the correct bucket? The API does not support RE-hashing. And probabaly rightfully so.

Is there anyway, we can overcome this problem? There are multiple solutions. One common path is to not include mutable properties as part of the hashCode() implementation and instead use a an alternate key or business key of the object for the same. In our domain, we know that an Application is uniquely identified by its name, so we can use that for the hashCode computation as shown below:




  1 public class AppWithBizKeyEquals {
2 private Integer id;
3
4 // This is a part of the business Key
5 private final String name;
6
7 public AppWithBizKeyEquals(String name) {
8 this.name = name;
9 }
10 // No setter for name. However there are setter's for id
11 .....
12 .....
13 @Override public int hashCode() {
14 final int prime = 31;
15 int result = 1;
16 result = prime * result + ((name == null)
17 ? 0
18 : name.hashCode());
19 return result;
20 }
21
22 @Override public boolean equals(Object obj) {
23 if (this == obj) return true;
24 if (obj == null) return false;
25 if (getClass() != obj.getClass()) return false;
26 final AppWithBizKeyEquals other = (AppWithBizKeyEquals) obj;
27 if (name == null) {
28 if (other.name != null)
29 return false;
30 }
31 else if (!name.equals(other.name))
32 return false;
33 return true;
34 }


In the above class, the name field is what constituted the business key and is set as immutable. One cannot change the value of the name after object creation. We are still able to alter the "id" property of the object after creation. Lets take a look at some tests:




  1 @Test public void testBusinessKeyEqualHashCode() {
2 HashSet<AppWithBizKeyEquals> appSet = new HashSet<AppWithBizKeyEquals>();
3
4 AppWithBizKeyEquals app = new AppWithBizKeyEquals("Foo");
5 AppWithBizKeyEquals appOther = new AppWithBizKeyEquals("Foo");
6
7 appSet.add(app);
8
9 assertTrue("Both instances must be equal", app.equals(appOther));
10 assertTrue("Both instances must have same hashcode", app.hashCode() == appOther.hashCode());
11
12 assertTrue("Set must contain inserted application", appSet.contains(app));
13 assertTrue("Set must return a match for contains of appOther as equals/hashCode are same now",
14 appSet.contains(appOther));
15
16 app.setId(new Integer(10));
17
18 assertTrue("Both instances are still equal as Id is not part of biz equality", app
19 .equals(appOther));
20 assertTrue(
21 "Both instances must still have same hashcode as Id should not have changed hashCode", app
22 .hashCode() == appOther.hashCode());
23
24 assertTrue("Set must contain inserted application", appSet.contains(app));
25 assertTrue(
26 "Set must return a match for contains of appOther as equals/hashCode are unaffected by id change",
27 appSet.contains(appOther));
28 }


In the above example, notice that both "app" and "appOther" are constructed by providing the name of the application. Both their hashCode() and equals() match. Changing the "id" property of the "app" object has no effect on locating the object in the Set as the "id" property does not participate in the hashCode() calculation.

This works great. However, has some deficiencies:

a. We need to find a business key always. Sometimes, it might be the entire object.
b. Business key becomes immutable

In our example, we could identify the name of the Application as the business key. However, as per our requirement, the name of an Application can be changed. The works agains't the immutability constraints that we have imposed on our object. There are work around to the same but clearly this not ideal.

So, can we overcome this problem where changing of a property that participates in the hashCode calculation of an object still allows us to locate the object by querying the HashSet?

One direction we could have hoped to take is that before the "id" of the object is set, we remove the object from the Set and after the Id is set we re-insert it back. That would work. However, what if the object was part of another object like an Organization and saving the Organization object would implicitly save the children Application objects thus providing "id" for them? How can we intercept the same?




  1 Organization org = new Organization("Foo Org");
2 Application app = new Application();
3 app.setName("Foo");
4 org.getApplications().add(app);
5 persister.save(app);
6
7 assertTrue(org.getApplications().contains(app));


Clearly the above code would fail the validation based of the previous mentioned examples.

So is there no way we can accomplish this???

Well, there may be others. I however, thought of one really bad hack that would make this work. It requires some deviations but acheives the results. The solution is neither performant, not safe, not anything else and I am not patenting the same here!!! There go the $$$$ :-))))

So what is the hack? What if we could acheive the removal and addition of the Object into the Set implicitly thereby simulating a rehash of the object?

To assist with the hack, enter the design patterns such as Observer (Listener).

The Hack:
To aid with the hack, we start with an Annotation. The annotation, HashCodeParticipator, will be applied to any field in a model object that will participate in the calculation of the hashCode().



  1 @Retention(RetentionPolicy.RUNTIME) public @interface HashcodeParticipator {}

We will be utilizing the PropertyChange support of Java Beans. To aid with our solution, we define an event class that is an extension of java.beans.PropertyChangeEvent. This event will be fired when a Property that participates in the hashCode() is:

a. About to be changed
b. Changed




  1 public class HashCodeElementChangeEvent extends PropertyChangeEvent {
2 public static final int HASH_PARTICIPANT_WILL_CHANGE = 0;
3 public static final int HASH_PARTICIPANT_CHANGED = 1;
4
5 private final int eventType;
6
7 public HashCodeElementChangeEvent(Object source, String propertyName, int eventType) {
8 super(source, propertyName, null, null);
9 if (eventType != HASH_PARTICIPANT_CHANGED && eventType != HASH_PARTICIPANT_WILL_CHANGE) {
10 throw new IllegalArgumentException("Invalid Event Type");
11 }
12 this.eventType = eventType;
13 }
14
15 public boolean isChangingNotificationEvent() {
16 return eventType == HASH_PARTICIPANT_WILL_CHANGE;
17 }
18 }


We also define an interface called PropertyChangeNotifier, that implementing objects will utilize to notify listeners when a property that participates in the hashCode() computation is undergoing change.




  1 public interface PropertyChangeNotifier {
2 public void addHashCodePropertyChangeListener(PropertyChangeListener propertyChangeListener);
3 public void removeHashCodePropertyChangeListener(PropertyChangeListener propertyChangeListener);
4 }


Ok, so we now have a notifier. Show me the listener. We define an extension of a HashSet that is a listener of these events. The extended HashSet will receive two types of events. The first when a property that participates in the source object is about to be mutated and a second event after the mutation. Upon receipt of the pre-mutation event, the Set will remove the source object from its collection and upon receiving the post mutation event, it will re-insert the same into its collection of maintained objects. Lets take a look at the listener Set:




  1 public class PropertyListenerHashSet<E extends PropertyChangeNotifier> extends HashSet<E> implements
2 PropertyChangeListener {
3
4 public PropertyListenerHashSet() {
5 super();
6 }
7 // Other constructors..
8 ....
9
10 /**
11 * Addition to the set involves registering the Set as a Listener
12 * of the Object being added.
13 *
14 * @param o Object to add to the Set.
15 */

16 @Override public boolean add(E o) {
17 boolean retVal = super.add(o);
18 // Add Listener
19 ((PropertyChangeNotifier) o).addHashCodePropertyChangeListener(this);
20
21 return retVal;
22 }
23
24 /**
25 * Removes specified object from the set.
26 * Part of the removal operation involves de-registering the set
27 * as a listener of the object.
28 */

29 @Override public boolean remove(Object o) {
30 removeListener(o);
31 return super.remove(o);
32 }
33
34 /**
35 * On A PropertyChangeEvent, if the event is a notification that
36 * a HashCode participating attribute is altering, then the object
37 * is removed from the collection. A subsequent property change event
38 * will ensure the object is added back to the collection.
39 */

40 public void propertyChange(PropertyChangeEvent evt) {
41 if (! (evt instanceof HashCodeElementChangeEvent)) {
42 return;
43 }
44
45 HashCodeElementChangeEvent event = (HashCodeElementChangeEvent) evt;
46
47 if (event.isChangingNotificationEvent()) {
48 super.remove(event.getSource());
49 } else {
50 // Note that the object already has this Set as a listener
51 super.add((E) evt.getSource());
52 }
53 }
54
55 @Override
56 public void clear() {
57 for (E item : this) {
58 ((PropertyChangeNotifier) item).removeHashCodePropertyChangeListener(this);
59 }
60 super.clear();
61 }
62
63 private void removeListener(Object obj) {
64 for (Iterator<E> i = iterator(); i.hasNext();) {
65 E item = i.next();
66 if (item.equals(obj)) {
67 ((PropertyChangeNotifier) item).removeHashCodePropertyChangeListener(this);
68 break;
69 }
70 }
71 }
72 }
73


The items to note in the above class are; that whenever an element is added, the Set regsiter's as a listener of the object and when removed its the opposite. Also note the behavior when the PropertyChangeEvent is received, i.e., the addition and removal of the source object.

Now, lets look at an implementation of the PropertyChangeNotifier interface. For the sake of ease, we define a base class called BO (Business Object for short) as shown below:




  1 public class BO implements PropertyChangeNotifier {
2 protected transient PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);
3 private final Set<String> hashCodeProperties = new TreeSet<String>();
4
5 @SuppressWarnings("unchecked") public BO() {
6 // Doing so we will not have to listener for all property changes.
7 Class child = this.getClass();
8 Field[] fields = child.getDeclaredFields();
9
10 for (Field field : fields) {
11 // If field is Participating in hashCode by virtue of annotation
12 if (field.getAnnotation(HashcodeParticipator.class) != null) {
13 hashCodeProperties.add(field.getName());
14 }
15 }
16 }
17 ....
18 ....
19 // Register only to be notified on properties that are hash code participants
20 public void addHashCodePropertyChangeListener(PropertyChangeListener propertyChangeListener) {
21 for (String hashCodeParticipant : annotatedProperties) {
22 propertyChangeSupport.addPropertyChangeListener(hashCodeParticipant, propertyChangeListener);
23 }
24 }
25 .....
26 .....
27
28 // Fire an event when a hash code participant property is about to change
29 public void fireHashCodeParticipantChangingEvent(String hashCodeParticipant) {
30 propertyChangeSupport.firePropertyChange(new HashCodeElementChangeEvent(this, hashCodeParticipant,
31 HashCodeElementChangeEvent.HASH_PARTICIPANT_WILL_CHANGE));
32 }
33
34 // Fire an event when the hash code participant property has been mutated.
35 public void fireHashCodeParticipantChangedEvent(String hashCodeParticipant) {
36 propertyChangeSupport.firePropertyChange(new HashCodeElementChangeEvent(this, hashCodeParticipant,
37 HashCodeElementChangeEvent.HASH_PARTICIPANT_CHANGED));
38 }
39 }
40


Lets look an implementation of our SmartApplication object that extends the BO. The object uses only the "id" property for equals() and hashCode() implementations. I am not showing the entire class but only the methods of interest below:




  1   /**
2 * Note this should probabaly be aspect handled. 1. beforeMethod - If method contains
3 * HashCodeParticipator annotation fireGoingtoChangeEvent 2. executeMethod 3. afterMethod - If
4 * method contains HashCodeParticipator fireChangedEvent
5 *
6 * @param id
7 */

8 public void setId(Integer id) {
9 fireHashCodeParticipantChangingEvent("id");
10 this.id = id;
11 fireHashCodeParticipantChangedEvent("id");
12 }
13
14 public String getName() {
15 return name;
16 }
17
18 public void setName(String name) {
19 String origName = this.name;
20 this.name = name;
21 propertyChangeSupport.firePropertyChange("name", origName, name);
22 }
23


In the above methods of the Smart Application class, the setId() method first fires an event to listeners that a hashCode property will be changed and after changing the property, fires another event stating the property has been mutated. Although not shown, the "id" property has an annotation of @HashCodeParticipant. Also note that when the "name" property is set, a simple property change event is fired.

Lets take a look at some unit tests that utilize the hacked framework:




  1 @Test public void testSmartApplication() throws Exception {
2 HashSet<SmartApplication> appSet = new PropertyListenerHashSet<SmartApplication>();
3
4 SmartApplication app = new SmartApplication();
5
6 appSet.add(app);
7 assertEquals("Should have only one listner, i.e., the Set", 1, app.getListenerCount());
8 assertTrue("Set must contain inserted application", appSet.contains(app));
9
10 // Mutate a hashCode participating propertt
11 app.setId(new Integer(10));
12
13 // The set now returns true for the contains due to hacked re-hash
14 assertTrue(appSet.contains(app));
15 assertEquals(1, app.getListenerCount());
16
17 // Remove from Set
18 assertTrue(appSet.remove(app));
19 assertEquals(0, app.getListenerCount());
20
21 assertFalse(appSet.contains(app));
22 assertEquals(0, appSet.size());
23
24 appSet.add(app);
25 assertEquals(1, appSet.size());
26 assertEquals(1, app.getListenerCount());
27
28 // Should not cause a property change event
29 app.setName("Foo");
30 appSet.clear();
31 assertEquals(0, appSet.size());
32 assertEquals(0, app.getListenerCount());
33 }


In the above example, even after the "id" property is changed, i.e., a property that participates in the hashCode() computation, the original object can still be located in the HashSet :-))) We managed to implement a transparent re-hash hack!

So what next???

Will this work with Hibernate?
The hibernate FAQ on equals/hashCode states the following:

"Will this work?
HashSet set = new HashSet();
User u = new User();
set.add(u);
session.save(u);
assert(set.contains(u));"

The answer is no, it will not work, for all the reasons discussed above. However, if we utilize the hack-framework mentioned above, it will.

To illustrate the solution, we introduce the Organization object. The Organization object contains an instance of our extended HashSet of Application objects like so:




  1   @OneToMany(mappedBy="organization",cascade=CascadeType.ALL, fetch=FetchType.EAGER)
2 @JoinColumn(name="ORG_ID")
3 @Fetch(value=FetchMode.JOIN)
4 private Set<SmartApplication> applications = new PropertyListenerHashSet<SmartApplication>();


One important thing to note is that Hibernate assigns the properties using "Reflection" by default. If our SmartApplication object's "id" property is assigned using Reflection, we cannot fire a property change. Therefore we need to ensure that the "id" property is set using the setter method. The same is accomplished using the annotation @AccessType (value="property") as shown below:




  1   @AccessType(value="property")
2 @HashcodeParticipator
3 private Integer id;


Now the unit test:




  1 @Test
2 public void testPersistence() {
3 Session session = HibernateUtil.getSessionFactory().getCurrentSession();
4 Transaction tx = null;
5 try {
6 tx = session.beginTransaction();
7 Organization org = new Organization();
8 org.setName("Foo Company");
9
10 SmartApplication app = new SmartApplication();
11 app.setName("Auditing");
12
13 org.addApplication(app);
14
15 session.save(org);
16
17 // This is the acid test....
18 assertTrue(org.getApplications().contains(app));
19 tx.commit();
20 } catch (Exception e) {
21 e.printStackTrace();
22 tx.rollBack();
23 }
24 }


The above test passes :-))))))))))))))))))))))

Conclusion:

The above mentioned solution is a hack!!! Please do not implement the same. I was only curious as to how one could overcome the problem and let my insane mind play. The code/article has not had any reading or advocation. There are issues such as performance, synchronization, Hibernate Read etc, etc, etc to be investigated.

I am a brooder by nature where someone's comment on a topic sends me over-analysing the same. My thinking does not necessarily equate to productivity or a good solution. The blog was meant more as an education to myself and a singular place to understand/refer to equals/hashCode. I only hope the above helps someone treading the same path as myself understand the esoteric world of Hashing better :-))). If I am in error, I would appreciate direction.

I have attached herewith a Maven project with the different scenarios. As a parting note, when implementing equals/hashCode

1. Attempt to find a business equals/hashCode when applicable
2. Ensure that you check for immediate object identity via this == other.
3. Check for class equality. This is specially important when a subtype fails to override equals/hashcode and a super-type evaluates as being the same as the sub-type which does not make sense. For example every Car is not a Porsche ;-)
4. Use a mutable property for hashCode judiciously. Watch for Id's specially.
5. Any finally, override equals and hashCode if the object in concern will ever be used as a Key of a HashMap or as an item in a HashSet.
6. GUID - This is a different blog.
7. Objects that are part of equals do not have to be part of hashCode
8. Attempt to contact more prominent brains such as Gosling, Gaving King or Rod Johnson for assistance....;-)..kidding.

G.N...what a Rant!!!! This must take the cake :-)))))

Monday, June 30, 2008

Software a Religious Experience...

Software and Religion, not a conventional topic to really blog. And I sure don't want to start any controversies with the same :-). So what brings me to blog on this topic is that I remembered an incident that occured a long time ago while working for one of my former employers. It's so long ago that I might be bordering on exaggeration and hyperbole as I pen the following... I however need to document this rant for memory.

One fine day, a long time ago, in a galaxy that I happen to live in, the system administrator (an extremely nice person, one of the nicest I have met) at my employer stopped by my cube to enlighten me about the fact that after much pondering he finally had the names that they had been searching for the different environments of our different deployment tiers. I eagerly awaited his revelation. Very enthusiastically, he said the names, "Brahma, Vishnu and Shiva". These three names were to represent the development, testing and production boxes respectively.

To those unfamiliar with Hindu mythology, Brahma, Vishnu and Shiva are the supreme gods of the Hindu religion. Brahma is known as the creator, Vishnu the preserver and Shiva the destroyer. It made sense that the development environment was to be known as Brahma, the QA environment as Vishnu but the fact that production was to be named as "Shiva the destroyer" escaped me :-).

Regardless, the point to note was I expressed my displeasure with the names chosen. I cannot claim myself to be a zealot Hindu (I am a Krishna devotee and fan though). However, I do pray and do believe in some superior power who makes everything work so fantastically in harmony. Such a well oiled machine IMO is definitely more than the work of evolution, i.e., my stance on the evolution versus religion debate; "This is too good to be true!" .. Maybe a later blog on Quantum physics, Temporal Physics, Universe expansion theories would be nice.

So where does the displeasure part come in? Well the three deities mentioned have a very prominent foothold in Hindu mythology and religion. Hindu's, pray to these deities constantly. It was great that our deities were also given the honor of having their names included with current day technology. But, the reason why I did not want the different systems named after the deities was, I was concerned that a developer/QA person/Manager might in a bout of frustration say some untoward remark toward the environment that would inadvertently reflect on the deity in concern :-). In other words, I was not comfortable knowing that a time might come where someone would utter words that would not be appropriate and I would need to listen to the same.

I guess, my point was well taken by the system admin and his kindness and religious tolerance prevented him from pursuing his most revolutionary naming convention. I acknowledge his tolerance and am grateful for the same!

Now the above are totally my paranoiac assumptions. The repercussions to the act mentioned might never have occurred. However being in the software industry, I have seem my share of people cursing different environments for one reason or another. I do not know whether I over reacted in this case. Maybe I did. However unapologetically!

I must acknowledge that I am a hypocrite in a way. I have worked on environments titled "Apollo", "Saturn" and even "Venus"..The last one I am least apologetic about ;-). I too might have stated things that might be conceived as religiously/sexually incorrect . I apologize if there is a greek who's sentiments I might have inadvertently hurt :-(. I might have made statements such as "Apollo is down! Venus sucks big time", i.e., statements that might be considered religiously incorrect . Please note that with Celestial bodies we have the same risk right now.

Regretfully, I must state that if tomorrow I was asked to work on an environment titled "Aphrodite" or"Venus", I would not be the first with a protest (sheepish) of the naming convention..:-)

It seems that gods of the past, i.e., those that do not have a following in the current main stream religions do not have advocates defending how their names are used. The gods of the present day, i.e, those with an active religious following do have strong advocates.

Growing up, I remember that my family used to often pay obeisance to a deity prior to starting some critical task, i.e., starting an industry, buying a car, building a home etc. A token of that prayer often found itself itself into the endeavour. For example, we buried a metallic symbol of our deity under the foundation stone of our home when we moved in for luck and to guard us from some untoward incident and bad luck.

Another case in point, during my university days, I was a Teaching Assistant. One course that I taught was Internet technologies such as HTML, Archie, Gopher etc etc...I used to take my students to a sample home page that I created. I was young and during that phase, I had different preferences. My home page had a banner with two flaming skulls decorating the same. Well, the skulls were not well received and one of my students complained to the professor in charge that she believed that I was a "Satanic Worshipper" and took objection to the skulls. My professor immediately ordered me to take down the site. Phew! Do I look Satanic in any way? People please go beyond my features when and if you decide to comment :-)))! Anyway, the point to note is that my actions had hurt someones sentiments.

Being religious as I am, I often wonder, what would be consequences if I had a simple TEXT file along with my software project that wished the project well. In Hinduism, we have prayers that are executed when an auspicious task is to be performed. As a religious software person, if I were to include a text file with prayers for the success of the project, would I be placing myself in bad light? How would it be received?

The atheist would not prefer its presence as they would argue that software is based on pure science without religious connotations. A person from a different religion would potentially not feel comfortable with working on a project that has the blessings of a deity that is not part of their religion? If the language of the well wishes were not based off a particular religion, I guess the latter audience could be appeased.

The software developer and audience therein are a mix. It is a blend of people from multiple religions, races, atheists, agnostics working together to accomplish a common goal. We break barriers such as races, religion and other separations to achieve our goals. We each subtly bring in the richness of our beliefs without imposing the same over our fellow man. These hidden beliefs and strengths cannot be discounted...

In conclusion to this light hearted memory, I would say, like in politics, one needs to be careful regarding where religion and software meet. Tolerance is the word my friends!

Live long and Prosper!

Monday, June 16, 2008

Jupiter Eclipse Plugin for Code Reviews

Recently, I had my code reviewed. My reviewer had some very nice suggestions for change. Also had things in there, I didn't necessarily agree on, but that's a whole different blog :-))

The day's of printing out the code and running through the same in a meeting room is a bit passe! Primarily for security reasons, what if one touches a raw nerve of the guy whose code we are reviewing...with all these mall incidents etc, its too scary..remote handling of the matter that too with day and age of the internet is definitely the preferred and prudent route. I am sure my reviewer would have felt the same seeing my halloween features :-))..I better be kidding here!

My reviewer had painstakingly (thanks again if he ever reads this) interspersed in my code comments for improvement and checked the same into SVN. Eclipse has a TASK's tab that can display items like TODO's or FIXME's etc. Pretty neat. I could then open Eclipse's task tab and run through the individual TODO's and FIXME's and address the review issues.

So I started off addressing his issues. Where do i start? Which is high priority? What should I do after fixing the concern, just delete the comments? Hopefully he will do a "diff" on the source files when he re-reviews :-))))...maybe I can be smart enough to just delete his comments and leave for the day ...lol...kidding of course ..I don't advocate this practice...

Somethings to note about this. There is no PRIORITY associated with the FIXME's so all appear as the same severity. Of course going into each of those FIXME's can provide commented details.

Additionally the review involves the user having to repeat themselves in writing the comments. For example, Coding Style, Optimization etc and specifics there in (no common language). Also the review involves polluting the code base with the comments that later need to be removed after addressing the same; ugh for pain !

Also, in a team environment, it is possible that a checked in code base is reviewed by more than one individual with different comments and potentially different severity. Also, in terms of feedback to the reviewer, there is no easy way of saying what one did to fix the issue or the fact that they won't fix an issue because of some reason or how it was fixed unless maybe listing in an SVN commit or within the code as java doc. Also from a Team perspective, with Task comments, it's not easy to assign specific elements of a review to different people in the team. "You John Doe, get all the FIXME's that contain 'Code ishtyle in the comments'"!. What about duration since a review was posted and the review did not meet a resolution? Maybe this can be addressed by creating a project in your favorite bug tracking tool for the code review? Any thoughts about the pain points there in? "Sanjay pretending to be in deep thought (an anomaly of nature) now, surely there could be an easier way!"

Let me speak a bit in the third person, my son does so all the time, "Yash hungry, Yash going away, Yash Sad, Yash gonna beat u up!"..so emulating him , "Sanjay searching web for something as he is lazy to do something or contribute personally!". "Yay! Sanjay finds something!"

Helping out with all the above but not limited to, is a nice plugin for Eclipse called Jupiter.
Think of Jupiter as a Bug tracking tool for code reviews. Its an Eclipse embedded JIRA of sorts. I tried to use this on a simple project that I created and was rather impressed with what I saw. I could expand with examples of how it works but primarily due to laziness, will allow you to discover its features via the user guide and trying it out :-))) Sheeeeepish and totally shameless here!

As a note , I couldn't get the plugin to work using the update site. The plugin was not picking any of my source files for review..kinda defeated the purpose. Had to manually install the jar I obtained from HERE. After that all was smooth as silk, got em examples woiking...

Don't think I wan't to recommend this to my reviewer, as I definitely don't want a review of a code-review tool...its hard enough finishing the code review ;-))) kidding as always!!!!

If I were leading a team, I would definitely look toward exploring this tool further...

Enjoy!

Saturday, June 14, 2008

ThreadDeath and Throwable

I was looking at some code recently and noticed that java.lang.Throwable was being caught around code doing some business processing. Catching Throwable or for that matter catching java.lang.Exception should be done with due consideration. Catching specific exceptions and handling the same should be the preferred route unless there are good cases for catching top level exceptions, which there are.

Anyway, my interest is not really with the above but more on the class java.lang.ThreadDeath. ThreadDeath is a sub-class of java.lang.Error. If one catches, java.lang.Throwable, then there is a possibility of also catching java.lang.ThreadDeath in that code.

The javadocs on ThreadDeath states:

"An instance of ThreadDeath is thrown in the victim thread when the stop method with zero arguments in class Thread is called.
An application should catch instances of this class only if it must clean up after being terminated asynchronously. If ThreadDeath is caught by a method, it is important that it be re thrown so that the thread actually dies.

The top-level error handler does not print out a message if ThreadDeath is never caught.

The class ThreadDeath is specifically a subclass of Error rather than Exception, even though it is a "normal occurrence", because many applications catch all occurrences of Exception and then discard the exception. "


So ThreadDeath can be noticed only if a call to thread.stop() has been made.
The stop() method in Java has been deprecated due to its usage being inherently unsafe.

As per the Java doc recommendation, code that might catch ThreadDeath should propagate the same higher up like shown below:

So for example:

try {
// Do some biz processing
....
} catch (Throwable t) {
if (t instanceof ThreadDeath)) {
throw ((ThreadDeath) t);
}

.......
.......
}

I remember my Architect (a very very smart man) during my earlier days when using jdk1.2.X, jdk1.3.X etc making sure of the above.

Now with stop() being deprecated can we safely assume that no code would ever call stop() on a Thread? I don't think we can be that optimistic as even though deprecated, it could still be invoked. So until the stop method is removed from the JDK, I believe it would be prudent to throw ThreadDeath when catching Throwable or Error specifically for the two reasons mentioned in the javadoc (contrary views I'd love to hear):

"If ThreadDeath is caught by a method, it is important that it be rethrown so that the thread actually dies.
The top-level error handler does not print out a message if ThreadDeath is never caught. "


I wonder if the practice of re-throwing ThreadDeath is still being followed :-) ?
Eric Williams book about the Threads is a nice read. Look for Thread Death in the example.

I cant wait to read Brian Goetz's Java Concurrency in practice, need to buy this book soon...:-). I love async code but it has been rather long since I have played (note the wicked word played :--)) with threads as last few years have all been spent faithfully following j2ee container thread usage recommendations.

Wednesday, June 11, 2008

Generics and Erasure in Java 5.X+

This blog is primarily as a note to myself in understanding generics. Prior to moving to OSTK, I had very limited understanding of Generics and Java 5.X+ features. Although not an expert by any means, I am comfortable ranting about the same :-). With Java 5.X, Sun introduced the concept of Generics. Generics allow for compile time type-checking and thus a safer code base.

For example,
In 1.4.X of JDK the following would be allowed at compiled time:


List l = new ArrayList();
l.add(new String());
l.add(new Integer(2));

In other words, we could accidentally place an Integer into a List of Strings.
With generics in Java 5.X+ one could alter the above code to:


List<String> l = new ArrayList<String>();
l.add(new String());
l.add(new Integer(2)); //--- Compile Time Failure

At compilation time, the above code will fail to compile as the List has been defined to contain only the type of "String" and the line showing addition of an Integer will fail. Nice and safe compile time type checking :-).

Consider an Object Holder that can hold an object of any type defined in Java 1.4.X:

public ObjectHolder {
private Object object;

public ObjectHolder(Object object) {
this.object = object;
}

public Object getObject() {
return object;
}
}
ObjectHolder stringHolder = new ObjectHolder("Hello world"); 
ObjectHolder integerHolder = new ObjectHolder(new Integer(2));

..What about...the following line:

String string = (String) integerHolder.getObject(); //-----No Compile time warning only Class Cast....run time

With 1.5.X things change for the Better:



public class ObjectHolder<T> {
private T object;
public ObjectHolder(T object) {
this.object = object;
}

public T getObject() {
return object;
}
}



where T holds the type information and we can also use the above to declare type safe Object holders like:


Code using the Object holder could say:



ObjectHolder<String> stringHolder = new ObjectHolder<String>(new String("Hello World"));

or

ObjectHolder<Integer> integerHolder = new ObjectHolder<Integer>(new Integer(4423));

String string = stringHolder.getObject();

Integer integer = integerHolder.getObject();



Note that there is no casts involved in the above code.

However, what we cannot do is add a method in the Object Holder that does the following (for example, not the best) :

public T createNew() { 

T other = new T();

return other;
}


Why we cannot do the above is due to erasure in Java. What we mean by erasure is that the Type information is lost in the generated byte code or in other words, the generated class files are like 1.4.X classes without Type info.

Why Sun did the same is to support backward compatibility. Backward compatibility is not to be able to run 1.5.X compiled code on a 1.4.X VM but instead to allow 1.4.X VM compiled code to co-exist with 1.5.X compiled classes and run on a 1.5.X Java VM. Running 1.5.X compiled classes on a 1.4.X VM will result in a class version incompatibility error.

The cost of this backward compatibility is that with 1.5 we do not have access to the Type information at run time.

I have a couple of feelings on this. Maybe 1.5 should not have supported backward compatibility and supported Runtime Type generics retention. However, maybe their thought process is that if they they could drop the backward compatibility at version 1.8 and by then all folks must have converted their libraries to be 1.5.X+ generics ready. A definitely good argument. Maybe on the flip side, they should have released a 1.4.X version that had many of the performance and other enhancements (for loop etc) and have 1.5.X version that was not backward compatible. A generics enabling option in the JDK and JRE might have been another option?

Additionally, if one wanted to run 1.5.X compiled code on a 1.4.X VM, there is always Retroweaver to the rescue :-)...thanks to Richard Rodrigues a former colleague of mine, that I know about this tool.

A confession to those reading this post, all the code above has NOT been tried on a compiler, I just wrote it here..so if it has compilation errors, I shameless blame the blogger that they do not have in built java compiler :-)))) as yet!

Some Links:
Neal Gafter's Blog on erasure
Java Forum Discussion on erasure