#

Forecasting zum Ersten

Nach reichlicher Überlegungszeit und einigen anfänglichen Versuchen, bin ich derzeit schon recht weit in die Anfänge meiner API gestoßen. Dabei benutze ich derzeit eine Art Inkremental-Refactoring-Development, bei dem ich einzelne Komponenten umsetze und die dabei entstehenden Anforderungen in Interfaces kapsele, refactore und damit die API Schritt für Schritt erstelle.

Komponenten

Für die Realisierung der Komponenten habe ich mich vorerst für die JavaBean Architektur entschieden. Hierbei werden alle public Methoden mit get-/set-Paaren sowie Listener Methoden mit add-/removeListener durch Reflectionmechanismen extrahiert und stehen dem Programmierer zur Verfügung. Gleichzeitig müssen alle JavaBeans auch das Serializable Interface erben, um gespeichert und geladen werden zu können.
Eine Reihe von Interfaces legt fest, welche Methoden ein bestimmter Komponententyp implementieren muss. Derzeit ist das mindeste Interface für Komponenten das Interface IComponent:

import java.io.Serializable;
 
public interface IComponent extends Serializable{
  public long getSysId();
  public void setSysId(long id);
}

Die SysId dient später jede Instanz einer Komponente über ein zentrales Verzeichnis (bzw. dem Modell) zu identifizieren. Natürlich würde es auch die Pointerprüfung (componentA==componentB) tun, aber die Id ist weitsichtiger angelegt, um später auch das Verteilen von Komponenten auf verschiedene Rechner zu ermöglichen.
Gleichzeitig kann hier schon der nächste Schritt der JavaBean Konventionen erkannt werden: Während die JavaBeans bei get-/set-Methoden lediglich auf den Rest der Methode achten, wird in meiner API zusätzlich zwischen getSys-/setSys-Methoden sowie getUser-/setUser-Methoden unterschieden. Sys-Methoden werden nur API-intern benutzt, während User-Methoden Parameter darstellen, welche nur vom Benutzer festgelegt werden können. Daneben bleiben die restlichen get-/set-Methoden für die Schnittstelle der Komponenten untereinander erhalten.

Die nächste Stufe der Komponenten unterscheidet zwischen Komponenten, welche Attribute haben, die sich ändern können (und von anderen Komponenten beobachtet werden können) und Komponenten, die statisch sind.

public interface IPropertyChangingComponent extends IComponent{
  public void addPropertyChangeListener(PropertyChangeListener listener);
  public void removePropertyChangeListener(PropertyChangeListener listener);
}

Hierbei handelt es sich nicht um die von Java angebotenen Klassen PropertyChangeListener, sondern um eigene Varianten, da ein alter Parameter nicht übermittelt werden soll.
Einige Hilfsklassen, wie der PropertyChangeListenerManager helfen, schnell eine solche Komponente umzusetzen. Auch existiert bereits eine abstrakte Klasse, welche dies bereits tut:

public abstract class AbstractPropertyChangingComponent extends AbstractComponent implements
    IPropertyChangingComponent {
  protected PropertyChangeListenerManager propertyListeners;
 
  public AbstractPropertyChangingComponent(){
    propertyListeners = new PropertyChangeListenerManager(this);
  }
 
  public void addPropertyChangeListener(PropertyChangeListener listener){
    propertyListeners.addListener(listener);
  }
 
  public void removePropertyChangeListener(PropertyChangeListener listener){
    propertyListeners.removeListener(listener);
  }
}

Die Kommunikation zwischen zwei Komponenten wird nicht direkt gemacht, sondern durch eine Verbindung zwischen beiden Komponenten. Diese Verbindung, derzeit in der Klasse ComponentConnection realisiert, implementiert auch das Interface PropertyChangeListener. Eine Verbindung erwartet vier Parameter:

  • Die Quellkomponente, welche vom Typ IPropertyChangingComponent ist.
  • Die Quellproperty welche der get-Methode entspricht, die Daten liefern soll.
  • Die Zielkomponente, welche lediglich das Interface IComponent benötigt.
  • Die Zielproperty, welche eine set-Methode der Zielkomponente ist und mit den Daten der Quellkomponente gefüttert werden soll.

  • Entsprechend können zwei Komponenten nur miteinander Verbunden werden, wenn der Rückgabetyp der Quellmethode dem Eingabetyp der Zielmethode entspricht. Hierbei hilft JavaBean. Löst die Quellkomponente nun ein notify aus, so wird die Verbindung aktiviert. Diese wiederum nimmt die Daten der Quellkomponente und liefert diese an die Zielkomponente:

    public class ComponentConnection implements PropertyChangeListener {
      ...
     
      public void propertyChange(PropertyChangeEvent event) {
        // ein cast zu IPropertyChangingComponent wäre auch möglich
        IComponent source = (IComponent)  event.getSource();
        String propertyName = event.getPropertyName();
        if (source==sourceComponent && sourceProperty.equals(propertyName)){
          Method target = ComponentManager.getInstance()
            .getWriteMethod(targetComponent, targetProperty);
          Object [] targetParameters = {event.getValue()};
          target.invoke(targetComponent, targetParameters);
        }
      }
    }

    Nun kommen wir zum zweiten interessanten Grundstein: der Komponente, welche auch (parallele) Arbeit verrichten kann. Dieser Komponente entsprechen alle Komponenten, welche Rechenzeit benötigen, um etwas zu berechnen oder zu tuen. Hierzu existiert das Interface IProcessableComponent.

    public interface IProcessableComponent extends IComponent{
      public static final int STATE_PROCESS_INIT = 0;
      public static final int STATE_PROCESS_IN_PROGRESS = 1;
      public static final int STATE_PROCESS_DONE = 2;
     
      public void doProcessing();
      public boolean needsProcessing();
      public int getProcessingState();
    }

    Die drei Zustände (bin mir noch nicht sicher, ob ich wirklich mehr brauche) legen den Lebenszyklus einer solchen Komponente fest. Bei der Erzeugung besitzt eine Komponente üblicherweise den Zustand STATE_PROCESS_INIT, muss dies aber nicht. Die komplette Kontrolle über den Zustand besitzt die Komponente selber. Sobald sich eine Komponente im Zustand STATE_PROCESS_IN_PROGRESS befindet, wird ihr Rechenleistung zugeteilt. Derzeit existiert ein Thread, der alle ausführbaren Komponenten nach ihrem Rechenbedarf fragt (needsProcessing()). Falls dies der Fall ist, so wird ein neuer Thread erzeugt, welcher die Methode doProcessing() aufruft. (Später soll hier ein Threadpool her, welcher die Threads wiederbenutzt.)

    public class ProcessingManager implements Runnable{
      // ...
     
      public void addProcessableComponent(IProcessableComponent
        processablePomponent){
        if (!processableComponents.contains(processablePomponent)){
          processableComponents.add(processablePomponent);
        }
      }
     
      // ...
     
      public void run() {
        while (isRunning){
          List processableComponentsClone;
          synchronized (processableComponents){
            processableComponentsClone = 
              (List) processableComponents.clone();
          }
          IProcessableComponent processableComponent;
          for (int i=0; i<processablecomponentsclone .size(); i++){
            processableComponent = 
              (IProcessableComponent) processableComponentsClone.get(i); 
            if (processableComponent.needsProcessing())
              new ProcessableComponentProcessorThread(processableComponent);
          }
          try {
            Thread.sleep(100);
          } 
          catch (InterruptedException e) {}
        }
      }
    }

    public class ProcessableComponentProcessorThread implements Runnable {
      // ...
     
      public void run() {
        synchronized (processableComponent){
          processableComponent.doProcessing();
        }
      }
    }

    Sinnvollerweise unterteilt eine Komponente ihren gesamten Rechenaufwand in kleine atomarere Teile und führt diese beim Aufruf der Methode aus. Liefert eine Komponente false beim Aufruf der needsProcessing() Methode, so wird ihr keine Rechenzeit zugeteilt. Die Prüfung findet zyklisch statt.
    In einer abstrakten Implementierung dieses Interface werden drei Hilfsmethoden zur Verfügung gestellt (setProcessingStateXXX()), welche das Umschalten und Handhaben des Status erleichtern sollen:

    public abstract class AbstractProcessableComponent extends AbstractPropertyChangingComponent implements
        IProcessableComponent{
     
      protected Integer processState = new Integer(STATE_PROCESS_INIT);
     
      protected AbstractProcessableComponent(){
        super();
      }
     
      // IProcessableComponent
     
      public boolean needsProcessing() {
        return getProcessingState() == STATE_PROCESS_IN_PROGRESS;
      }
     
      public int getProcessingState(){
        int processState;
        synchronized(this.processState){
          processState = this.processState.intValue();
        }
        return processState;
      }
     
      protected void setProcessingStateDone(){
        synchronized(processState){
          processState = new Integer(STATE_PROCESS_DONE);
        }
      }
     
      protected void setProcessingStateInit(){
        synchronized(processState){
          processState = new Integer(STATE_PROCESS_INIT);
        }
      }
     
      protected void setProcessingStateInProgress(){
        synchronized(processState){
          processState = new Integer(STATE_PROCESS_IN_PROGRESS);
        }
      }
    }

    Eine erste Komponente

    Die erste implementierte Komponente, welche auch die Grundlagen für die Interfaces legte, war die DescriptiveStatisticsBean, die statistische Eckdaten zu einer Reihe berechnet. Als zugrunde liegende mathematische API habe ich mich für COLT entschieden, welche die Berechnung der Daten durchführt.

    public class DescriptiveStatisticsBean extends AbstractProcessableComponent{
      /**
       * 
       */
      private static final long serialVersionUID = 8109834120708330427L;
     
      private TimedDoubleArrayList data;
      private double mean;
      private double variance;
      private double standardDeviation;
      private double sum;
      private double sumOfSquares;
     
      private static final int STATE_PROCESS_MEAN = 11;
      private static final int STATE_PROCESS_SUM = 12;
      private static final int STATE_PROCESS_SUMSQUARES = 13;
      private static final int STATE_PROCESS_VARIANCE = 14;
      private static final int STATE_PROCESS_STDDEV = 15;
     
      private int state = STATE_PROCESS_MEAN;
     
      public DescriptiveStatisticsBean(){
        super();
      }
     
     
      // javabean
     
      public void setSourceData(TimedDoubleArrayList data){
        if (data != this.data){
          this.data = data;
          state = STATE_PROCESS_MEAN;
          setProcessingStateInProgress();
          propertyListeners.notifyListeners("sourceData", data);
        }
      }
     
      public TimedDoubleArrayList getSourceData(){
        return data;
      }
     
      public double getMean() {
        return mean;
      }
     
      public void setMean(double mean) {
        if (this.mean != mean){
          this.mean = mean;
          propertyListeners.notifyListeners("mean", new Double(mean));
        }
      }
     
      public double getStandardDeviation() {
        return standardDeviation;
      }
     
      public void setStandardDeviation(double standardDeviation) {
        if (this.standardDeviation != standardDeviation){
          this.standardDeviation = standardDeviation;
          propertyListeners.notifyListeners("standardDeviation",
            new Double(standardDeviation));
        }
      }
     
      public double getVariance() {
        return variance;
      }
     
      public void setVariance(double variance) {
        if (this.variance!=variance){
          this.variance = variance;
          propertyListeners.notifyListeners("variance",
            new Double(variance));
        }
      }
     
     
      // IProcessableComponent
     
      public void doProcessing() {
        if (data != null) {
          synchronized (processState) {
            switch (processState.intValue()) {
            case STATE_PROCESS_IN_PROGRESS:
              switch (state) {
                case STATE_PROCESS_MEAN:
                  double tmpMean;
                  synchronized (data) {
                    tmpMean = Descriptive.mean(data.getData());
                  }
                  setMean(tmpMean);
                  state = STATE_PROCESS_SUM;
                  break;
                case STATE_PROCESS_SUM:
                  synchronized (data) {
                    sum = Descriptive.sum(data.getData());
                  }
                  state = STATE_PROCESS_SUMSQUARES;
                  break;
                case STATE_PROCESS_SUMSQUARES:
                  synchronized (data) {
                    sumOfSquares = Descriptive.sumOfSquares(data.getData());
                  }
                  state = STATE_PROCESS_VARIANCE;
                  break;
                case STATE_PROCESS_VARIANCE:
                  double tmpVar;
                  synchronized (data) {
                    tmpVar = Descriptive.variance(data.getData().size(), sum,
                        sumOfSquares);
                  }
                  setVariance(tmpVar);
                  state = STATE_PROCESS_STDDEV;
                  break;
                case STATE_PROCESS_STDDEV:
                  double tmpStddev;
                  synchronized (data) {
                    tmpStddev = Descriptive.standardDeviation(variance);
                  }
                  setStandardDeviation(tmpStddev);
                  setProcessingStateDone();
                  break;
              }
            }
          }
        }
      }
    }

    Wie man erkennen kann, verwaltet diese Komponente einen eigenen Zustand (state), welcher es ihm ermöglicht, die Berechnungen in doProcessing() auf mehrere kleinere Teile aufzusplitten. Eine weitere Komponente kann Datenreihen differenzieren:

    public class DifferentiationBean extends AbstractProcessableComponent{
     
      /**
       * 
       */
      private static final long serialVersionUID = -517441682998649244L;
     
      private TimedDoubleArrayList processedData;
      private TimedDoubleArrayList sourceData;
      private MinInteger order = new MinInteger(0, 1);
      private int lag = 1;
      private int actualOrder;
     
      public DifferentiationBean(){
        super();
      }
     
     
     
      // javabean
     
      public void setSourceData(TimedDoubleArrayList sourceData){
        if (sourceData != this.sourceData){
          this.sourceData = sourceData;
          setProcessingStateInProgress();
          actualOrder = 0;
          propertyListeners.notifyListeners("sourceData", sourceData);
        }
     
      }
     
      public TimedDoubleArrayList getSourceData(){
        return sourceData;
      }
     
      public TimedDoubleArrayList getProcessedData(){
        return processedData;
      }
     
      public void setOrder(int order){
        if (this.order.getValue() != order){
          this.order.setValue(order);
          actualOrder = 0;
          if (sourceData != null) setProcessingStateInProgress();
          else setProcessingStateInit();
          propertyListeners.notifyListeners("order", new Integer(order));
        }
      }
     
      public int getOrder(){
        return order.getValue();
      }
     
      public void setLag(int lag){
        if (this.lag != lag){
          this.lag = lag;
          actualOrder = 0;
          if (sourceData != null) setProcessingStateInProgress();
          else setProcessingStateInit();
          propertyListeners.notifyListeners("lag", new Integer(lag));
        }
      }
     
      public int getLag(){
        return lag;
      }
     
     
     
      // IProcessableComponent
     
      public void doProcessing() {
        if (sourceData != null && order.getValue()>0){
          synchronized(processState){
            switch (processState.intValue()){
              case STATE_PROCESS_IN_PROGRESS:
                if (actualOrder<order .getValue()){
                  if (actualOrder==0)  processedData = sourceData;
                  synchronized (processedData){
                    processedData = TimedDoubleArrayListUtil.dif(
                      processedData, lag);
                  }
                  actualOrder++;
                }
                else{
                  setProcessingStateDone();
                  propertyListeners.notifyListeners("processedData", 
                    processedData);
                }
                break;
            }
          }
        }
      }
    }

    Dabei wird in jedem Arbeitsschritt lediglich eine der geforderten Differenzierungen durchgeführt. Später könnte es natürlich sinnvoll sein, eine Differeinzierung sehr großer Datenmengen in kleinere Datenschritte aufzuteilen.

    Weiterhin existieren derzeit einzelne Singleton Klassen, welche die einzelnen Komponenten verwalten und Metainformationen cachen (wie z. B. die Reflection Methoden). Aus diesen Klassen wird sich wohl das eigentliche Modell entwickeln. Views und XML-Serialisationen sind geplant, jedoch noch in etwas weiter ferne.

    2 Responses to “Forecasting zum Ersten” »»

    1. Comment by Ralf K. | 11:12 02.02.06|X

      Hallo Serhat,

      ein interessanter Ansatz :-)

      Für eine XML-Serialisation kann ich dir XStream von codehaus empfehlen.

      http://xstream.codehaus.org/

      GrüÃe
      Ralf

    2. Comment by Secco | 14:40 03.02.06|X

      hi ralf,
      danke für den tip. ich habe xstream bereits kennengelernt und finde, dass es eine sehr gute api ist. ich habe es auch schon für das projekt in betracht gezogen, aber das dauert noch ein weilchen, bis ich soweit bin :)

    Leave a Reply »»

    Note: All comments are manually approved to avoid spam. So if your comment doesn't appear immediately, that's ok. Have patience, it can take some days until I have the time to approve my comments.