0N/A/*
0N/A * CDDL HEADER START
0N/A *
0N/A * The contents of this file are subject to the terms of the
0N/A * Common Development and Distribution License (the "License").
0N/A * You may not use this file except in compliance with the License.
0N/A *
0N/A * See LICENSE.txt included in this distribution for the specific
0N/A * language governing permissions and limitations under the License.
0N/A *
0N/A * When distributing Covered Code, include this CDDL HEADER in each
0N/A * file and include the License file at LICENSE.txt.
0N/A * If applicable, add the following below this CDDL HEADER, with the
0N/A * fields enclosed by brackets "[]" replaced with your own identifying
0N/A * information: Portions Copyright [yyyy] [name of copyright owner]
0N/A *
0N/A * CDDL HEADER END
0N/A */
0N/A
0N/A/*
1072N/A * Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
0N/A */
0N/Apackage org.opensolaris.opengrok.history;
0N/A
453N/Aimport java.io.BufferedInputStream;
628N/Aimport java.io.ByteArrayInputStream;
0N/Aimport java.io.File;
0N/Aimport java.io.IOException;
628N/Aimport java.io.InputStream;
749N/Aimport java.text.DateFormat;
453N/Aimport java.text.ParseException;
453N/Aimport java.text.SimpleDateFormat;
59N/Aimport java.util.ArrayList;
457N/Aimport java.util.List;
456N/Aimport java.util.Locale;
453N/Aimport java.util.logging.Level;
1327N/Aimport java.util.logging.Logger;
1327N/A
453N/Aimport javax.xml.parsers.SAXParser;
453N/Aimport javax.xml.parsers.SAXParserFactory;
1327N/A
170N/Aimport org.opensolaris.opengrok.configuration.RuntimeEnvironment;
628N/Aimport org.opensolaris.opengrok.util.Executor;
1015N/Aimport org.opensolaris.opengrok.util.Interner;
453N/Aimport org.xml.sax.Attributes;
453N/Aimport org.xml.sax.ext.DefaultHandler2;
0N/A
0N/A/**
182N/A * Parse source history for a Subversion Repository
0N/A *
0N/A * @author Trond Norbye
0N/A */
770N/Aclass SubversionHistoryParser implements Executor.StreamHandler {
628N/A
1327N/A private static final Logger logger =
1327N/A Logger.getLogger(SubversionHistoryParser.class.getName());
628N/A private SAXParser saxParser = null;
628N/A private Handler handler;
453N/A
453N/A private static class Handler extends DefaultHandler2 {
1327N/A private static final Logger logger = Logger
1327N/A .getLogger(SubversionHistoryParser.Handler.class.getName());
453N/A final String prefix;
453N/A final String home;
453N/A final int length;
457N/A final List<HistoryEntry> entries = new ArrayList<HistoryEntry>();
749N/A final DateFormat format;
453N/A HistoryEntry entry;
453N/A StringBuilder sb;
1015N/A private final Interner<String> stringInterner = new Interner<String>();
453N/A
749N/A Handler(String home, String prefix, int length, DateFormat df) {
628N/A this.home = home;
628N/A this.prefix = prefix;
628N/A this.length = length;
749N/A format = df;
453N/A sb = new StringBuilder();
453N/A }
453N/A
453N/A @Override
1182N/A public void startElement(String uri, String localName, String qname, Attributes attr) {
453N/A if ("logentry".equals(qname)) {
453N/A entry = new HistoryEntry();
453N/A entry.setActive(true);
453N/A entry.setRevision(attr.getValue("revision"));
453N/A }
453N/A sb.setLength(0);
453N/A }
453N/A
453N/A @Override
1182N/A public void endElement(String uri, String localName, String qname) {
453N/A String s = sb.toString();
453N/A if ("author".equals(qname)) {
453N/A entry.setAuthor(s);
453N/A } else if ("date".equals(qname)) {
453N/A try {
453N/A entry.setDate(format.parse(s));
453N/A } catch (ParseException ex) {
1327N/A logger.severe("Failed to parse date " + s + ": " + ex.getMessage());
1327N/A logger.log(Level.FINE, "endElement", ex);
453N/A }
453N/A } else if ("path".equals(qname)) {
453N/A if (s.startsWith(prefix)) {
453N/A File file = new File(home, s.substring(prefix.length()));
453N/A String path = file.getAbsolutePath().substring(length);
1015N/A // The same file names may be repeated in many commits,
1015N/A // so intern them to reduce the memory footprint.
1015N/A // Bug #15956: Don't use String.intern(), since that may
1015N/A // exhaust the permgen space. Instead, use our own
1015N/A // interner that allocates space on the heap.
1015N/A path = stringInterner.intern(path);
1015N/A entry.addFile(path);
453N/A } else {
1327N/A logger.log(Level.FINE, "Skipping file outside repository ''{0}''", s);
453N/A }
453N/A } else if ("msg".equals(qname)) {
453N/A entry.setMessage(s);
456N/A } if ("logentry".equals(qname)) {
453N/A entries.add(entry);
453N/A }
453N/A sb.setLength(0);
453N/A }
453N/A
453N/A @Override
1182N/A public void characters(char[] arg0, int arg1, int arg2) {
453N/A sb.append(arg0, arg1, arg2);
453N/A }
453N/A }
453N/A
59N/A /**
628N/A * Initialize the SAX parser instance.
59N/A */
1101N/A private void initSaxParser() throws HistoryException {
453N/A SAXParserFactory factory = SAXParserFactory.newInstance();
628N/A saxParser = null;
453N/A try {
453N/A saxParser = factory.newSAXParser();
1101N/A } catch (Exception ex) {
1101N/A throw new HistoryException("Failed to create SAX parser", ex);
453N/A }
628N/A }
628N/A
628N/A /**
628N/A * Parse the history for the specified file.
628N/A *
628N/A * @param file the file to parse history for
628N/A * @param repos Pointer to the SubversionReporitory
784N/A * @param sinceRevision the revision number immediately preceding the first
784N/A * revision we want, or {@code null} to fetch the entire history
628N/A * @return object representing the file's history
628N/A */
784N/A History parse(File file, SubversionRepository repos, String sinceRevision)
784N/A throws HistoryException {
628N/A initSaxParser();
1190N/A handler = new Handler(repos.getDirectoryName(), repos.reposPath,
1470N/A RuntimeEnvironment.getConfig().getSourceRoot().length(),
749N/A repos.getDateFormat());
1190N/A
784N/A Executor executor = repos.getHistoryLogExecutor(file, sinceRevision);
628N/A int status = executor.exec(true, this);
237N/A
628N/A if (status != 0) {
1327N/A throw new HistoryException("Failed to get history for '" +
1327N/A file.getAbsolutePath() + "' - Exit code " + status);
0N/A }
59N/A
784N/A List<HistoryEntry> entries = handler.entries;
784N/A
784N/A // If we only fetch parts of the history, we're not interested in
784N/A // sinceRevision. Remove it.
784N/A if (sinceRevision != null) {
967N/A repos.removeAndVerifyOldestChangeset(entries, sinceRevision);
784N/A }
784N/A
784N/A return new History(entries);
82N/A }
784N/A
628N/A /**
628N/A * Process the output from the log command and insert the HistoryEntries
628N/A * into the history field.
628N/A *
628N/A * @param input The output from the process
628N/A */
815N/A @Override
1182N/A public void processStream(InputStream input) {
628N/A try {
628N/A initSaxParser();
628N/A saxParser.parse(new BufferedInputStream(input), handler);
628N/A } catch (Exception e) {
1327N/A logger.warning("An error occurred while parsing the command output (xml): "
1327N/A + e.getMessage());
1327N/A logger.log(Level.FINE, "processStream", e);
628N/A }
628N/A }
628N/A
1470N/A private static SimpleDateFormat DF
1470N/A = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault());
628N/A /**
628N/A * Parse the given string.
1190N/A *
628N/A * @param buffer The string to be parsed
628N/A * @return The parsed history
628N/A * @throws IOException if we fail to parse the buffer
628N/A */
770N/A History parse(String buffer) throws IOException {
1470N/A handler = new Handler("/", "", 0, (DateFormat) DF.clone());
628N/A processStream(new ByteArrayInputStream(buffer.getBytes("UTF-8")));
784N/A return new History(handler.entries);
628N/A }
0N/A}