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