package pt.ist.fenixframework.txintrospector; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import pt.ist.fenixframework.DomainObject; import pt.ist.fenixframework.FenixFramework; import pt.ist.fenixframework.backend.BackEndId; import pt.ist.fenixframework.dml.runtime.Relation; import pt.ist.fenixframework.dml.runtime.RelationListener; public class TxStats implements TxIntrospector { public static final boolean ENABLED = checkEnabled(); public static boolean checkEnabled() { String param = BackEndId.getBackEndId().getParam(TXINTROSPECTOR_ON_CONFIG_KEY); return (param != null) && param.trim().equalsIgnoreCase(TXINTROSPECTOR_ON_CONFIG_VALUE); } private static final boolean FILTER = Boolean.getBoolean("pt.ist.fenixframework.txintrospector.filter"); private final List<DomainObject> newObjects = new ArrayList<DomainObject>(); private final Set<DomainObject> modifiedObjects = new HashSet<DomainObject>(); private final Map<RelationChangelog, RelationChangelog> relationsChangelog = new HashMap<RelationChangelog, RelationChangelog>(); private TxStats() { } public static TxStats newInstance() { if (!ENABLED) return null; return new TxStats(); } public static TxStats getInstance(TxStats txStats) { if (txStats == null) { throw new RuntimeException("TxIntrospector is disabled, please enable it and rebuild your application"); } return txStats; } public void addNewObject(DomainObject object) { if (filter(object)) return; newObjects.add(object); } public void addModifiedObject(DomainObject object) { if (filter(object)) return; modifiedObjects.add(object); } @Override public void addModifiedRelation(RelationChangelog relation) { if (filter(relation.first)) return; RelationChangelog prev = relationsChangelog.put(relation, relation); if (prev != null && (prev.remove != relation.remove)) { // Two changelog entries with opposite remove values cancel out relationsChangelog.remove(relation); } } // Filter out internal Fenix Framework structures private boolean filter(DomainObject object) { return FILTER && object.getClass().getPackage().getName().startsWith("pt.ist.fenixframework.core.adt.bplustree"); } private static String collectionToStringSafe(Collection<? extends Object> collection) { StringBuilder sb = new StringBuilder(); sb.append('['); for (Object o : collection) { sb.append(o.getClass().getName()); sb.append('@'); sb.append(Integer.toHexString(System.identityHashCode(o))); sb.append(", "); } if (sb.length() > 1) sb.delete(sb.length() - 2, sb.length()); sb.append(']'); return sb.toString(); } @Override public String toString() { return "TxStats\n\tnewObjects: " + collectionToStringSafe(getNewObjects()) + "\n\tdirectlyModifiedObjects: " + collectionToStringSafe(getDirectlyModifiedObjects()) + "\n\tmodifiedObjects: " + collectionToStringSafe(getModifiedObjects()) + "\n\trelationsChangelog: " + collectionToStringSafe(getRelationsChangelog()); } /** * Version of toString() that may fail because it uses the DomainObject's * toString() operation to print them, as calling toString() on a * DomainObject may cause further changes to objects or relations. */ public String unsafeToString() { return "TxStats\n\tnewObjects: " + getNewObjects() + "\n\tdirectlyModifiedObjects: " + getDirectlyModifiedObjects() + "\n\tmodifiedObjects: " + getModifiedObjects() + "\n\trelationsChangelog: " + getRelationsChangelog(); } // TxIntrospector implementation @Override public Collection<DomainObject> getNewObjects() { return Collections.unmodifiableList(newObjects); } @Override public Collection<DomainObject> getDirectlyModifiedObjects() { Set<DomainObject> realModifiedObjects = new HashSet<DomainObject>(modifiedObjects); // Any slot change triggers an object being added to modifiedObjects // However, we want to separate new objects (even with modified slots) // from existing objects // that were modified, so we duplicate the modifiedObjects set, and // remove from it the newObjects realModifiedObjects.removeAll(newObjects); return Collections.unmodifiableSet(realModifiedObjects); } @Override public Collection<DomainObject> getModifiedObjects() { Set<DomainObject> realModifiedObjects = new HashSet<DomainObject>(modifiedObjects); // Add objects from relationship changes for (RelationChangelog relationChange : relationsChangelog.values()) { /* if (relationChange.first != null) */realModifiedObjects.add(relationChange.first); /* if (relationChange.second != null) */realModifiedObjects.add(relationChange.second); } // Separate new objects from existing objects that were modified realModifiedObjects.removeAll(newObjects); return Collections.unmodifiableSet(realModifiedObjects); } @Override public Collection<Entry> getReadSetLog() { throw new RuntimeException("Not implemented"); } @Override public Collection<Entry> getWriteSetLog() { throw new RuntimeException("Not implemented"); } @Override public Collection<RelationChangelog> getRelationsChangelog() { return Collections.unmodifiableCollection(relationsChangelog.values()); } // RelationListener used to track changes to relations @SuppressWarnings("rawtypes") public static final RelationListener STATS_LISTENER = new RelationListener<DomainObject, DomainObject>() { @Override public void beforeAdd(Relation<DomainObject, DomainObject> rel, DomainObject o1, DomainObject o2) { if (o1 == null || o2 == null) return; // Ignore relations with null FenixFramework.getTransaction().getTxIntrospector() .addModifiedRelation(new RelationChangelog(rel.getName(), o1, o2, false)); } @Override public void afterAdd(Relation<DomainObject, DomainObject> rel, DomainObject o1, DomainObject o2) { // intentionally empty } @Override public void beforeRemove(Relation<DomainObject, DomainObject> rel, DomainObject o1, DomainObject o2) { if (o1 == null || o2 == null) return; // Ignore relations with null FenixFramework.getTransaction().getTxIntrospector() .addModifiedRelation(new RelationChangelog(rel.getName(), o1, o2, true)); } @Override public void afterRemove(Relation<DomainObject, DomainObject> rel, DomainObject o1, DomainObject o2) { // intentionally empty } }; }