2009年5月30日土曜日

Java 6 Mustang のXML機能(JAXB)によるXmlDAO

Java 6 MustangのXMLの機能(JAXB)の機能がかなり拡張され、これを利用してXML用のDAOクラスを作ってみました。

まず、Java 6に付属しているSchemagenというツールにより、DAOで扱うクラスのスキーマファイルを生成します。
たとえば次のようなSampleクラスを作成します。schemaのルートノードとなるクラス(ここではSampleクラス)には
アノテーションXmlRootElementを指定します。

Sampleクラス:

import javax.xml.bind.annotation.XmlRootElement;

/**
* サンプルクラス
*/
@XmlRootElement
public class Sample {

/* 名前 */
private String name;

/* 年齢 */
private int age;

/**
* 名前を取得する。
*
* @return 名前
*/
public String getName() {
return name;
}

/**
* 名前を設定する。
*
* @param 名前
*/
public void setName(String name) {
this.name = name;
}

/**
* 年齢を取得する。
*
* @return 年齢
*/
public int getAge() {
return age;
}

/**
* 年齢を設定する。
*
* @param 年齢
*/
public void setAge(int age) {
this.age = age;
}
}



Sampleクラスに対するスキーマを作成する場合以下のようにコマンドを実行します。

> schemagen -classpath <クラスパス> Sample.java

※なぜかMacでは動作しなかった(InvocationTargetExceptionが発生してしまう)ので、Windowsでschemagenを実行しました。

schemagenを実行するとschema1.xsdというファイルが生成されますので、これをSample.xsdという名前に変更し保存します。


今回作成したXmlDAOにより、上記のSampleクラスの保存および読み込みのロジックは記述すると次のようなコードになります。
XmlDAOのテストコード:

import java.io.File;

import jp.aarkiton.xml.dao.XmlDAO;

/**
* XmlDAO用テストソース
*/
public class XmlDAOTest {

public static void main(String args[]) throws Exception {
// 保存対象のテスト用オブジェクト
Sample sample = new Sample();
sample.setName("tama");
sample.setAge(10);

// XmlDAOのインスタンス生成
XmlDAO<Sample> xmlDAO = XmlDAO.getInstance(Sample.class, "Sample.xsd");

// オブジェクトの保存
xmlDAO.save(sample, new File("sample.xml"));
// 一旦オブジェクトを解放
sample = null;

// オブジェクトの読み込み
sample = xmlDAO.load(new File("sample.xml"));

System.out.println(sample.getName());
System.out.println(sample.getAge());

}

}


上記コードを実行した結果得られるsample.xmlは以下のようになります。
sample.xml:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<sample>
<age>10</age>
<name>tama</name>
</sample>


最後に今回作成したClassUtils、XmlDAO、のクラスを記載します。
ClassUtils:

/*
* Copyright 2009 (C) Aarkiton Soft. All Rights Reserved.
*
* This software is open source.
*/
package jp.aarkiton.util;

import java.io.InputStream;
import java.net.URL;

/**
* クラス、パッケージ関連のユーティリティクラス
* @author Takanori
* @version 1.0.0
* @since 1.0.0
*/
public class ClassUtils {

/**
* 指定したクラスのパッケージパスから、指定したファイルのURLを取得する。
* @param cls クラス
* @param name ファイル名
* @param URL
*/
@SuppressWarnings("unchecked")
public static URL getResource(Class cls,String name){
return cls.getClassLoader().getResource(getPackagePath(cls)+"/"+name);
}

/**
* 指定したクラスのパッケージパスから、指定したファイルの入力ストリームを取得する。
* @param cls クラス
* @param name ファイル名
* @param 入力ストリーム
*/
@SuppressWarnings("unchecked")
public static InputStream getResourceAsStream(Class cls,String name){
return cls.getClassLoader().getResourceAsStream(getPackagePath(cls)+"/"+name);
}

/**
* クラスを指定してパッケージのパスを取得する。
* @param cls クラス
* @return パッケージのパス
*/
@SuppressWarnings("unchecked")
private static String getPackagePath(Class cls){
return cls.getPackage().getName().replace('.','/');
}

}


XmlDAO:

/*
* Copyright 2009 (C) Aarkiton Soft. All Rights Reserved.
*
* This software is open source.
*/
package jp.aarkiton.xml.dao;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;

import javax.xml.XMLConstants;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;

import jp.aarkiton.util.ClassUtils;

import org.xml.sax.SAXException;

/**
* Java 6 Mustang のXML機能を使用したJavaオブジェクトのシリアライザークラス

* Java 6 から同梱されたschemagenにより、Javaのクラスからスキーマを生成することができ、
* その生成されたスキーマを使用しオブジェクトのロード、セーブを行う。
* スキーマファイル名を指定しない場合、スキーマは対象となるクラスのパッケージパスの
* 「クラス名.xsd」という名前のスキーマファイルを使用する。
*
*

* (1)スキーマの生成
* Usage: schemagen [-options ...] <java files>
* Options:
* -d <path> : 出力先ディレクトリ
* -cp <path> : ユーザ定義ファイルの検索パス
* -classpath <path> : ユーザ定義ファイルの検索パス
* -version : バージョン情報
*
* (2)クラスの使用例
* 例1)SampleクラスのインスタンスをXMLスキーマ定義(Sample.xsd)に基づき、
*     XMLファイル(sample.xml)として保存する。
*
* Sample sample = new Sample();
* :
* XmlDAO<Sample> xmldao = XmlDAO.getInstance(Sample.class,"Sample.xsd");
* xmldao.save(sample, new File("sample.xml"));
*
* 例2)SampleクラスのインスタンスをXMLファイル(sample.xml)として保存する。
*     (XMLスキーマ定義を指定しなかった場合、パッケージフォルダにある<クラス名>.xsdファイルが使用される)
* Sample sample = new Sample();
* :
* XmlDAO<Sample> xmldao = XmlDAO.getInstance(Sample.class);
* xmldao.save(sample, new File("sample.xml"));
*
* 例3)SampleクラスのインスタンスをXMLスキーマ定義(Sample.xsd)に基づき、
*     XMLファイル(sample.xml)からロードする。
*
* XmlDAO<Sample> xmldao = XmlDAO.getInstance(Sample.class,"Sample.xsd");
* Sample sample = xmldao.load(new File("sample.xml"));
*
* 例4)SampleクラスのインスタンスをXMLファイル(sample.xsd)からロードする。
*     (XMLスキーマ定義を指定しなかった場合、パッケージフォルダにある<クラス名>.xsdファイルが使用される)
* XmlDAO<Sample> xmldao = XmlDAO.getInstance(Sample.class);
* Sample sample = xmldao.load(new File("sample.xml"));
*
*


* @author Takanori
* @version 1.0.0
* @since 1.0.0
*
* @param
*/
public class XmlDAO {

/** 永続化クラス */
protected Class targetClass;

/** Marshallerオブジェクトに渡すプロパティ */
protected Map properties;

/** スキーマファイル */
protected File schemaFile = null;

/**
* クラスオブジェクトを入力としてXmlDAOのインスタスを生成するファクトリメソッド。
*
* @param targetClass
* ターゲットクラス
*/
public static XmlDAO getInstance(Class targetClass) {
return new XmlDAO(targetClass);
}

/**
* クラスオブジェクト、スキーマファイル名を入力としてXmlDAOのインスタスを生成するファクトリメソッド。
*
* @param targetClass
* ターゲットクラス
* @param schemaFilename
* スキーマファイル名
*/
public static XmlDAO getInstance(Class targetClass,
String schemaFilename) {
return new XmlDAO(targetClass, schemaFilename);
}

/**
* クラスオブジェクト、スキーマファイル名を入力としてXmlDAOのインスタスを生成するファクトリメソッド。
*
* @param targetClass
* ターゲットクラス
* @param schemaFile
* スキーマファイル
*/
public static XmlDAO getInstance(Class targetClass,
File schemaFile) {
return new XmlDAO(targetClass, schemaFile);
}

/**
* クラスオブジェクトを入力としてXmlDAOのインスタスを生成するコンストラクタ。
*
* @param targetClass
* ターゲットクラス
*/
protected XmlDAO(Class targetClass) {
this(targetClass, (File) null);
}

/**
* クラスオブジェクト、スキーマファイル名を入力としてXmlDAOのインスタスを生成するコンストラクタ。
*
* @param targetClass
* ターゲットクラス
* @param schemaFile
* スキーマファイル名
*/
protected XmlDAO(Class targetClass, String schemaFilename) {
this(targetClass, new File(schemaFilename));
}

/**
* クラスオブジェクト、スキーマファイルを入力としてXmlDAOのインスタスを生成するコンストラクタ。
*
* @param targetClass
* ターゲットクラス
* @param schemaFile
* スキーマファイル
*/
protected XmlDAO(Class targetClass, File schemaFile) {
this.targetClass = targetClass;
this.schemaFile = schemaFile;
this.properties = getDefaultProperties();
}

/**
* Marshallerオブジェクトに渡すデフォルトのプロパティを取得する。
*
* @return デフォルトのプロパティ
*/
protected Map getDefaultProperties() {
Map defualtProperteis = new HashMap();
defualtProperteis.put(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
return defualtProperteis;
}

/**
* オブジェクトをセーブする。
*
* @param o
* ターゲットオブジェクト
* @param output
* 出力ファイル
*/
public void save(T o, File output) throws JAXBException, SAXException,
IOException {
save(o, new FileOutputStream(output));
}

/**
* オブジェクトをセーブする。
*
* @param o
* ターゲットオブジェクト
* @param output
* 出力ストリーム
*/
public void save(T o, OutputStream output) throws JAXBException,
SAXException, IOException {
// ターゲットクラスのためのコンテキストを作成.
JAXBContext context = JAXBContext.newInstance(targetClass);

// 使用する Schema オブジェクトを作成.
Schema schema = getSchema();

// Java オブジェクトから XML に変換するための Marshaller を作成.
Marshaller marshaller = context.createMarshaller();
marshaller.setSchema(schema);
for (Map.Entry entry : properties.entrySet()) {
marshaller.setProperty(entry.getKey(), entry.getValue());
}

// XML に変換.
marshaller.marshal(o, output);
}

/**
* オブジェクトをロードする。
*
* @param input
* 入力ファイル
* @return オブジェクト
*/
public T load(File input) throws JAXBException, SAXException, IOException {
return load(new FileInputStream(input));
}

/**
* オブジェクトをロードする。
*
* @param input
* 入力ストリーム
* @return オブジェクト
*/
@SuppressWarnings("unchecked")
public T load(InputStream input) throws JAXBException, SAXException,
IOException {
// ターゲットクラスのためのコンテキストを作成.
JAXBContext context = JAXBContext.newInstance(targetClass);

// 使用する Schema オブジェクトを作成.
Schema schema = getSchema();

// XML から Java オブジェクトに変換するための Unmarshaller を作成.
Unmarshaller unmarshaller = context.createUnmarshaller();
unmarshaller.setSchema(schema);

// XML から Java オブジェクトに変換.
return (T) unmarshaller.unmarshal(input);

}

/**
* オブジェクトを入力にストリームに変換する。
*
* @param o
* ターゲットオブジェクト
*/
public InputStream getInputStream(T o) throws JAXBException, SAXException,
IOException {
// ターゲットクラスのためのコンテキストを作成.
JAXBContext context = JAXBContext.newInstance(targetClass);

// 使用する Schema オブジェクトを作成.
Schema schema = getSchema();

// XML から Java オブジェクトに変換するための Unmarshaller を作成.
Unmarshaller unmarshaller = context.createUnmarshaller();
unmarshaller.setSchema(schema);

// Java オブジェクトから XML に変換するための Marshaller を作成.
Marshaller marshaller = context.createMarshaller();
marshaller.setSchema(schema);
for (Map.Entry entry : properties.entrySet()) {
marshaller.setProperty(entry.getKey(), entry.getValue());
}
ByteArrayOutputStream output = new ByteArrayOutputStream();
// XML に変換.
marshaller.marshal(o, output);

return new ByteArrayInputStream(output.toByteArray());
}

/* クラスと同一フォルダからスキーマを読み込む。 */
private Schema getSchema() throws SAXException {
SchemaFactory factory = SchemaFactory
.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
if (schemaFile != null) {
return factory.newSchema(schemaFile);
} else {
return factory.newSchema(ClassUtils.getResource(targetClass,
targetClass.getSimpleName() + ".xsd"));
}
}

}

2009年5月27日水曜日

汎用Diffライブラリ

仕事上の必要性にかられ、汎用Diffライブラリを作ってみました。



Diff関連の資料をいろいろと調べたところ、「An O(NP) Sequence Comparison Algorithm」のアルゴリズムがよさそうだったのでこれをもとに実装しています。
実装にあたっては、可読性>メモリ使用量>性能の優先順位をおいて実装しているので性能的には改善の余地があるとは思っています。ただ、自分としてはとにかくシンプルに拡張できることが重要だったのであまり凝った作りにはしていません。

実際に使う場合は、以下の図にあるDiffSourceを実装することで様々なDiffをとることができます。標準の実装として文字列用のDiffStringSourceというクラスが用意してあるので文字列のDiffであればこちらを使用してください。



以下は文字列用のDiffStringSourceを使用したときの結果イメージを示しています。図のようにDiffSequenceに開始、終了インデックスが格納されDiffSequenceのリストとして結果を取得できます。



ソースと結果イメージは次のようになります。
サンプルソース:

public class DiffTest {

public static void main(String[] args)
throws Exception
{

DiffSource s1 = new DiffStringSource("abcdefg");
DiffSource s2 = new DiffStringSource("abzge");
Diff diff = new Diff();
List diffList = diff.diff(s1, s2);
int i = 0;
for(DiffSequence seq : diffList){
if(seq instanceof CommonSequence){
System.out.println(i+":共通部分");
System.out.println(
" source("+seq.getSourceStartIndex()
+","+seq.getSourceEndIndex()
+") = "+seq.getSourceElements());
System.out.println(
" target("+seq.getTargetStartIndex()
+","+seq.getTargetEndIndex()
+") = "+seq.getTargetElements());
}else{
System.out.println(i+":差異部分");
System.out.println(
" source("+seq.getSourceStartIndex()
+","+seq.getSourceEndIndex()
+") = "+seq.getSourceElements());
System.out.println(
" target("+seq.getTargetStartIndex()
+","+seq.getTargetEndIndex()
+") = "+seq.getTargetElements());
}
i++;
}
}
}


結果:
0:共通部分
source(0,2) = ab
target(0,2) = ab
1:差異部分
source(2,4) = cd
target(2,4) = zg
2:共通部分
source(4,5) = e
target(4,5) = e
3:差異部分
source(5,7) = fg
target(5,5) =