/*
 * $Header: /home/cvspublic/jakarta-slide/proposals/tamino/src/store/org/apache/slide/store/tamino/jdomobjects/XStateMachine.java,v 1.4 2005/01/12 13:12:57 pnever Exp $
 * $Revision: 1.4 $
 * $Date: 2005/01/12 13:12:57 $
 *
 * ====================================================================
 *
 * Copyright 1999-2004 The Apache Software Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.slide.store.tamino.jdomobjects;

import com.softwareag.common.instrumentation.logging.Level;
import com.softwareag.common.instrumentation.logging.Logger;
import com.softwareag.common.instrumentation.logging.LoggerFactory;
import com.softwareag.common.instrumentation.logging.LoggerUtil;
import org.apache.slide.store.tamino.common.IDesc;
import org.apache.slide.util.XAssertionFailed;

/**
 * Implements the state transitions for XDescriptors. One instance of XStateMachine
 * handles the states of one instance of XDescriptors.
 *
 * @author    martin.wallmer@softwareag.com
 *
 * @version   $Revision: 1.4 $
 *
 * @see       XDescriptors
 *
 */
public class XStateMachine {
    private static final String LOGNAME = LoggerUtil.getThisClassName();
    private static Logger logger = LoggerFactory.getLogger(LOGNAME);

    private static final int INVALID = -1;

    private static final int INIT_NEW = 0;
    private static final int INIT_DB  = 1;
    private static final int SET      = 2;
    private static final int REMOVE   = 3;
    private static final int R_LAST   = 4;
    private static final int COMMIT   = 5;

    /**
     * this table has the knowledge about the valid state transitions
     *
     * @invariant (if not already done in class header)
     *
     * @see        java.lang.String#equals
     */
    private static final int TRANSITIONS [][] = new int [IDesc.CHANGED + 1] [COMMIT + 1];
    static {
        TRANSITIONS [IDesc.NO_STATE][INIT_NEW] = IDesc.CREATED_DELETED;
        TRANSITIONS [IDesc.NO_STATE][INIT_DB]  = IDesc.UNCHANGED;
        TRANSITIONS [IDesc.NO_STATE][SET]      = INVALID;
        TRANSITIONS [IDesc.NO_STATE][REMOVE]   = INVALID;
        TRANSITIONS [IDesc.NO_STATE][R_LAST]   = INVALID;
        TRANSITIONS [IDesc.NO_STATE][COMMIT]   = INVALID;

        TRANSITIONS [IDesc.CREATED] [INIT_NEW] = INVALID;
        TRANSITIONS [IDesc.CREATED] [INIT_DB]  = INVALID;
        TRANSITIONS [IDesc.CREATED] [SET]      = IDesc.CREATED;
        TRANSITIONS [IDesc.CREATED] [REMOVE]   = IDesc.CREATED;
        TRANSITIONS [IDesc.CREATED] [R_LAST]   = IDesc.CREATED_DELETED;
        TRANSITIONS [IDesc.CREATED] [COMMIT]   = IDesc.UNCHANGED;

        TRANSITIONS [IDesc.CREATED_DELETED][INIT_NEW] = INVALID;
        TRANSITIONS [IDesc.CREATED_DELETED][INIT_DB]  = INVALID;
        TRANSITIONS [IDesc.CREATED_DELETED][SET]      = IDesc.CREATED;
        TRANSITIONS [IDesc.CREATED_DELETED][REMOVE]   = IDesc.CREATED_DELETED;
        TRANSITIONS [IDesc.CREATED_DELETED][R_LAST]   = IDesc.CREATED_DELETED;
        TRANSITIONS [IDesc.CREATED_DELETED][COMMIT]   = IDesc.NO_STATE;

        TRANSITIONS [IDesc.UNCHANGED][INIT_NEW] = INVALID;
        TRANSITIONS [IDesc.UNCHANGED][INIT_DB]  = INVALID;
        TRANSITIONS [IDesc.UNCHANGED][SET]      = IDesc.CHANGED;
        TRANSITIONS [IDesc.UNCHANGED][REMOVE]   = IDesc.CHANGED;
        TRANSITIONS [IDesc.UNCHANGED][R_LAST]   = IDesc.DELETED;
        TRANSITIONS [IDesc.UNCHANGED][COMMIT]   = IDesc.UNCHANGED;

        TRANSITIONS [IDesc.CHANGED][INIT_NEW] = INVALID;
        TRANSITIONS [IDesc.CHANGED][INIT_DB]  = INVALID;
        TRANSITIONS [IDesc.CHANGED][SET]      = IDesc.CHANGED;
        TRANSITIONS [IDesc.CHANGED][REMOVE]   = IDesc.CHANGED;
        TRANSITIONS [IDesc.CHANGED][R_LAST]   = IDesc.DELETED;
        TRANSITIONS [IDesc.CHANGED][COMMIT]   = IDesc.UNCHANGED;

        TRANSITIONS [IDesc.DELETED][INIT_NEW] = INVALID;
        TRANSITIONS [IDesc.DELETED][INIT_DB]  = INVALID;
        TRANSITIONS [IDesc.DELETED][SET]      = IDesc.CHANGED;
        TRANSITIONS [IDesc.DELETED][REMOVE]   = IDesc.DELETED;
        TRANSITIONS [IDesc.DELETED][R_LAST]   = IDesc.DELETED;
        TRANSITIONS [IDesc.DELETED][COMMIT]   = IDesc.NO_STATE;

    }

    //---

    /** tracks the number of active aspects. If zero, the last aspect was removed */
    private int activeAspects;

    private int state;

    /**
     * be sure to construct not before uri is set
     *
     * @pre desc.getUri () != null
     */
    XStateMachine () {
        this(0, TRANSITIONS [IDesc.NO_STATE][INIT_NEW]);
    }

    XStateMachine(XStateMachine orig) {
        this(orig.activeAspects, orig.state);
    }

    XStateMachine(int activeAspects, int state) {
        this.activeAspects = activeAspects;
        this.state = state;
    }

    /**
     * called when a set operation is called on an XDescriptor
     */
    void set () {
        if( logger.isLoggable(Level.FINE) )
            logger.fine ("Statemachine: set");

        activeAspects++;
        state = TRANSITIONS [state][SET];
        checkState();
    }

    /**
     * Remove.
     * @param number number
     */
    void remove (int number) {
        if( logger.isLoggable(Level.FINE) )
            logger.fine ("Statemachine: remove " + number);

        activeAspects = activeAspects - number;
        if (activeAspects == 0)
            state = TRANSITIONS [state][R_LAST];
        else
            state = TRANSITIONS [state][REMOVE];

        checkState();
    }

    /**
     * Remove.
     */
    void remove () {
        remove(1);
    }

    /**
     * Commit.
     */
    void commit () {
        if( logger.isLoggable(Level.FINE) )
            logger.fine ("Statemachine: commit");
        state = TRANSITIONS [state][COMMIT];
        checkState();
    }

    /**
     * State.
     * @return the state
     */
    int state () {
        return state;
    }

    /**
     * The current number of aspects must be made persistent.
     *
     * @return the current number of active aspects
     */
    int getActiveAspects () {
        return activeAspects;
    }

    public void checkState () {
        if( logger.isLoggable(Level.FINE) )
            logger.fine ("state: " + state + " activeAspects: " + activeAspects);

        if (activeAspects < 0)
            throw new XAssertionFailed ("negativ aspects count: " + activeAspects);
        if (state == INVALID)
            throw new XAssertionFailed ("state 'INVALID'");
    }
    
    public String toString() {
        switch (state) {
            case INVALID:
                return "INVALID";
            case INIT_NEW:
                return "NO_STATE";
            case INIT_DB:
                return "CREATED";
            case SET:
                return "CREATED_DELETED";
            case REMOVE:
                return "DELETED";
            case R_LAST:
                return "UNCHANGED";
            case COMMIT:
                return "CHANGED";
            default:
                return "***UNKNOWN STATE***";
        }
    }
}
