/*******************************************************************************
* Copyright (c) 2004, 2005
* Thomas Hallgren, Kenneth Olwing, Mitch Sonies
* Pontus Rydin, Nils Unden, Peer Torngren
* The code, documentation and other materials contained herein have been
* licensed under the Eclipse Public License - v 1.0 by the individual
* copyright holders listed above, as Initial Contributors under such license.
* The text of such license is available at www.eclipse.org.
*******************************************************************************/
package org.eclipse.buckminster.cvspkg.internal;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.osgi.util.NLS;
import org.eclipse.team.internal.ccvs.core.CVSMessages;
import org.eclipse.team.internal.ccvs.core.CVSStatus;
import org.eclipse.team.internal.ccvs.core.ICVSFolder;
import org.eclipse.team.internal.ccvs.core.ICVSRepositoryLocation;
import org.eclipse.team.internal.ccvs.core.client.CommandOutputListener;
/**
* An RLogListener that collects global meta-data about the repository. The
* following data is collected
* <ul>
* <li>The timestamp of the last modified revision</li>
* <li>All tag names, i.e. tags that are not associated with a branch revision</li>
* <li>All branch names, i.e. tags that are associated with a branch revision</li>
* </ul>
*/
@SuppressWarnings("restriction")
public class MetaDataCollector extends CommandOutputListener {
private static final int BEGIN = 0;
private static final int HEADER = 1;
private static final int REVISION = 3;
private static final int NEXT_REV_OR_BEGIN = 4;
private static final String LOG_TIMESTAMP_FORMAT = "yyyy-MM-dd HH:mm:ss zzz";//$NON-NLS-1$
// A new format for log dates was introduced in 1.12.9
//
private static final String LOG_TIMESTAMP_FORMAT_OLD = "yyyy/MM/dd HH:mm:ss zzz";//$NON-NLS-1$
private static final Locale LOG_TIMESTAMP_LOCALE = Locale.US;
// Server message prefix used for error detection
//
private static final String NOTHING_KNOWN_ABOUT = "nothing known about "; //$NON-NLS-1$
private static final Pattern revDataExpr = Pattern.compile(
"^date:\\s*([^;]+);\\s*author:[^;]+;\\s*state:\\s*([^;]+);.*$", Pattern.CASE_INSENSITIVE); //$NON-NLS-1$
private static final int SYMBOLIC_NAMES = 2;
/**
* Converts a time stamp as sent from a cvs server for a "log" command into
* a <code>Date</code>.
*/
private static Date convertFromLogTime(String modTime) {
if (modTime == null)
return null;
String timestampFormat = LOG_TIMESTAMP_FORMAT;
// Compatibility for older cvs version (pre 1.12.9)
if (modTime.length() > 4 && modTime.charAt(4) == '/')
timestampFormat = LOG_TIMESTAMP_FORMAT_OLD;
SimpleDateFormat format = new SimpleDateFormat(timestampFormat, LOG_TIMESTAMP_LOCALE);
try {
return format.parse(modTime);
} catch (ParseException e) {
// fallback is to return null
return null;
}
}
/**
* branch tags have odd number of segments or have an even number with a
* zero as the second last segment e.g: 1.1.1, 1.26.0.2 are branch revision
* numbers
*/
private static boolean isBranchTag(String tagName) {
// First check if we have an odd number of segments (i.e. even number of
// dots)
//
int numberOfDots = 0;
int lastDot = 0;
int top = tagName.length();
for (int i = 0; i < top; i++) {
if (tagName.charAt(i) == '.') {
numberOfDots++;
lastDot = i;
}
}
if ((numberOfDots % 2) == 0)
return true;
if (numberOfDots == 1)
return false;
// If not, check if the second lat segment is a zero
//
return tagName.charAt(lastDot - 1) == '0' && tagName.charAt(lastDot - 2) == '.';
}
private final HashSet<String> branches = new HashSet<String>();
private final HashSet<String> tags = new HashSet<String>();
private Date lastModificationTime;
private int state = BEGIN;
@Override
public IStatus errorLine(String line, ICVSRepositoryLocation location, ICVSFolder commandRoot, IProgressMonitor monitor) {
String serverMessage = getServerMessage(line, location);
if (serverMessage != null && serverMessage.startsWith(NOTHING_KNOWN_ABOUT))
return new CVSStatus(IStatus.ERROR, CVSStatus.DOES_NOT_EXIST, NLS.bind(CVSMessages.CVSStatus_messageWithRoot, new String[] {
commandRoot.getName(), line }), (Throwable) null);
return OK;
}
/**
* Returns the names of all branches found in this repository
*
* @return All known branch names.
*/
public Set<String> getBranchNames() {
return branches;
}
public Date getLastModificationTime() {
return lastModificationTime;
}
/**
* Returns the names of all tags found in this repository
*
* @return All known tag names.
*/
public Set<String> getTagNames() {
return tags;
}
@Override
public IStatus messageLine(String line, ICVSRepositoryLocation location, ICVSFolder commandRoot, IProgressMonitor monitor) {
switch (state) {
case BEGIN:
if (line.startsWith("RCS file:")) //$NON-NLS-1$
state = HEADER;
break;
case NEXT_REV_OR_BEGIN:
case HEADER:
if (line.startsWith("RCS file:")) //$NON-NLS-1$
state = HEADER;
else if (line.startsWith("revision ")) //$NON-NLS-1$
state = REVISION;
else if (line.startsWith("symbolic names:")) //$NON-NLS-1$
state = SYMBOLIC_NAMES;
break;
case SYMBOLIC_NAMES:
if (line.startsWith("keyword substitution:")) //$NON-NLS-1$
state = HEADER;
else
this.symbolicName(line);
break;
case REVISION:
this.revision(line, location);
break;
}
return OK;
}
private void revision(String line, ICVSRepositoryLocation location) {
Matcher matcher = revDataExpr.matcher(line);
if (matcher.matches()) {
Date date = convertFromLogTime(matcher.group(1) + " GMT"); //$NON-NLS-1$
if (lastModificationTime == null || lastModificationTime.compareTo(date) < 0)
lastModificationTime = date;
}
state = NEXT_REV_OR_BEGIN;
}
private void symbolicName(String line) {
int firstColon = line.indexOf(':');
String tag = line.substring(1, firstColon);
String rev = line.substring(firstColon + 2);
if (isBranchTag(rev))
branches.add(tag);
else
tags.add(tag);
}
}