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