ebb9f739bca3bc9382340b628554b484e4837d6aKnut Anders Hatlen/*
ebb9f739bca3bc9382340b628554b484e4837d6aKnut Anders Hatlen * CDDL HEADER START
ebb9f739bca3bc9382340b628554b484e4837d6aKnut Anders Hatlen *
ebb9f739bca3bc9382340b628554b484e4837d6aKnut Anders Hatlen * The contents of this file are subject to the terms of the
ebb9f739bca3bc9382340b628554b484e4837d6aKnut Anders Hatlen * Common Development and Distribution License (the "License").
ebb9f739bca3bc9382340b628554b484e4837d6aKnut Anders Hatlen * You may not use this file except in compliance with the License.
ebb9f739bca3bc9382340b628554b484e4837d6aKnut Anders Hatlen *
ebb9f739bca3bc9382340b628554b484e4837d6aKnut Anders Hatlen * See LICENSE.txt included in this distribution for the specific
ebb9f739bca3bc9382340b628554b484e4837d6aKnut Anders Hatlen * language governing permissions and limitations under the License.
ebb9f739bca3bc9382340b628554b484e4837d6aKnut Anders Hatlen *
ebb9f739bca3bc9382340b628554b484e4837d6aKnut Anders Hatlen * When distributing Covered Code, include this CDDL HEADER in each
ebb9f739bca3bc9382340b628554b484e4837d6aKnut Anders Hatlen * file and include the License file at LICENSE.txt.
ebb9f739bca3bc9382340b628554b484e4837d6aKnut Anders Hatlen * If applicable, add the following below this CDDL HEADER, with the
ebb9f739bca3bc9382340b628554b484e4837d6aKnut Anders Hatlen * fields enclosed by brackets "[]" replaced with your own identifying
ebb9f739bca3bc9382340b628554b484e4837d6aKnut Anders Hatlen * information: Portions Copyright [yyyy] [name of copyright owner]
ebb9f739bca3bc9382340b628554b484e4837d6aKnut Anders Hatlen *
ebb9f739bca3bc9382340b628554b484e4837d6aKnut Anders Hatlen * CDDL HEADER END
ebb9f739bca3bc9382340b628554b484e4837d6aKnut Anders Hatlen */
ebb9f739bca3bc9382340b628554b484e4837d6aKnut Anders Hatlen
ebb9f739bca3bc9382340b628554b484e4837d6aKnut Anders Hatlen/*
ded3903ab1e55f8a983d4e7741ef55504c487d69Kryštof Tulinger * Copyright (c) 2006, 2016, Oracle and/or its affiliates. All rights reserved.
ebb9f739bca3bc9382340b628554b484e4837d6aKnut Anders Hatlen */
ebb9f739bca3bc9382340b628554b484e4837d6aKnut Anders Hatlenpackage org.opensolaris.opengrok.history;
ebb9f739bca3bc9382340b628554b484e4837d6aKnut Anders Hatlen
20a0bde399487a651cdeb66fc8b44b2212036355Trond Norbyeimport java.io.BufferedInputStream;
68a986af15f28e9fd0bdcac5af761097740b21aaJorgen Austvikimport java.io.ByteArrayInputStream;
ebb9f739bca3bc9382340b628554b484e4837d6aKnut Anders Hatlenimport java.io.File;
ebb9f739bca3bc9382340b628554b484e4837d6aKnut Anders Hatlenimport java.io.IOException;
68a986af15f28e9fd0bdcac5af761097740b21aaJorgen Austvikimport java.io.InputStream;
3bd91b9bbb9915421b772c357165fbc6fdeaf286Trond Norbyeimport java.text.DateFormat;
20a0bde399487a651cdeb66fc8b44b2212036355Trond Norbyeimport java.text.ParseException;
94c7362941c6089e9a113f4b55fa3b9f7e058f34Knut Anders Hatlenimport java.util.ArrayList;
9e81ea65408ad01e22a2c01118fd29139e20336bJorgen Austvikimport java.util.List;
20a0bde399487a651cdeb66fc8b44b2212036355Trond Norbyeimport java.util.logging.Level;
6336b638e9afd018de5f6c516eac4775d140fdaeJHKSTimport java.util.logging.Logger;
20a0bde399487a651cdeb66fc8b44b2212036355Trond Norbyeimport javax.xml.parsers.SAXParser;
20a0bde399487a651cdeb66fc8b44b2212036355Trond Norbyeimport javax.xml.parsers.SAXParserFactory;
308470174aea8cf0128aa8baf7018c9500f6131cTrond Norbyeimport org.opensolaris.opengrok.configuration.RuntimeEnvironment;
6336b638e9afd018de5f6c516eac4775d140fdaeJHKSTimport org.opensolaris.opengrok.logger.LoggerFactory;
68a986af15f28e9fd0bdcac5af761097740b21aaJorgen Austvikimport org.opensolaris.opengrok.util.Executor;
f4355bfc8c6c2cfc89ccf21dfe3cc6b452089bfcKnut Anders Hatlenimport org.opensolaris.opengrok.util.Interner;
20a0bde399487a651cdeb66fc8b44b2212036355Trond Norbyeimport org.xml.sax.Attributes;
20a0bde399487a651cdeb66fc8b44b2212036355Trond Norbyeimport org.xml.sax.ext.DefaultHandler2;
ebb9f739bca3bc9382340b628554b484e4837d6aKnut Anders Hatlen
ebb9f739bca3bc9382340b628554b484e4837d6aKnut Anders Hatlen/**
8247ba75ae77540a334b19527df7d963265c590bTrond Norbye * Parse source history for a Subversion Repository
ebb9f739bca3bc9382340b628554b484e4837d6aKnut Anders Hatlen *
ebb9f739bca3bc9382340b628554b484e4837d6aKnut Anders Hatlen * @author Trond Norbye
ebb9f739bca3bc9382340b628554b484e4837d6aKnut Anders Hatlen */
20c666f595e231740b3d6e0cee9348eec5befdd9Knut Anders Hatlenclass SubversionHistoryParser implements Executor.StreamHandler {
68a986af15f28e9fd0bdcac5af761097740b21aaJorgen Austvik
6336b638e9afd018de5f6c516eac4775d140fdaeJHKST private static final Logger LOGGER = LoggerFactory.getLogger(SubversionHistoryParser.class);
6336b638e9afd018de5f6c516eac4775d140fdaeJHKST
68a986af15f28e9fd0bdcac5af761097740b21aaJorgen Austvik private SAXParser saxParser = null;
68a986af15f28e9fd0bdcac5af761097740b21aaJorgen Austvik private Handler handler;
20a0bde399487a651cdeb66fc8b44b2212036355Trond Norbye
20a0bde399487a651cdeb66fc8b44b2212036355Trond Norbye private static class Handler extends DefaultHandler2 {
20a0bde399487a651cdeb66fc8b44b2212036355Trond Norbye
20a0bde399487a651cdeb66fc8b44b2212036355Trond Norbye final String prefix;
20a0bde399487a651cdeb66fc8b44b2212036355Trond Norbye final String home;
20a0bde399487a651cdeb66fc8b44b2212036355Trond Norbye final int length;
9e81ea65408ad01e22a2c01118fd29139e20336bJorgen Austvik final List<HistoryEntry> entries = new ArrayList<HistoryEntry>();
3bd91b9bbb9915421b772c357165fbc6fdeaf286Trond Norbye final DateFormat format;
20a0bde399487a651cdeb66fc8b44b2212036355Trond Norbye HistoryEntry entry;
20a0bde399487a651cdeb66fc8b44b2212036355Trond Norbye StringBuilder sb;
f4355bfc8c6c2cfc89ccf21dfe3cc6b452089bfcKnut Anders Hatlen private final Interner<String> stringInterner = new Interner<String>();
20a0bde399487a651cdeb66fc8b44b2212036355Trond Norbye
3bd91b9bbb9915421b772c357165fbc6fdeaf286Trond Norbye Handler(String home, String prefix, int length, DateFormat df) {
68a986af15f28e9fd0bdcac5af761097740b21aaJorgen Austvik this.home = home;
68a986af15f28e9fd0bdcac5af761097740b21aaJorgen Austvik this.prefix = prefix;
68a986af15f28e9fd0bdcac5af761097740b21aaJorgen Austvik this.length = length;
3bd91b9bbb9915421b772c357165fbc6fdeaf286Trond Norbye format = df;
20a0bde399487a651cdeb66fc8b44b2212036355Trond Norbye sb = new StringBuilder();
20a0bde399487a651cdeb66fc8b44b2212036355Trond Norbye }
20a0bde399487a651cdeb66fc8b44b2212036355Trond Norbye
20a0bde399487a651cdeb66fc8b44b2212036355Trond Norbye @Override
8ea4b8d9796de43443cdf7b66e3f185aedf7b570Jens Elkner public void startElement(String uri, String localName, String qname, Attributes attr) {
20a0bde399487a651cdeb66fc8b44b2212036355Trond Norbye if ("logentry".equals(qname)) {
20a0bde399487a651cdeb66fc8b44b2212036355Trond Norbye entry = new HistoryEntry();
20a0bde399487a651cdeb66fc8b44b2212036355Trond Norbye entry.setActive(true);
20a0bde399487a651cdeb66fc8b44b2212036355Trond Norbye entry.setRevision(attr.getValue("revision"));
20a0bde399487a651cdeb66fc8b44b2212036355Trond Norbye }
20a0bde399487a651cdeb66fc8b44b2212036355Trond Norbye sb.setLength(0);
20a0bde399487a651cdeb66fc8b44b2212036355Trond Norbye }
20a0bde399487a651cdeb66fc8b44b2212036355Trond Norbye
20a0bde399487a651cdeb66fc8b44b2212036355Trond Norbye @Override
8ea4b8d9796de43443cdf7b66e3f185aedf7b570Jens Elkner public void endElement(String uri, String localName, String qname) {
20a0bde399487a651cdeb66fc8b44b2212036355Trond Norbye String s = sb.toString();
20a0bde399487a651cdeb66fc8b44b2212036355Trond Norbye if ("author".equals(qname)) {
20a0bde399487a651cdeb66fc8b44b2212036355Trond Norbye entry.setAuthor(s);
20a0bde399487a651cdeb66fc8b44b2212036355Trond Norbye } else if ("date".equals(qname)) {
20a0bde399487a651cdeb66fc8b44b2212036355Trond Norbye try {
20a0bde399487a651cdeb66fc8b44b2212036355Trond Norbye entry.setDate(format.parse(s));
20a0bde399487a651cdeb66fc8b44b2212036355Trond Norbye } catch (ParseException ex) {
6336b638e9afd018de5f6c516eac4775d140fdaeJHKST LOGGER.log(Level.SEVERE, "Failed to parse: " + s, ex);
20a0bde399487a651cdeb66fc8b44b2212036355Trond Norbye }
20a0bde399487a651cdeb66fc8b44b2212036355Trond Norbye } else if ("path".equals(qname)) {
f0bb58974b3787fbf6e4068a01a3c520b75bbdaeVladimir Kotal /*
f0bb58974b3787fbf6e4068a01a3c520b75bbdaeVladimir Kotal * We only want valid files in the repository, not the
f0bb58974b3787fbf6e4068a01a3c520b75bbdaeVladimir Kotal * top-level directory itself, hence the check for inequivality.
f0bb58974b3787fbf6e4068a01a3c520b75bbdaeVladimir Kotal */
f0bb58974b3787fbf6e4068a01a3c520b75bbdaeVladimir Kotal if (s.startsWith(prefix) && !s.equals(prefix)) {
20a0bde399487a651cdeb66fc8b44b2212036355Trond Norbye File file = new File(home, s.substring(prefix.length()));
20a0bde399487a651cdeb66fc8b44b2212036355Trond Norbye String path = file.getAbsolutePath().substring(length);
f4355bfc8c6c2cfc89ccf21dfe3cc6b452089bfcKnut Anders Hatlen // The same file names may be repeated in many commits,
f4355bfc8c6c2cfc89ccf21dfe3cc6b452089bfcKnut Anders Hatlen // so intern them to reduce the memory footprint.
f4355bfc8c6c2cfc89ccf21dfe3cc6b452089bfcKnut Anders Hatlen // Bug #15956: Don't use String.intern(), since that may
f4355bfc8c6c2cfc89ccf21dfe3cc6b452089bfcKnut Anders Hatlen // exhaust the permgen space. Instead, use our own
f4355bfc8c6c2cfc89ccf21dfe3cc6b452089bfcKnut Anders Hatlen // interner that allocates space on the heap.
f4355bfc8c6c2cfc89ccf21dfe3cc6b452089bfcKnut Anders Hatlen path = stringInterner.intern(path);
f4355bfc8c6c2cfc89ccf21dfe3cc6b452089bfcKnut Anders Hatlen entry.addFile(path);
20a0bde399487a651cdeb66fc8b44b2212036355Trond Norbye } else {
6336b638e9afd018de5f6c516eac4775d140fdaeJHKST LOGGER.log(Level.FINER, "Skipping file outside repository: " + s);
20a0bde399487a651cdeb66fc8b44b2212036355Trond Norbye }
20a0bde399487a651cdeb66fc8b44b2212036355Trond Norbye } else if ("msg".equals(qname)) {
20a0bde399487a651cdeb66fc8b44b2212036355Trond Norbye entry.setMessage(s);
04eceded116c11ab5ed16e40196adbe969b94aabJorgen Austvik } if ("logentry".equals(qname)) {
20a0bde399487a651cdeb66fc8b44b2212036355Trond Norbye entries.add(entry);
20a0bde399487a651cdeb66fc8b44b2212036355Trond Norbye }
20a0bde399487a651cdeb66fc8b44b2212036355Trond Norbye sb.setLength(0);
20a0bde399487a651cdeb66fc8b44b2212036355Trond Norbye }
20a0bde399487a651cdeb66fc8b44b2212036355Trond Norbye
20a0bde399487a651cdeb66fc8b44b2212036355Trond Norbye @Override
8ea4b8d9796de43443cdf7b66e3f185aedf7b570Jens Elkner public void characters(char[] arg0, int arg1, int arg2) {
20a0bde399487a651cdeb66fc8b44b2212036355Trond Norbye sb.append(arg0, arg1, arg2);
20a0bde399487a651cdeb66fc8b44b2212036355Trond Norbye }
20a0bde399487a651cdeb66fc8b44b2212036355Trond Norbye }
20a0bde399487a651cdeb66fc8b44b2212036355Trond Norbye
94c7362941c6089e9a113f4b55fa3b9f7e058f34Knut Anders Hatlen /**
68a986af15f28e9fd0bdcac5af761097740b21aaJorgen Austvik * Initialize the SAX parser instance.
94c7362941c6089e9a113f4b55fa3b9f7e058f34Knut Anders Hatlen */
eb80c2082e2c8f61acc964d2b09e53e1efec2f0dTrond Norbye private void initSaxParser() throws HistoryException {
20a0bde399487a651cdeb66fc8b44b2212036355Trond Norbye SAXParserFactory factory = SAXParserFactory.newInstance();
68a986af15f28e9fd0bdcac5af761097740b21aaJorgen Austvik saxParser = null;
20a0bde399487a651cdeb66fc8b44b2212036355Trond Norbye try {
20a0bde399487a651cdeb66fc8b44b2212036355Trond Norbye saxParser = factory.newSAXParser();
eb80c2082e2c8f61acc964d2b09e53e1efec2f0dTrond Norbye } catch (Exception ex) {
eb80c2082e2c8f61acc964d2b09e53e1efec2f0dTrond Norbye throw new HistoryException("Failed to create SAX parser", ex);
20a0bde399487a651cdeb66fc8b44b2212036355Trond Norbye }
68a986af15f28e9fd0bdcac5af761097740b21aaJorgen Austvik }
68a986af15f28e9fd0bdcac5af761097740b21aaJorgen Austvik
68a986af15f28e9fd0bdcac5af761097740b21aaJorgen Austvik /**
68a986af15f28e9fd0bdcac5af761097740b21aaJorgen Austvik * Parse the history for the specified file.
68a986af15f28e9fd0bdcac5af761097740b21aaJorgen Austvik *
68a986af15f28e9fd0bdcac5af761097740b21aaJorgen Austvik * @param file the file to parse history for
68a986af15f28e9fd0bdcac5af761097740b21aaJorgen Austvik * @param repos Pointer to the SubversionReporitory
d5892f6c63fd9586a79b0d75228e5cbcf1dfd8c4Knut Anders Hatlen * @param sinceRevision the revision number immediately preceding the first
d5892f6c63fd9586a79b0d75228e5cbcf1dfd8c4Knut Anders Hatlen * revision we want, or {@code null} to fetch the entire history
68a986af15f28e9fd0bdcac5af761097740b21aaJorgen Austvik * @return object representing the file's history
68a986af15f28e9fd0bdcac5af761097740b21aaJorgen Austvik */
d5892f6c63fd9586a79b0d75228e5cbcf1dfd8c4Knut Anders Hatlen History parse(File file, SubversionRepository repos, String sinceRevision)
d5892f6c63fd9586a79b0d75228e5cbcf1dfd8c4Knut Anders Hatlen throws HistoryException {
68a986af15f28e9fd0bdcac5af761097740b21aaJorgen Austvik initSaxParser();
ff5eba819da0cf7964d884630fb13262ef12c505Trond Norbye handler = new Handler(repos.getDirectoryName(), repos.reposPath,
3bd91b9bbb9915421b772c357165fbc6fdeaf286Trond Norbye RuntimeEnvironment.getInstance().getSourceRootPath().length(),
3bd91b9bbb9915421b772c357165fbc6fdeaf286Trond Norbye repos.getDateFormat());
ff5eba819da0cf7964d884630fb13262ef12c505Trond Norbye
d5892f6c63fd9586a79b0d75228e5cbcf1dfd8c4Knut Anders Hatlen Executor executor = repos.getHistoryLogExecutor(file, sinceRevision);
68a986af15f28e9fd0bdcac5af761097740b21aaJorgen Austvik int status = executor.exec(true, this);
308470174aea8cf0128aa8baf7018c9500f6131cTrond Norbye
68a986af15f28e9fd0bdcac5af761097740b21aaJorgen Austvik if (status != 0) {
9d25eb783c9ed2d047072934b15f178ab1c4d2acTrond Norbye throw new HistoryException("Failed to get history for: \"" +
68a986af15f28e9fd0bdcac5af761097740b21aaJorgen Austvik file.getAbsolutePath() + "\" Exit code: " + status);
68a986af15f28e9fd0bdcac5af761097740b21aaJorgen Austvik }
94c7362941c6089e9a113f4b55fa3b9f7e058f34Knut Anders Hatlen
d5892f6c63fd9586a79b0d75228e5cbcf1dfd8c4Knut Anders Hatlen List<HistoryEntry> entries = handler.entries;
d5892f6c63fd9586a79b0d75228e5cbcf1dfd8c4Knut Anders Hatlen
d5892f6c63fd9586a79b0d75228e5cbcf1dfd8c4Knut Anders Hatlen // If we only fetch parts of the history, we're not interested in
d5892f6c63fd9586a79b0d75228e5cbcf1dfd8c4Knut Anders Hatlen // sinceRevision. Remove it.
d5892f6c63fd9586a79b0d75228e5cbcf1dfd8c4Knut Anders Hatlen if (sinceRevision != null) {
e96415d1881924946efa1caef7e8bdaec79cb92eKnut Anders Hatlen repos.removeAndVerifyOldestChangeset(entries, sinceRevision);
d5892f6c63fd9586a79b0d75228e5cbcf1dfd8c4Knut Anders Hatlen }
d5892f6c63fd9586a79b0d75228e5cbcf1dfd8c4Knut Anders Hatlen
d5892f6c63fd9586a79b0d75228e5cbcf1dfd8c4Knut Anders Hatlen return new History(entries);
68a986af15f28e9fd0bdcac5af761097740b21aaJorgen Austvik }
d5892f6c63fd9586a79b0d75228e5cbcf1dfd8c4Knut Anders Hatlen
68a986af15f28e9fd0bdcac5af761097740b21aaJorgen Austvik /**
68a986af15f28e9fd0bdcac5af761097740b21aaJorgen Austvik * Process the output from the log command and insert the HistoryEntries
68a986af15f28e9fd0bdcac5af761097740b21aaJorgen Austvik * into the history field.
68a986af15f28e9fd0bdcac5af761097740b21aaJorgen Austvik *
68a986af15f28e9fd0bdcac5af761097740b21aaJorgen Austvik * @param input The output from the process
68a986af15f28e9fd0bdcac5af761097740b21aaJorgen Austvik */
2aef10a5d2ebeaeb6b9cb6fed41933d19a76292fTrond Norbye @Override
8ea4b8d9796de43443cdf7b66e3f185aedf7b570Jens Elkner public void processStream(InputStream input) {
68a986af15f28e9fd0bdcac5af761097740b21aaJorgen Austvik try {
68a986af15f28e9fd0bdcac5af761097740b21aaJorgen Austvik initSaxParser();
68a986af15f28e9fd0bdcac5af761097740b21aaJorgen Austvik saxParser.parse(new BufferedInputStream(input), handler);
68a986af15f28e9fd0bdcac5af761097740b21aaJorgen Austvik } catch (Exception e) {
6336b638e9afd018de5f6c516eac4775d140fdaeJHKST LOGGER.log(Level.SEVERE, "An error occurred while parsing the xml output", e);
308470174aea8cf0128aa8baf7018c9500f6131cTrond Norbye }
68a986af15f28e9fd0bdcac5af761097740b21aaJorgen Austvik }
308470174aea8cf0128aa8baf7018c9500f6131cTrond Norbye
68a986af15f28e9fd0bdcac5af761097740b21aaJorgen Austvik /**
68a986af15f28e9fd0bdcac5af761097740b21aaJorgen Austvik * Parse the given string.
ff5eba819da0cf7964d884630fb13262ef12c505Trond Norbye *
68a986af15f28e9fd0bdcac5af761097740b21aaJorgen Austvik * @param buffer The string to be parsed
68a986af15f28e9fd0bdcac5af761097740b21aaJorgen Austvik * @return The parsed history
68a986af15f28e9fd0bdcac5af761097740b21aaJorgen Austvik * @throws IOException if we fail to parse the buffer
68a986af15f28e9fd0bdcac5af761097740b21aaJorgen Austvik */
20c666f595e231740b3d6e0cee9348eec5befdd9Knut Anders Hatlen History parse(String buffer) throws IOException {
ded3903ab1e55f8a983d4e7741ef55504c487d69Kryštof Tulinger handler = new Handler("/", "", 0, new SubversionRepository().getDateFormat());
68a986af15f28e9fd0bdcac5af761097740b21aaJorgen Austvik processStream(new ByteArrayInputStream(buffer.getBytes("UTF-8")));
d5892f6c63fd9586a79b0d75228e5cbcf1dfd8c4Knut Anders Hatlen return new History(handler.entries);
cf8959cf6e52c61f0e7a0c58bb3ff286fb4167e0Knut Anders Hatlen }
ebb9f739bca3bc9382340b628554b484e4837d6aKnut Anders Hatlen}