DoubleSystemOperator.java

/*
 * $Id: SystemOperator.java,v 1.43 2008/06/26 10:10:35 koga Exp $
 *
 * Copyright (C) 2004 Koga Laboratory. All rights reserved.
 *
 */

package org.mklab.tool.control.system;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;

import org.mklab.nfc.matrix.ArrayElement;
import org.mklab.tool.control.DoubleLinearSystem;
import org.mklab.tool.control.system.parameter.DoubleParameterContainer;
import org.mklab.tool.control.system.parameter.NoSuchParameterException;
import org.mklab.tool.control.system.parameter.Parameter;
import org.mklab.tool.control.system.parameter.ParameterAccessException;


/**
 * システムオペレータ(数式モデル)を表すクラスです。
 * 
 * @author Koga Laboratory
 * @version $Revision: 1.43 $, 2004/11/09
 */
public abstract class DoubleSystemOperator implements ArrayElement<DoubleSystemOperator,DoubleAdjacencyMatrix>, Cloneable {

  /** 状態の数 */
  private int stateSize;
  /** 入力の数 */
  private int inputSize = -1;
  /** 出力の数 */
  private int outputSize = -1;
  /** 直達項があれば(出力が入力に直接依存すれば)true */
  private boolean hasDirectFeedthrough = true;
  /** 状態が入力に依存すればtrue */
  private boolean forcedSystem = true;
  /** 動的システムならばtrue */
  private boolean dynamic = false;
  /** 線形システムならばtrue */
  private boolean linear = false;

  /** 入力が入力ノードならばtrue */
  private boolean inlet = false;
  /** 出力が出力ノードならばtrue */
  private boolean outlet = false;

  /** パラメータ */
  private Map<String, DoubleParameterContainer> parameters;

  /** 自動的に入出力の数を設定するならばtrue */
  private boolean autoSize = false;

  /** システムオペレータを識別するためのID文字列 */
  private String id;

  /**
   * 新しく生成された<code>SystemOperator</code>オブジェクトを初期化します。
   */
  public DoubleSystemOperator() {
    try {
      setupParameters(this.getClass());
      this.id = Integer.toString(localHashCode());
    } catch (ParameterAccessException e) {
      throw new InternalError(e.getMessage());
    }
  }

  /**
   * @see org.mklab.nfc.matrix.GridElement#clone()
   */
  @Override
  public DoubleSystemOperator clone() {
    return this; // クローンを生成してはならない!
  }

  /**
   * @see java.lang.Object#equals(java.lang.Object)
   */
  @Override
  public boolean equals(Object opponent) {
    if (this == opponent) {
      return true;
    }
    if (opponent == null) {
      return false;
    }
    if (opponent.getClass() != getClass()) {
      return false;
    }
    DoubleSystemOperator castedObj = (DoubleSystemOperator)opponent;
    if (((this.stateSize != castedObj.stateSize) || (this.inputSize != castedObj.inputSize) || (this.outputSize != castedObj.outputSize)
        || (this.hasDirectFeedthrough != castedObj.hasDirectFeedthrough) || (this.forcedSystem != castedObj.forcedSystem) || (this.dynamic != castedObj.dynamic) || (this.linear != castedObj.linear)
        || (this.inlet != castedObj.inlet) || (this.outlet != castedObj.outlet) || (this.autoSize != castedObj.autoSize) || (this.id.equals(castedObj.id) == false))) {
      return false;
    }

    if (this.parameters == null && castedObj.parameters == null) {
      return true;
    }

    if (this.parameters != null && castedObj.parameters == null) {
      return false;
    }
    if (this.parameters == null && castedObj.parameters != null) {
      return false;
    }

    if (this.parameters.size() != castedObj.parameters.size()) {
      return false;
    }

    for (final String key : this.parameters.keySet()) {
      if (castedObj.parameters.containsKey(key) == false) {
        return false;
      }

      if (this.parameters.get(key).equals(castedObj.parameters.get(key)) == false) {
        return false;
      }
    }

    return true;
  }

  /**
   * @see java.lang.Object#hashCode()
   */
  @Override
  public int hashCode() {
    int hashCode = 1;
    hashCode = 31 * hashCode + this.stateSize;
    hashCode = 31 * hashCode + this.inputSize;
    hashCode = 31 * hashCode + this.outputSize;
    hashCode = 31 * hashCode + (this.hasDirectFeedthrough ? 1231 : 1237);
    hashCode = 31 * hashCode + (this.forcedSystem ? 1231 : 1237);
    hashCode = 31 * hashCode + (this.dynamic ? 1231 : 1237);
    hashCode = 31 * hashCode + (this.linear ? 1231 : 1237);
    hashCode = 31 * hashCode + (this.inlet ? 1231 : 1237);
    hashCode = 31 * hashCode + (this.outlet ? 1231 : 1237);
    hashCode = 31 * hashCode + (this.parameters == null ? 0 : getParameterHashCode());
    hashCode = 31 * hashCode + (this.autoSize ? 1231 : 1237);
    hashCode = 31 * hashCode + (this.id == null ? 0 : this.id.hashCode());
    return hashCode;
  }

  /**
   * タグ文字列を使用しないハッシュコードを返します。
   * 
   * @return タグ文字列を使用しないハッシュコード
   */
  private int localHashCode() {
    int hashCode = 1;
    hashCode = 31 * hashCode + this.stateSize;
    hashCode = 31 * hashCode + this.inputSize;
    hashCode = 31 * hashCode + this.outputSize;
    hashCode = 31 * hashCode + (this.hasDirectFeedthrough ? 1231 : 1237);
    hashCode = 31 * hashCode + (this.forcedSystem ? 1231 : 1237);
    hashCode = 31 * hashCode + (this.dynamic ? 1231 : 1237);
    hashCode = 31 * hashCode + (this.linear ? 1231 : 1237);
    hashCode = 31 * hashCode + (this.inlet ? 1231 : 1237);
    hashCode = 31 * hashCode + (this.outlet ? 1231 : 1237);
    hashCode = 31 * hashCode + (this.parameters == null ? 0 : getParameterHashCode());
    hashCode = 31 * hashCode + (this.autoSize ? 1231 : 1237);
    return hashCode;
  }

  /**
   * パラメータのハッシュコードを返します。
   * 
   * @return パラメータのハッシュコード
   */
  private int getParameterHashCode() {
    if (this.parameters == null) {
      return 0;
    }

    int hashCode = 1;
    for (final String key : this.parameters.keySet()) {
      hashCode = 31 * hashCode + this.parameters.get(key).hashCode();
    }

    return hashCode;
  }

  /**
   * (ハッシュコードに使用される)ID文字列を設定します。
   * 
   * @param id (ハッシュコード使用される)ID文字列
   */
  void setID(final String id) {
    this.id = id;
  }

  /**
   * (ハッシュコードに使用される)ID文字列を返します。
   * 
   * @return (ハッシュコードに使用される)ID文字列
   */
  String getID() {
    return this.id;
  }

  /**
   * 次数(状態の数)を返します。
   * 
   * @return 状態の数
   */
  public int getStateSize() {
    return this.stateSize;
  }

  /**
   * 次数(状態の数)を設定します。
   * 
   * @param stateSize 状態の数
   */
  public void setStateSize(final int stateSize) {
    this.stateSize = stateSize;
  }

  /**
   * 入力の数を返します。
   * 
   * @return 入力の数
   */
  public int getInputSize() {
    return this.inputSize;
  }

  /**
   * 入力の数を設定します。
   * 
   * @param inputSize 入力の数
   */
  public void setInputSize(final int inputSize) {
    this.inputSize = inputSize;
  }

  /**
   * 出力の数を返します。
   * 
   * @return 出力の数
   */
  final public int getOutputSize() {
    return this.outputSize;
  }

  /**
   * 出力の数を設定します。
   * 
   * @param outputSize 出力の数
   */
  public void setOutputSize(final int outputSize) {
    this.outputSize = outputSize;
  }

  /**
   * 直達項があるか(出力が入力に直接依存するか)判定します。
   * 
   * @return 直達項があれば(出力が入力に直接依存すれば)true、そうでなければfalse
   */
  final public boolean hasDirectFeedthrough() {
    return this.hasDirectFeedthrough;
  }

  /**
   * 直達項があるか(出力が入力に直接依存するか)設定します。
   * 
   * @param hasDirectFeedthrough 直達項があれば(出力が入力に直接依存すれば)true、そうでなければfalse
   */
  final protected void setHasDirectFeedthrough(final boolean hasDirectFeedthrough) {
    this.hasDirectFeedthrough = hasDirectFeedthrough;
    if (hasDirectFeedthrough) {
      this.forcedSystem = true;
    }
  }

  /**
   * 入力が入力端であるかを設定します。
   * 
   * @param inlet 入力が入力端ならばtrue、そうでなければfalse
   */
  public void setInlet(final boolean inlet) {
    this.inlet = inlet;
  }

  /**
   * 入力が入力端であるか判定します。
   * 
   * @return 入力が入力端ならばtrue、そうでなければfalse
   */
  public boolean isInlet() {
    return this.inlet;
  }

  /**
   * 出力が出力端であるかを設定します。
   * 
   * @param outlet 出力が出力端ならばtrue、そうでなければfalse
   */
  public void setOutlet(final boolean outlet) {
    this.outlet = outlet;
  }

  /**
   * 出力が出力端であるか判定します。
   * 
   * @return 出力が出力端ならばtrue、そうでなければfalse
   */
  public boolean isOutlet() {
    return this.outlet;
  }

  /**
   * 状態が入力に依存するか判定します。
   * 
   * @return 状態が入力に依存すればtrue、そうでなければfalse
   */
  final public boolean isForecdSystem() {
    return this.forcedSystem;
  }

  /**
   * 状態が入力に依存するか設定します。
   * 
   * @param forcedSystem 状態が入力に依存すればtrue、そうでなければfalse
   */
  final protected void setForcedSystem(final boolean forcedSystem) {
    this.forcedSystem = forcedSystem;
  }

  /**
   * 動的システムであるか判定します。
   * 
   * @return 動的システムならばtrue、そうでなければfalse
   */
  final public boolean isDynamic() {
    return this.dynamic;
  }

  /**
   * 静的システムであるか判定します。
   * 
   * @return 静的システムならばtrue、そうでなければfalse
   */
  final public boolean isStatic() {
    return !this.dynamic;
  }

  /**
   * 動的システムであるか設定します。
   * 
   * @param dynamic 動的システムならばtrue、そうでなければfalse
   */
  final protected void setDynamic(final boolean dynamic) {
    this.dynamic = dynamic;
  }

  /**
   * 線形システムであるか設定します。
   * 
   * @param linear 線形システムならばtrue、そうでなければfalse
   */
  final public void setLinear(final boolean linear) {
    this.linear = linear;
  }

  /**
   * 線形システムであるか判定します。
   * 
   * @return 線形システムならばtrue、そうでなければfalse
   */
  final public boolean isLinear() {
    return this.linear;
  }

  /**
   * 線形システムの場合、線形システムの式を返します。
   * 
   * @return 線形システムの式
   */
  public DoubleLinearSystem getLinearSystem() {
    throw new RuntimeException(Messages.getString("SystemOperator.0")); //$NON-NLS-1$
  }

  /**
   * 状態などの初期化を行います。
   */
  public abstract void initialize();

  /**
   * 入力数、出力数、状態数を文字列に変換します。
   * 
   * @see java.lang.Object#toString()
   */
  @Override
  public String toString() {
    return getClass().getSimpleName()
        + "(" + this.inputSize + " inputs, "+ this.outputSize + " outputs, " + this.stateSize + " states)"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
  }

  /**
   * @see org.mklab.nfc.matrix.GridElement#toString(java.lang.String)
   */
  @Override
  public String toString( String valueFormat) {
    return getClass().getSimpleName()
        + "(" + this.inputSize + " inputs, " + this.outputSize + " outputs, "+ this.stateSize + " states)"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
  }

  /**
   * SISO(1入力1出力)システムであるか判定します。
   * 
   * @return SISO(1入力1出力)システムならばtrue、そうでなければfalse
   */
  public boolean isSISO() {
    return getInputSize() == 1 && getOutputSize() == 1;
  }

  /**
   * @see org.mklab.nfc.matrix.ArrayElement#createGrid(org.mklab.nfc.matrix.ArrayElement[][])
   */
  @Override
  public DoubleAdjacencyMatrix createGrid(final DoubleSystemOperator[][] elements) {
    return new DoubleAdjacencyMatrix(elements);
  }

  /**
   * @see org.mklab.nfc.matrix.ArrayElement#createGrid(org.mklab.nfc.matrix.ArrayElement[])
   */
  @Override
  public DoubleAdjacencyMatrix createGrid( final DoubleSystemOperator[] elements) {
    throw new IllegalArgumentException();
    //return new AdjacencyMatrix((SystemOperator[])elements);
  }

  /**
   * @see org.mklab.nfc.matrix.GridElement#createZero()
   */
  @Override
  public DoubleSystemOperator createZero() {
    return DoubleZeroSystem.getInstance();
  }

  /**
   * @see org.mklab.nfc.matrix.GridElement#isZero()
   */
  @Override
  public boolean isZero() {
    return this == DoubleZeroSystem.getInstance();
  }

  /**
   * @see org.mklab.nfc.matrix.GridElement#compare(java.lang.String, org.mklab.nfc.matrix.GridElement)
   */
  @Override
  public boolean compare(final String operator, final DoubleSystemOperator element) {
    if (operator.equals(".==")) { //$NON-NLS-1$
      return equals(element);
    }
    if (operator.equals(".!=")) { //$NON-NLS-1$
      return !equals(element);
    }
    throw new RuntimeException(Messages.getString("SystemOperator.5")); //$NON-NLS-1$
  }

  /**
   * @see org.mklab.nfc.matrix.GridElement#createArray(int)
   */
  @Override
  public DoubleSystemOperator[] createArray(final int size) {
    return new DoubleSystemOperator[size];
  }

  /**
   * @see org.mklab.nfc.matrix.GridElement#createArray(int, int)
   */
  @Override
  public DoubleSystemOperator[][] createArray(final int rowSize, final int columnSize) {
    return new DoubleSystemOperator[rowSize][columnSize];
  }

//  /**
//   * @see org.mklab.nfc.matrix.GridElement#createArray(org.mklab.nfc.matrix.GridElement[])
//   */
//  public SystemOperator[] createArray(SystemOperator[] elements) {
//    final int size = elements.length;
//    final SystemOperator[] array = elements[0].createArray(size);
//    System.arraycopy(elements, 0, array, 0, size);
//    return array;
//  }
//
//  /**
//   * @see org.mklab.nfc.matrix.GridElement#createArray(GridElement[][])
//   */
//  public SystemOperator[][] createArray(SystemOperator[][] elements) {
//    final int rowSize = elements.length;
//    final int columnSize = rowSize == 0 ? 0 : elements[0].length;
//    final SystemOperator[][] array = elements[0][0].createArray(rowSize, columnSize);
//    for (int row = 0; row < rowSize; row++) {
//      System.arraycopy(elements[row], 0, array[row], 0, columnSize);
//    }
//    return array;
//  }

//  /**
//   * @see org.mklab.nfc.matrix.GridElement#transformFrom(org.mklab.nfc.matrix.GridElement)
//   */
//  @Override
//  public SystemOperator transformFrom(final SystemOperator value) {
//    if (value instanceof SystemOperator) {
//      return (SystemOperator)value; // .clone(); クローンを生成してはならない!
//    }
//
//    throw new RuntimeException(Messages.getString("SystemOperator.6")); //$NON-NLS-1$
//  }
//
//  /**
//   * @see org.mklab.nfc.matrix.GridElement#transformTo(org.mklab.nfc.matrix.GridElement)
//   */
//  @Override
//  public GridElement<?> transformTo(final SystemOperator value) {
//    if (value instanceof SystemOperator) {
//      return this; // .clone(); クローンを生成してはならない!
//    }
//
//    throw new RuntimeException(Messages.getString("SystemOperator.7")); //$NON-NLS-1$
//  }

//  /**
//   * @see org.mklab.nfc.matrix.GridElement#isTransformableFrom(org.mklab.nfc.matrix.GridElement)
//   */
//  @Override
//  public boolean isTransformableFrom(final SystemOperator value) {
//    if (value instanceof SystemOperator) {
//      return true;
//    }
//
//    return false;
//  }
//
//  /**
//   * @see org.mklab.nfc.matrix.GridElement#isTransformableTo(org.mklab.nfc.matrix.GridElement)
//   */
//  @Override
//  public boolean isTransformableTo(final SystemOperator value) {
//    if (value instanceof SystemOperator) {
//      return true;
//    }
//
//    return false;
//  }

  /**
   * 指定された名前のパラメータを返します。
   * 
   * @param name パラメータの名前
   * @return 指定された名前のパラメータ
   * @throws NoSuchParameterException 指定された名前のパラメータが存在しない場合
   */
  public DoubleParameterContainer getParameter(final String name) throws NoSuchParameterException {
    final DoubleParameterContainer parameter = this.parameters.get(name);
    if (parameter == null) {
      throw new NoSuchParameterException();
    }
    return parameter;
  }

  /**
   * 指定された名前のパラメータの値を設定します。
   * 
   * @param name パラメータの名前
   * @param value パラメータの値
   * @throws NoSuchParameterException 指定された名前のパラメータが存在しない場合
   * @throws ParameterAccessException パラメータへのアクセス権がない場合
   */
  public void setParameter(final String name, final Object value) throws NoSuchParameterException, ParameterAccessException {
    final DoubleParameterContainer parameter = this.parameters.get(name);
    if (parameter == null) {
      throw new NoSuchParameterException();
    }

    parameter.setValue(value);

    if (value.getClass().isArray()) {
      try {
        //this.parameters.putAll(createArrayElement(this.getClass().getDeclaredField(name), name, value));
        this.parameters.putAll(createArrayElement(findField(this.getClass(), name), name, value));
      } catch (SecurityException e) {
        throw new ParameterAccessException(e);
      } catch (NoSuchFieldException e) {
        throw new ParameterAccessException(e);
      }
    }
  }

  /**
   * 指定されたクラスとそのスーパークラスに属するパラメータの集合を生成します。
   * 
   * @param klass パラメータ集合を生成するクラス
   * @throws ParameterAccessException パラメータにアクセスできない場合
   */
  public void setupParameters(Class<? extends DoubleSystemOperator> klass) throws ParameterAccessException {
    this.parameters = createParameters(klass);
  }

  /**
   * 指定されたクラスとそのスーパークラスに属するパラメータの集合を返します。
   * 
   * <p>同一の名前のパラメータが1個のクラス中に存在すると、例外が投げられます。 同一の名前のパラメータが親クラスに存在する場合、子クラスのパラメータが有効となります。
   * 
   * @param klass パラメータ集合を生成するクラス
   * @return 指定されたクラスとそのスーパークラスに属するパラメータの集合
   * @throws ParameterAccessException パラメータにアクセスできない場合
   */
  private Map<String, DoubleParameterContainer> createParameters(Class<? extends DoubleSystemOperator> klass) throws ParameterAccessException {
    final Map<String, DoubleParameterContainer> subParameters = new TreeMap<>();

    if (DoubleSystemOperator.class.isAssignableFrom(klass) && DoubleSystemOperator.class != klass) {
      final Class<? extends DoubleSystemOperator> superKlass = klass.getSuperclass().asSubclass(DoubleSystemOperator.class);
      final Map<String, DoubleParameterContainer> superParameters = createParameters(superKlass);
      subParameters.putAll(superParameters);
    }

    try {
      for (final Field field : klass.getDeclaredFields()) {
        final Parameter parameter = field.getAnnotation(Parameter.class);
        if (parameter == null) {
          continue;
        }

        final String name;

        if (1 <= parameter.name().length()) {
          name = parameter.name();
        } else {
          name = field.getName();
        }

        if (subParameters.containsKey(name)) {
          throw new IllegalArgumentException(Messages.getString("SystemOperator.8") + name); //$NON-NLS-1$
        }
        subParameters.put(name, new DoubleParameterContainer(this, field, parameter, name));

        if (field.getType().isArray()) {
          field.setAccessible(true);
          final Object array = field.get(this);
          if (array == null) {
            continue;
          }
          subParameters.putAll(createArrayElement(field, name, array));
        }
      }
    } catch (IllegalArgumentException e) {
      throw new ParameterAccessException(e);
    } catch (IllegalAccessException e) {
      throw new ParameterAccessException(e);
    }

    return subParameters;
  }

  /**
   * クラスおよびそのスーパークラスの指定された名前のフィールドを返します。
   * 
   * @param klass 対象となるクラス
   * @param name フィールドの名前
   * @return クラスおよびそのスーパークラスの指定された名前のフィールド
   * @throws SecurityException セキュリティの権限がない場合
   * @throws NoSuchFieldException フィールドがない場合
   */
  private Field findField(Class<? extends DoubleSystemOperator> klass, final String name) throws SecurityException, NoSuchFieldException {
    if (containsField(klass, name)) {
      return klass.getDeclaredField(name);
    }

    if (DoubleSystemOperator.class.isAssignableFrom(klass) && DoubleSystemOperator.class != klass) {
      final Class<? extends DoubleSystemOperator> superKlass = klass.getSuperclass().asSubclass(DoubleSystemOperator.class);
      return findField(superKlass, name);
    }

    throw new NoSuchFieldException();
  }

  /**
   * クラスが指定された名前のフィールドをもつか判定します。
   * 
   * @param klass 対象となるクラス
   * @param name フィールドの名前
   * @return クラスが指定された名前のフィールドをもつならばtrue、そうでなければfalse
   */
  private boolean containsField(Class<? extends DoubleSystemOperator> klass, final String name) {
    final Field[] fields = klass.getDeclaredFields();
    for (final Field field : fields) {
      if (field.getName().equals(name)) {
        return true;
      }
    }

    return false;
  }

  /**
   * 配列パラメータをパラメータ集合を返します。
   * 
   * @param field フィールド
   * @param name 名前
   * @param array 配列パラメータ
   * @return 配列パラメータのパラメータ集合
   */
  private Map<String, DoubleParameterContainer> createArrayElement(final Field field, final String name, final Object array) {
    final Map<String, DoubleParameterContainer> subParameters = new TreeMap<>();
    final Parameter parameter = field.getAnnotation(Parameter.class);

    final int length = Array.getLength(array);
    for (int i = 0; i < length; i++) {
      final String elementName;
      if (length == 1) {
        elementName = name + "[" + (i + 1) + "]"; //$NON-NLS-1$ //$NON-NLS-2$
      } else {
        elementName = name + "[" + (i + 1) + "/" + length + "]"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
      }

      if (subParameters.containsKey(elementName)) {
        throw new RuntimeException(Messages.getString("SystemOperator.14") + elementName); //$NON-NLS-1$
      }

      subParameters.put(elementName, new DoubleParameterContainer(this, field, parameter, elementName));
    }

    return subParameters;
  }

  /**
   * パラメータの集合を返します。
   * 
   * @return パラメータの集合
   */
  public Set<DoubleParameterContainer> getParameters() {
    final Set<DoubleParameterContainer> paras = new TreeSet<>();
    for (final DoubleParameterContainer parameter : this.parameters.values()) {
      if (parameter.getType().isArray() && parameter.getName().contains("[") == false) { //$NON-NLS-1$
        continue;
      }

      paras.add(parameter);
    }

    return paras;
  }

  /**
   * 自動的に入出力の数を設定するか判定します。
   * 
   * @return 自動的に入出力の数を設定するならばtrue、そうでなければfalse
   */
  public boolean isAutoSize() {
    return this.autoSize;
  }

  /**
   * 自動的に入出力の数を設定するか設定します。
   * 
   * @param autoSize 自動的に入出力の数を設定するならばtrue、そうでなければfalse
   */
  public void setAutoSize(final boolean autoSize) {
    this.autoSize = autoSize;
  }

  /**
   * 自動的に入出力の数を設定するシステムの入出力数をリセットします。
   */
  public void resetAutoSize() {
    if (this.autoSize == false) {
      return;
    }

    if (this.inputSize != -1) {
      this.inputSize = -1;
    }
    if (this.outputSize != -1) {
      this.outputSize = -1;
    }
  }

  /**
   * 入出力数が決定されたか判定します。
   * 
   * @return 入出力数が決定されていればtrue、そうでなければfalse
   */
  public boolean isSizeDefined() {
    return this.inputSize != -1 && this.outputSize != -1;
  }

}