JavaBeanを別のJavaBeanに変換する
JavaBeanを別のJavaBeanに型変換して値をコピーするユーティリティ。DXOを書くのが面倒になったときに使えます。
機能
- JavaBeanを別のJavaBeanに型変換します。
- 変換前のクラス、変換後のクラス共にJavaBeanである必要があります。
- 同じ名前のプロパティをコピーします。
- 変換元からgetterで取得したプロパティを変換後クラスののsetterを使ってコピーします。
- このとき、プロパティ値の型も再帰的に変換されます。
- 変換元にのみあるプロパティはコピーされません。
- 型はチェックしていません。(エラーになります。)
- 変換元からgetterで取得したプロパティを変換後クラスののsetterを使ってコピーします。
サンプル
変換するJavaBean。
// 変換元のBean static class Kitten { private String name; private boolean isFemale; public final String getName () { return name; } public final void setName ( String name ) { this.name = name; } public final boolean isFemale () { return isFemale; } public final void setFemale ( boolean isFemale ) { this.isFemale = isFemale; } public String toString() { return getClass().getName() + ":" + getName() + ":" + String.valueOf( isFemale() ); } } // 変換後のBean static class Lion { private String name; private boolean isFemale; public final String getName () { return name; } public final void setName ( String name ) { this.name = name; } public final boolean isFemale () { return isFemale; } public final void setFemale ( boolean isFemale ) { this.isFemale = isFemale; } public String toString() { return getClass().getName() + ":" + getName() + ":" + String.valueOf( isFemale() ); } }
変換のコードは次の通りです。
// 変換ルールを作成。 BeanTransformer.Recipe recipe = new BeanTransformer.Recipe(); recipe.map( Kitten.class, Lion.class ); // Kitten <-> Lionに相互変換。 // 変換器を作成。 BeanTransformer transformer = new BeanTransformer( recipe ); Kitten mii = new Kitten(); mii.setFemale( true ); mii.setName( "mii" ); System.out.println( mii ); System.out.println( transformer.transform( mii ) ); // 変換 System.out.println( transformer.transform(transformer.transform( mii )) ); // 逆変換
実行結果です。
BeanTransform$Kitten:mii:true BeanTransform$Lion:mii:true BeanTransform$Kitten:mii:true
ユーティリティのソース
import java.beans.BeanInfo; import java.beans.IntrospectionException; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.lang.reflect.Array; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; /** * 指定された変換規則に基づき、JavaBeanを変換する。 * * @version $Revision:$ * @author $Author:$ */ public class BeanTransformer { /** * 変換規則 */ private final Recipe recipe; /** * コンストラクタ * @param recipe 変換規則 */ public BeanTransformer(Recipe recipe) { this.recipe = recipe; } /** * JavaBeanを変換する * @param src 変換元オブジェクト * @return 変換後のオブジェクト * @throws IllegalArgumentException プロパティの型が一致しない場合。 * @throws IllegalAccessException プロパティのsetter/getterへのアクセスが許可されない場合。 * @throws InvocationTargetException setter/getterの実行に失敗した場合。 * @throws IntrospectionException Beanの情報取得に失敗した場合 * @throws InstantiationException 変換後オブジェクトのインスタンス化に失敗した場合 */ public Object transform ( Object src ) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, IntrospectionException, InstantiationException { if ( src == null ) { return null; } Class srcClass = src.getClass(); Class dstClass = recipe.getDestination( srcClass ); // 配列やコレクションは別処理。 if ( src instanceof List ) { // List Collection srcColection = (Collection) src; List tmp = new ArrayList(); Iterator it = srcColection.iterator(); while ( it.hasNext() ) { tmp.add( transform( it.next() ) ); } return tmp; } else if ( src instanceof Set ) { // Set Collection srcColection = (Collection) src; Set tmp = new HashSet(); Iterator it = srcColection.iterator(); while ( it.hasNext() ) { tmp.add( transform( it.next() ) ); } return tmp; } else if ( src instanceof Map ) { // Map Map srcMap = (Map) src; Map tmp = new HashMap(); Iterator it = srcMap.entrySet().iterator(); while ( it.hasNext() ) { Map.Entry e = ( Map.Entry ) it.next(); tmp.put( transform( e.getKey() ), transform( e.getValue()) ); } return tmp; } else if( srcClass.isArray()) { // 配列 dstClass = recipe.getDestination( srcClass.getComponentType()); if ( dstClass == null ) { return src; } int size = Array.getLength( src ) ; Object dst = Array.newInstance( dstClass, size ); for ( int i = 0; i < size; i++ ) { Object o = transform(Array.get( src, i )); Array.set( dst, i, o ); } return dst; } if ( dstClass == null ) { return src; } // プロパティの情報を取得 Map<String, PropertyDescriptor> srcProperties= toPropertyMap(Introspector.getBeanInfo( srcClass )); Map<String, PropertyDescriptor> dstProperties= toPropertyMap(Introspector.getBeanInfo( dstClass )); Object dst = dstClass.newInstance(); // インスタンス作成 // プロパティの設定 for ( Map.Entry<String, PropertyDescriptor> entry : srcProperties.entrySet() ) { // 対応するプロパティが変換先に存在する場合のみ、設定する。 PropertyDescriptor srcProperty = entry.getValue(); PropertyDescriptor dstProperty = dstProperties.get( entry.getKey() ); if ( srcProperty != null && srcProperty.getReadMethod() != null && dstProperty != null && dstProperty.getWriteMethod() != null ) { Object value = srcProperty.getReadMethod().invoke( src, new Object[0] ); value = transform( value ); dstProperty.getWriteMethod().invoke( dst, new Object[] { value } ); } } return dst; } private static Map<String, PropertyDescriptor> toPropertyMap( BeanInfo info ){ Map<String, PropertyDescriptor> map = new HashMap<String, PropertyDescriptor>(); for ( PropertyDescriptor p : info.getPropertyDescriptors() ) { map.put( p.getName(), p ); } return map; } /** * 変換規則 */ public static class Recipe { private final Map<Class, Class> mapping = new HashMap<Class, Class>(); /** * コンストラクタ */ public Recipe( ) {} /** * 変換規則を追加する。(a→bに変換、b→aに変換) * @param a クラス * @param b クラス */ public void map( Class a, Class b ) { this.mapping.put( a, b ); this.mapping.put( b, a ); } /** * 変換後のクラスを取得する。 * @param source 変換元のクラス * @return 変換後のクラス */ public Class getDestination( Class source ) { return mapping.get( source ); } } }
追記 (2007/08/21)
以下のバグがあったのでなおしました。
- プロパティにBeanを持つBeanの変換でエラー。子のBeanの型変換が行われない。