KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > hp > hpl > jena > reasoner > rulesys > impl > oldCode > OrigFBRuleInfGraph


1 /******************************************************************
2  * File: FBRuleInfGraph.java
3  * Created by: Dave Reynolds
4  * Created on: 28-May-2003
5  *
6  * (c) Copyright 2003, 2004, 2005 Hewlett-Packard Development Company, LP
7  * [See end of file]
8  * $Id: OrigFBRuleInfGraph.java,v 1.6 2005/02/21 12:18:05 andy_seaborne Exp $
9  *****************************************************************/

10 package com.hp.hpl.jena.reasoner.rulesys.impl.oldCode;
11
12 import com.hp.hpl.jena.mem.GraphMem;
13 import com.hp.hpl.jena.reasoner.rulesys.impl.*;
14 import com.hp.hpl.jena.reasoner.rulesys.*;
15 import com.hp.hpl.jena.reasoner.transitiveReasoner.*;
16 import com.hp.hpl.jena.reasoner.*;
17 import com.hp.hpl.jena.graph.*;
18
19 import java.util.*;
20
21 //import com.hp.hpl.jena.util.PrintUtil;
22
import com.hp.hpl.jena.util.OneToManyMap;
23 import com.hp.hpl.jena.util.iterator.*;
24 import com.hp.hpl.jena.vocabulary.RDFS;
25 import com.hp.hpl.jena.vocabulary.ReasonerVocabulary;
26 //import com.hp.hpl.jena.util.PrintUtil;
27
//import com.hp.hpl.jena.vocabulary.RDF;
28

29 import org.apache.commons.logging.Log;
30 import org.apache.commons.logging.LogFactory;
31
32 /**
33  * An inference graph that uses a mixture of forward and backward
34  * chaining rules. The forward rules can create direct deductions from
35  * the source data and schema and can also create backward rules. A
36  * query is answered by consulting the union of the raw data, the forward
37  * derived results and any relevant backward rules (whose answers are tabled
38  * for future reference).
39  *
40  * @author <a HREF="mailto:der@hplb.hpl.hp.com">Dave Reynolds</a>
41  * @version $Revision: 1.6 $ on $Date: 2005/02/21 12:18:05 $
42  */

43 public class OrigFBRuleInfGraph extends BasicForwardRuleInfGraph implements BackwardRuleInfGraphI {
44     
45     /** Single context for the reasoner, used when passing information to builtins */
46     protected BBRuleContext context;
47      
48     /** A finder that searches across the data, schema, axioms and forward deductions*/
49     protected Finder dataFind;
50     
51     /** The core backward rule engine which includes all the memoized results */
52     protected BRuleEngine bEngine;
53     
54     /** The original rule set as supplied */
55     protected List rawRules;
56     
57     /** The rule list after possible extension by preprocessing hooks */
58     protected List rules;
59     
60     /** Static switch from Basic to RETE implementation of the forward component */
61     public static boolean useRETE = true;
62
63     /** Flag, if true then subClass and subProperty lattices will be optimized using TGCs */
64     protected boolean useTGCCaching = false;
65     
66     /** Optional precomputed cache of the subClass/subproperty lattices */
67     protected TransitiveEngine transitiveEngine;
68     
69     /** Optional list of preprocessing hooks to be run in sequence during preparation time */
70     protected List preprocessorHooks;
71     
72     /** Cache of temporary property values inferred through getTemp calls */
73     protected TempNodeCache tempNodecache;
74     
75     static Log logger = LogFactory.getLog(FBRuleInfGraph.class);
76
77 // =======================================================================
78
// Constructors
79

80     /**
81      * Constructor.
82      * @param reasoner the reasoner which created this inf graph instance
83      * @param schema the (optional) schema graph to be included
84      */

85     public OrigFBRuleInfGraph(Reasoner reasoner, Graph schema) {
86         super(reasoner, schema);
87         bEngine = new BRuleEngine(this);
88         tempNodecache = new TempNodeCache(this);
89     }
90
91     /**
92      * Constructor.
93      * @param reasoner the reasoner which created this inf graph instance
94      * @param rules the rules to process
95      * @param schema the (optional) schema graph to be included
96      */

97     public OrigFBRuleInfGraph(Reasoner reasoner, List rules, Graph schema) {
98         super(reasoner, rules, schema);
99         this.rawRules = rules;
100         bEngine = new BRuleEngine(this);
101         tempNodecache = new TempNodeCache(this);
102     }
103
104     /**
105      * Constructor.
106      * @param reasoner the reasoner which created this inf graph instance
107      * @param rules the rules to process
108      * @param schema the (optional) schema graph to be included
109      * @param data the data graph to be processed
110      */

111     public OrigFBRuleInfGraph(Reasoner reasoner, List rules, Graph schema, Graph data) {
112         super(reasoner, rules, schema, data);
113         this.rawRules = rules;
114         bEngine = new BRuleEngine(this);
115         tempNodecache = new TempNodeCache(this);
116     }
117
118     /**
119      * Instantiate the forward rule engine to use.
120      * Subclasses can override this to switch to, say, a RETE imlementation.
121      * @param rules the rule set or null if there are not rules bound in yet.
122      */

123     protected void instantiateRuleEngine(List rules) {
124         if (rules != null) {
125             if (useRETE) {
126                 engine = new RETEEngine(this, rules);
127             } else {
128                 engine = new FRuleEngine(this, rules);
129             }
130         } else {
131             if (useRETE) {
132                 engine = new RETEEngine(this);
133             } else {
134                 engine = new FRuleEngine(this);
135             }
136         }
137     }
138     
139     /**
140      * Instantiate the optional caches for the subclass/suproperty lattices.
141      * Unless this call is made the TGC caching will not be used.
142      */

143     public void setUseTGCCache() {
144         useTGCCaching = true;
145         if (schemaGraph != null) {
146             transitiveEngine = new TransitiveEngine(((OrigFBRuleInfGraph)schemaGraph).transitiveEngine);
147         } else {
148             transitiveEngine = new TransitiveEngine(
149                 new TransitiveGraphCache(ReasonerVocabulary.directSubClassOf.asNode(), RDFS.subClassOf.asNode()),
150                 new TransitiveGraphCache(ReasonerVocabulary.directSubPropertyOf.asNode(), RDFS.subPropertyOf.asNode()));
151         }
152     }
153     
154 // =======================================================================
155
// Interface between infGraph and the goal processing machinery
156

157     
158     /**
159      * Search the combination of data and deductions graphs for the given triple pattern.
160      * This may different from the normal find operation in the base of hybrid reasoners
161      * where we are side-stepping the backward deduction step.
162      */

163     public ExtendedIterator findDataMatches(Node subject, Node predicate, Node object) {
164         return dataFind.find(new TriplePattern(subject, predicate, object));
165     }
166     
167     /**
168      * Search the combination of data and deductions graphs for the given triple pattern.
169      * This may different from the normal find operation in the base of hybrid reasoners
170      * where we are side-stepping the backward deduction step.
171      */

172     public ExtendedIterator findDataMatches(TriplePattern pattern) {
173         return dataFind.find(pattern);
174     }
175             
176     /**
177      * Process a call to a builtin predicate
178      * @param clause the Functor representing the call
179      * @param env the BindingEnvironment for this call
180      * @param rule the rule which is invoking this call
181      * @return true if the predicate succeeds
182      */

183     public boolean processBuiltin(ClauseEntry clause, Rule rule, BindingEnvironment env) {
184         if (clause instanceof Functor) {
185             context.setEnv(env);
186             context.setRule(rule);
187             return((Functor)clause).evalAsBodyClause(context);
188         } else {
189             throw new ReasonerException("Illegal builtin predicate: " + clause + " in rule " + rule);
190         }
191     }
192     
193     /**
194      * Adds a new Backward rule as a rusult of a forward rule process. Only some
195      * infgraphs support this.
196      */

197     public void addBRule(Rule brule) {
198 // logger.debug("Adding rule " + brule);
199
bEngine.addRule(brule);
200         bEngine.reset();
201     }
202        
203     /**
204      * Deletes a new Backward rule as a rules of a forward rule process. Only some
205      * infgraphs support this.
206      */

207     public void deleteBRule(Rule brule) {
208 // logger.debug("Deleting rule " + brule);
209
bEngine.deleteRule(brule);
210         bEngine.reset();
211     }
212     
213     /**
214      * Adds a set of new Backward rules
215      */

216     public void addBRules(List rules) {
217         for (Iterator i = rules.iterator(); i.hasNext(); ) {
218             Rule rule = (Rule)i.next();
219 // logger.debug("Adding rule " + rule);
220
bEngine.addRule(rule);
221         }
222         bEngine.reset();
223     }
224     
225     /**
226      * Return an ordered list of all registered backward rules. Includes those
227      * generated by forward productions.
228      */

229     public List getBRules() {
230         return bEngine.getAllRules();
231     }
232     
233     /**
234      * Return the originally supplied set of rules, may be a mix of forward
235      * and backward rules.
236      */

237     public List getRules() {
238         return rules;
239     }
240     
241     /**
242      * Return a compiled representation of all the registered
243      * forward rules.
244      */

245     private Object JavaDoc getForwardRuleStore() {
246         return engine.getRuleStore();
247     }
248     
249     /**
250      * Add a new deduction to the deductions graph.
251      */

252     public void addDeduction(Triple t) {
253         getDeductionsGraph().add(t);
254         if (useTGCCaching) {
255             transitiveEngine.add(t);
256         }
257     }
258
259     /**
260      * Retrieve or create a bNode representing an inferred property value.
261      * @param instance the base instance node to which the property applies
262      * @param prop the property node whose value is being inferred
263      * @param pclass the (optional, can be null) class for the inferred value.
264      * @return the bNode representing the property value
265      */

266     public Node getTemp(Node instance, Node prop, Node pclass) {
267         return tempNodecache.getTemp(instance, prop, pclass);
268     }
269    
270 // =======================================================================
271
// Core inf graph methods
272

273     /**
274      * Add a new rule to the rule set. This should only be used by implementations
275      * of RuleProprocessHook (which are called during rule system preparation phase).
276      * If called at other times the rule won't be correctly transferred into the
277      * underlying engines.
278      */

279     public void addRuleDuringPrepare(Rule rule) {
280         if (rules == rawRules) {
281             // Ensure the original is preserved in case we need to do a restart
282
if (rawRules instanceof ArrayList) {
283                 rules = (ArrayList) ((ArrayList)rawRules).clone();
284             } else {
285                 rules = new ArrayList(rawRules);
286             }
287             // Rebuild the forward engine to use the cloned rules
288
instantiateRuleEngine(rules);
289         }
290         rules.add(rule);
291     }
292     
293     /**
294      * Add a new preprocessing hook defining an operation that
295      * should be run when the preparation phase is underway.
296      */

297     public void addPreprocessingHook(RulePreprocessHook hook) {
298         if (preprocessorHooks == null) {
299             preprocessorHooks = new ArrayList();
300         }
301         preprocessorHooks.add(hook);
302     }
303     
304     /**
305      * Perform any initial processing and caching. This call is optional. Most
306      * engines either have negligable set up work or will perform an implicit
307      * "prepare" if necessary. The call is provided for those occasions where
308      * substantial preparation work is possible (e.g. running a forward chaining
309      * rule system) and where an application might wish greater control over when
310      * this prepration is done.
311      */

312     public void prepare() {
313         if (!isPrepared) {
314             isPrepared = true;
315             
316             // Restore the original pre-hookProcess rules
317
rules = rawRules;
318             
319             // Is there any data to bind in yet?
320
Graph data = null;
321             if (fdata != null) data = fdata.getGraph();
322             
323             // initilize the deductions graph
324
fdeductions = new FGraph( new GraphMem() );
325             dataFind = (data == null) ? fdeductions : FinderUtil.cascade(fdeductions, fdata);
326             
327             // Initialize the optional TGC caches
328
if (useTGCCaching) {
329                 if (schemaGraph != null) {
330                     // Check if we can just reuse the copy of the raw
331
if (
332                         (transitiveEngine.checkOccurance(TransitiveReasoner.subPropertyOf, data) ||
333                          transitiveEngine.checkOccurance(TransitiveReasoner.subClassOf, data) ||
334                          transitiveEngine.checkOccurance(RDFS.domain.asNode(), data) ||
335                          transitiveEngine.checkOccurance(RDFS.range.asNode(), data) )) {
336                 
337                         // The data graph contains some ontology knowledge so split the caches
338
// now and rebuild them using merged data
339
transitiveEngine.insert(((OrigFBRuleInfGraph)schemaGraph).fdata, fdata);
340                     }
341                 } else {
342                     if (data != null) {
343                         transitiveEngine.insert(null, fdata);
344                     }
345                 }
346                 // Insert any axiomatic statements into the caches
347
for (Iterator i = rules.iterator(); i.hasNext(); ) {
348                     Rule r = (Rule)i.next();
349                     if (r.bodyLength() == 0) {
350                         // An axiom
351
for (int j = 0; j < r.headLength(); j++) {
352                             Object JavaDoc head = r.getHeadElement(j);
353                             if (head instanceof TriplePattern) {
354                                 TriplePattern h = (TriplePattern) head;
355                                 transitiveEngine.add(h.asTriple());
356                             }
357                         }
358                     }
359                 }
360
361                 transitiveEngine.setCaching(true, true);
362 // dataFind = FinderUtil.cascade(subClassCache, subPropertyCache, dataFind);
363
dataFind = FinderUtil.cascade(dataFind, transitiveEngine.getSubClassCache(), transitiveEngine.getSubPropertyCache());
364             }
365             
366             // Call any optional preprocessing hook
367
Finder dataSource = fdata;
368             if (preprocessorHooks != null && preprocessorHooks.size() > 0) {
369                 Graph inserts = new GraphMem();
370                 for (Iterator i = preprocessorHooks.iterator(); i.hasNext(); ) {
371                     RulePreprocessHook hook = (RulePreprocessHook)i.next();
372                     // The signature is wrong to do this having moved this code into the attic
373
// If reinstanting the old code uncomment the next line and remove the exception
374
// hook.run(this, dataFind, inserts);
375
throw new ReasonerException("Internal error: attempted to invoke obsoleted reasoner with preprocessing hook");
376                 }
377                 if (inserts.size() > 0) {
378                     FGraph finserts = new FGraph(inserts);
379                     dataSource = FinderUtil.cascade(fdata, finserts);
380                     dataFind = FinderUtil.cascade(dataFind, finserts);
381                 }
382             }
383             
384             boolean rulesLoaded = false;
385             if (schemaGraph != null) {
386                 Graph rawPreload = ((InfGraph)schemaGraph).getRawGraph();
387                 if (rawPreload != null) {
388                     dataFind = FinderUtil.cascade(dataFind, new FGraph(rawPreload));
389                 }
390                 rulesLoaded = preloadDeductions(schemaGraph);
391             }
392             if (rulesLoaded) {
393                 engine.fastInit(dataSource);
394             } else {
395                 // No preload so do the rule separation
396
addBRules(extractPureBackwardRules(rules));
397                 engine.init(true, dataSource);
398             }
399             // Prepare the context for builtins run in backwards engine
400
context = new BBRuleContext(this);
401             
402         }
403     }
404     
405     /**
406      * Cause the inference graph to reconsult the underlying graph to take
407      * into account changes. Normally changes are made through the InfGraph's add and
408      * remove calls are will be handled appropriately. However, in some cases changes
409      * are made "behind the InfGraph's back" and this forces a full reconsult of
410      * the changed data.
411      */

412     public void rebind() {
413         if (bEngine != null) bEngine.reset();
414         isPrepared = false;
415     }
416     
417 // Suppressed - not all engines do static compilation. Now done as part of preload phase.
418

419 // /**
420
// * Create a compiled representation of a list of rules.
421
// * @param rules a list of Rule objects
422
// * @return a datastructure containing precompiled representations suitable
423
// * for initializing FBRuleInfGraphs
424
// */
425
// public static RuleStore compile(List rules) {
426
// Object fRules = FRuleEngine.compile(rules, true);
427
// List bRules = extractPureBackwardRules(rules);
428
// return new RuleStore(rules, fRules, bRules);
429
// }
430
//
431
// /**
432
// * Attach a compiled rule set to this inference graph.
433
// * @param rulestore a compiled set of rules.
434
// */
435
// public void setRuleStore(RuleStore ruleStore) {
436
// this.rules = ruleStore.rawRules;
437
// addBRules(ruleStore.bRules);
438
// engine.setRuleStore(ruleStore.fRuleStore);
439
// }
440

441     /**
442      * Set the state of the trace flag. If set to true then rule firings
443      * are logged out to the Log at "INFO" level.
444      */

445     public void setTraceOn(boolean state) {
446         super.setTraceOn(state);
447         bEngine.setTraceOn(state);
448     }
449
450     /**
451      * Set to true to enable derivation caching
452      */

453     public void setDerivationLogging(boolean recordDerivations) {
454         this.recordDerivations = recordDerivations;
455         engine.setDerivationLogging(recordDerivations);
456         bEngine.setDerivationLogging(recordDerivations);
457         if (recordDerivations) {
458             derivations = new OneToManyMap();
459         } else {
460             derivations = null;
461         }
462     }
463    
464     /**
465      * Return the number of rules fired since this rule engine instance
466      * was created and initialized
467      */

468     public long getNRulesFired() {
469         return engine.getNRulesFired() + bEngine.getNRulesFired();
470     }
471     
472     /**
473      * Extended find interface used in situations where the implementator
474      * may or may not be able to answer the complete query. It will
475      * attempt to answer the pattern but if its answers are not known
476      * to be complete then it will also pass the request on to the nested
477      * Finder to append more results.
478      * @param pattern a TriplePattern to be matched against the data
479      * @param continuation either a Finder or a normal Graph which
480      * will be asked for additional match results if the implementor
481      * may not have completely satisfied the query.
482      */

483     public ExtendedIterator findWithContinuation(TriplePattern pattern, Finder continuation) {
484         checkOpen();
485         if (!isPrepared) prepare();
486         
487         ExtendedIterator result = null;
488         if (continuation == null) {
489             result = UniqueExtendedIterator.create( new TopGoalIterator(bEngine, pattern) );
490         } else {
491             result = UniqueExtendedIterator.create( new TopGoalIterator(bEngine, pattern) )
492                             .andThen(continuation.find(pattern));
493         }
494         return result.filterDrop(Functor.acceptFilter);
495     }
496    
497     /**
498      * Returns an iterator over Triples.
499      * This implementation assumes that the underlying findWithContinuation
500      * will have also consulted the raw data.
501      */

502     public ExtendedIterator graphBaseFind(Node subject, Node property, Node object) {
503         return findWithContinuation(new TriplePattern(subject, property, object), null);
504     }
505
506     /**
507      * Basic pattern lookup interface.
508      * This implementation assumes that the underlying findWithContinuation
509      * will have also consulted the raw data.
510      * @param pattern a TriplePattern to be matched against the data
511      * @return a ExtendedIterator over all Triples in the data set
512      * that match the pattern
513      */

514     public ExtendedIterator find(TriplePattern pattern) {
515         return findWithContinuation(pattern, null);
516     }
517
518     /**
519      * Flush out all cached results. Future queries have to start from scratch.
520      */

521     public void reset() {
522         bEngine.reset();
523         isPrepared = false;
524     }
525
526     /**
527      * Add one triple to the data graph, run any rules triggered by
528      * the new data item, recursively adding any generated triples.
529      */

530     public synchronized void performAdd(Triple t) {
531         fdata.getGraph().add(t);
532         if (useTGCCaching) {
533             if (transitiveEngine.add(t)) isPrepared = false;
534         }
535         if (isPrepared) {
536             engine.add(t);
537         }
538         bEngine.reset();
539     }
540
541     /**
542      * Removes the triple t (if possible) from the set belonging to this graph.
543      */

544     public void performDelete(Triple t) {
545         fdata.getGraph().delete(t);
546         if (useTGCCaching) {
547             if (transitiveEngine.delete(t)) {
548                 if (isPrepared) {
549                     bEngine.deleteAllRules();
550                 }
551                 isPrepared = false;
552             }
553         }
554         if (isPrepared) {
555             getDeductionsGraph().delete(t);
556             engine.delete(t);
557         }
558         bEngine.reset();
559     }
560     
561     /**
562      * Return a new inference graph which is a clone of the current graph
563      * together with an additional set of data premises. Attempts to the replace
564      * the default brute force implementation by one that can reuse some of the
565      * existing deductions.
566      */

567     public InfGraph cloneWithPremises(Graph premises) {
568         prepare();
569         FBRuleInfGraph graph = new FBRuleInfGraph(getReasoner(), rawRules, this);
570         if (useTGCCaching) graph.setUseTGCCache();
571         graph.setDerivationLogging(recordDerivations);
572         graph.setTraceOn(traceOn);
573         // Implementation note: whilst current tests pass its not clear that
574
// the nested passing of FBRuleInfGraph's will correctly handle all
575
// cases of indirectly bound schema data. If we do uncover a problem here
576
// then either include the raw schema in a Union with the premises or
577
// revert of a more brute force version.
578
graph.rebind(premises);
579         return graph;
580     }
581
582 // =======================================================================
583
// Helper methods
584

585     /**
586      * Scan the initial rule set and pick out all the backward-only rules with non-null bodies,
587      * and transfer these rules to the backward engine.
588      */

589     private static List extractPureBackwardRules(List rules) {
590         List bRules = new ArrayList();
591         for (Iterator i = rules.iterator(); i.hasNext(); ) {
592             Rule r = (Rule)i.next();
593             if (r.isBackward() && r.bodyLength() > 0) {
594                 bRules.add(r);
595             }
596         }
597         return bRules;
598     }
599
600     /**
601      * Adds a set of precomputed triples to the deductions store. These do not, themselves,
602      * fire any rules but provide additional axioms that might enable future rule
603      * firing when real data is added. Used to implement bindSchema processing
604      * in the parent Reasoner.
605      * @return true if the preload was able to load rules as well
606      */

607     protected boolean preloadDeductions(Graph preloadIn) {
608         Graph d = fdeductions.getGraph();
609         OrigFBRuleInfGraph preload = (OrigFBRuleInfGraph)preloadIn;
610         // If the rule set is the same we can reuse those as well
611
if (preload.rules == rules) {
612             // Load raw deductions
613
for (Iterator i = preload.getDeductionsGraph().find(null, null, null); i.hasNext(); ) {
614                 d.add((Triple)i.next());
615             }
616             // Load backward rules
617
addBRules(preload.getBRules());
618             // Load forward rules
619
engine.setRuleStore(preload.getForwardRuleStore());
620             // Add access to raw data
621
return true;
622         } else {
623             return false;
624         }
625     }
626     
627 // /**
628
// * Temporary debuggin support. List the dataFind graph.
629
// */
630
// public void debugListDataFind() {
631
// logger.debug("DataFind contains (ty and sc only:");
632
// for (Iterator i = dataFind.findWithContinuation(new TriplePattern(null, RDF.type.asNode(), null),null); i.hasNext(); ) {
633
// logger.debug(" " + PrintUtil.print(i.next()));
634
// }
635
// for (Iterator i = dataFind.findWithContinuation(new TriplePattern(null, RDFS.subClassOf.asNode(), null),null); i.hasNext(); ) {
636
// logger.debug(" " + PrintUtil.print(i.next()));
637
// }
638
// }
639

640 // =======================================================================
641
// Inner classes
642

643     /**
644      * Structure used to wrap up pre-processed/compiled rule sets.
645      */

646     public static class RuleStore {
647         
648         /** The raw rules */
649         protected List rawRules;
650         
651         /** The indexed store used by the forward chainer */
652         protected Object JavaDoc fRuleStore;
653         
654         /** The separated backward rules */
655         protected List bRules;
656         
657         /**
658          * Constructor.
659          */

660         public RuleStore(List rawRules, Object JavaDoc fRuleStore, List bRules) {
661             this.rawRules = rawRules;
662             this.fRuleStore = fRuleStore;
663             this.bRules = bRules;
664         }
665         
666     }
667 }
668
669
670 /*
671     (c) Copyright 2003, 2004, 2005 Hewlett-Packard Development Company, LP
672     All rights reserved.
673
674     Redistribution and use in source and binary forms, with or without
675     modification, are permitted provided that the following conditions
676     are met:
677
678     1. Redistributions of source code must retain the above copyright
679        notice, this list of conditions and the following disclaimer.
680
681     2. Redistributions in binary form must reproduce the above copyright
682        notice, this list of conditions and the following disclaimer in the
683        documentation and/or other materials provided with the distribution.
684
685     3. The name of the author may not be used to endorse or promote products
686        derived from this software without specific prior written permission.
687
688     THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
689     IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
690     OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
691     IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
692     INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
693     NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
694     DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
695     THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
696     (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
697     THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
698 */
Popular Tags