/
codebase/
codebase/area/
codebase/doc/
codebase/etc/
codebase/src/net/sourceforge/pain/data/trigger/
codebase/src/net/sourceforge/pain/logic/
codebase/src/net/sourceforge/pain/logic/affect/
codebase/src/net/sourceforge/pain/logic/event/
codebase/src/net/sourceforge/pain/logic/event/deploy/
codebase/src/net/sourceforge/pain/logic/event/guitool/
codebase/src/net/sourceforge/pain/logic/event/guitool/event/
codebase/src/net/sourceforge/pain/logic/fn/util/
codebase/src/net/sourceforge/pain/logic/trigger/
codebase/src/net/sourceforge/pain/logic/trigger/impl/
codebase/src/net/sourceforge/pain/network/console/
codebase/src/net/sourceforge/pain/network/console/telnet/
codebase/src/net/sourceforge/pain/network/guitool/
codebase/src/net/sourceforge/pain/plugin/
codebase/src/net/sourceforge/pain/plugin/command/
codebase/src/net/sourceforge/pain/plugin/reset/
codebase/src/net/sourceforge/pain/plugin/shutdown/
codebase/src/net/sourceforge/pain/plugin/social/
codebase/src/net/sourceforge/pain/util/
db/doc/javadoc/resources/
db/src/net/sourceforge/pain/util/
gui/
gui/lib/
gui/src/net/sourceforge/pain/tools/guitool/dbbrowse/
gui/src/net/sourceforge/pain/tools/guitool/dialog/
gui/src/net/sourceforge/pain/tools/guitool/menu/
gui/src/net/sourceforge/pain/tools/guitool/resources/
gui/src/net/sourceforge/pain/tools/guitool/resources/images/
gui/src/net/sourceforge/pain/tools/guitool/resources/images/explorer/
tests/
tests/src/
tests/src/net/sourceforge/pain/db/data/
package net.sourceforge.pain.data;

import net.sourceforge.pain.db.*;
import net.sourceforge.pain.util.*;

import java.lang.reflect.*;
import java.util.*;

/**
 * todo: doc
 */
public final class Root extends DbObject implements LogicalObject {

    /* persistent schema: */
	
    /**
     * Roles of the objects determines it's type(meaning)
     * Roles could be added or removed dynamically
     */
    private static final int ROLES = 0;

    /**
     * TRIGGERS is a collection of listeners
     * that want to know if some event is done on object (or object state changes)
     * (Observer pattern)
     * Example: snoopers, move triggers, loggers
     */
    private static final int TRIGGERS_BY_TYPE = 1;
    private static final int TRIGGERS_BY_ROLE = 2;

    private static final int AFFECTS_BY_TYPE = 3;

    private static final int NFIELDS = 4;

    private static final Class[] defaultParams = new Class[]{PainDB.class};
    private static Object[] initargs;

    /**
     * used by db during startup
     */
    public Root() {
    }

    /**
     * used to create new object
     */
    Root(PainDB db) {
        super(db);
    }


    public final DbClassSchema provideSchema() {
        byte types[] = new byte[NFIELDS];
        String names[] = new String[NFIELDS];

        types[ROLES] = DbType.INT_KEY_MAP;
        names[ROLES] = "types";

        types[TRIGGERS_BY_TYPE] = DbType.INT_KEY_MAP;
        names[TRIGGERS_BY_TYPE] = "triggers_by_type";

        types[TRIGGERS_BY_ROLE] = DbType.INT_KEY_MAP;
        names[TRIGGERS_BY_ROLE] = "triggers_by_role";

        types[AFFECTS_BY_TYPE] = DbType.INT_KEY_MAP;
        names[AFFECTS_BY_TYPE] = "affects_by_type";

        return new DbClassSchema(types, names);
    }

    public boolean is(Class typeClass) {
        return getRole(typeClass) != null;
    }

    /**
     * There will many different types written by different coders
     * but type creation is very specific operation - its create
     * persistent image and identity in db in creation time (in factory)
     * and any exception in subclass factory can lead to mem-leak in db
     * so we need to have control on type creation in only one place
     * to avoid bugs.
     * Here this place!
     */
    public final Role addRole(final Class roleClass) throws Exception {
        if (roleClass == null) {
            throw new NullPointerException("role class is null");
        }
        final PainDB db = getDB();
        DbTransaction t = new DbTransaction() {
            public Object execute(Object[] params) throws Exception {
                return _addRole(roleClass); // will check if role already exists
            }
        };
        return (Role) db.execute(t);
    }

    public Role getRole(Class roleClass) {
        return (Role) getIntKeyMap(ROLES).get(roleClassToRoleId(roleClass));
    }

    /**
     * package internal use only
     * if role already exists this method
     * do nothing and
     * returns it
     */
    Role _addRole(Class roleClass) throws Exception {
        Log.debug("Root: _addRole:" + roleClass);
        int roleId = roleClassToRoleId(roleClass);
        Role role;
        if (roleId != -1 && (role = (Role) getIntKeyMap(ROLES).get(roleId)) != null) {
            // this item was already added with different hierarchy
            return role;
        }
        try {
            role = (Role) roleClass.getDeclaredConstructor(defaultParams).newInstance(getDefaultInitArgs());
        } catch (InvocationTargetException e) {
            Log.error(e);
            throw (Exception) e.getCause();
        }
        role.init(this);
        Class superRoles[] = role.getSuperroles();
        if (superRoles.length > 0) {
            for (int i = 0; i < superRoles.length; i++) {
                Role superRole = _addRole(superRoles[i]);
                superRole.incNSubroles();
            }
        }
        if (roleId == -1) {
            roleId = role.getRoleClassId();
        }
        // Log.debug("Adding Role:"+roleClass+" roleId:"+roleId);
        getIntKeyMap(ROLES).put(roleId, role);
        return role;
    }

    public void removeRole(Class roleClass) throws Exception {
        Role role = (Role) getIntKeyMap(ROLES).get(roleClassToRoleId(roleClass));
        removeRole(role);
    }

    void removeRole(Role role) {
        if (role != null) {
            _removeRole(role);
        }
    }

    private int roleClassToRoleId(Class roleClass) {
        if (roleClass == null) {
            throw new NullPointerException("Role class is null!");
        }
        DbClass c = getDB().getDbClass(roleClass);
        if (c == null) { //no object with this class was created in db so we do not have it's schema in db
            //todo: something more exciting here
            return -1;
        }
        return c.pain_getIndexId();
    }

    private void _removeRole(Role role) {
        if (role.hasSubroles()) {
            throw new RuntimeException("cant remove type:" + role.getClass().getName() + "!, Active subtypes found!");
        }
        Class[] superRoles = role.getSuperroles();
        for (int i = 0; i < superRoles.length; i++) {
            Role superRole = getRole(superRoles[i]);
            superRole.decNSubroles();
        }
        removeRoleTriggersAndAffects(role);
        role._nullRoot();
        role._delete();
        if (getIntKeyMap(ROLES).isEmpty()) {
            this.delete();
        }
    }

    protected final void _delete() {
        Map roles = getIntKeyMap(ROLES);
        //deleting  roles
        if (!roles.isEmpty()) {
            for (Iterator it = getIntKeyMap(ROLES).valuesIterator(); it.hasNext();) {
                Role role = (Role) it.next();
                it.remove();
                role._delete();
            }
        }
        //deleting triggers
        DbIntKeyMap triggers = getIntKeyMap(TRIGGERS_BY_TYPE);
        if (!triggers.isEmpty()) {
            for (Iterator it = triggers.valuesIterator(); it.hasNext();) {
                TriggersDataSet set = (TriggersDataSet) it.next();
                set.delete();//will delete all trigger objects it contains
            }
        }
        //deleting affects
        DbIntKeyMap affects = getIntKeyMap(AFFECTS_BY_TYPE);
        if (!triggers.isEmpty()) {
            for (Iterator it = affects.valuesIterator(); it.hasNext();) {
                AffectData ad = (AffectData) it.next();
                ad.delete();
            }
        }
        super.delete();
    }

    public final void delete() {
        _delete();
    }


    Collection getRoles() {
        return getIntKeyMap(ROLES).values();
    }

    public boolean sameObjectAs(LogicalObject obj) {
        if (obj == this) {
            return true;
        }
        if (obj.getClass() == Root.class) {
            return false;
        }
        return this == ((Role) obj).getRoot();
    }

    public Iterator rolesIterator() {
        return new RolesIterator();
    }

    private final class RolesIterator implements Iterator {
        private final Iterator it;
        Role r;

        RolesIterator() {
            it = getRoles().iterator();
        }

        public void remove() {
            it.remove();
            try {
                _removeRole(r);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

        public boolean hasNext() {
            if (isDeleted()) {
                return false;
            }
            return it.hasNext();
        }

        public Object next() {
            r = (Role) it.next();
            return r;
        }
    }

    private Object[] getDefaultInitArgs() {
        if (initargs == null) {
            initargs = new Object[1];
            initargs[0] = getDB();
        }
        return initargs;
    }


    public void onTriggerCreated(final Role role, TriggerData td) throws Exception {
        int roleId = role.getRoleClassId();
        DbIntKeyMap tMap = getIntKeyMap(TRIGGERS_BY_TYPE);
        final int triggerEventType = td.getTriggerEventType();
        TriggersDataSet tSet = (TriggersDataSet) tMap.get(triggerEventType);
        if (tSet == null) {
            tSet = new TriggersDataSet(getDB());
            tMap.put(triggerEventType, tSet);
        }
        tSet.addTrigger(td);

        DbIntKeyMap rMap = getIntKeyMap(TRIGGERS_BY_TYPE);
        TriggersDataSet rSet = (TriggersDataSet) rMap.get(roleId);
        if (rSet == null) {
            rSet = new TriggersDataSet(getDB());
            rMap.put(roleId, tSet);
        }
        rSet.addTrigger(td);
    }


    void addAffect(final Role role, final AffectData ad) throws Exception {
        DbIntKeyMap tMap = getIntKeyMap(AFFECTS_BY_TYPE);
        int affectType = ad.getAffectType();
        if (tMap.get(affectType) != null) {
            throw new IllegalStateException("Already affected:" + affectType);
        }
        tMap.put(affectType, ad);
    }

    public boolean isAffected(int affectType) {
        return getIntKeyMap(AFFECTS_BY_TYPE).containsKey(affectType);
    }


    /**
     * method called on role remove.
     * no callbacks for triggers or affects called
     *
     * @param role
     */
    private void removeRoleTriggersAndAffects(Role role) {
        final int roleId = role.getDbClass().pain_getIndexId();
        {//removing triggers

            DbIntKeyMap map = getIntKeyMap(TRIGGERS_BY_ROLE);
            TriggersDataSet set = (TriggersDataSet) map.get(roleId);
            if (set != null) {
                set.removeAll();
                set.delete(); // should automatically remove this record from map;
            }
        }
        {//removing affects
            DbIntKeyMap map = getIntKeyMap(AFFECTS_BY_TYPE);
            for (Iterator it = map.entrySet().iterator(); it.hasNext();) {
                AffectData d = (AffectData) it.next();
                if (d.getAffectedRole() == role) {
                    it.remove();
                    d.delete();
                }
            }
        }
    }

    Iterator getRoleTriggers(Role role) {
        DbIntKeyMap rMap = getIntKeyMap(TRIGGERS_BY_TYPE);
        TriggersDataSet rSet = (TriggersDataSet) rMap.get(role.getRoleClassId());
        return rSet.iterator();
    }

    Iterator getAffects() {
        DbIntKeyMap tMap = getIntKeyMap(AFFECTS_BY_TYPE);
        return new ReadOnlyIterator(tMap.values().iterator());
    }

    Iterator getTriggersByEventType(int eventType) {
        DbIntKeyMap map = getIntKeyMap(TRIGGERS_BY_TYPE);
        final TriggersDataSet tds = (TriggersDataSet) map.get(eventType);
        if (tds == null) {
            return EMPTY_ITERATOR;
        }
        return tds.iterator();
    }

    /**
     * optimization
     */
    static final Iterator EMPTY_ITERATOR = new Iterator() {
        public void remove() {
            throw new IllegalStateException();
        }

        public boolean hasNext() {
            return false;
        }

        public Object next() {
            throw new IllegalStateException();
        }
    };

    public AffectData getAffectImage(int affectType) {
        return (AffectData) getIntKeyMap(AFFECTS_BY_TYPE).get(affectType);
    }


}