| /* |
| * Sun Public License Notice |
| * |
| * The contents of this file are subject to the Sun Public License |
| * Version 1.0 (the "License"). You may not use this file except in |
| * compliance with the License. A copy of the License is available at |
| * http://www.sun.com/ |
| * |
| * The Original Code is NetBeans. The Initial Developer of the Original |
| * Code is Sun Microsystems, Inc. Portions Copyright 1997-2000 Sun |
| * Microsystems, Inc. All Rights Reserved. |
| */ |
| package org.netbeans.lib.cvsclient.command.log; |
| |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.util.text.SyncDateFormat; |
| import org.jetbrains.annotations.NonNls; |
| import org.netbeans.lib.cvsclient.JavaCvsSrcBundle; |
| import org.netbeans.lib.cvsclient.command.AbstractMessageParser; |
| import org.netbeans.lib.cvsclient.command.KeywordSubstitution; |
| import org.netbeans.lib.cvsclient.event.IEventSender; |
| import org.netbeans.lib.cvsclient.file.ICvsFileSystem; |
| import org.netbeans.lib.cvsclient.util.BugLog; |
| |
| import java.io.File; |
| import java.text.ParseException; |
| import java.text.SimpleDateFormat; |
| import java.util.*; |
| import java.util.regex.Pattern; |
| |
| /** |
| * @author Thomas Singer |
| */ |
| final public class LogMessageParser extends AbstractMessageParser { |
| |
| // Constants ============================================================== |
| |
| |
| @NonNls private static final String RCS_FILE = "RCS file: "; |
| @NonNls private static final String WORKING_FILE = "Working file: "; |
| @NonNls private static final String HEAD = "head: "; |
| @NonNls private static final String BRANCH = "branch:"; |
| @NonNls private static final String LOCKS = "locks: "; |
| @NonNls private static final String ACCESS_LIST = "access list:"; |
| @NonNls private static final String SYMBOLIC_NAMES = "symbolic names:"; |
| @NonNls private static final String KEYWORD_SUBST = "keyword substitution: "; |
| @NonNls private static final String TOTAL_REVISIONS = "total revisions: "; |
| @NonNls private static final String SELECTED_REVISIONS = ";\tselected revisions: "; |
| @NonNls private static final String DESCRIPTION = "description:"; |
| @NonNls private static final String REVISION = "revision "; |
| @NonNls private static final String DATE = "date: "; |
| @NonNls private static final String BRANCHES = "branches: "; |
| @NonNls private static final String AUTHOR = " author: "; |
| @NonNls private static final String STATE = " state: "; |
| @NonNls private static final String LINES = " lines: "; |
| @NonNls private static final String SPLITTER = "----------------------------"; |
| @NonNls private static final String FINAL_SPLIT = "============="; |
| @NonNls private static final String FINAL_SPLIT_WITH_TAB = "\t============="; |
| |
| private static final SyncDateFormat[] EXPECTED_DATE_FORMATS = new SyncDateFormat[2]; |
| @NonNls private static final String NO_FILE_MESSAGE = "no file"; |
| |
| static { |
| initDateFormats(); |
| } |
| |
| @SuppressWarnings({"HardCodedStringLiteral"}) |
| private static void initDateFormats() { |
| EXPECTED_DATE_FORMATS[0] = new SyncDateFormat(new SimpleDateFormat("yyyy/MM/dd HH:mm:ss", Locale.US)); |
| EXPECTED_DATE_FORMATS[0].setTimeZone(TimeZone.getTimeZone("GMT")); |
| |
| EXPECTED_DATE_FORMATS[1] = new SyncDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US)); |
| EXPECTED_DATE_FORMATS[1].setTimeZone(TimeZone.getTimeZone("GMT")); |
| } |
| |
| // Fields ================================================================= |
| |
| private final IEventSender eventSender; |
| private final ICvsFileSystem cvsFileSystem; |
| private LogInformation logInfo; |
| private Revision revision; |
| private boolean addingSymNames; |
| private boolean addingDescription; |
| private boolean processingRevision; |
| private List<String> logMessageBuffer; |
| private final Pattern myRevisionPattern; |
| |
| // Setup ================================================================== |
| |
| public LogMessageParser(IEventSender eventSender, ICvsFileSystem cvsFileSystem) { |
| BugLog.getInstance().assertNotNull(eventSender); |
| BugLog.getInstance().assertNotNull(cvsFileSystem); |
| |
| this.cvsFileSystem = cvsFileSystem; |
| this.eventSender = eventSender; |
| |
| myRevisionPattern = Pattern.compile("revision \\d+(\\.\\d+){1,3}.*"); |
| } |
| |
| // Implemented ============================================================ |
| |
| protected void outputDone() { |
| if (addingDescription) { |
| addingDescription = false; |
| } |
| if (processingRevision) { |
| revision.setMessage(getMessageFromBuffer()); |
| logInfo.addRevision(revision); |
| revision = null; |
| processingRevision = false; |
| |
| } |
| if (logInfo != null) { |
| eventSender.notifyFileInfoListeners(logInfo); |
| logInfo = null; |
| } |
| logMessageBuffer = null; |
| } |
| |
| private String getMessageFromBuffer() { |
| if (logMessageBuffer.size() > 0 && lastLogMessageIsFinalSeparator(logMessageBuffer.get(logMessageBuffer.size()-1))) { |
| logMessageBuffer.remove(logMessageBuffer.size()-1); |
| } |
| else if (logMessageBuffer.size() > 1 && |
| lastLogMessageIsFinalSeparator(logMessageBuffer.get(logMessageBuffer.size()-2)) && |
| logMessageBuffer.get(logMessageBuffer.size()-1).length() == 0) { |
| logMessageBuffer.remove(logMessageBuffer.size()-2); |
| logMessageBuffer.remove(logMessageBuffer.size()-1); |
| } |
| |
| if (logMessageBuffer.size() > 0) { |
| return StringUtil.join(logMessageBuffer, "\n") + "\n"; |
| } |
| return ""; |
| } |
| |
| private static boolean lastLogMessageIsFinalSeparator(String logMessageString) { |
| return logMessageString.startsWith(FINAL_SPLIT) || logMessageString.startsWith(FINAL_SPLIT_WITH_TAB); |
| } |
| |
| public void parseLine(String line, boolean isErrorMessage) { |
| if (isErrorMessage) return; |
| if (processingRevision) { |
| if (line.startsWith(RCS_FILE)) { |
| processRcsFile(line.substring(RCS_FILE.length())); |
| return; |
| } |
| |
| if (myRevisionPattern.matcher(line).matches()) { |
| processRevisionStart(line); |
| return; |
| } |
| |
| if (line.startsWith(DATE)) { |
| processRevisionDate(line); |
| return; |
| } |
| |
| // first check for the branches tag |
| if (line.startsWith(BRANCHES)) { |
| processBranches(line.substring(BRANCHES.length())); |
| } |
| else { |
| logMessageBuffer.add(line); |
| } |
| return; |
| } |
| if (addingSymNames) { |
| if (line.startsWith("\t")) { |
| processSymbolicNames(line.substring(1)); |
| return; |
| } |
| } |
| // revision stuff first -> will be the most common to parse |
| if (line.startsWith(REVISION)) { |
| processRevisionStart(line); |
| return; |
| } |
| if (line.startsWith(KEYWORD_SUBST)) { |
| final String keywordSubstitution = line.substring(KEYWORD_SUBST.length()).trim(); |
| logInfo.setKeywordSubstitution(KeywordSubstitution.getValue(keywordSubstitution)); |
| addingSymNames = false; |
| return; |
| } |
| |
| if (line.startsWith(RCS_FILE)) { |
| processRcsFile(line.substring(RCS_FILE.length())); |
| return; |
| } |
| if (line.startsWith(WORKING_FILE)) { |
| processWorkingFile(line.substring(WORKING_FILE.length())); |
| return; |
| } |
| if (line.startsWith(HEAD)) { |
| logInfo.setHeadRevision(line.substring(HEAD.length()).trim()); |
| return; |
| } |
| if (line.startsWith(BRANCH)) { |
| logInfo.setBranch(line.substring(BRANCH.length()).trim()); |
| return; |
| } |
| if (line.startsWith(LOCKS)) { |
| logInfo.setLocks(line.substring(LOCKS.length()).trim()); |
| return; |
| } |
| if (line.startsWith(ACCESS_LIST)) { |
| logInfo.setAccessList(line.substring(ACCESS_LIST.length()).trim()); |
| return; |
| } |
| if (line.startsWith(SYMBOLIC_NAMES)) { |
| addingSymNames = true; |
| return; |
| } |
| if (line.startsWith(TOTAL_REVISIONS)) { |
| final String separator = SELECTED_REVISIONS; |
| final int semicolonIndex = line.indexOf(separator); |
| if (semicolonIndex < 0) { |
| // no selected revisions here.. |
| logInfo.setTotalRevisions(line.substring(TOTAL_REVISIONS.length()).trim()); |
| logInfo.setSelectedRevisions("0"); |
| } |
| else { |
| final String totalRevisions = line.substring(0, semicolonIndex); |
| final String selectedRevisions = line.substring(semicolonIndex); |
| logInfo.setTotalRevisions(totalRevisions.substring(TOTAL_REVISIONS.length()).trim()); |
| logInfo.setSelectedRevisions(selectedRevisions.substring(SELECTED_REVISIONS.length()).trim()); |
| } |
| return; |
| } |
| |
| if (addingDescription) { |
| if (!processingRevision && line.startsWith(SPLITTER)) { |
| return; |
| } |
| logMessageBuffer.add(line); |
| return; |
| } |
| |
| if (line.startsWith(DESCRIPTION)) { |
| logMessageBuffer = new ArrayList<String>(); |
| logMessageBuffer.add(line.substring(DESCRIPTION.length())); |
| addingDescription = true; |
| } |
| } |
| |
| // Utils ================================================================== |
| |
| private void processRcsFile(String line) { |
| if (logInfo != null) { |
| outputDone(); |
| } |
| logInfo = new LogInformation(); |
| logInfo.setRcsFileName(line.trim()); |
| } |
| |
| private void processWorkingFile(String line) { |
| String fileName = line.trim(); |
| if (fileName.startsWith(NO_FILE_MESSAGE)) { |
| fileName = fileName.substring(8); |
| } |
| |
| logInfo.setFile(createFile(fileName)); |
| } |
| |
| private void processBranches(String line) { |
| final int ind = line.lastIndexOf(';'); |
| if (ind > 0) { |
| line = line.substring(0, ind); |
| } |
| revision.setBranches(line.trim()); |
| } |
| |
| private void processSymbolicNames(String line) { |
| final int index = line.lastIndexOf(':'); |
| if (index < 0) { |
| return; |
| } |
| |
| final String symName = line.substring(0, index).trim(); |
| final String revName = line.substring(index + 1, line.length()).trim(); |
| logInfo.addSymbolicName(symName, revName); |
| } |
| |
| private void processRevisionStart(String line) { |
| revisionProcessingFinished(); |
| |
| int tabIndex = line.indexOf('\t', REVISION.length()); |
| if (tabIndex < 0) { |
| tabIndex = line.length(); |
| } |
| |
| final String revisionNumber = line.substring(REVISION.length(), tabIndex); |
| revision = new Revision(revisionNumber); |
| processingRevision = true; |
| } |
| |
| private void revisionProcessingFinished() { |
| if (revision != null) { |
| if (logMessageBuffer.size() > 0 && logMessageBuffer.get(logMessageBuffer.size()-1).startsWith(SPLITTER)) { |
| logMessageBuffer.remove(logMessageBuffer.size()-1); |
| } |
| processingRevision = false; |
| revision.setMessage(getMessageFromBuffer()); |
| |
| logInfo.addRevision(revision); |
| } |
| } |
| |
| private void processRevisionDate(String line) { |
| // a line may looks like: |
| // date: 2003/02/20 14:52:06; author: tom; state: Exp; lines: +1 -1; kopt: o; commitid: 3803e54eb96167d; |
| // or: |
| // date: 2003/01/11 17:56:27; author: tom; state: Exp; |
| final StringTokenizer token = new StringTokenizer(line, ";", false); |
| if (token.hasMoreTokens()) { |
| final String date = token.nextToken(); |
| final String dateString = date.substring(DATE.length()); |
| Date parsedDate = null; |
| for (SyncDateFormat expectedDateFormat : EXPECTED_DATE_FORMATS) { |
| try { |
| parsedDate = expectedDateFormat.parse(dateString); |
| } |
| catch (ParseException e) { |
| //ignore |
| } |
| if (parsedDate != null) break; |
| } |
| if (parsedDate != null) { |
| revision.setDate(parsedDate); |
| } |
| else { |
| BugLog.getInstance().showException(new Exception(JavaCvsSrcBundle.message("line.could.not.be.parsed.error.message", line))); |
| } |
| } |
| if (token.hasMoreTokens()) { |
| final String author = token.nextToken(); |
| if (author.startsWith(AUTHOR)) { |
| revision.setAuthor(author.substring(AUTHOR.length())); |
| } |
| } |
| if (token.hasMoreTokens()) { |
| final String state = token.nextToken(); |
| if (state.startsWith(STATE)) { |
| revision.setState(state.substring(STATE.length())); |
| } |
| } |
| if (token.hasMoreTokens()) { |
| final String linesModified = token.nextToken(); |
| if (linesModified.startsWith(LINES)) { |
| revision.setLines(linesModified.substring(LINES.length())); |
| } |
| } |
| |
| processingRevision = true; |
| logMessageBuffer = new ArrayList<String>(); |
| } |
| |
| private File createFile(String fileName) { |
| return cvsFileSystem.getLocalFileSystem().getFile(fileName); |
| } |
| |
| public void binaryMessageSent(final byte[] bytes) { |
| } |
| } |