View Source

h2. Migrate super class and sub class separately

Continue with version "1" of the Task class defined in [Getting Started] chapter:
{code}
package com.pmease.commons.xmt.bean;

import java.util.List;
import org.dom4j.Element;
import com.pmease.commons.xmt.VersionedDocument;

public class Task {
enum Priority {HIGH, MEDIUM, LOW}

public Priority priority;

@SuppressWarnings("unused")
private void migrate1(VersionedDocument dom, List<String> versions) {
Element element = dom.getRootElement().element("prioritized");
element.setName("priority");
if (element.getText().equals("true"))
element.setText("HIGH");
else
element.setText("LOW");
}
}
{code}
Now we have class _CompileTask_ subclassing from Task class as below:
{code}
package example;

import java.util.List;

public class CompileTask extends Task {
public List<String> srcFiles;
}
{code}
Instance of _CompileTask_ can be serialized to XML with XMT as below:
{code}
package example;

import java.util.ArrayList;

import com.pmease.commons.xmt.VersionedDocument;

public class Test {
public static void main(String args[]) {
CompileTask task = new CompileTask();
task.priority = Task.Priority.HIGH;
task.srcFiles = new ArrayList<String>();
task.srcFiles.add("Class1.java");
task.srcFiles.add("Class2.java");
String xml = VersionedDocument.fromBean(task).toXML();
saveXMLToFileOrDatabase(xml);
}

private static void saveXMLToFileOrDatabase(String xml) {
// save XML to file or database here
}
}
{code}
The resulting XML will be:
{code}
<example.CompileTask version="1.0">
<priority>HIGH</priority>
<srcFiles>
<string>Class1.java</string>
<string>Class2.java</string>
</srcFiles>
</example.CompileTask>
{code}
Pay attention to version attribute of the root element: XMT examines the class hierarchy (except for class _java.lang.Object_) to get current version of each class, and concatenates them with period. Since class _Task_ is of version "1" and class _CompileTask_ is of version "0", the resulting version of the hierarchy (or composite version) is "1.0".
When deserializing the compile task object from XML, XMT splits this composite version to get XML version for each class in the hierarchy, and repeats the process described in [getting started] chapter for each of these classes. So if class _Task_ is evolved to use numeric priority field, we simply add migrate methods in _Task_ class, while keep _CompileTask_ class intacted. On the other hand, if we evolve class _CompileTask_ to include a _destDir_ field, we can define the migrate method in _CompileTask_ like below, while keep class _Task_ intacted:
{code}
package example;

import java.util.List;

public class CompileTask extends Task {
public List<String> srcFiles;

public String destDir = "classes";

@SuppressWarnings("unused")
private void migrate1(VersionedDocument dom, List<String> versions) {
dom.getRootElement().addElement("destDir").setText("classes");
}
}
{code}
This separation of concerns is very important since there might exist many sub classes, and you certainly do not want to modify those sub classes if super class is evolved, and vice versa.

h2. Address class hierarchy change problem

Now we want to introduce class _AbstractCompileTask_ in the middle of _Task_ and _CompileTask_ like below:
{code}
package example;

public abstract class AbstractCompileTask extends Task {
public String options = "-debug";
}
{code}
{code}
package example;

public class CompileTask extends AbstractCompileTask {
public List<String> srcFiles;

public String destDir = "classes";

@SuppressWarnings("unused")
private void migrate1(VersionedDocument dom, List<String> versions) {
dom.getRootElement().addElement("destDir").setText("classes");
}
}
{code}

Now let's assume that class _Task_ is removed and class _CompileTask_ needs to take care of the priority field:
{code}
package example;

import java.util.List;
import com.pmease.commons.xmt.VersionedDocument;
import com.pmease.commons.xmt.MigrationHelper;

public class CompileTask {
public int priority;

public List<String> srcFiles;

public String options = "-debug";

@SuppressWarnings("unused")
private void migrate1(VersionedDocument dom, List<String> versions) {
dom.getRootElement().addElement("options").setText("-debug");
}
}
{code}
However deserialization from below XML will not work:
{code}
<example.CompileTask version="0.0">
<prioritized>true</prioritized>
<srcFiles>
<string>Class1.java</string>
<string>Class2.java</string>
</srcFiles>
</example.CompileTask>
{code}
There are two obstacles:
# There is no migration logic in class _CompileTask_ to migrate priority field which is handled previously by class _Task_.
# The version recorded in XML is "0.0" and XMT expects for two classes in the hierarchy for migration. However there is only one class now.

To solve this issue, we create a class _TaskMigrator_ just to hold the migration logic previously exists in _Task_ class and call it in method _migrate2_ like below:
{code}
package example;

import java.util.List;
import com.pmease.commons.xmt.VersionedDocument;
import com.pmease.commons.xmt.MigrationHelper;

public class CompileTask {
public int priority;

public List<String> srcFiles;

public String options = "-debug";

@SuppressWarnings("unused")
private void migrate1(VersionedDocument dom, List<String> versions) {
dom.getRootElement().addElement("options").setText("-debug");
}

@SuppressWarnings("unused")
private void migrate2(VersionedDocument dom, List<String> versions) {
String taskVersion = versions.remove(0);
MigrationHelper.migrate(taskVersion, TaskMigrator.class, dom);
}

private static class TaskMigrator {

@SuppressWarnings("unused")
private void migrate1(VersionedDocument dom, List<String> versions) {
Element element = dom.getRootElement().element("prioritized");
element.setName("priority");
if (element.getText().equals("true"))
element.setText("HIGH");
else
element.setText("LOW");
}

@SuppressWarnings("unused")
private void migrate2(VersionedDocument dom, List<String> versions) {
Element element = dom.getRootElement().element("priority");
if (element.getText().equals("HIGH"))
element.setText("10");
else if (element.getText().equals("MEDIUM"))
element.setText("5");
else
element.setText("1");
}
}
}
{code}
With this modification, XMT will then follow below procedure to migrate from the aforementioned XML:
# It splits version "0.0" recorded in XML as a list, with the first element being version of _CompileTask_ and second element being version of _Task_.
# It