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); } }