001: /*****************************************************************************
002: * Source code information
003: * -----------------------
004: * Original author Ian Dickinson, HP Labs Bristol
005: * Author email ian.dickinson@hp.com
006: * Package @package@
007: * Web site http://jena.sourceforge.net/
008: * Created 20-Apr-2004
009: * Filename $RCSfile: WebOntTests.java,v $
010: * Revision $Revision: 1.13 $
011: * Release status $State: Exp $
012: *
013: * Last modified on $Date: 2008/01/02 12:10:28 $
014: * by $Author: andy_seaborne $
015: *
016: * (c) Copyright 2003, 2004, 2005, 2006, 2007, 2008 Hewlett-Packard Development Company, LP
017: *****************************************************************************/package com.hp.hpl.jena.reasoner.dig.test;
018:
019: // Imports
020: ///////////////
021: import com.hp.hpl.jena.graph.query.*;
022: import com.hp.hpl.jena.graph.*;
023: import com.hp.hpl.jena.ontology.*;
024: import com.hp.hpl.jena.ontology.OntDocumentManager;
025: import com.hp.hpl.jena.ontology.OntModelSpec;
026: import com.hp.hpl.jena.rdf.model.*;
027: import com.hp.hpl.jena.reasoner.*;
028: import com.hp.hpl.jena.reasoner.dig.*;
029: import com.hp.hpl.jena.reasoner.dig.DIGReasoner;
030: import com.hp.hpl.jena.reasoner.dig.DIGReasonerFactory;
031: import com.hp.hpl.jena.reasoner.test.WGReasonerTester;
032: import com.hp.hpl.jena.util.iterator.ExtendedIterator;
033: import com.hp.hpl.jena.vocabulary.*;
034:
035: import java.io.*;
036: import java.util.*;
037:
038: import org.apache.commons.logging.LogFactory;
039:
040: /**
041: * <p>
042: * Test harness for running the WebOnt working group tests on the DIG reasoner
043: * interface. This class is derived from Dave's
044: * {@link com.hp.hpl.jena.reasoner.rulesys.test.WebOntTestHarness WebOntTestHarness}.
045: * </p>
046: *
047: * @author Ian Dickinson, HP Labs ( <a href="mailto:Ian.Dickinson@hp.com">email
048: * </a>)
049: * @version Release @release@ ($Id: eclipse-template.txt,v 1.2 2003/10/20
050: * 22:03:02 ian_dickinson Exp $)
051: */
052: public class WebOntTests {
053: // Constants
054: //////////////////////////////////
055:
056: /** The base directory for the working group test files to use */
057: public static final String BASE_TESTDIR = "testing/wg/";
058:
059: /** The base URI in which the files are purported to reside */
060: public static String BASE_URI = "http://www.w3.org/2002/03owlt/";
061:
062: /** The namespace for terms in the owl test ontology */
063: public static final String OTEST_NS = BASE_URI + "testOntology#";
064:
065: /** The base URI for the results file */
066: public static String BASE_RESULTS_URI = "http://jena.sourceforge.net/data/owl-results.rdf";
067:
068: /** The list of subdirectories to process (omits the rdf/rdfs dirs) */
069: public static final String[] TEST_DIRS = { "AllDifferent",
070: "AnnotationProperty", "DatatypeProperty",
071: "FunctionalProperty", "I3.2", "I3.4", "I4.1", "I4.5",
072: "I4.6", "I5.1", "I5.2", "I5.21", "I5.24", "I5.26", "I5.3",
073: "I5.5", "I5.8", "InverseFunctionalProperty", "Nothing",
074: "Restriction", "SymmetricProperty", "Thing",
075: "TransitiveProperty", "Class", "allValuesFrom",
076: "amp-in-url", "cardinality", "complementOf", "datatypes",
077: "differentFrom", "disjointWith", "distinctMembers",
078: "equivalentClass", "equivalentProperty", "imports",
079: "intersectionOf", "inverseOf", "localtests",
080: "maxCardinality", "miscellaneous", "oneOf", "sameAs",
081: "someValuesFrom", "statement-entailment", "unionOf",
082: "xmlbase", "description-logic", "extra-credit", };
083:
084: /**
085: * List of tests that are blocked because they test language features
086: * beyond OWL DL
087: */
088: public static final String[] BLOCKED_TESTS = {};
089:
090: /**
091: * The list of status values to include. If approvedOnly then only the
092: * first entry is allowed
093: */
094: public static final String[] STATUS_FLAGS = { "APPROVED",
095: "PROPOSED" };
096:
097: /** List of acceptable test levels */
098: public static final List ACCEPTABLE_TEST_LEVELS = Arrays
099: .asList(new Resource[] { OWLTest.Lite, OWLTest.DL });
100:
101: /** List of predicates we don't want in the premises (because we will try to prove them) */
102: protected static List UNSAFE_PREMISE_PREDICATES = new ArrayList();
103: static {
104: UNSAFE_PREMISE_PREDICATES.add(OWL.equivalentClass);
105: UNSAFE_PREMISE_PREDICATES.add(OWL.equivalentProperty);
106: UNSAFE_PREMISE_PREDICATES.add(OWL.sameAs);
107: UNSAFE_PREMISE_PREDICATES.add(RDFS.subClassOf);
108: UNSAFE_PREMISE_PREDICATES.add(RDFS.subPropertyOf);
109: UNSAFE_PREMISE_PREDICATES.add(DAML_OIL.sameClassAs);
110: UNSAFE_PREMISE_PREDICATES.add(DAML_OIL.sameIndividualAs);
111: UNSAFE_PREMISE_PREDICATES.add(DAML_OIL.samePropertyAs);
112: UNSAFE_PREMISE_PREDICATES.add(DAML_OIL.subClassOf);
113: UNSAFE_PREMISE_PREDICATES.add(DAML_OIL.subPropertyOf);
114: }
115:
116: // Static variables
117: //////////////////////////////////
118:
119: /** Set to true to include modified test versions */
120: protected static boolean s_includeModified = false;
121:
122: /** Set to true to use approved tests only */
123: protected static boolean s_approvedOnly = true;
124:
125: // Instance variables
126: //////////////////////////////////
127:
128: /** The reasoner being tested */
129: private DIGReasoner m_reasoner;
130:
131: /** The total set of known tests */
132: private Model m_testDefinitions;
133:
134: /** The number of tests run */
135: private int m_testCount = 0;
136:
137: /** The time cost in ms of the last test to be run */
138: private long m_lastTestDuration = 0;
139:
140: /** Number of tests passed */
141: private int m_passCount = 0;
142:
143: /** The model describing the results of the run */
144: private Model m_testResults;
145:
146: /**
147: * The resource which acts as a description for the Jena2 instance being
148: * tested
149: */
150: private Resource m_jena2;
151:
152: // Constructors
153: //////////////////////////////////
154:
155: public WebOntTests() {
156: m_testDefinitions = loadAllTestDefinitions();
157: DIGReasonerFactory drf = (DIGReasonerFactory) ReasonerRegistry
158: .theRegistry().getFactory(DIGReasonerFactory.URI);
159: m_reasoner = (DIGReasoner) drf.createWithOWLAxioms(null);
160: initResults();
161: }
162:
163: // External signature methods
164: //////////////////////////////////
165:
166: public static void main(String[] args) throws IOException {
167: String resultFile = "owl-results.rdf";
168: String testName = null;
169:
170: if (args.length >= 1) {
171: testName = args[0];
172: }
173:
174: WebOntTests harness = new WebOntTests();
175:
176: // initialise the document manager
177: OntDocumentManager
178: .getInstance()
179: .addAltEntry(
180: "http://www.w3.org/2002/03owlt/miscellaneous/consistent002",
181: "file:testing/wg/miscellaneous/consistent002.rdf");
182: OntDocumentManager
183: .getInstance()
184: .addAltEntry(
185: "http://www.w3.org/2002/03owlt/miscellaneous/consistent001",
186: "file:testing/wg/miscellaneous/consistent001.rdf");
187:
188: if (testName == null) {
189: harness.runTests();
190: } else {
191: harness.runTest(testName);
192: }
193:
194: RDFWriter writer = harness.m_testResults
195: .getWriter("RDF/XML-ABBREV");
196: OutputStream stream = new FileOutputStream(resultFile);
197: writer.setProperty("showXmlDeclaration", "true");
198: harness.m_testResults.setNsPrefix("",
199: "http://www.w3.org/1999/xhtml");
200: writer.write(harness.m_testResults, stream, BASE_RESULTS_URI);
201: }
202:
203: /**
204: * Run all relevant tests.
205: */
206: public void runTests() {
207: System.out.println("Testing "
208: + (s_approvedOnly ? "only APPROVED"
209: : "APPROVED and PROPOSED"));
210: System.out.println("Positive entailment: ");
211: runTests(findTestsOfType(OWLTest.PositiveEntailmentTest));
212: System.out.println("\nNegative entailment: ");
213: runTests(findTestsOfType(OWLTest.NegativeEntailmentTest));
214: System.out.println("\nTrue tests: ");
215: runTests(findTestsOfType(OWLTest.TrueTest));
216: System.out.println("\nOWL for OWL tests: ");
217: runTests(findTestsOfType(OWLTest.OWLforOWLTest));
218: System.out.println("\nImport entailment tests: ");
219: runTests(findTestsOfType(OWLTest.ImportEntailmentTest));
220: System.out.println("\nInconsistency tests: ");
221: runTests(findTestsOfType(OWLTest.InconsistencyTest));
222: System.out.println("\nPassed " + m_passCount + " out of "
223: + m_testCount);
224: }
225:
226: /**
227: * Run all tests in the given list.
228: */
229: public void runTests(List tests) {
230: for (Iterator i = tests.iterator(); i.hasNext();) {
231: runTest((Resource) i.next());
232: }
233: }
234:
235: /**
236: * Run a single test of any sort, performing any appropriate logging and
237: * error reporting.
238: */
239: public void runTest(String test) {
240: runTest(m_testDefinitions.getResource(test));
241: }
242:
243: /**
244: * Run a single test of any sort, performing any appropriate logging and
245: * error reporting.
246: */
247: public void runTest(Resource test) {
248: System.out.println("Running " + test);
249: boolean success = false;
250: boolean fail = false;
251: try {
252: success = doRunTest(test);
253: } catch (Exception e) {
254: fail = true;
255: System.err.print("\nException: " + e);
256: e.printStackTrace();
257: }
258: m_testCount++;
259:
260: if (success) {
261: System.out.print((m_testCount % 40 == 0) ? ".\n" : ".");
262: System.out.flush();
263: m_passCount++;
264: } else {
265: System.out.println("\nFAIL: " + test);
266: }
267: Resource resultType = null;
268:
269: if (fail) {
270: resultType = OWLResults.FailingRun;
271: } else {
272: if (test.hasProperty(RDF.type,
273: OWLTest.NegativeEntailmentTest)
274: || test.hasProperty(RDF.type,
275: OWLTest.ConsistencyTest)) {
276: resultType = success ? OWLResults.PassingRun
277: : OWLResults.FailingRun;
278: } else {
279: resultType = success ? OWLResults.PassingRun
280: : OWLResults.IncompleteRun;
281: }
282: }
283:
284: // log to the rdf result format
285: m_testResults.createResource().addProperty(RDF.type,
286: OWLResults.TestRun).addProperty(RDF.type, resultType)
287: .addProperty(OWLResults.test, test).addProperty(
288: OWLResults.system, m_jena2);
289: }
290:
291: /**
292: * Run a single test of any sort, return true if the test succeeds.
293: */
294: public boolean doRunTest(Resource test) throws IOException {
295: if (test.hasProperty(RDF.type, OWLTest.PositiveEntailmentTest)
296: || test.hasProperty(RDF.type,
297: OWLTest.NegativeEntailmentTest)
298: || test.hasProperty(RDF.type, OWLTest.OWLforOWLTest)
299: || test.hasProperty(RDF.type,
300: OWLTest.ImportEntailmentTest)
301: || test.hasProperty(RDF.type, OWLTest.TrueTest)) {
302: // Entailment tests
303: System.out.println("Starting: " + test);
304: boolean processImports = test.hasProperty(RDF.type,
305: OWLTest.ImportEntailmentTest);
306: Model premises = getDoc(test, RDFTest.premiseDocument,
307: processImports);
308: Model conclusions = getDoc(test, RDFTest.conclusionDocument);
309:
310: long t1 = System.currentTimeMillis();
311: boolean correct = testEntailment(conclusions, m_reasoner
312: .bind(premises.getGraph()));
313: m_lastTestDuration = System.currentTimeMillis() - t1;
314:
315: if (test.hasProperty(RDF.type,
316: OWLTest.NegativeEntailmentTest)) {
317: correct = !correct;
318: }
319: return correct;
320: } else if (test
321: .hasProperty(RDF.type, OWLTest.InconsistencyTest)) {
322: System.out.println("Starting: " + test);
323: Model input = getDoc(test, RDFTest.inputDocument);
324: long t1 = System.currentTimeMillis();
325: InfGraph graph = m_reasoner.bind(input.getGraph());
326: boolean correct = !graph.validate().isValid();
327: m_lastTestDuration = System.currentTimeMillis() - t1;
328: return correct;
329: } else if (test.hasProperty(RDF.type, OWLTest.ConsistencyTest)) {
330: System.out.println("Starting: " + test);
331: Model input = getDoc(test, RDFTest.inputDocument);
332: long t1 = System.currentTimeMillis();
333: InfGraph graph = m_reasoner.bind(input.getGraph());
334: boolean correct = graph.validate().isValid();
335: long t2 = System.currentTimeMillis();
336: m_lastTestDuration = t2 - t1;
337: return correct;
338: } else {
339: for (StmtIterator i = test.listProperties(RDF.type); i
340: .hasNext();) {
341: System.out.println("Test type = "
342: + i.nextStatement().getObject());
343: }
344: throw new ReasonerException("Unknown test type");
345: }
346: }
347:
348: /**
349: * Load the premises or conclusions for the test, optional performing
350: * import processing.
351: */
352: public Model getDoc(Resource test, Property docType,
353: boolean processImports) throws IOException {
354: if (processImports) {
355: Model result = ModelFactory.createOntologyModel(
356: OntModelSpec.OWL_MEM, null);
357: StmtIterator si = test.listProperties(docType);
358: while (si.hasNext()) {
359: String fname = si.nextStatement().getObject()
360: .toString()
361: + ".rdf";
362: loadFile(fname, result);
363: }
364: return result;
365: } else {
366: return getDoc(test, docType);
367: }
368: }
369:
370: /**
371: * Load the premises or conclusions for the test.
372: */
373: public Model getDoc(Resource test, Property docType)
374: throws IOException {
375: Model result = ModelFactory.createDefaultModel();
376: StmtIterator si = test.listProperties(docType);
377: while (si.hasNext()) {
378: String fname = si.nextStatement().getObject().toString()
379: + ".rdf";
380: loadFile(fname, result);
381: }
382: return result;
383: }
384:
385: /**
386: * Utility to load a file into a model a Model. Files are assumed to be
387: * relative to the BASE_URI.
388: *
389: * @param file the file name, relative to baseDir
390: * @return the loaded Model
391: */
392: public static Model loadFile(String file, Model model)
393: throws IOException {
394: String langType = "RDF/XML";
395: if (file.endsWith(".nt")) {
396: langType = "N-TRIPLE";
397: } else if (file.endsWith("n3")) {
398: langType = "N3";
399: }
400: String fname = file;
401: if (fname.startsWith(BASE_URI)) {
402: fname = fname.substring(BASE_URI.length());
403: }
404: Reader reader = new BufferedReader(new FileReader(BASE_TESTDIR
405: + fname));
406: model.read(reader, BASE_URI + fname, langType);
407: return model;
408: }
409:
410: /**
411: * Test a conclusions graph against a result graph. This works by
412: * translating the conclusions graph into a find query which contains one
413: * variable for each distinct bNode in the conclusions graph.
414: */
415: public boolean testEntailment(Model conclusions, InfGraph inf) {
416: List queryRoots = listQueryRoots(conclusions);
417: Model result = ModelFactory.createDefaultModel();
418:
419: for (Iterator i = queryRoots.iterator(); i.hasNext();) {
420: Resource root = (Resource) i.next();
421:
422: for (StmtIterator j = root.listProperties(); j.hasNext();) {
423: Statement rootQuery = j.nextStatement();
424: Resource subject = rootQuery.getSubject();
425: RDFNode object = rootQuery.getObject();
426:
427: OntModel premises = ModelFactory.createOntologyModel(
428: OntModelSpec.OWL_MEM, null);
429: premises.setStrictMode(false);
430:
431: if (subject.isAnon()) {
432: // subject is assumed to be an expression
433: addSubGraph(subject, premises);
434: }
435: if (object instanceof Resource
436: && ((Resource) object).isAnon()) {
437: addSubGraph((Resource) object, premises);
438: }
439:
440: // add the resulting triples to the graph
441: try {
442: ExtendedIterator k = inf.find(rootQuery
443: .getSubject().asNode(), rootQuery
444: .getPredicate().asNode(), rootQuery
445: .getObject().asNode(), premises.getGraph());
446: while (k.hasNext()) {
447: //Triple t = (Triple) k.next();
448: Object x = k.next();
449: Triple t = (Triple) x;
450: LogFactory.getLog(getClass()).debug(
451: "testEntailment got triple " + t);
452: result.getGraph().add(t);
453: }
454:
455: // transcribe the premises into the results
456: result.add(premises);
457: } catch (DIGErrorResponseException e) {
458: LogFactory.getLog(getClass()).error(
459: "DIG reasoner returned error: "
460: + e.getMessage());
461: return false;
462: }
463: }
464: }
465:
466: result.write(System.out, "RDF/XML-ABBREV");
467: // now check that the conclusions, framed as a query, holds
468: QueryHandler qh = result.queryHandler();
469: Query query = WGReasonerTester.graphToQuery(conclusions
470: .getGraph());
471: Iterator i = qh.prepareBindings(query, new Node[] {})
472: .executeBindings();
473: return i.hasNext();
474: }
475:
476: // Internal implementation methods
477: //////////////////////////////////
478:
479: /** Load all of the known manifest files into a single model */
480: protected Model loadAllTestDefinitions() {
481: System.out.print("Loading manifests ");
482: System.out.flush();
483: Model testDefs = ModelFactory.createDefaultModel();
484: int count = 0;
485: for (int idir = 0; idir < TEST_DIRS.length; idir++) {
486: File dir = new File(BASE_TESTDIR + TEST_DIRS[idir]);
487: String[] manifests = dir.list(new FilenameFilter() {
488: public boolean accept(File df, String name) {
489: return name.startsWith("Manifest")
490: && name.endsWith(".rdf")
491: && (s_includeModified || !name
492: .endsWith("-mod.rdf"));
493: }
494: });
495: if (manifests == null) {
496: System.err.println("No manifests for " + BASE_TESTDIR
497: + TEST_DIRS[idir]);
498: } else {
499: for (int im = 0; im < manifests.length; im++) {
500: String manifest = manifests[im];
501: File mf = new File(dir, manifest);
502: try {
503: testDefs.read(new FileInputStream(mf), "file:"
504: + mf);
505: count++;
506: if (count % 8 == 0) {
507: System.out.print(".");
508: System.out.flush();
509: }
510: } catch (FileNotFoundException e) {
511: System.out.println("File not readable - " + e);
512: }
513: }
514: }
515: }
516: System.out.println("loaded");
517: return testDefs;
518: }
519:
520: /**
521: * Initialize the result model.
522: */
523: protected void initResults() {
524: m_testResults = ModelFactory.createDefaultModel();
525: m_jena2 = m_testResults.createResource(BASE_RESULTS_URI
526: + "#jena2");
527: m_jena2
528: .addProperty(
529: RDFS.comment,
530: m_testResults
531: .createLiteral(
532: "<a xmlns=\"http://www.w3.org/1999/xhtml\" href=\"http://jena.sourceforce.net/\">Jena2</a> includes a rule-based inference engine for RDF processing, "
533: + "supporting both forward and backward chaining rules. Its OWL rule set is designed to provide sound "
534: + "but not complete instance resasoning for that fragment of OWL/Full limited to the OWL/lite vocabulary. In"
535: + "particular it does not support unionOf/complementOf.",
536: true));
537: m_jena2.addProperty(RDFS.label, "Jena2");
538: m_testResults.setNsPrefix("results", OWLResults.NS);
539: }
540:
541: /**
542: * Return a list of all tests of the given type, according to the current
543: * filters
544: */
545: public List findTestsOfType(Resource testType) {
546: ArrayList result = new ArrayList();
547: StmtIterator si = m_testDefinitions.listStatements(null,
548: RDF.type, testType);
549: while (si.hasNext()) {
550: Resource test = si.nextStatement().getSubject();
551: boolean accept = true;
552:
553: // Check test status
554: Literal status = (Literal) test.getProperty(RDFTest.status)
555: .getObject();
556: if (s_approvedOnly) {
557: accept = status.getString().equals(STATUS_FLAGS[0]);
558: } else {
559: accept = false;
560: for (int i = 0; i < STATUS_FLAGS.length; i++) {
561: if (status.getString().equals(STATUS_FLAGS[i])) {
562: accept = true;
563: break;
564: }
565: }
566: }
567:
568: // Check for blocked tests
569: for (int i = 0; i < BLOCKED_TESTS.length; i++) {
570: if (BLOCKED_TESTS[i].equals(test.toString())) {
571: accept = false;
572: }
573: }
574:
575: // Check test level
576: if (accept) {
577: boolean reject = true;
578: for (StmtIterator i = test
579: .listProperties(OWLTest.level); i.hasNext();) {
580: if (ACCEPTABLE_TEST_LEVELS.contains(i
581: .nextStatement().getResource())) {
582: reject = false;
583: }
584: }
585:
586: if (reject) {
587: LogFactory
588: .getLog(getClass())
589: .debug(
590: "Ignoring test "
591: + test
592: + " because it either has no test level defined, or an unacceptable test level");
593: accept = false;
594: }
595: }
596:
597: // End of filter tests
598: if (accept) {
599: result.add(test);
600: }
601: }
602: return result;
603: }
604:
605: /**
606: * The query roots of are the set of subjects we want to ask the DIG
607: * reasoner about ... we interpret this as every named resource in the given model
608: */
609: protected List listQueryRoots(Model m) {
610: List roots = new ArrayList();
611:
612: for (ResIterator i = m.listSubjects(); i.hasNext();) {
613: Resource subj = i.nextResource();
614: if (!subj.isAnon()) {
615: roots.add(subj);
616: }
617: }
618:
619: for (Iterator i = roots.iterator(); i.hasNext();) {
620: LogFactory.getLog(getClass()).debug(
621: "Found query root: " + i.next());
622: }
623: return roots;
624: }
625:
626: /**
627: * Add the reachable sub-graph from root, unless it traverses a predicate
628: * that we might be trying to establish.
629: * @param root
630: * @param premises
631: */
632: protected void addSubGraph(Resource root, Model premises) {
633: List q = new ArrayList();
634: Set seen = new HashSet();
635: q.add(root);
636:
637: while (!q.isEmpty()) {
638: Resource r = (Resource) q.remove(0);
639:
640: if (!seen.contains(r)) {
641: for (StmtIterator i = r.listProperties(); i.hasNext();) {
642: Statement s = i.nextStatement();
643:
644: if (safePremise(s.getPredicate())) {
645: premises.add(s);
646: if (s.getObject() instanceof Resource) {
647: q.add(s.getObject());
648: }
649: }
650: }
651: seen.add(r);
652: }
653: }
654: }
655:
656: /**
657: * <p>Answer true if p is a property that is safe to add as a premise without
658: * assertng what we are trying to find out. Properties ruled out by this
659: * test are owl:equivalentClass, owl:equivalentProperty, etc.
660: * @param p A property to test
661: * @return True if p is safe to add to the premises
662: */
663: protected boolean safePremise(Property p) {
664: return !(UNSAFE_PREMISE_PREDICATES.contains(p));
665: }
666:
667: //==============================================================================
668: // Inner class definitions
669: //==============================================================================
670:
671: }
672:
673: /*
674: * (c) Copyright 2003, 2004, 2005, 2006, 2007, 2008 Hewlett-Packard Development Company, LP
675: * All rights reserved.
676: *
677: * Redistribution and use in source and binary forms, with or without
678: * modification, are permitted provided that the following conditions are met:
679: * 1. Redistributions of source code must retain the above copyright notice,
680: * this list of conditions and the following disclaimer.
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: * 3. The name of the author may not be used to endorse or promote products
685: * derived from this software without specific prior written permission.
686: *
687: * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
688: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
689: * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
690: * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
691: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
692: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
693: * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
694: * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
695: * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
696: * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
697: */
|