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    }