ParameterContainer.java

/*
 * Created on 2007/01/23
 * Copyright (C) 2007 Koga Laboratory. All rights reserved.
 *
 */
package org.mklab.tool.control.system.parameter;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import org.mklab.nfc.matrix.ComplexNumericalMatrix;
import org.mklab.nfc.matrix.RealNumericalMatrix;
import org.mklab.nfc.scalar.ComplexNumericalScalar;
import org.mklab.nfc.scalar.RealNumericalScalar;
import org.mklab.tool.control.system.SystemOperator;


/**
 * パラメータの値とアノテーションをまとめて管理するクラスです。
 * 
 * @author Koga Laboratory
 * @version $Revision: 1.12 $, 2007/01/23
 * @param <RS> type of real scalar
 * @param <RM> type of real matrix
 * @param <CS> type of complex scalar
 * @param <CM> type of complex matrix
 */
public class ParameterContainer<RS extends RealNumericalScalar<RS,RM,CS,CM>, RM extends RealNumericalMatrix<RS,RM,CS,CM>, CS extends ComplexNumericalScalar<RS,RM,CS,CM>, CM extends ComplexNumericalMatrix<RS,RM,CS,CM>> implements Comparable<ParameterContainer<RS,RM,CS,CM>> {

  /** パラメータをもつシステム */
  private SystemOperator<RS,RM,CS,CM> system;

  /** パラメータの値を保持するフィールド */
  private Field value;

  /** パラメータのアノテーション */
  private Parameter anotation;

  /** 名前 */
  private String name;

  /** パラメータの定義式 */
  private String expression;

  /**
   * 新しく生成された<code>ParameterContainer</code>オブジェクトを初期化します。
   * 
   * @param system パラメータをもつシステム
   * @param value パラメータの値を保持するフィールド
   * @param anotation パラメータのアノテーション
   * @param name 名前
   */
  public ParameterContainer(SystemOperator<RS,RM,CS,CM> system, final Field value, final Parameter anotation, final String name) {
    this.system = system;
    this.value = value;
    this.anotation = anotation;
    this.name = name;
  }

  /**
   * パラメータのアノテーションを返します。
   * 
   * @return パラメータのアノテーション
   */
  public Parameter getAnotation() {
    return this.anotation;
  }

  /**
   * パラメータの値を返します。
   * 
   * @return パラメータの値
   * @throws ParameterAccessException パラメータにアクセスする権利が無い場合
   */
  public Object getValue() throws ParameterAccessException {
    try {
      this.value.setAccessible(true);

      final Class<?> type = this.value.getType();

      if (type.isArray() && this.name.contains("[")) { //$NON-NLS-1$
        return Array.get(this.value.get(this.system), getIndexOfArray(this.name));
      }

      return this.value.get(this.system);
    } catch (IllegalArgumentException e) {
      throw new ParameterAccessException(e);
    } catch (IllegalAccessException e) {
      throw new ParameterAccessException(e);
    }
  }

  /**
   * パラメータに値を設定します。
   * 
   * @param value 設定する値
   * 
   * @throws ParameterAccessException パラメータにアクセスする権利が無い場合
   */
  public void setValue(Object value) throws ParameterAccessException {
    try {
      this.value.setAccessible(true);

      if (this.value.getType().isArray() && value.getClass().isArray() == false) {
        Array.set(this.value.get(this.system), getIndexOfArray(this.name), value);
      } else {
        this.value.set(this.system, value);
      }

      updateWithParameter();
    } catch (IllegalArgumentException e) {
      throw new ParameterAccessException(e);
    } catch (IllegalAccessException e) {
      throw new ParameterAccessException(e);
    }
  }

  /**
   * パラメータの定義式を設定します。
   * 
   * @param expression パラメータの定義式
   */
  public void setExpression(final String expression) {
    this.expression = expression;
  }

  /**
   * パラメータの値を返します。
   * 
   * @return パラメータの値
   * @throws ParameterAccessException パラメータにアクセスする権利が無い場合
   */
  public boolean getBoolean() throws ParameterAccessException {
    try {
      this.value.setAccessible(true);

      if (this.value.getType().isArray()) {
        return Array.getBoolean(this.value.get(this.system), getIndexOfArray(this.name));
      }

      return this.value.getBoolean(this.system);
    } catch (IllegalArgumentException e) {
      throw new ParameterAccessException(e);
    } catch (IllegalAccessException e) {
      throw new ParameterAccessException(e);
    }
  }

  /**
   * パラメータの値を設定します。
   * 
   * @param value パラメータの値
   * @throws ParameterAccessException パラメータにアクセスする権利が無い場合
   */
  public void setBoolean(final boolean value) throws ParameterAccessException {
    try {
      this.value.setAccessible(true);

      if (this.value.getType().isArray()) {
        Array.setBoolean(this.value.get(this.system), getIndexOfArray(this.name), value);
      } else {
        this.value.setBoolean(this.system, value);
      }
      updateWithParameter();
    } catch (IllegalArgumentException e) {
      throw new ParameterAccessException(e);
    } catch (IllegalAccessException e) {
      throw new ParameterAccessException(e);
    }
  }

  /**
   * パラメータの値を返します。
   * 
   * @return パラメータの値
   * @throws ParameterAccessException パラメータにアクセスする権利が無い場合
   */
  public byte getByte() throws ParameterAccessException {
    try {
      this.value.setAccessible(true);

      if (this.value.getType().isArray()) {
        return Array.getByte(this.value.get(this.system), getIndexOfArray(this.name));
      }

      return this.value.getByte(this.system);
    } catch (IllegalArgumentException e) {
      throw new ParameterAccessException(e);
    } catch (IllegalAccessException e) {
      throw new ParameterAccessException(e);
    }
  }

  /**
   * パラメータの値を設定します。
   * 
   * @param value パレメータの値
   * @throws ParameterAccessException パラメータにアクセスする権利が無い場合
   */
  public void setByte(final byte value) throws ParameterAccessException {
    try {
      this.value.setAccessible(true);

      if (this.value.getType().isArray()) {
        Array.setByte(this.value.get(this.system), getIndexOfArray(this.name), value);
      } else {
        this.value.setByte(this.system, value);
      }
      updateWithParameter();
    } catch (IllegalArgumentException e) {
      throw new ParameterAccessException(e);
    } catch (IllegalAccessException e) {
      throw new ParameterAccessException(e);
    }
  }

  /**
   * パラメータの値を返します。
   * 
   * @return パラメータの値
   * @throws ParameterAccessException パラメータにアクセスする権利が無い場合
   */
  public char getChar() throws ParameterAccessException {
    try {
      this.value.setAccessible(true);

      if (this.value.getType().isArray()) {
        return Array.getChar(this.value.get(this.system), getIndexOfArray(this.name));
      }

      return this.value.getChar(this.system);
    } catch (IllegalArgumentException e) {
      throw new ParameterAccessException(e);
    } catch (IllegalAccessException e) {
      throw new ParameterAccessException(e);
    }
  }

  /**
   * パラメータの値を設定します。
   * 
   * @param value パレメータの値
   * @throws ParameterAccessException パラメータにアクセスする権利が無い場合
   */
  public void setChar(final char value) throws ParameterAccessException {
    try {
      this.value.setAccessible(true);

      if (this.value.getType().isArray()) {
        Array.setChar(this.value.get(this.system), getIndexOfArray(this.name), value);
      } else {
        this.value.setChar(this.system, value);
      }
      updateWithParameter();
    } catch (IllegalArgumentException e) {
      throw new ParameterAccessException(e);
    } catch (IllegalAccessException e) {
      throw new ParameterAccessException(e);
    }
  }

  /**
   * パラメータの値を返します。
   * 
   * @return パラメータの値
   * @throws ParameterAccessException パラメータにアクセスする権利が無い場合
   */
  public double getDouble() throws ParameterAccessException {
    try {
      this.value.setAccessible(true);

      if (this.value.getType().isArray()) {
        return Array.getDouble(this.value.get(this.system), getIndexOfArray(this.name));
      }

      return this.value.getDouble(this.system);
    } catch (IllegalArgumentException e) {
      throw new ParameterAccessException(e);
    } catch (IllegalAccessException e) {
      throw new ParameterAccessException(e);
    }
  }

  /**
   * パラメータの値を設定します。
   * 
   * @param value パレメータの値
   * @throws ParameterAccessException パラメータにアクセスする権利が無い場合
   */
  public void setDouble(final double value) throws ParameterAccessException {
    try {
      this.value.setAccessible(true);

      if (this.value.getType().isArray()) {
        Array.setDouble(this.value.get(this.system), getIndexOfArray(this.name), value);
      } else {
        this.value.setDouble(this.system, value);
      }

      updateWithParameter();
    } catch (IllegalArgumentException e) {
      throw new ParameterAccessException(e);
    } catch (IllegalAccessException e) {
      throw new ParameterAccessException(e);
    }
  }

  /**
   * パラメータの値を返します。
   * 
   * @return パラメータの値
   * @throws ParameterAccessException パラメータにアクセスする権利が無い場合
   */
  public float getFloat() throws ParameterAccessException {
    try {
      this.value.setAccessible(true);

      if (this.value.getType().isArray()) {
        return Array.getFloat(this.value.get(this.system), getIndexOfArray(this.name));
      }

      return this.value.getFloat(this.system);
    } catch (IllegalArgumentException e) {
      throw new ParameterAccessException(e);
    } catch (IllegalAccessException e) {
      throw new ParameterAccessException(e);
    }
  }

  /**
   * パラメータの値を設定します。
   * 
   * @param value パレメータの値
   * @throws ParameterAccessException パラメータにアクセスする権利が無い場合
   */
  public void setFloat(final float value) throws ParameterAccessException {
    try {
      this.value.setAccessible(true);

      if (this.value.getType().isArray()) {
        Array.setFloat(this.value.get(this.system), getIndexOfArray(this.name), value);
      } else {
        this.value.setFloat(this.system, value);
      }
      updateWithParameter();
    } catch (IllegalArgumentException e) {
      throw new ParameterAccessException(e);
    } catch (IllegalAccessException e) {
      throw new ParameterAccessException(e);
    }
  }

  /**
   * パラメータの値を返します。
   * 
   * @return パラメータの値
   * @throws ParameterAccessException パラメータにアクセスする権利が無い場合
   */
  public int getInt() throws ParameterAccessException {
    try {
      this.value.setAccessible(true);

      if (this.value.getType().isArray()) {
        return Array.getInt(this.value.get(this.system), getIndexOfArray(this.name));
      }

      return this.value.getInt(this.system);
    } catch (IllegalArgumentException e) {
      throw new ParameterAccessException(e);
    } catch (IllegalAccessException e) {
      throw new ParameterAccessException(e);
    }
  }

  /**
   * パラメータの値を設定します。
   * 
   * @param value パレメータの値
   * @throws ParameterAccessException パラメータにアクセスする権利が無い場合
   */
  public void setInt(final int value) throws ParameterAccessException {
    try {
      this.value.setAccessible(true);

      if (this.value.getType().isArray()) {
        Array.setInt(this.value.get(this.system), getIndexOfArray(this.name), value);
      } else {
        this.value.setInt(this.system, value);
      }

      updateWithParameter();
    } catch (IllegalArgumentException e) {
      throw new ParameterAccessException(e);
    } catch (IllegalAccessException e) {
      throw new ParameterAccessException(e);
    }
  }

  /**
   * パラメータの値を返します。
   * 
   * @return パラメータの値
   * @throws ParameterAccessException パラメータにアクセスする権利が無い場合
   */
  public long getLong() throws ParameterAccessException {
    try {
      this.value.setAccessible(true);

      if (this.value.getType().isArray()) {
        return Array.getLong(this.value.get(this.system), getIndexOfArray(this.name));
      }

      return this.value.getLong(this.system);
    } catch (IllegalArgumentException e) {
      throw new ParameterAccessException(e);
    } catch (IllegalAccessException e) {
      throw new ParameterAccessException(e);
    }
  }

  /**
   * パラメータの値を設定します。
   * 
   * @param value パレメータの値
   * @throws ParameterAccessException パラメータにアクセスする権利が無い場合
   */
  public void setLong(final long value) throws ParameterAccessException {
    try {
      this.value.setAccessible(true);

      if (this.value.getType().isArray()) {
        Array.setLong(this.value.get(this.system), getIndexOfArray(this.name), value);
      } else {
        this.value.setLong(this.system, value);
      }

      updateWithParameter();
    } catch (IllegalArgumentException e) {
      throw new ParameterAccessException(e);
    } catch (IllegalAccessException e) {
      throw new ParameterAccessException(e);
    }
  }

  /**
   * パラメータの値を返します。
   * 
   * @return パラメータの値
   * @throws ParameterAccessException パラメータにアクセスする権利が無い場合
   */
  public short getShort() throws ParameterAccessException {
    try {
      this.value.setAccessible(true);

      if (this.value.getType().isArray()) {
        return Array.getShort(this.value.get(this.system), getIndexOfArray(this.name));
      }

      return this.value.getShort(this.system);
    } catch (IllegalArgumentException e) {
      throw new ParameterAccessException(e);
    } catch (IllegalAccessException e) {
      throw new ParameterAccessException(e);
    }
  }

  /**
   * パラメータの値を設定します。
   * 
   * @param value パレメータの値
   * @throws ParameterAccessException パラメータにアクセスする権利が無い場合
   */
  public void setShort(final short value) throws ParameterAccessException {
    try {
      this.value.setAccessible(true);

      if (this.value.getType().isArray()) {
        Array.setShort(this.value.get(this.system), getIndexOfArray(this.name), value);
      } else {
        this.value.setShort(this.system, value);
      }

      updateWithParameter();
    } catch (IllegalArgumentException e) {
      throw new ParameterAccessException(e);
    } catch (IllegalAccessException e) {
      throw new ParameterAccessException(e);
    }
  }

  /**
   * <code>opponent</code>と比較した結果を返します。
   * 
   * <p> <code>opponent</code>より順序が前のとき正、 <code>opponent</code>より順序が後のとき負、 等しいとき0を返します。
   * 
   * @param opponent 比較対象
   * @return <code>opponent</code>と比較した結果
   */
  public int compareTo(final ParameterContainer<RS,RM,CS,CM> opponent) {
    final String thisName = getName();
    final String opponentName = opponent.getName();

    int ans = thisName.compareToIgnoreCase(opponentName);
    if (ans == 0) {
      ans = thisName.compareTo(opponentName);
    }
    return ans;
  }

  /**
   * パラメータの名前を返します。
   * 
   * @return パラメータの名前
   */
  public String getName() {
    return this.name;
  }

  /**
   * パラメータの定義式を返します。
   * 
   * @return パラメータの定義式
   */
  public String getExpression() {
    return this.expression;
  }

  /**
   * パラメータの型を識別するための Class オブジェクトを返します。
   * 
   * @return パラメータの型を識別するための Class オブジェクトを返します。
   */
  public Class<?> getType() {
    return this.value.getType();
  }

  /**
   * パラメータを宣言するクラスまたはインタフェースを表す Class オブジェクトを返します。
   * 
   * @return パラメータを宣言するクラスまたはインタフェースを表す Class オブジェクト
   */
  public Class<?> getParameterClass() {
    return this.value.getDeclaringClass();
  }

  /**
   * @see java.lang.Object#equals(java.lang.Object)
   */
  @Override
  public boolean equals(Object o) {
    if (this == o) {
      return true;
    }
    if (o == null) {
      return false;
    }
    if (o.getClass() != getClass()) {
      return false;
    }
    final ParameterContainer<RS,RM,CS,CM> castedObj = (ParameterContainer<RS,RM,CS,CM>)o;
    return ((this.system == null ? castedObj.system == null : this.system.equals(castedObj.system)) && (this.value == null ? castedObj.value == null : this.value.equals(castedObj.value))
        && (this.anotation == null ? castedObj.anotation == null : this.anotation.equals(castedObj.anotation)) && (this.name == null ? castedObj.name == null : this.name.equals(castedObj.name)) && (this.expression == null
          ? castedObj.expression == null : this.expression.equals(castedObj.expression)));
  }

  /**
   * @see java.lang.Object#hashCode()
   */
  @Override
  public int hashCode() {
    int hashCode = 1;
    // hashCode = 31 * hashCode + (this.system == null ? 0 :
    // this.system.hashCode());// これを入れるとループ
    hashCode = 31 * hashCode + (this.value == null ? 0 : this.value.hashCode());
    hashCode = 31 * hashCode + (this.anotation == null ? 0 : this.anotation.hashCode());
    hashCode = 31 * hashCode + (this.name == null ? 0 : this.name.hashCode());
    hashCode = 31 * hashCode + (this.expression == null ? 0 : this.expression.hashCode());
    return hashCode;
  }

  /**
   * @see java.lang.Object#toString()
   */
  @Override
  public String toString() {
    Object object = ""; //$NON-NLS-1$
    try {
      this.value.setAccessible(true);
      object = this.value.get(this.system);
    } catch (@SuppressWarnings("unused") IllegalArgumentException e) {
      // nothing
    } catch (@SuppressWarnings("unused") IllegalAccessException e) {
      // nothing
    }

    String unitString = ""; //$NON-NLS-1$
    if (getUnit()[0] != SIunit.undefined) {
      unitString = " [" + SIunit.toString(getUnit()) + "]"; //$NON-NLS-1$ //$NON-NLS-2$
    }

    String descriptionString = ""; //$NON-NLS-1$
    if (getDescription().length() != 0) {
      descriptionString = " (" + getDescription() + ")"; //$NON-NLS-1$ //$NON-NLS-2$
    }

    return getName() + " = " + object + unitString + descriptionString; //$NON-NLS-1$
  }

  /**
   * 説明を返します。
   * 
   * @return 説明
   */
  public String getDescription() {
    if (this.anotation.description().equals("") == false) { //$NON-NLS-1$
      return this.anotation.description();
    }
    if (this.anotation.quantity() != QuantityType.UNDEFINED) {
      return this.anotation.quantity().getName();
    }

    return ""; //$NON-NLS-1$
  }

  /**
   * 単位を返します。
   * 
   * @return 単位
   */
  public SIunit[] getUnit() {
    if (this.anotation.unit()[0] != SIunit.undefined) {
      return this.anotation.unit();
    }
    if (this.anotation.quantity() != QuantityType.UNDEFINED) {
      return this.anotation.quantity().getUnitAsArray();
    }

    return new SIunit[] {SIunit.undefined};
  }

  /**
   * パラメータ変更に伴う更新を行います。
   * 
   * @throws ParameterAccessException パラメータ変更に伴う更新ができない場合
   */
  private void updateWithParameter() throws ParameterAccessException {
    if (this.anotation.update() == false) {
      return;
    }

    if ((this.system instanceof ParameterUpdator) == false) {
      return;
    }

    final boolean success = ((ParameterUpdator)this.system).updateWith(getName());
    if (success) {
      return;
    }

    updateWithSuperParameter(this.system.getClass());
  }

  /**
   * パラメータ変更に伴う更新をスーパークラスに対して行います。
   * 
   * @param klass 対象となるクラス
   * @throws ParameterAccessException パラメータ変更に伴う更新ができない場合
   */
  private void updateWithSuperParameter(final Class<? extends SystemOperator> klass) throws ParameterAccessException {
    if (SystemOperator.class.isAssignableFrom(klass) == false || SystemOperator.class == klass) {
      return;
    }

    final Class<? extends SystemOperator> superKlass = klass.getSuperclass().asSubclass(SystemOperator.class);
    if (ParameterUpdator.class.isAssignableFrom(superKlass) == false) {
      return;
    }

    try {
      final Method method = superKlass.getMethod("updateWith", String.class); //$NON-NLS-1$
      final Boolean success = (Boolean)method.invoke(this.system, getName());
      if (success.booleanValue()) {
        return;
      }

      updateWithSuperParameter(superKlass);
    } catch (SecurityException e) {
      throw new ParameterAccessException(e);
    } catch (NoSuchMethodException e) {
      throw new ParameterAccessException(e);
    } catch (IllegalArgumentException e) {
      throw new ParameterAccessException(e);
    } catch (IllegalAccessException e) {
      throw new ParameterAccessException(e);
    } catch (InvocationTargetException e) {
      throw new ParameterAccessException(e.getTargetException());
    }
  }

  /**
   * 配列パラメータの指数を返します。
   * 
   * @param elementName 配列パラメータの名前
   * @return 配列パラメータの指数
   */
  public static int getIndexOfArray(final String elementName) {
    final int start = elementName.indexOf("["); //$NON-NLS-1$
    final int end1 = elementName.indexOf("/"); //$NON-NLS-1$
    final int end2 = elementName.indexOf("]"); //$NON-NLS-1$

    if (end1 < 0) {
      return Integer.parseInt(elementName.substring(start + 1, end2)) - 1;
    }

    final int index = Integer.parseInt(elementName.substring(start + 1, end1)) - 1;
    return index;
  }

  /**
   * 配列パラメータの成分の個数を返します。
   * 
   * @param elementName 配列パラメータの名前
   * @return 配列パラメータの成分の個数
   */
  public static int getArrayLength(final String elementName) {
    final int start = elementName.indexOf("["); //$NON-NLS-1$
    final int end1 = elementName.indexOf("/"); //$NON-NLS-1$
    final int end2 = elementName.indexOf("]"); //$NON-NLS-1$

    if (start < 0) {
      throw new IllegalArgumentException(Messages.getString("ParameterContainer.18")); //$NON-NLS-1$
    }

    if (end1 < 0) {
      return Integer.parseInt(elementName.substring(start + 1, end2));
    }

    return Integer.parseInt(elementName.substring(end1 + 1, end2));
  }

  /**
   * パラメータの説明が国際化されているか判定します。
   * 
   * @return パラメータの説明が国際化されていればtrue、そうでなければfalse
   */
  public boolean isInternationalized() {
    return this.anotation.internationalization();
  }

  /**
   * 国際化されたパラメータの説明を返します。
   * 
   * @param key キー
   * @return 国際化されたパラメータの説明
   * @throws ParameterException パラメータの説明が国際化されていない場合
   */
  public String getInternationalizedString(final String key) throws ParameterException {
    if (this.system instanceof StringExternalizable) {
      return ((StringExternalizable)this.system).getString(key);
    }

    throw new ParameterException(Messages.getString("ParameterContainer.19")); //$NON-NLS-1$
  }

}