/** * Recovers the change transactions for the co-change graph from the revision information, i.e., * it assigns the transaction ids for the revisions. * * @param revisionList is a list of revisions. * @return Sorted map that maps timestamps to collections of transactions, where transactions are * sets of revisions. */ private SortedMap<Long, Collection<SortedSet<Revision>>> recoverTransactions( List<Revision> revisionList) { // Step 1: Transform the list of revisions to a sorted data structure. // A map user -> msg-entry. Map<String, Map<String, SortedMap<Long, SortedSet<String>>>> userMap = new HashMap<String, Map<String, SortedMap<Long, SortedSet<String>>>>(); // A map logmsg -> time-entry. Map<String, SortedMap<Long, SortedSet<String>>> msgMap; // A map time -> file. SortedMap<Long, SortedSet<String>> timeMap; // A set of files. SortedSet<String> fileSet; for (Revision revision : revisionList) { // A map logmsg -> time-entry. msgMap = userMap.get(revision.user); if (msgMap == null) { msgMap = new HashMap<String, SortedMap<Long, SortedSet<String>>>(); userMap.put(revision.user, msgMap); } // A map time -> file. timeMap = msgMap.get(revision.logmsg); if (timeMap == null) { timeMap = new TreeMap<Long, SortedSet<String>>(); msgMap.put(revision.logmsg, timeMap); } // A set of files. fileSet = timeMap.get(revision.time); if (fileSet == null) { fileSet = new TreeSet<String>(); timeMap.put(revision.time, fileSet); } // Add file to set. fileSet.add(revision.filename); } // Step 2: Create the result, which is // a map timestamp -> set of transactions (Long -> SortedSet), // where one transaction is a set of revisions. SortedMap<Long, Collection<SortedSet<Revision>>> result = new TreeMap<Long, Collection<SortedSet<Revision>>>(); int transaction = 0; Set<String> userSet = userMap.keySet(); for (String user : userSet) { msgMap = userMap.get(user); Set<String> msgSet = msgMap.keySet(); for (String logmsg : msgSet) { timeMap = msgMap.get(logmsg); Set<Long> timeSet = timeMap.keySet(); long firstTime = 0; Set<String> tmpFilesSeen = new TreeSet<String>(); // Detect a time window that is too long. Collection<SortedSet<Revision>> transColl; // Collection of transactions. SortedSet<Revision> revSet = null; // Transaction, i.e., set of revisions. for (Long time : timeSet) { if (time.longValue() - firstTime > timeWindow) { // Start new transaction. transaction++; firstTime = time.longValue(); tmpFilesSeen.clear(); // Retrieve (or create new) set of transactions for the timestamp. transColl = result.get(time); if (transColl == null) { transColl = new ArrayList<SortedSet<Revision>>(); result.put(time, transColl); } // New transaction (set of revisions). revSet = new TreeSet<Revision>(); transColl.add(revSet); } else if (sliding) { // The time window 'slides' with the files. firstTime = time.longValue(); } fileSet = timeMap.get(time); for (String filename : fileSet) { // Detect a time window that is too long. if (verbosity.isAtLeast(Verbosity.WARNING) && tmpFilesSeen.contains(filename)) { System.err.println( "Transaction-recovery warning: " + "Time window might be to wide "); System.err.println("(currently '" + timeWindow + "' milli-seconds). "); System.err.println( "File '" + filename + "' already contained in current transaction."); } tmpFilesSeen.add(filename); // Create revision and add revision to resulting list. Revision revision = new Revision(); revision.relName = "CO-CHANGE"; revision.filename = filename; revision.time = time; revision.user = user; revision.logmsg = logmsg; revision.transaction = transaction; revSet.add(revision); } } } } return result; }
/** * Parses the CVS log data and extracts revisions. * * @return List of revisions. */ private List<Revision> readRevisionList() { List<Revision> result = new ArrayList<Revision>(); String line = ""; // String relName = "CO-CHANGE"; String filename = null; Long time; String user; String logmsg; int lineno = 1; try { while ((line = reader.readLine()) != null) { // New working file. if (line.startsWith("Working file: ")) { // Set name of the current working file, // for which we pasre the revisions. filename = line.substring(14); } // New revision. if (line.startsWith("date: ")) { // Set date, author, and logmsg of the current revision. // Parse date. Start right after "date: ". time = parseDate(line.substring(6, line.indexOf("author: "))); if (time == null) { System.err.print("Error while reading the CVS date info for file: "); System.err.println(filename + "."); } // Parse author. Start right after "author: ". int posBegin = line.indexOf("author: ") + 8; int posEnd = line.indexOf(';', posBegin); user = line.substring(posBegin, posEnd); // Parse logmsg. Start on next line the date/author line. logmsg = ""; ++lineno; while (((line = reader.readLine()) != null) && !line.startsWith("----") && !line.startsWith("====")) { if (!line.startsWith("branches: ")) { logmsg += line + ReaderDataGraph.endl; } ++lineno; } // Create revision and add revision to resulting list. Revision revision = new Revision(); // revision.relName = relName.replace(' ', '_'); // Replace blanks by underline. revision.filename = filename.replace(' ', '_'); // Replace blanks by underline. revision.time = time; revision.user = user; revision.logmsg = logmsg; result.add(revision); // System.out.print("Relation: "+ relName + // " File: " + filename + // " Time: " + time.toString() + // " User: "******" LogMsg: " + logmsg); } ++lineno; } // while } catch (Exception e) { System.err.println("Exception while reading the CVS log at line " + lineno + ":"); System.err.println(e); System.err.print("Read line: "); System.err.println(line); } return result; }