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 }