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    }