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