001: /*
002: * hammurapi-rules @mesopotamia.version@
003: * Hammurapi rules engine.
004: * Copyright (C) 2005 Hammurapi Group
005: *
006: * This program is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation; either
009: * version 2 of the License, or (at your option) any later version.
010: *
011: * This program is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: *
016: * You should have received a copy of the GNU Lesser General Public
017: * License along with this library; if not, write to the Free Software
018: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
019: *
020: * URL: http://http://www.hammurapi.biz
021: * e-Mail: support@hammurapi.biz
022: */
023: package biz.hammurapi.rules;
024:
025: import java.io.Serializable;
026: import java.util.Collections;
027: import java.util.HashMap;
028: import java.util.Iterator;
029: import java.util.Map;
030: import java.util.Set;
031: import java.util.Map.Entry;
032:
033: import javax.swing.table.DefaultTableModel;
034: import javax.swing.table.TableModel;
035: import javax.swing.tree.DefaultMutableTreeNode;
036: import javax.swing.tree.MutableTreeNode;
037:
038: import org.w3c.dom.Element;
039:
040: import biz.hammurapi.config.Context;
041: import biz.hammurapi.config.PropertyParser;
042: import biz.hammurapi.swing.CompositeVisualizer;
043: import biz.hammurapi.swing.Visualizable;
044: import biz.hammurapi.xml.dom.CompositeDomSerializer;
045: import biz.hammurapi.xml.dom.DomSerializable;
046:
047: /**
048: * Base class for conclusions.
049: * @author Pavel Vlasov
050: * @revision $Revision$
051: */
052: public class Conclusion implements DomSerializable, Serializable,
053: Visualizable, Negatable, Supercedable {
054:
055: /**
056: *
057: */
058: private static final long serialVersionUID = -2364827338956686564L;
059:
060: /**
061: * Map is used to merge derivations without having to introduce
062: * side effects in equals().
063: */
064: private Map derivations = new HashMap();
065:
066: /**
067: * Slots (other facts) belonging to this fact.
068: */
069: protected HashMap slots = new HashMap();
070:
071: private int hashCode = getClass().getName().hashCode();
072:
073: private int depth;
074:
075: private String pattern;
076:
077: /**
078: * Puts value to a slot.
079: * Value gets converted to a handle.
080: * @param slotName
081: * @param value
082: */
083: protected synchronized void setSlot(String slotName, Object value) {
084: if (value != null) {
085: hashCode ^= value.hashCode();
086: }
087: hashCode ^= slotName.hashCode();
088: Object oldValue = slots.put(slotName, value);
089: if (oldValue != null) {
090: hashCode ^= oldValue.hashCode();
091: }
092: }
093:
094: public int hashCode() {
095: return hashCode;
096: }
097:
098: public boolean equals(Object obj) {
099: if (obj == this ) {
100: return true;
101: }
102:
103: return obj != null && hashCode == obj.hashCode()
104: && getClass().equals(obj.getClass())
105: && slots.equals(((Conclusion) obj).slots);
106: }
107:
108: /**
109: * Merges derivations of two equal conclusions. This is useful when
110: * two there are two paths to the same conclusion.
111: * @param otherConclusion
112: */
113: public void mergeDerivations(Conclusion otherConclusion) {
114: if (this != otherConclusion && equals(otherConclusion)) {
115: // Merging derivations sets.
116: Iterator it = otherConclusion.getDerivations().iterator();
117: while (it.hasNext()) {
118: Object o = it.next();
119: derivations.put(o, o);
120: }
121: otherConclusion.derivations = new HashMap(derivations);
122: }
123: }
124:
125: /**
126: * One conclusion supercedes another if it is a subclass of the other
127: * and slots of the both are equal. In other words more specific
128: * conclusion supercedec more generic. E.g. conclusion that Mary is a mother of Joe
129: * is more specific than that Mary is a parent of Joe.
130: * @param conclusion
131: * @return true if this fact is more specific than argument.
132: */
133: public synchronized boolean super cedes(Object obj) {
134: return obj instanceof Conclusion
135: && obj.getClass().isAssignableFrom(getClass())
136: && !getClass().isAssignableFrom(obj.getClass())
137: && slots.equals(((Conclusion) obj).slots);
138: }
139:
140: /**
141: * It is possible to come to the same conclusion through multiple
142: * inference paths.
143: * @return Set of derivations.
144: */
145: public Set getDerivations() {
146: return Collections.unmodifiableSet(derivations.keySet());
147: }
148:
149: /**
150: * @return number of derivations.
151: */
152: public int getCardinality() {
153: return derivations.size();
154: }
155:
156: /**
157: * @return minimum derivation depth, in other words shortest
158: * logical chain which led to this conclusion.
159: */
160: public int getDepth() {
161: return depth;
162: }
163:
164: public synchronized void toDom(Element holder) {
165: holder.setAttribute("type", getClass().getName());
166: holder.setAttribute("depth", String.valueOf(depth));
167: holder.setAttribute("hash-code", Integer.toString(hashCode,
168: Character.MAX_RADIX));
169:
170: CompositeDomSerializer domSerializer = CompositeDomSerializer
171: .getThreadInstance();
172:
173: Iterator it = slots.keySet().iterator();
174: while (it.hasNext()) {
175: Element se = holder.getOwnerDocument()
176: .createElement("slot");
177: holder.appendChild(se);
178: String slotName = (String) it.next();
179: se.setAttribute("slot-name", slotName);
180: domSerializer.toDomSerializable(getSlot(slotName))
181: .toDom(se);
182: }
183:
184: it = derivations.keySet().iterator();
185: while (it.hasNext()) {
186: Derivation derivation = (Derivation) it.next();
187: Element de = holder.getOwnerDocument().createElement(
188: "derivation");
189: holder.appendChild(de);
190: derivation.toDom(de);
191: }
192: }
193:
194: /**
195: * Returns true if negator negates this conclusion any of its slots or all its derivations.
196: * If conclusion has more than one derivation that negated derivations are removed from
197: * derivations collection. Conclusion is negated based on derivations only if at leas one of its derivations
198: * is negated. In other words conclusion which collection of derivations is empty will not be negated based
199: * on derivations but only based on self and slots.
200: * @param negator
201: * @return
202: */
203: public synchronized boolean isNegatedBy(Negator negator) {
204:
205: // Don't negate self.
206: if (negator == this || this .equals(negator)) {
207: return false;
208: }
209:
210: // If negator negates this return true;
211: if (negator.negates(this )) {
212: return true;
213: }
214:
215: // If negator negates any of slots return true;
216: Iterator it = slots.values().iterator();
217: while (it.hasNext()) {
218: if (negator.negates(it.next())) {
219: return true;
220: }
221: }
222:
223: // If conclusion is not derived then return false;
224: if (derivations.isEmpty()) {
225: return false;
226: }
227:
228: // If all derivations are negated return true
229: it = derivations.keySet().iterator();
230: while (it.hasNext()) {
231: Derivation d = (Derivation) it.next();
232: if (!d.negatedBy(negator)) {
233: return false; // If there is derivation which is not negated return false;
234: }
235: }
236:
237: return true; // All derivations were negated
238: }
239:
240: /**
241: * Adds a derivation to conclusion.
242: * @param derivation
243: */
244: synchronized void addDerivation(Derivation derivation) {
245: if (derivation != null) {
246: derivations.put(derivation, derivation);
247: depth = (derivations.size() == 1 ? derivation.getDepth()
248: : Math.min(depth, derivation.getDepth())) + 1;
249: }
250: }
251:
252: public MutableTreeNode toTree(final String title) {
253: DefaultMutableTreeNode ret = new DefaultMutableTreeNode(
254: Conclusion.this ) {
255: public String toString() {
256: return title + " ["
257: + Conclusion.this .getClass().getName() + "] "
258: + Conclusion.this ;
259: }
260: };
261:
262: DefaultMutableTreeNode slotsNode = new DefaultMutableTreeNode(
263: "Slots");
264: ret.add(slotsNode);
265:
266: Iterator it = slots.keySet().iterator();
267: while (it.hasNext()) {
268: String key = (String) it.next();
269: slotsNode.add(CompositeVisualizer.getThreadInstance()
270: .toVisualizable(getSlot(key)).toTree(key));
271: }
272:
273: if (!derivations.isEmpty()) {
274: DefaultMutableTreeNode derivationsNode = new DefaultMutableTreeNode(
275: "Derivations");
276: ret.add(derivationsNode);
277:
278: it = derivations.keySet().iterator();
279: while (it.hasNext()) {
280: derivationsNode.add(CompositeVisualizer
281: .getThreadInstance().toVisualizable(it.next())
282: .toTree(""));
283: }
284: }
285:
286: return ret;
287: }
288:
289: public TableModel toTable() {
290: DefaultTableModel tm = new DefaultTableModel(slots.size() + 1,
291: 3);
292: tm
293: .setColumnIdentifiers(new String[] { "Slot", "Type",
294: "Value" });
295:
296: tm.setValueAt("(this)", 0, 0);
297: tm.setValueAt(Conclusion.this .getClass().getName(), 0, 1);
298: tm.setValueAt(Conclusion.this , 0, 2);
299:
300: Iterator it = slots.keySet().iterator();
301: for (int i = 1; it.hasNext(); i++) {
302: String key = (String) it.next();
303: Object slot = getSlot(key);
304: tm.setValueAt(key, i, 0);
305: tm.setValueAt(slot.getClass().getName(), i, 1);
306: tm.setValueAt(slot, i, 2);
307: }
308:
309: return tm;
310: }
311:
312: /**
313: * Returns slot value
314: */
315: protected Object getSlot(String name) {
316: return slots.get(name);
317: }
318:
319: /**
320: * If pattern is null then outputs class name and list of slots and their values,
321: * otherwise formats pattern.
322: */
323: public String toString() {
324: if (pattern == null) {
325: StringBuffer ret = new StringBuffer("["
326: + getClass().getName() + "] ");
327: Iterator it = slots.entrySet().iterator();
328: while (it.hasNext()) {
329: Map.Entry entry = (Entry) it.next();
330: ret.append(entry.getKey());
331: ret.append("=");
332: ret.append(entry.getValue());
333: if (it.hasNext()) {
334: ret.append(", ");
335: }
336: }
337: return ret.toString();
338: }
339:
340: return new PropertyParser(new Context() {
341: public Object get(String name) {
342: return getSlot(name);
343: }
344: }, false).parse(pattern);
345: }
346:
347: /**
348: * @param pattern Pattern to use in toString(). The pattern should use ${<code>slot name</code>} placeholders.
349: * E.g. Parent conclusion can use pattern "<code>${parent} is parent of ${child}</code>".
350: */
351: protected Conclusion(String pattern) {
352: this .pattern = pattern;
353: }
354:
355: /**
356: * Default constructor
357: */
358: public Conclusion() {
359: // Default constructor.
360: }
361:
362: /**
363: * Convenience method to properly negate objects taking implementations of
364: * Negatable into account.
365: * @param o Object to be negated
366: * @param n Negator
367: * @return true if negator negates object
368: */
369: public static boolean object2Negator(Object o, Negator n) {
370: return o instanceof Negatable ? ((Negatable) o).isNegatedBy(n)
371: : n.negates(o);
372: }
373:
374: /**
375: *
376: * @param fact
377: * @return True if the fact equals to one of slots or one of derivations
378: * is based on this fact.
379: */
380: public boolean isDerivedFrom(Object fact) {
381: if (fact == null) {
382: return false;
383: }
384:
385: // If fact equalsany of slots return true;
386: Iterator it = slots.values().iterator();
387: while (it.hasNext()) {
388: if (fact.equals(it.next())) {
389: return true;
390: }
391: }
392:
393: // If any of derivations is derived from the fact return true
394: it = derivations.keySet().iterator();
395: while (it.hasNext()) {
396: Derivation d = (Derivation) it.next();
397: if (d.isDerivedFrom(fact)) {
398: return true;
399: }
400: }
401:
402: return false;
403: }
404:
405: }
|