001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: */
018: package org.apache.tools.ant.taskdefs.cvslib;
019:
020: import java.text.ParseException;
021: import java.text.SimpleDateFormat;
022: import java.util.Date;
023: import java.util.Enumeration;
024: import java.util.Hashtable;
025: import java.util.Locale;
026: import java.util.TimeZone;
027:
028: /**
029: * A class used to parse the output of the CVS log command.
030: *
031: */
032: class ChangeLogParser {
033: //private static final int GET_ENTRY = 0;
034: private static final int GET_FILE = 1;
035: private static final int GET_DATE = 2;
036: private static final int GET_COMMENT = 3;
037: private static final int GET_REVISION = 4;
038: private static final int GET_PREVIOUS_REV = 5;
039:
040: // FIXME formatters are not thread-safe
041:
042: /** input format for dates read in from cvs log */
043: private static final SimpleDateFormat INPUT_DATE = new SimpleDateFormat(
044: "yyyy/MM/dd HH:mm:ss", Locale.US);
045: /**
046: * New formatter used to parse CVS date/timestamp.
047: */
048: private static final SimpleDateFormat CVS1129_INPUT_DATE = new SimpleDateFormat(
049: "yyyy-MM-dd HH:mm:ss Z", Locale.US);
050:
051: static {
052: TimeZone utc = TimeZone.getTimeZone("UTC");
053: INPUT_DATE.setTimeZone(utc);
054: CVS1129_INPUT_DATE.setTimeZone(utc);
055: }
056:
057: //The following is data used while processing stdout of CVS command
058: private String file;
059: private String date;
060: private String author;
061: private String comment;
062: private String revision;
063: private String previousRevision;
064:
065: private int status = GET_FILE;
066:
067: /** rcs entries */
068: private final Hashtable entries = new Hashtable();
069:
070: /**
071: * Get a list of rcs entries as an array.
072: *
073: * @return a list of rcs entries as an array
074: */
075: public CVSEntry[] getEntrySetAsArray() {
076: final CVSEntry[] array = new CVSEntry[entries.size()];
077: int i = 0;
078: for (Enumeration e = entries.elements(); e.hasMoreElements();) {
079: array[i++] = (CVSEntry) e.nextElement();
080: }
081: return array;
082: }
083:
084: /**
085: * Receive notification about the process writing
086: * to standard output.
087: * @param line the line to process
088: */
089: public void stdout(final String line) {
090: switch (status) {
091: case GET_FILE:
092: // make sure attributes are reset when
093: // working on a 'new' file.
094: reset();
095: processFile(line);
096: break;
097: case GET_REVISION:
098: processRevision(line);
099: break;
100:
101: case GET_DATE:
102: processDate(line);
103: break;
104:
105: case GET_COMMENT:
106: processComment(line);
107: break;
108:
109: case GET_PREVIOUS_REV:
110: processGetPreviousRevision(line);
111: break;
112:
113: default:
114: // Do nothing
115: break;
116: }
117: }
118:
119: /**
120: * Process a line while in "GET_COMMENT" state.
121: *
122: * @param line the line
123: */
124: private void processComment(final String line) {
125: final String lineSeparator = System
126: .getProperty("line.separator");
127: if (line
128: .equals("=============================================================================")) {
129: //We have ended changelog for that particular file
130: //so we can save it
131: final int end = comment.length() - lineSeparator.length(); //was -1
132: comment = comment.substring(0, end);
133: saveEntry();
134: status = GET_FILE;
135: } else if (line.equals("----------------------------")) {
136: final int end = comment.length() - lineSeparator.length(); //was -1
137: comment = comment.substring(0, end);
138: status = GET_PREVIOUS_REV;
139: } else {
140: comment += line + lineSeparator;
141: }
142: }
143:
144: /**
145: * Process a line while in "GET_FILE" state.
146: *
147: * @param line the line to process
148: */
149: private void processFile(final String line) {
150: if (line.startsWith("Working file:")) {
151: file = line.substring(14, line.length());
152: status = GET_REVISION;
153: }
154: }
155:
156: /**
157: * Process a line while in "REVISION" state.
158: *
159: * @param line the line to process
160: */
161: private void processRevision(final String line) {
162: if (line.startsWith("revision")) {
163: revision = line.substring(9);
164: status = GET_DATE;
165: } else if (line.startsWith("======")) {
166: //There were no revisions in this changelog
167: //entry so lets move onto next file
168: status = GET_FILE;
169: }
170: }
171:
172: /**
173: * Process a line while in "DATE" state.
174: *
175: * @param line the line to process
176: */
177: private void processDate(final String line) {
178: if (line.startsWith("date:")) {
179: // The date format is using a - format since 1.12.9 so we have:
180: // 1.12.9-: 'date: YYYY/mm/dd HH:mm:ss; author: name;'
181: // 1.12.9+: 'date: YYYY-mm-dd HH:mm:ss Z; author: name'
182: int endOfDateIndex = line.indexOf(';');
183: date = line.substring("date: ".length(), endOfDateIndex);
184:
185: int startOfAuthorIndex = line.indexOf("author: ",
186: endOfDateIndex + 1);
187: int endOfAuthorIndex = line.indexOf(';',
188: startOfAuthorIndex + 1);
189: author = line.substring("author: ".length()
190: + startOfAuthorIndex, endOfAuthorIndex);
191:
192: status = GET_COMMENT;
193:
194: //Reset comment to empty here as we can accumulate multiple lines
195: //in the processComment method
196: comment = "";
197: }
198: }
199:
200: /**
201: * Process a line while in "GET_PREVIOUS_REVISION" state.
202: *
203: * @param line the line to process
204: */
205: private void processGetPreviousRevision(final String line) {
206: if (!line.startsWith("revision ")) {
207: throw new IllegalStateException(
208: "Unexpected line from CVS: " + line);
209: }
210: previousRevision = line.substring("revision ".length());
211:
212: saveEntry();
213:
214: revision = previousRevision;
215: status = GET_DATE;
216: }
217:
218: /**
219: * Utility method that saves the current entry.
220: */
221: private void saveEntry() {
222: final String entryKey = date + author + comment;
223: CVSEntry entry;
224: if (!entries.containsKey(entryKey)) {
225: Date dateObject = parseDate(date);
226: entry = new CVSEntry(dateObject, author, comment);
227: entries.put(entryKey, entry);
228: } else {
229: entry = (CVSEntry) entries.get(entryKey);
230: }
231:
232: entry.addFile(file, revision, previousRevision);
233: }
234:
235: /**
236: * Parse date out from expected format.
237: *
238: * @param date the string holding date
239: * @return the date object or null if unknown date format
240: */
241: private Date parseDate(final String date) {
242: try {
243: return INPUT_DATE.parse(date);
244: } catch (ParseException e) {
245: try {
246: return CVS1129_INPUT_DATE.parse(date);
247: } catch (ParseException e2) {
248: throw new IllegalStateException("Invalid date format: "
249: + date);
250: }
251: }
252: }
253:
254: /**
255: * Reset all internal attributes except status.
256: */
257: public void reset() {
258: this.file = null;
259: this.date = null;
260: this.author = null;
261: this.comment = null;
262: this.revision = null;
263: this.previousRevision = null;
264: }
265: }
|