package hudson.plugins.pvcs_scm;
import hudson.plugins.pvcs_scm.changelog.PvcsChangeLogSet;
import hudson.plugins.pvcs_scm.changelog.PvcsChangeLogEntry;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.IOException;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.ArrayList;
import java.util.StringTokenizer;
import java.text.SimpleDateFormat;
import java.text.ParseException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Generates a changelog set from the output of <code>pcli vlog</code>.
*
* @author Brian Lalor <blalor@bravo5.org>
*/
public class PvcsLogReader implements Runnable
{
private final Log logger = LogFactory.getLog(getClass());
private final String lineSep = System.getProperty("line.separator");
private final SimpleDateFormat outDateFormat = new SimpleDateFormat("MMM dd yyyy HH:mm:ss");
private final SimpleDateFormat outDateFormatSub = new SimpleDateFormat("dd MMM yyyy HH:mm:ss");
/** Reader to read from. */
private BufferedReader reader;
/**
* Root of path where archive files are stored in the repository. This is
* not directly related to the "project root" (the exposed end-point of
* the repository).
*/
private String archiveRoot;
/** . */
private String pathPrefix;
/** Archive file stuffix. */
private String archiveFileSuffix = "_v";
/** Reference time. */
private Date lastBuild;
private boolean firstRev = true;
private boolean firstModifiedTime = true;
private boolean firstUserName = true;
private boolean nextLineIsComment = false;
private boolean waitingForNextValidStart = false;
private PvcsChangeLogSet changeLogSet;
private PvcsChangeLogEntry modification;
// {{{ constructor
public PvcsLogReader(final InputStream is,
final String archiveRoot,
final String pathPrefix,
final Date lastBuild)
{
this.reader = new BufferedReader(new InputStreamReader(is));
this.archiveRoot = archiveRoot;
this.pathPrefix = pathPrefix;
this.lastBuild = lastBuild;
changeLogSet = PvcsChangeLogSet.Factory.newInstance();
}
// }}}
// {{{ getChangeLogSet
public PvcsChangeLogSet getChangeLogSet() {
return changeLogSet;
}
// }}}
// {{{ run
/**
*
*/
public void run() {
try {
String s = reader.readLine();
while (s != null) {
consumeLine(s);
s = reader.readLine();
}
} catch (IOException e) {
// ignored
} finally {
try {
reader.close();
} catch (IOException e) {
// ignored
}
}
}
// }}}
// {{{ consumeLine
/**
*
*/
private void consumeLine(final String line) {
if (logger.isTraceEnabled()) {
logger.trace("line: " + line);
}
if (line.startsWith("Archive:")) {
if ((modification != null) & (! waitingForNextValidStart)) {
// didn't find a valid entry before; delete this most recent
// one
logger.warn("discarding incomplete change log\n" + modification);
changeLogSet.removeEntry(changeLogSet.sizeOfEntryArray() - 1);
}
modification = changeLogSet.addNewEntry();
firstModifiedTime = true;
firstUserName = true;
firstRev = true;
nextLineIsComment = false;
waitingForNextValidStart = false;
int startIndex = line.indexOf(archiveRoot);
if (startIndex != -1) {
// found the branch in the archive path
startIndex += archiveRoot.length();
} else {
startIndex = 0;
}
int endIndex = line.indexOf(archiveFileSuffix);
if (endIndex == -1) {
endIndex = line.length();
}
String fileName = line.substring(startIndex, endIndex);
if (fileName.startsWith("/") || fileName.startsWith("\\")) {
fileName = fileName.substring(1);
}
if (pathPrefix != null) {
fileName = pathPrefix + fileName;
}
modification.setFileName(fileName);
}
else if (waitingForNextValidStart) {
// we're in this state after we've got the last useful line
// from the previous item, but haven't yet started a new one
// -- we should just skip these lines till we start a new one
// return
// } else if (line.startsWith("Workfile:")) {
// modification.createModifiedFile(line.substring(18), null);
}
// else if (line.startsWith("Archive created:")) {
// try {
// String createdDate = line.substring(18);
//
// Date createTime;
// try {
// createTime = outDateFormat.parse(createdDate);
// } catch (ParseException e) {
// createTime = outDateFormatSub.parse(createdDate);
// }
//
// ModifiedFile file = (ModifiedFile) modification.files.get(0);
// if (createTime.after(lastBuild)) {
// file.action = "added";
// } else {
// file.action = "modified";
// }
// } catch (ParseException e) {
// LOGGER.error("Error parsing create date: " + e.getMessage(), e);
// }
// }
else if (line.startsWith("Rev") && !line.startsWith("Rev count")) {
if (firstRev) {
firstRev = false;
modification.setRevision(line.substring(4));
}
}
else if (line.startsWith("Checked in:")) {
/*
* PVCS reports both "Checked in" and "Last modified"; I'm not
* sure when "last modified" is updated, but I think "checked in"
* is the best one to use for our purposes here.
*/
// if this is the newest revision...
if (firstModifiedTime) {
firstModifiedTime = false;
String lastMod = line.substring(16);
Date modDate = null;
try {
modDate = outDateFormat.parse(lastMod);
} catch (ParseException e) {
logger.debug(String.format("Unable to parse modification time %s with %s",
lastMod,
outDateFormat.toPattern()));
try {
modDate = outDateFormatSub.parse(lastMod);
} catch (ParseException pe) {
logger.error("Error parsing modification time " + lastMod + ": ", e);
}
}
Calendar modCal = Calendar.getInstance();
modCal.setTime(modDate);
modification.setModifiedTime(modCal);
}
}
else if (nextLineIsComment) {
// used boolean because don't know what comment will
// startWith....
boolean isDashesLine = line.equals("-----------------------------------");
boolean isEqualsLine = line.equals("===================================");
boolean isEndOfCommentsLine = isDashesLine || isEqualsLine;
if (! (modification.getComment() != null) || (modification.getComment().length() == 0)) {
modification.setComment(line);
} else if (! isEndOfCommentsLine) {
modification.setComment(modification.getComment() + lineSep + line);
} else {
// then set indicator to ignore future lines till next new
// item
waitingForNextValidStart = true;
}
}
else if (line.startsWith("Author id:")) {
// if this is the newest revision...
if (firstUserName) {
StringTokenizer st = new StringTokenizer(line.substring(11), " ");
modification.setUserName(st.nextToken().trim());
firstUserName = false;
nextLineIsComment = true;
}
} // end of Author id
}
// }}}
}