001    /*
002     * (c) Copyright 2009 University of Bristol
003     * All rights reserved.
004     * [See end of file]
005     */
006    package net.rootdev.javardfa;
007    
008    import com.hp.hpl.jena.iri.IRI;
009    import com.hp.hpl.jena.iri.IRIFactory;
010    import java.io.IOException;
011    import java.io.StringWriter;
012    import java.util.Arrays;
013    import java.util.Collection;
014    import java.util.Collections;
015    import java.util.EnumSet;
016    import java.util.HashMap;
017    import java.util.HashSet;
018    import java.util.Iterator;
019    import java.util.LinkedList;
020    import java.util.List;
021    import java.util.Map;
022    import java.util.Set;
023    import javax.xml.namespace.NamespaceContext;
024    import javax.xml.namespace.QName;
025    import javax.xml.stream.XMLEventFactory;
026    import javax.xml.stream.XMLEventReader;
027    import javax.xml.stream.XMLEventWriter;
028    import javax.xml.stream.XMLOutputFactory;
029    import javax.xml.stream.XMLStreamException;
030    import javax.xml.stream.events.Attribute;
031    import javax.xml.stream.events.StartElement;
032    import javax.xml.stream.events.XMLEvent;
033    import org.xml.sax.Attributes;
034    import org.xml.sax.ContentHandler;
035    import org.xml.sax.Locator;
036    import org.xml.sax.SAXException;
037    
038    /**
039     * @author Damian Steer <pldms@mac.com>
040     */
041    public class Parser implements ContentHandler {
042    
043        final static List<String> _allowed = Arrays.asList(
044                "alternate", "appendix", "bookmark", "cite",
045                "chapter", "contents", "copyright", "first",
046                "glossary", "help", "icon", "index", "last",
047                "license", "meta", "next", "p3pv1", "prev",
048                "collection", "role", "section", "stylesheet",
049                "subsection", "start", "top", "up");
050        final static Set<String> SpecialRels = new HashSet<String>(_allowed);
051        final static IRIFactory IRIFact = IRIFactory.semanticWebImplementation();
052        final static XMLOutputFactory outputFactory = XMLOutputFactory.newInstance();
053        final static XMLEventFactory EventFactory = XMLEventFactory.newInstance();
054        private final XMLEventReader reader;
055        private final StatementSink sink;
056        // Suggestion: switch this for object produced by factory that matches QNames
057        // we can then en-slacken if needed by passing in different factory etc
058        final QName about = new QName("about"); // safe
059        final QName resource = new QName("resource"); // safe
060        final QName href = new QName("href"); // URI
061        final QName src = new QName("src"); // URI
062        final QName property = new QName("property"); // CURIE
063        final QName datatype = new QName("datatype"); // CURIE
064        final QName typeof = new QName("typeof"); // CURIE
065        final QName rel = new QName("rel"); // Link types and CURIES
066        final QName rev = new QName("rev"); // Link type and CURIES
067        final QName content = new QName("content");
068        final QName xmllang = new QName("http://www.w3.org/XML/1998/namespace", "lang", "xml");
069        final QName lang = new QName("lang");
070        final QName fakeXmlLang = new QName("xml:lang");
071        final QName base = new QName("http://www.w3.org/1999/xhtml", "base");
072        final QName head = new QName("http://www.w3.org/1999/xhtml", "head");
073        final QName body = new QName("http://www.w3.org/1999/xhtml", "body");
074        // Hack bits
075        final QName input = new QName("input");
076        final QName name = new QName("name");
077        final QName form = new QName("form");
078        final Collection<String> rdfType = Collections.singleton("http://www.w3.org/1999/02/22-rdf-syntax-ns#type");
079        final String xmlLiteral = "http://www.w3.org/1999/02/22-rdf-syntax-ns#XMLLiteral";
080        final Set<Setting> settings = EnumSet.noneOf(Setting.class);
081    
082        public enum Setting {
083    
084            FormMode, ManualNamespaces
085        }
086    
087        public Parser(StatementSink sink) {
088            this.reader = null;
089            this.sink = sink;
090            outputFactory.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, true);
091        }
092    
093        public void enable(Setting setting) {
094            settings.add(setting);
095        }
096    
097        public void disable(Setting setting) {
098            settings.remove(setting);
099        }
100    
101        public void setBase(String base) {
102            this.context = new EvalContext(base);
103        }
104    
105        EvalContext parse(EvalContext context, StartElement element) throws XMLStreamException {
106            boolean recurse = true;
107            boolean skipElement = false;
108            String newSubject = null;
109            String currentObject = null;
110            List<String> forwardProperties = new LinkedList(context.forwardProperties);
111            List<String> backwardProperties = new LinkedList(context.backwardProperties);
112            String currentLanguage = context.language;
113            boolean langIsLang = context.langIsLang;
114    
115            if (element.getAttributeByName(xmllang) != null) {
116                currentLanguage = element.getAttributeByName(xmllang).getValue();
117            }
118    
119            if (settings.contains(Setting.ManualNamespaces) &&
120                    element.getAttributeByName(fakeXmlLang) != null &&
121                    !langIsLang) {
122                currentLanguage = element.getAttributeByName(fakeXmlLang).getValue();
123            }
124    
125            if (settings.contains(Setting.ManualNamespaces) &&
126                    element.getAttributeByName(lang) != null) {
127                langIsLang = true;
128                currentLanguage = element.getAttributeByName(lang).getValue();
129            }
130    
131            if (base.equals(element.getName()) &&
132                    element.getAttributeByName(href) != null) {
133                context.setBase(element.getAttributeByName(href).getValue());
134            }
135    
136            if (element.getAttributeByName(rev) == null &&
137                    element.getAttributeByName(rel) == null) {
138                Attribute nSubj = findAttribute(element, about, src, resource, href);
139                if (nSubj != null) {
140                    newSubject = getURI(context.base, element, nSubj);
141                } else {
142                    if (element.getAttributeByName(typeof) != null) {
143                        if (body.equals(element.getName()) ||
144                                head.equals(element.getName())) {
145                            newSubject = context.base;
146                        } else {
147                            newSubject = createBNode();
148                        }
149                    } else {
150                        if (context.parentObject != null) {
151                            newSubject = context.parentObject;
152                        }
153                        if (element.getAttributeByName(property) == null) {
154                            skipElement = true;
155                        }
156                    }
157                }
158            } else {
159                Attribute nSubj = findAttribute(element, about, src);
160                if (nSubj != null) {
161                    newSubject = getURI(context.base, element, nSubj);
162                } else {
163                    // TODO if element is head or body assume about=""
164                    if (element.getAttributeByName(typeof) != null) {
165                        newSubject = createBNode();
166                    } else {
167                        if (context.parentObject != null) {
168                            newSubject = context.parentObject;
169                        }
170                    }
171                }
172                Attribute cObj = findAttribute(element, resource, href);
173                if (cObj != null) {
174                    currentObject = getURI(context.base, element, cObj);
175                }
176            }
177    
178            if (newSubject != null && element.getAttributeByName(typeof) != null) {
179                List<String> types = getURIs(context.base, element, element.getAttributeByName(typeof));
180                for (String type : types) {
181                    emitTriples(newSubject,
182                            rdfType,
183                            type);
184                }
185            }
186    
187            if (newSubject == null) {
188                newSubject = context.parentSubject;
189            }
190    
191            // Dodgy extension
192            if (settings.contains(Setting.FormMode)) {
193                if (form.equals(element.getName())) {
194                    emitTriples(newSubject, rdfType, "http://www.w3.org/1999/xhtml/vocab/#form"); // Signal entering form
195                }
196                if (input.equals(element.getName()) &&
197                        element.getAttributeByName(name) != null) {
198                    currentObject = "?" + element.getAttributeByName(name).getValue();
199                }
200    
201            }
202    
203            if (currentObject != null) {
204                if (element.getAttributeByName(rel) != null) {
205                    emitTriples(newSubject,
206                            getURIs(context.base, element, element.getAttributeByName(rel)),
207                            currentObject);
208                }
209                if (element.getAttributeByName(rev) != null) {
210                    emitTriples(currentObject,
211                            getURIs(context.base, element, element.getAttributeByName(rev)),
212                            newSubject);
213                }
214            } else {
215                if (element.getAttributeByName(rel) != null) {
216                    forwardProperties.addAll(getURIs(context.base, element, element.getAttributeByName(rel)));
217                }
218                if (element.getAttributeByName(rev) != null) {
219                    backwardProperties.addAll(getURIs(context.base, element, element.getAttributeByName(rev)));
220                }
221                if (element.getAttributeByName(rel) != null || // if predicate present
222                        element.getAttributeByName(rev) != null) {
223                    currentObject = createBNode();
224                }
225            }
226    
227            // Getting literal values. Complicated!
228    
229            if (element.getAttributeByName(property) != null) {
230                List<String> props = getURIs(context.base, element, element.getAttributeByName(property));
231                String dt = getDatatype(element);
232                if (element.getAttributeByName(content) != null) { // The easy bit
233                    String lex = element.getAttributeByName(content).getValue();
234                    if (dt == null || dt.length() == 0) {
235                        emitTriplesPlainLiteral(newSubject, props, lex, currentLanguage);
236                    } else {
237                        emitTriplesDatatypeLiteral(newSubject, props, lex, dt);
238                    }
239                } else {
240                    //recurse = false;
241                    level = 1;
242                    theDatatype = dt;
243                    literalWriter = new StringWriter();
244                    litProps = props;
245                    if (dt == null) // either plain or xml. defer decision
246                    {
247                        queuedEvents = new LinkedList<XMLEvent>();
248                    } else if (xmlLiteral.equals(dt)) // definitely xml?
249                    {
250                        xmlWriter = outputFactory.createXMLEventWriter(literalWriter);
251                    }
252    
253                }
254            }
255    
256            if (!skipElement && newSubject != null) {
257                emitTriples(context.parentSubject,
258                        context.forwardProperties,
259                        newSubject);
260    
261                emitTriples(newSubject,
262                        context.backwardProperties,
263                        context.parentSubject);
264            }
265    
266            if (recurse) {
267                EvalContext ec = new EvalContext(context);
268    
269                if (skipElement) {
270                    ec.language = currentLanguage;
271                    ec.langIsLang = langIsLang;
272                    ec.original = context.original;
273                } else {
274                    if (newSubject != null) {
275                        ec.parentSubject = newSubject;
276                    } else {
277                        ec.parentSubject = context.parentSubject;
278                    }
279    
280                    if (currentObject != null) {
281                        ec.parentObject = currentObject;
282                    } else if (newSubject != null) {
283                        ec.parentObject = newSubject;
284                    } else {
285                        ec.parentObject = context.parentSubject;
286                    }
287    
288                    ec.language = currentLanguage;
289                    ec.langIsLang = langIsLang;
290                    ec.forwardProperties = forwardProperties;
291                    ec.backwardProperties = backwardProperties;
292                }
293    
294                return ec;
295            }
296    
297            return null;
298        }
299    
300        private Attribute findAttribute(StartElement element, QName... names) {
301            for (QName aName : names) {
302                Attribute a = element.getAttributeByName(aName);
303                if (a != null) {
304                    return a;
305                }
306            }
307            return null;
308        }
309    
310        private void emitTriples(String subj, Collection<String> props, String obj) {
311            for (String prop : props) {
312                sink.addObject(subj, prop, obj);
313            }
314        }
315    
316        private void emitTriplesPlainLiteral(String subj, Collection<String> props, String lex, String language) {
317            for (String prop : props) {
318                sink.addLiteral(subj, prop, lex, language, null);
319            }
320        }
321    
322        private void emitTriplesDatatypeLiteral(String subj, Collection<String> props, String lex, String datatype) {
323            for (String prop : props) {
324                sink.addLiteral(subj, prop, lex, null, datatype);
325            }
326        }
327    
328        private String getURI(String base, StartElement element, Attribute attr) {
329            QName attrName = attr.getName();
330            if (attrName.equals(href) || attrName.equals(src)) // A URI
331            {
332                if (attr.getValue().length() == 0) {
333                    return base;
334                }
335                IRI uri = IRIFact.construct(base);
336                IRI resolved = uri.resolve(attr.getValue());
337                return resolved.toString();
338            }
339            if (attrName.equals(about) || attrName.equals(resource)) // Safe CURIE or URI
340            {
341                return expandSafeCURIE(base, element, attr.getValue());
342            }
343            if (attrName.equals(datatype)) // A CURIE
344            {
345                return expandCURIE(element, attr.getValue());
346            }
347            throw new RuntimeException("Unexpected attribute: " + attr);
348        }
349    
350        private List<String> getURIs(String base, StartElement element, Attribute attr) {
351            List<String> uris = new LinkedList<String>();
352            String[] curies = attr.getValue().split("\\s+");
353            boolean permitReserved = rel.equals(attr.getName()) ||
354                    rev.equals(attr.getName());
355            for (String curie : curies) {
356                boolean isSpecial = (settings.contains(Setting.ManualNamespaces)) ? SpecialRels.contains(curie.toLowerCase()) : SpecialRels.contains(curie);
357                if (isSpecial && settings.contains(Setting.ManualNamespaces)) {
358                    curie = curie.toLowerCase();
359                }
360                if (permitReserved && isSpecial) {
361                    uris.add("http://www.w3.org/1999/xhtml/vocab#" + curie);
362                } else if (!isSpecial) {
363                    String uri = expandCURIE(element, curie);
364                    if (uri != null) {
365                        uris.add(uri);
366                    }
367                }
368            }
369            return uris;
370        }
371        int bnodeId = 0;
372    
373        private String createBNode() // TODO probably broken? Can you write bnodes in rdfa directly?
374        {
375            return "_:node" + (bnodeId++);
376        }
377    
378        private String expandCURIE(StartElement element, String value) {
379            if (value.startsWith("_:") && element.getNamespaceURI("_") == null) {
380                return value;
381            }
382            if (settings.contains(Setting.FormMode) && // variable
383                    value.startsWith("?")) {
384                return value;
385            }
386            int offset = value.indexOf(":") + 1;
387            if (offset == 0) {
388                //throw new RuntimeException("Is this a curie? \"" + value + "\"");
389                return null;
390            }
391            String prefix = value.substring(0, offset - 1);
392            String namespaceURI = prefix.length() == 0 ? "http://www.w3.org/1999/xhtml/vocab#" : element.getNamespaceURI(prefix);
393            if (namespaceURI == null) {
394                return null;
395                //throw new RuntimeException("Unknown prefix: " + prefix);
396            }
397            if (offset != value.length() && value.charAt(offset) == '#') {
398                offset += 1; // ex:#bar
399            }
400            if (namespaceURI.endsWith("/") || namespaceURI.endsWith("#")) {
401                return namespaceURI + value.substring(offset);
402            } else {
403                return namespaceURI + "#" + value.substring(offset);
404            }
405        }
406    
407        private String expandSafeCURIE(String base, StartElement element, String value) {
408            if (value.startsWith("[") && value.endsWith("]")) {
409                return expandCURIE(element, value.substring(1, value.length() - 1));
410            } else {
411                if (value.length() == 0) {
412                    return base;
413                }
414    
415                if (settings.contains(Setting.FormMode) &&
416                        value.startsWith("?")) {
417                    return value;
418                }
419    
420                IRI uri = IRIFact.construct(base);
421                IRI resolved = uri.resolve(value);
422                return resolved.toString();
423            }
424        }
425    
426        private String getDatatype(StartElement element) {
427            Attribute de = element.getAttributeByName(datatype);
428            if (de == null) {
429                return null;
430            }
431            String dt = de.getValue();
432            if (dt.length() == 0) {
433                return dt;
434            }
435            return expandCURIE(element, dt);
436        }
437    
438        static class EvalContext implements NamespaceContext {
439    
440            EvalContext parent;
441            String base;
442            String parentSubject;
443            String parentObject;
444            String language;
445            List<String> forwardProperties;
446            List<String> backwardProperties;
447            Map<String, String> prefixToUri = new HashMap<String, String>();
448            boolean original;
449            boolean langIsLang = false; // html 5 oddity
450    
451            private EvalContext(String base) {
452                this.base = base;
453                this.parentSubject = base;
454                this.forwardProperties = new LinkedList<String>();
455                this.backwardProperties = new LinkedList<String>();
456                original = true;
457            }
458    
459            public EvalContext(EvalContext toCopy) {
460                this.base = toCopy.base;
461                this.parentSubject = toCopy.parentSubject;
462                this.parentObject = toCopy.parentObject;
463                this.language = toCopy.language;
464                this.forwardProperties = new LinkedList<String>(toCopy.forwardProperties);
465                this.backwardProperties = new LinkedList<String>(toCopy.backwardProperties);
466                this.langIsLang = toCopy.langIsLang;
467                this.original = false;
468                this.parent = toCopy;
469            }
470    
471            public void setBase(String abase) {
472                if (abase.contains("#")) {
473                    this.base = abase.substring(0, abase.indexOf("#"));
474                } else {
475                    this.base = abase;
476                }
477                // Not great, but passes tests.
478                // We want to say: if parentSubject hasn't been changed, it's base
479                if (this.original) {
480                    this.parentSubject = this.base;
481                }
482                if (parent != null) {
483                    parent.setBase(base);
484                }
485            }
486    
487            public void setNamespaceURI(String prefix, String uri) {
488                if (uri.length() == 0) {
489                    uri = base;
490                }
491                prefixToUri.put(prefix, uri);
492            }
493    
494            public String getNamespaceURI(String prefix) {
495                if (prefixToUri.containsKey(prefix)) {
496                    return prefixToUri.get(prefix);
497                } else if (parent != null) {
498                    return parent.getNamespaceURI(prefix);
499                } else {
500                    return null;
501                }
502            }
503    
504            public String getPrefix(String uri) {
505                throw new UnsupportedOperationException("Not supported yet.");
506            }
507    
508            public Iterator getPrefixes(String uri) {
509                throw new UnsupportedOperationException("Not supported yet.");
510            }
511        }
512    
513        private void getNamespaces(Attributes attrs) {
514            for (int i = 0; i < attrs.getLength(); i++) {
515                String qname = attrs.getQName(i);
516                String prefix = getPrefix(qname);
517                if ("xmlns".equals(prefix)) {
518                    context.setNamespaceURI(getLocal(prefix, qname), attrs.getValue(i));
519                }
520            }
521        }
522    
523        private String getPrefix(String qname) {
524            if (!qname.contains(":")) {
525                return "";
526            }
527            return qname.substring(0, qname.indexOf(":"));
528        }
529    
530        private String getLocal(String prefix, String qname) {
531            if (prefix.length() == 0) {
532                return qname;
533            }
534            return qname.substring(prefix.length() + 1);
535        }
536        /**
537         * SAX methods
538         */
539        private Locator locator;
540        //private NSMapping mapping;
541        private EvalContext context = new EvalContext("http://www.example.com/");
542        // For literals (what fun!)
543        private List<XMLEvent> queuedEvents;
544        private int level = -1;
545        private XMLEventWriter xmlWriter;
546        private StringWriter literalWriter;
547        private String theDatatype;
548        private List<String> litProps;
549    
550        public void setDocumentLocator(Locator arg0) {
551            this.locator = arg0;
552        }
553    
554        public void startDocument() throws SAXException {
555            sink.start();
556        }
557    
558        public void endDocument() throws SAXException {
559            sink.end();
560        }
561    
562        public void startPrefixMapping(String arg0, String arg1)
563                throws SAXException {
564            context.setNamespaceURI(arg0, arg1);
565        }
566    
567        public void endPrefixMapping(String arg0) throws SAXException {
568        }
569    
570        public void startElement(String arg0, String localname, String qname, Attributes arg3) throws SAXException {
571            try {
572                //System.err.println("Start element: " + arg0 + " " + arg1 + " " + arg2);
573                // Dammit, not quite the same as XMLEventFactory
574                String prefix = (localname.equals(qname)) ? ""
575                        : qname.substring(0, qname.indexOf(':'));
576                if (settings.contains(Setting.ManualNamespaces)) {
577                    getNamespaces(arg3);
578                }
579                StartElement e = EventFactory.createStartElement(
580                        prefix, arg0, localname,
581                        fromAttributes(arg3), null, context);
582    
583                if (level != -1) { // getting literal
584                    handleForLiteral(e);
585                    return;
586                }
587                context = parse(context, e);
588            } catch (XMLStreamException ex) {
589                throw new RuntimeException("Streaming issue", ex);
590            }
591    
592        }
593    
594        public void endElement(String arg0, String localname, String qname) throws SAXException {
595            //System.err.println("End element: " + arg0 + " " + arg1 + " " + arg2);
596            if (level != -1) { // getting literal
597                String prefix = (localname.equals(qname)) ? ""
598                        : qname.substring(0, qname.indexOf(':'));
599                XMLEvent e = EventFactory.createEndElement(prefix, arg0, localname);
600                handleForLiteral(e);
601                if (level != -1) {
602                    return; // if still handling literal duck out now
603                }
604            }
605            context = context.parent;
606        }
607    
608        public void characters(char[] arg0, int arg1, int arg2) throws SAXException {
609            if (level != -1) {
610                XMLEvent e = EventFactory.createCharacters(String.valueOf(arg0, arg1, arg2));
611                handleForLiteral(e);
612                return;
613            }
614        }
615    
616        public void ignorableWhitespace(char[] arg0, int arg1, int arg2) throws SAXException {
617            //System.err.println("Whitespace...");
618            if (level != -1) {
619                XMLEvent e = EventFactory.createIgnorableSpace(String.valueOf(arg0, arg1, arg2));
620                handleForLiteral(e);
621            }
622        }
623    
624        public void processingInstruction(String arg0, String arg1) throws SAXException {
625        }
626    
627        public void skippedEntity(String arg0) throws SAXException {
628        }
629    
630        private Iterator fromAttributes(Attributes attributes) {
631            List toReturn = new LinkedList();
632            boolean haveLang = false;
633            for (int i = 0; i < attributes.getLength(); i++) {
634                String qname = attributes.getQName(i);
635                String prefix = qname.contains(":") ? qname.substring(0, qname.indexOf(":")) : "";
636                Attribute attr = EventFactory.createAttribute(
637                        prefix, attributes.getURI(i),
638                        attributes.getLocalName(i), attributes.getValue(i));
639                if (xmllang.getLocalPart().equals(attributes.getLocalName(i)) &&
640                        xmllang.getNamespaceURI().equals(attributes.getURI(i))) {
641                    haveLang = true;
642                }
643                toReturn.add(attr);
644            }
645            // Copy xml lang across if in literal
646            if (level == 1 && context.language != null && !haveLang) {
647                toReturn.add(EventFactory.createAttribute(xmllang, context.language));
648            }
649            return toReturn.iterator();
650        }
651    
652        private void handleForLiteral(XMLEvent e) {
653            try {
654                handleForLiteralEx(e);
655            } catch (XMLStreamException ex) {
656                throw new RuntimeException("Literal handling error", ex);
657            } catch (IOException ex) {
658                throw new RuntimeException("Literal handling error", ex);
659            }
660        }
661    
662        private void handleForLiteralEx(XMLEvent e) throws XMLStreamException, IOException {
663            if (e.isStartElement()) {
664                level++;
665                if (queuedEvents != null) { // Aha, we ain't plain
666                    xmlWriter = outputFactory.createXMLEventWriter(literalWriter);
667                    for (XMLEvent ev : queuedEvents) {
668                        xmlWriter.add(ev);
669                    }
670                    queuedEvents = null;
671                    theDatatype = xmlLiteral;
672                }
673            }
674    
675            if (e.isEndElement()) {
676                level--;
677                if (level == 0) { // Finished!
678                    if (xmlWriter != null) {
679                        xmlWriter.close();
680                    } else if (queuedEvents != null) {
681                        for (XMLEvent ev : queuedEvents) {
682                            literalWriter.append(ev.asCharacters().getData());
683                        }
684                    }
685                    String lex = literalWriter.toString();
686                    if (theDatatype == null || theDatatype.length() == 0) {
687                        emitTriplesPlainLiteral(context.parentSubject,
688                                litProps, lex, context.language);
689                    } else {
690                        emitTriplesDatatypeLiteral(context.parentSubject,
691                                litProps, lex, theDatatype);
692                    }
693                    queuedEvents = null;
694                    xmlWriter = null;
695                    literalWriter = null;
696                    theDatatype = null;
697                    litProps = null;
698                    level = -1;
699                    return;
700                }
701            }
702    
703            if (xmlWriter != null) {
704                xmlWriter.add(e);
705            } else if (e.isCharacters() && queuedEvents != null) {
706                queuedEvents.add(e);
707            } else if (e.isCharacters()) {
708                literalWriter.append(e.asCharacters().getData());
709            }
710        }
711    }
712    
713    /*
714     * (c) Copyright 2009 University of Bristol
715     * All rights reserved.
716     *
717     * Redistribution and use in source and binary forms, with or without
718     * modification, are permitted provided that the following conditions
719     * are met:
720     * 1. Redistributions of source code must retain the above copyright
721     *    notice, this list of conditions and the following disclaimer.
722     * 2. Redistributions in binary form must reproduce the above copyright
723     *    notice, this list of conditions and the following disclaimer in the
724     *    documentation and/or other materials provided with the distribution.
725     * 3. The name of the author may not be used to endorse or promote products
726     *    derived from this software without specific prior written permission.
727     *
728     * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
729     * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
730     * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
731     * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
732     * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
733     * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
734     * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
735     * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
736     * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
737     * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
738     */