1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * See LICENSE.txt included in this distribution for the specific
9  * language governing permissions and limitations under the License.
10  *
11  * When distributing Covered Code, include this CDDL HEADER in each
12  * file and include the License file at LICENSE.txt.
13  * If applicable, add the following below this CDDL HEADER, with the
14  * fields enclosed by brackets "[]" replaced with your own identifying
15  * information: Portions Copyright [yyyy] [name of copyright owner]
16  *
17  * CDDL HEADER END
18  */
19 
20 /*
21  * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
22  * Portions Copyright (c) 2020, Chris Fraire <cfraire@me.com>.
23  */
24 package org.opengrok.indexer.history;
25 
26 import java.io.BufferedInputStream;
27 import java.io.IOException;
28 import java.io.InputStream;
29 import java.util.logging.Level;
30 import java.util.logging.Logger;
31 import javax.xml.XMLConstants;
32 import javax.xml.parsers.ParserConfigurationException;
33 import javax.xml.parsers.SAXParser;
34 import javax.xml.parsers.SAXParserFactory;
35 import org.opengrok.indexer.logger.LoggerFactory;
36 import org.opengrok.indexer.util.Executor;
37 import org.xml.sax.Attributes;
38 import org.xml.sax.SAXException;
39 import org.xml.sax.ext.DefaultHandler2;
40 
41 /**
42  * handles parsing the output of the {@code svn annotate}
43  * command into an annotation object.
44  */
45 public class SubversionAnnotationParser implements Executor.StreamHandler {
46     private static final Logger LOGGER = LoggerFactory.getLogger(SubversionAnnotationParser.class);
47 
48     /**
49      * Store annotation created by processStream.
50      */
51     private final Annotation annotation;
52 
53     private final String fileName;
54 
55     /**
56      * @param fileName the name of the file being annotated
57      */
SubversionAnnotationParser(String fileName)58     public SubversionAnnotationParser(String fileName) {
59         annotation = new Annotation(fileName);
60         this.fileName = fileName;
61     }
62 
63     /**
64      * Returns the annotation that has been created.
65      *
66      * @return annotation an annotation object
67      */
getAnnotation()68     public Annotation getAnnotation() {
69         return annotation;
70     }
71 
72     @Override
processStream(InputStream input)73     public void processStream(InputStream input) throws IOException {
74         SAXParserFactory factory = SAXParserFactory.newInstance();
75         SAXParser saxParser;
76         try {
77             saxParser = factory.newSAXParser();
78             saxParser.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); // Compliant
79             saxParser.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, ""); // compliant
80         } catch (ParserConfigurationException | SAXException ex) {
81             IOException err = new IOException("Failed to create SAX parser", ex);
82             throw err;
83         }
84 
85         AnnotateHandler handler = new AnnotateHandler(fileName, annotation);
86         try (BufferedInputStream in
87                 = new BufferedInputStream(input)) {
88             saxParser.parse(in, handler);
89         } catch (Exception e) {
90             LOGGER.log(Level.SEVERE,
91                     "An error occurred while parsing the xml output", e);
92         }
93     }
94 
95     private static class AnnotateHandler extends DefaultHandler2 {
96 
97         String rev;
98         String author;
99         final Annotation annotation;
100         final StringBuilder sb;
101 
AnnotateHandler(String filename, Annotation annotation)102         AnnotateHandler(String filename, Annotation annotation) {
103             this.annotation = annotation;
104             sb = new StringBuilder();
105         }
106 
107         @Override
startElement(String uri, String localName, String qname, Attributes attr)108         public void startElement(String uri, String localName, String qname,
109                 Attributes attr) {
110             sb.setLength(0);
111             if (null != qname) {
112                 switch (qname) {
113                     case "entry":
114                         rev = null;
115                         author = null;
116                         break;
117                     case "commit":
118                         rev = attr.getValue("revision");
119                         break;
120                 }
121             }
122         }
123 
124         @Override
endElement(String uri, String localName, String qname)125         public void endElement(String uri, String localName, String qname) {
126             if (null != qname) {
127                 switch (qname) {
128                     case "author":
129                         author = sb.toString();
130                         break;
131                     case "entry":
132                         annotation.addLine(rev, author, true);
133                         break;
134                 }
135             }
136         }
137 
138         @Override
characters(char[] arg0, int arg1, int arg2)139         public void characters(char[] arg0, int arg1, int arg2) {
140             sb.append(arg0, arg1, arg2);
141         }
142     }
143 }
144