Tbpgr Blog

Employee Experience Engineer tbpgr(てぃーびー) のブログ

Java | 複数のデータクラスをマージするツール

パンくず

Java
複数のデータクラスをマージするツール

概要

複数のデータクラスをマージするツールについて

要件

複数のデータクラスを指定可能
・除外名のリストに設定した項目はマージ対象外
・分離名のリストに設定した項目は名称+データ型名に名前を変更して対象とする
※例えば、クラスAでString型のhogeとクラスBでInteger型のhogeがあれば
hogeStringとhogeIntegerとして両方を出力対象とする。
・getterとsetterを生成する
・基底クラス指定可能
・インターフェース指定可能
・出力クラス名指定可能
・出力パッケージ名指定可能
・データクラスが対象なのでシリアルバージョンUIDはデフォルトで固定出力
public static final long serialVersionUID = 1L;は固定
・import文も自動出力
・フォーマットは整えず、自動生成後にフォーマッターで設定する前提とする

クラス構成

develop_util.data_class_merge
┗/JavaSampleCode/src/develop_util/data_class_merge/DataClassMerge.java(メインの自動生成処理)
develop_util.data_class_merge.conf
┣/JavaSampleCode/src/develop_util/data_class_merge/conf/DataClassDefinitionIntf.java(設定ファイルインターフェース)
┗/JavaSampleCode/src/develop_util/data_class_merge/conf/MergeHogeDefinition.java(設定ファイル。出力対象ごとに定義する)
develop_util.data_class_merge.sample_data_class
┣/JavaSampleCode/src/develop_util/data_class_merge/sample_data_class/Hage.java(テスト用マージ元クラス1)
┣/JavaSampleCode/src/develop_util/data_class_merge/sample_data_class/Hige.java(テスト用マージ元クラス2)
┣/JavaSampleCode/src/develop_util/data_class_merge/sample_data_class/Hoge.java(テスト用マージ元クラス3)
┣/JavaSampleCode/src/develop_util/data_class_merge/sample_data_class/Line.java(テスト用マージ元クラスから参照されるデータクラス1)
┣/JavaSampleCode/src/develop_util/data_class_merge/sample_data_class/Other.java(テスト用マージ元クラスから参照されるデータクラス1)
┗/JavaSampleCode/src/develop_util/data_class_merge/sample_data_class/Parent.java(テスト用マージ元クラスに指定される基底クラス)

サンプルコード

自動生成本体

DataClassMerge

package develop_util.data_class_merge;

import java.lang.reflect.Field;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import develop_util.data_class_merge.conf.DataClassDefinitionIntf;
import develop_util.data_class_merge.conf.MergeHogeDefinition;

/**
 * Dataクラスマージツール。
 */
public class DataClassMerge {
  private static String template = "";
  private static final String PACKAGE = "$package";
  private static final String IMPORT = "$import";
  private static final String CLASS_INFO = "$class_info";
  private static final String AUTHOR = "$author";
  private static final String CLASS_NAME = "$class_name";
  private static final String EXTENDS = "$extends";
  private static final String IMPLEMENTS = "$implements";
  private static final String FIELDS = "$fields";
  private static final String GETTERS = "$getters";
  private static final String SETTERS = "$setters";

  static {
    template += "package $package;\n";
    template += "$import\n";
    template += "\n";
    template += "/**\n";
    template += " *\n";
    template += " * $class_info.\n";
    template += " *\n";
    template += " * @author $author\n";
    template += " */\n";
    template += "public class $class_name $extends $implements {\n";
    template += "public static final long serialVersionUID = 1L;\n";
    template += "$fields\n";
    template += "$setters\n";
    template += "$getters\n";
    template += "}\n";

  }

  /**
   * マージ定義情報を元にデータクラスのマージ結果を標準出力する。
   *
   * @param definition マージ定義情報
   */
  public void mergeDataClass(DataClassDefinitionIntf definition) {
    String out = template;
    Set<String> importSet = new HashSet<String>();
    out = out.replace(DataClassMerge.PACKAGE, definition.getOutputPackage());
    out = out.replace(DataClassMerge.CLASS_INFO, definition.getOutputClassJavaDoc());
    out = out.replace(DataClassMerge.AUTHOR, definition.getAutor());
    out = out.replace(DataClassMerge.CLASS_NAME, definition.getOutputClassName());
    Class<?> extendsClass = definition.getExtendsClass();
    if (extendsClass != null) {
      out = out.replace(DataClassMerge.EXTENDS, "extends " + extendsClass.getSimpleName());
      importSet.add(extendsClass.getCanonicalName());
    } else {
      out = out.replace(DataClassMerge.EXTENDS, "");
    }
    Class<?> implementsInterface = definition.getImplementsInterface();
    if (implementsInterface != null) {
      out = out.replace(DataClassMerge.IMPLEMENTS, "implements " + implementsInterface.getSimpleName());
      importSet.add(implementsInterface.getCanonicalName());
    } else {
      out = out.replace(DataClassMerge.IMPLEMENTS, "");
    }

    out = setFields(out, definition, importSet);

    StringBuilder imports = new StringBuilder();
    for (String importPackage : importSet) {
      imports.append("import " + importPackage + ";\n");
    }
    out = out.replace(DataClassMerge.IMPORT, imports.toString());
    System.out.println(out);
  }

  /**
   * Field,setter,getterの設定
   *
   * @param out テンプレート
   * @param definition 入力クラス情報
   * @param importSet import文作成用Set
   * @return 置換後文字列
   */
  private String setFields(String out, DataClassDefinitionIntf definition, Set<String> importSet) {
    List<Class<?>> classes = definition.getBaseClasses();
    StringBuilder fieldOut = new StringBuilder();
    StringBuilder getterOut = new StringBuilder();
    StringBuilder setterOut = new StringBuilder();
    Set<String> nameSet = new HashSet<String>();
    for (Class<?> clazz : classes) {
      for (Field field : clazz.getDeclaredFields()) {
        String variable = field.getName();
        String typeName = field.getType().getSimpleName();
        if (variable == "serialVersionUID") {
          continue;
        }
        if (definition.getExcludeNames().contains(variable)) {
          continue;
        }
        List<String> separateNames = definition.getSeparateNames();
        variable = getSepareteVariableName(variable, typeName, separateNames);
        if (nameSet.contains(variable)) {
          continue;
        }

        if (field.getType().getSimpleName().equals("List")) {
          // List時
          String generic = field.getGenericType().toString();
          String simpleGeneric = generic.substring(generic.lastIndexOf(".") + 1);
          String genericType = typeName + "<" + simpleGeneric;

          // フィールド設定
          fieldOut.append("private " + genericType + " " + variable + ";\n");
          // Getter設定
          setGetter(getterOut, variable, genericType);
          // Setter設定
          setSetter(setterOut, variable, genericType);
          // import設定(List)
          importSet.add(field.getType().getName());
          // import設定(ジェネリックの型)
          importSet.add(generic.substring(generic.indexOf("<") + 1, generic.indexOf(">")));

        } else {
          // List以外

          // フィールド設定
          fieldOut.append("private " + typeName + " " + variable + ";\n");
          // Getter設定
          setGetter(getterOut, variable, typeName);
          // Setter設定
          setSetter(setterOut, variable, typeName);
          // import設定
          importSet.add(field.getType().getName());
        }
        nameSet.add(variable);
      }
    }
    out = out.replace(DataClassMerge.FIELDS, fieldOut.toString());
    out = out.replace(DataClassMerge.GETTERS, getterOut.toString());
    out = out.replace(DataClassMerge.SETTERS, setterOut.toString());

    return out;
  }

  private String getSepareteVariableName(String variable, String typeName, List<String> separateNames) {
    if (separateNames != null && separateNames.size() != 0) {
      for (String separete : separateNames) {
        if (variable.equals(separete)) {
          variable = variable + typeName;
        }
      }
    }
    return variable;
  }

  private void setSetter(StringBuilder setterOut, String variable, String genericType) {
    setterOut.append("public void set" + getVariableNameFromUpperName(variable) + "(" + genericType + " " + variable
        + ") {\n");
    setterOut.append("this." + variable + " = " + variable + ";\n");
    setterOut.append("}\n");
  }

  private void setGetter(StringBuilder getterOut, String variable, String genericType) {
    getterOut.append("public " + genericType + " get" + getVariableNameFromUpperName(variable) + "() {\n");
    getterOut.append("return this." + variable + ";\n");
    getterOut.append("}\n");
  }

  public static String getVariableNameFromUpperName(String upperName) {
    return upperName.substring(0, 1).toUpperCase() + upperName.substring(1);
  }

  public static void main(String[] args) {
    DataClassMerge merge = new DataClassMerge();
    merge.mergeDataClass(new MergeHogeDefinition());
  }

}
出力内容定義インターフェース

DataClassDefinitionIntf

package develop_util.data_class_merge.conf;

import java.util.List;

public interface DataClassDefinitionIntf {
  /**
   * 出力クラス名を指定する。
   * @return 出力クラス名
   */
  String getOutputClassName();

  /**
   * 出力クラスJavaDoc概要を指定する。
   * @return 出力クラスJavaDoc概要
   */
  String getOutputClassJavaDoc();

  /**
   * 作成者を指定する。
   * @return 作成者
   */
  String getAutor();

  /**
   * 出力先パッケージを指定する。
   * @return 出力先パッケージ
   */
  String getOutputPackage();

  /**
   * マージするクラスを列挙する。
   *
   * @return マージするクラスのリスト
   */
  List<Class<?>> getBaseClasses();

  /**
   * 除外するフィールド名を列挙する。
   *
   * @return 除外するフィールド名のリスト
   */
  List<String> getExcludeNames();

  /**
   * 分割するフィールド名を列挙する。
   *
   * @return 分割するフィールド名のリスト
   */
  List<String> getSeparateNames();

  /**
   * 継承クラスを指定する。
   *
   * @return 継承クラス
   */
  Class<?> getExtendsClass();

  /**
   * 実装インターフェースを指定する。
   *
   * @return 実装インターフェース
   */
  Class<?> getImplementsInterface();
}
テスト用出力内容定義クラス

MergeHogeDefinition

package develop_util.data_class_merge.conf;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import develop_util.data_class_merge.sample_data_class.Hage;
import develop_util.data_class_merge.sample_data_class.Hige;
import develop_util.data_class_merge.sample_data_class.Hoge;
import develop_util.data_class_merge.sample_data_class.Parent;

public class MergeHogeDefinition implements DataClassDefinitionIntf {

  @Override
  public List<Class<?>> getBaseClasses() {
    List<Class<?>> classes = new ArrayList<Class<?>>();
    classes.add(Hoge.class);
    classes.add(Hige.class);
    classes.add(Hage.class);
    return classes;
  }

  @Override
  public List<String> getExcludeNames() {
    return Arrays.asList("exclude");
  }

  @Override
  public List<String> getSeparateNames() {
    return Arrays.asList("separate");
  }

  @Override
  public Class<?> getExtendsClass() {
    return Parent.class;
  }

  @Override
  public String getOutputPackage() {
    return "develop_util.data_class_merge.output";
  }

  @Override
  public String getOutputClassName() {
    return "MergeHoge";
  }

  @Override
  public Class<?> getImplementsInterface() {
    return Serializable.class;
  }

  @Override
  public String getOutputClassJavaDoc() {
    return "マージ版ほげクラス";
  }

  @Override
  public String getAutor() {
    return "もよもと";
  }

}
以下、テスト用データクラス

Hage.java

package develop_util.data_class_merge.sample_data_class;

import java.math.BigDecimal;
import java.util.List;

public class Hage extends Parent {
  private String hageSt;
  private BigDecimal hageBi;
  private Other other;
  private List<Line> lines;
  private Other exclude;
  private Other separate;
  // ※getter、setterは略(実際はある)
}

Hige.java

package develop_util.data_class_merge.sample_data_class;

import java.math.BigDecimal;
import java.util.List;

public class Hige extends Parent {
  private String higeSt;
  private BigDecimal higeBi;
  private Other other;
  private List<Line> lines;
  private BigDecimal exclude;
  private BigDecimal separate;
  // ※getter、setterは略(実際はある)

Hoge.java

package develop_util.data_class_merge.sample_data_class;

import java.math.BigDecimal;
import java.util.List;

public class Hoge extends Parent {
  private String hogeSt;
  private BigDecimal hogeBi;
  private Other other;
  private List<Line> lines;
  private String exclude;
  private String separate;
    // ※getter、setterは略(実際はある)
}

Line.java

package develop_util.data_class_merge.sample_data_class;

public class Line {
  private String line;

  public String getLine() {
    return line;
  }

  public void setLine(String line) {
    this.line = line;
  }
}

Other.java

package develop_util.data_class_merge.sample_data_class;

public class Other {
  private String other;

  public String getOther() {
    return other;
  }

  public void setOther(String other) {
    this.other = other;
  }
}

Parent.java

package develop_util.data_class_merge.sample_data_class;

public class Parent {
  private String parent;

  public String getParent() {
    return parent;
  }

  public void setParent(String parent) {
    this.parent = parent;
  }
}

出力結果(フォーマットをかけたもの)

package develop_util.data_class_merge.output;

import develop_util.data_class_merge.sample_data_class.Other;
import develop_util.data_class_merge.sample_data_class.Line;
import java.util.List;
import develop_util.data_class_merge.sample_data_class.Parent;
import java.math.BigDecimal;
import java.lang.String;
import java.io.Serializable;

/**
 *
 * マージ版ほげクラス.
 *
 * @author もよもと
 */
public class MergeHoge extends Parent implements Serializable {
  public static final long serialVersionUID = 1L;
  private String hogeSt;
  private BigDecimal hogeBi;
  private Other other;
  private List<Line> lines;
  private String separateString;
  private String higeSt;
  private BigDecimal higeBi;
  private BigDecimal separateBigDecimal;
  private String hageSt;
  private BigDecimal hageBi;
  private Other separateOther;

  public void setHogeSt(String hogeSt) {
    this.hogeSt = hogeSt;
  }

  public void setHogeBi(BigDecimal hogeBi) {
    this.hogeBi = hogeBi;
  }

  public void setOther(Other other) {
    this.other = other;
  }

  public void setLines(List<Line> lines) {
    this.lines = lines;
  }

  public void setSeparateString(String separateString) {
    this.separateString = separateString;
  }

  public void setHigeSt(String higeSt) {
    this.higeSt = higeSt;
  }

  public void setHigeBi(BigDecimal higeBi) {
    this.higeBi = higeBi;
  }

  public void setSeparateBigDecimal(BigDecimal separateBigDecimal) {
    this.separateBigDecimal = separateBigDecimal;
  }

  public void setHageSt(String hageSt) {
    this.hageSt = hageSt;
  }

  public void setHageBi(BigDecimal hageBi) {
    this.hageBi = hageBi;
  }

  public void setSeparateOther(Other separateOther) {
    this.separateOther = separateOther;
  }

  public String getHogeSt() {
    return this.hogeSt;
  }

  public BigDecimal getHogeBi() {
    return this.hogeBi;
  }

  public Other getOther() {
    return this.other;
  }

  public List<Line> getLines() {
    return this.lines;
  }

  public String getSeparateString() {
    return this.separateString;
  }

  public String getHigeSt() {
    return this.higeSt;
  }

  public BigDecimal getHigeBi() {
    return this.higeBi;
  }

  public BigDecimal getSeparateBigDecimal() {
    return this.separateBigDecimal;
  }

  public String getHageSt() {
    return this.hageSt;
  }

  public BigDecimal getHageBi() {
    return this.hageBi;
  }

  public Other getSeparateOther() {
    return this.separateOther;
  }

}