/** * 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; }