/* Copyright 2004-2014 Jim Voris * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.qumasoft.guitools.qwin; import com.qumasoft.guitools.qwin.dialog.ProgressDialog; import com.qumasoft.guitools.qwin.filefilter.FileFilterInterface; import com.qumasoft.guitools.qwin.operation.OperationBaseClass; import com.qumasoft.guitools.qwin.revisionfilter.FilteredRevisionInfo; import com.qumasoft.guitools.qwin.revisionfilter.RevisionFilterFactory; import com.qumasoft.guitools.qwin.revisionfilter.RevisionFilterInterface; import com.qumasoft.qvcslib.AccessList; import com.qumasoft.qvcslib.ArchiveInfoInterface; import com.qumasoft.qvcslib.LogfileInfo; import com.qumasoft.qvcslib.MergedInfoInterface; import com.qumasoft.qvcslib.QVCSConstants; import com.qumasoft.qvcslib.RevisionHeader; import com.qumasoft.qvcslib.Utility; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.text.DecimalFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.TreeMap; import java.util.logging.Level; import javax.swing.JOptionPane; import javax.swing.SwingUtilities; /** * Report generator. Create .html reports. This is a singleton. * @author Jim Voris */ public final class ReportGenerator { private static final ReportGenerator REPORT_GENERATOR = new ReportGenerator(); private FilteredRevisionInfo previousFilteredRevisionInfo; private List<RevisionFilterInterface> revisionFilterArrayList; private int revisionCount = 0; private final String fileSeparator; private final String startParagraph; private final String endParagraph; private String reportFileName; private int fileCount = 0; private int totalRevisionCount = 0; private final String reportHeader; private final DecimalFormat longFormatter; /** * Creates a new instance of ReportGenerator. */ private ReportGenerator() { this.fileSeparator = "\n<hr>\n<p>"; this.startParagraph = "<p><b>"; this.endParagraph = "</b></p>"; this.longFormatter = new DecimalFormat("000000000000"); this.reportHeader = "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"><head><title>QVCS Enterprise Report</title></head><body bgcolor=\"#FFFFFF\">"; } /** * Get the report generator singleton. * @return the report generator singleton. */ public static ReportGenerator getReportGenerator() { return REPORT_GENERATOR; } synchronized void generateReport() { previousFilteredRevisionInfo = null; revisionCount = 0; final ProgressDialog progressDialog = OperationBaseClass.createProgressDialog("Generate Report", 10); progressDialog.setAutoClose(false); Runnable worker = new Runnable() { @Override public void run() { Collection revisions = collectRevisions(progressDialog); if (revisions != null) { reportRevisions(progressDialog, revisions); progressDialog.close(); // If the user has defined a viewer for .html files, then view // the report using that utility. java.io.File reportFilename = new java.io.File(reportFileName); String canonicalReportFileName; try { canonicalReportFileName = reportFilename.getCanonicalPath(); } catch (IOException e) { // Could not get the canonical name for the give report // file. I'm not sure how this can happen, but report // the problem to the user. final String errorMessage = "There was a problem in figuring out the report filename: " + e.getMessage(); // Run the update on the Swing thread. Runnable update = new Runnable() { @Override public void run() { JOptionPane.showMessageDialog(QWinFrame.getQWinFrame(), errorMessage, "Report Generation Error", JOptionPane.WARNING_MESSAGE); } }; SwingUtilities.invokeLater(update); return; } Utility.openURL(canonicalReportFileName); } } }; // Put all this on a separate worker thread. new Thread(worker).start(); } private Collection collectRevisions(final ProgressDialog progressDialog) { FilteredFileTableModel filteredFileTableModel = (FilteredFileTableModel) QWinFrame.getQWinFrame().getFileTable().getModel(); ArrayList sortedMergedInfoCollection = filteredFileTableModel.getSortedCollection(); TreeMap<Comparable, FilteredRevisionInfo> sortedCollection = new TreeMap<>(); // Build the collection of filters based on the set of file filter collection. buildRevisionFilterCollection(filteredFileTableModel); // Create the revision collection. Iterator mergedInfoIterator = sortedMergedInfoCollection.iterator(); OperationBaseClass.initProgressDialog("Collecting revision information: ", 0, sortedMergedInfoCollection.size(), progressDialog); // Zero out the counters we use, so we can report totals in the report header. fileCount = 0; totalRevisionCount = 0; int k = 0; while (mergedInfoIterator.hasNext() && !progressDialog.getIsCancelled()) { MergedInfoInterface mergedInfo = (MergedInfoInterface) mergedInfoIterator.next(); ArchiveInfoInterface archiveInfo = mergedInfo.getArchiveInfo(); OperationBaseClass.updateProgressDialog(k++, "Working with: " + mergedInfo.getShortWorkfileName(), progressDialog); if (archiveInfo != null) { fileCount++; LogfileInfo logfileInfo = archiveInfo.getLogfileInfo(); int revCount = logfileInfo.getLogFileHeaderInfo().getRevisionCount(); for (int i = 0; i < revCount; i++) { RevisionHeader revisionHeader = logfileInfo.getRevisionInformation().getRevisionHeader(i); FilteredRevisionInfo filteredRevisionInfo = new FilteredRevisionInfo(mergedInfo, revisionHeader, i); if (passesRevisionFilters(filteredRevisionInfo)) { sortedCollection.put(getSortKey(filteredRevisionInfo), filteredRevisionInfo); totalRevisionCount++; } } } } if (progressDialog.getIsCancelled()) { return null; } else { return sortedCollection.values(); } } private void reportRevisions(final ProgressDialog progressDialog, Collection revisions) { OutputStream outputStream = null; try { outputStream = createOutputStream(); writeReportHeader(outputStream); Iterator it = revisions.iterator(); int size = revisions.size(); int k = 0; OperationBaseClass.initProgressDialog("Generating report: ", 0, size, progressDialog); while (it.hasNext()) { FilteredRevisionInfo filteredRevisionInfo = (FilteredRevisionInfo) it.next(); MergedInfoInterface mergedInfo = filteredRevisionInfo.getMergedInfo(); OperationBaseClass.updateProgressDialog(k++, "Working with: " + mergedInfo.getShortWorkfileName(), progressDialog); writeRevision(outputStream, filteredRevisionInfo); } } catch (java.io.IOException e) { QWinUtility.logProblem(Level.WARNING, Utility.expandStackTraceToString(e)); } finally { if (outputStream != null) { try { outputStream.close(); } catch (java.io.IOException e) { QWinUtility.logProblem(Level.WARNING, Utility.expandStackTraceToString(e)); } } } } private OutputStream createOutputStream() throws java.io.IOException { FileOutputStream fileStream; // The output stream has a file name based on the current date/time. Calendar now = Calendar.getInstance(); int month = 1 + now.get(Calendar.MONTH); String dateTime = now.get(Calendar.YEAR) + "_" + month + "_" + now.get(Calendar.DAY_OF_MONTH) + "_" + now.get(Calendar.HOUR_OF_DAY) + "_" + now.get(Calendar.MINUTE) + "_" + now.get(Calendar.SECOND); reportFileName = System.getProperty("user.dir") + File.separator + QVCSConstants.QVCS_REPORTS_DIRECTORY + File.separator + QVCSConstants.QVCS_REPORT_NAME_PREFIX + dateTime + ".html"; File reportFile = new File(reportFileName); // Make sure the needed directories exists if (!reportFile.getParentFile().exists()) { reportFile.getParentFile().mkdirs(); } fileStream = new FileOutputStream(reportFile); return fileStream; } private void writeReportHeader(OutputStream outputStream) throws java.io.IOException { outputStream.write(reportHeader.getBytes()); FilteredFileTableModel filteredFileTableModel = (FilteredFileTableModel) QWinFrame.getQWinFrame().getFileTable().getModel(); Date now = new Date(); // Note the project name, and the sort order. StringBuilder projectDescription = new StringBuilder("<p><h2>"); projectDescription.append("Project: <i>").append(QWinFrame.getQWinFrame().getProjectName()).append("</i></h2>\n"); projectDescription.append("Report generated: <i>").append(now.toString()).append("</i><br>\n"); projectDescription.append("Appended Path: <i>").append(QWinFrame.getQWinFrame().getAppendedPath()).append("</i><br>\n"); String recurseFlag; if (QWinFrame.getQWinFrame().getRecurseFlag()) { recurseFlag = "Yes"; } else { recurseFlag = "No"; } projectDescription.append("Recurse Flag: <i>").append(recurseFlag).append("</i><br>\n"); String ascendingFlag; if (filteredFileTableModel.getAscendingFlag()) { ascendingFlag = " (Ascending)"; } else { ascendingFlag = " (Decending)"; } projectDescription.append("Sort Order: <i>").append(filteredFileTableModel.getSortColumn()).append(ascendingFlag).append("</i><br>\n"); projectDescription.append("File Count: <i>").append(Integer.toString(fileCount)).append("</i><br>\n"); projectDescription.append("Revision Count: <i>").append(Integer.toString(totalRevisionCount)).append("</i><br></p>\n"); outputStream.write(projectDescription.toString().getBytes()); // Include a description of the filters that are active. FilterCollection fileFilterCollection = filteredFileTableModel.getFilterCollection(); StringBuilder filterDescriptions = new StringBuilder("<p><b>Active Filter Collection: <i>" + fileFilterCollection.getCollectionName() + "</i></b><br>\n"); FileFilterInterface[] fileFilters = fileFilterCollection.listFilters(); for (FileFilterInterface fileFilter : fileFilters) { filterDescriptions.append("\tType: <i>").append(fileFilter.getFilterType()).append("</i> Criteria: <i>").append(fileFilter.getFilterData()).append("</i><br>\n"); } filterDescriptions.append("</p>\n<hr>\n"); outputStream.write(filterDescriptions.toString().getBytes()); } private void writeRevision(OutputStream outputStream, FilteredRevisionInfo filteredRevisionInfo) throws java.io.IOException { MergedInfoInterface previousMergedInfo = null; MergedInfoInterface mergedInfo = filteredRevisionInfo.getMergedInfo(); if (previousFilteredRevisionInfo != null) { previousMergedInfo = previousFilteredRevisionInfo.getMergedInfo(); } if (previousMergedInfo != null) { if (previousMergedInfo != mergedInfo) { writeFileSeparator(outputStream); outputStream.write(startParagraph.getBytes()); String longWorkfileName = mergedInfo.getArchiveDirManager().getAppendedPath() + File.separator + mergedInfo.getArchiveInfo().getShortWorkfileName(); outputStream.write(longWorkfileName.getBytes()); outputStream.write(endParagraph.getBytes()); } } else { outputStream.write(startParagraph.getBytes()); String longWorkfileName = mergedInfo.getArchiveDirManager().getAppendedPath() + File.separator + mergedInfo.getArchiveInfo().getShortWorkfileName(); outputStream.write(longWorkfileName.getBytes()); outputStream.write(endParagraph.getBytes()); } StringBuilder revisionInfo = new StringBuilder(); revisionInfo.append("\n<p><i><u>Revision: ").append(filteredRevisionInfo.getRevisionHeader().getRevisionString()).append("</u></i><br>"); revisionInfo.append("Checkin time: ").append(filteredRevisionInfo.getRevisionHeader().getCheckInDate().toString()).append("<br>"); revisionInfo.append("Workfile edit date: ").append(filteredRevisionInfo.getRevisionHeader().getEditDate().toString()).append("<br>"); AccessList accessList = new AccessList(filteredRevisionInfo.getMergedInfo().getLogfileInfo().getLogFileHeaderInfo().getModifierList()); String revisionCreator = accessList.indexToUser(filteredRevisionInfo.getRevisionHeader().getCreatorIndex()); revisionInfo.append("Created by: ").append(revisionCreator).append("<br>"); revisionInfo.append(filteredRevisionInfo.getRevisionHeader().getRevisionDescription()).append("</p>\n"); outputStream.write(revisionInfo.toString().getBytes()); previousFilteredRevisionInfo = filteredRevisionInfo; } private void buildRevisionFilterCollection(FilteredFileTableModel filteredFileTableModel) { // Get the file filter collection FilterCollection fileFilterCollection = filteredFileTableModel.getFilterCollection(); FileFilterInterface[] fileFilters = fileFilterCollection.listFilters(); revisionFilterArrayList = new ArrayList<>(); for (FileFilterInterface fileFilter : fileFilters) { RevisionFilterInterface revisionFilter = RevisionFilterFactory.buildFilter(fileFilter.getFilterType(), fileFilter.getRawFilterData(), fileFilter.getIsANDFilter()); if (revisionFilter != null) { revisionFilterArrayList.add(revisionFilter); } } } private boolean passesRevisionFilters(FilteredRevisionInfo filteredRevisionInfo) { // Default to passing filter boolean retVal = true; // Process the AND filters first... for (int i = 0; i < revisionFilterArrayList.size(); i++) { RevisionFilterInterface filter = revisionFilterArrayList.get(i); if (filter.getIsANDFilter()) { boolean flag = filter.passesFilter(filteredRevisionInfo); if (!flag) { retVal = false; break; } } } if (retVal) { boolean flag = false; boolean orFilterFound = false; for (int i = 0; i < revisionFilterArrayList.size(); i++) { RevisionFilterInterface filter = revisionFilterArrayList.get(i); if (filter.getIsORFilter()) { orFilterFound = true; if (filter.passesFilter(filteredRevisionInfo)) { flag = true; break; } } } if (orFilterFound) { retVal = flag; } } return retVal; } private Comparable getSortKey(FilteredRevisionInfo filteredRevisionInfo) { FilteredFileTableModel filteredFileTableModel = (FilteredFileTableModel) QWinFrame.getQWinFrame().getFileTable().getModel(); boolean ascendingFlag = filteredFileTableModel.getAscendingFlag(); MergedInfoInterface mergedInfo = filteredRevisionInfo.getMergedInfo(); RevisionHeader revHeader = filteredRevisionInfo.getRevisionHeader(); int column = filteredFileTableModel.getSortColumnInteger(); String appendedPath = mergedInfo.getArchiveDirManager().getAppendedPath(); String sortKey = mergedInfo.getShortWorkfileName() + appendedPath + Integer.toString(revisionCount); switch (column) { case AbstractFileTableModel.LOCKEDBY_COLUMN_INDEX: // Sort by locked by, then status, then filename if (mergedInfo.getLockedByString().length() > 0) { sortKey = "000000" + mergedInfo.getLockedByString() + mergedInfo.getStatusValue() + mergedInfo.getShortWorkfileName() + appendedPath + Integer.toString(revisionCount); } else { sortKey = mergedInfo.getStatusValue() + mergedInfo.getShortWorkfileName() + appendedPath + Integer.toString(revisionCount); } break; case AbstractFileTableModel.FILE_STATUS_COLUMN_INDEX: // Need to sort by filename also, since the locked by value may // be an empty string. sortKey = mergedInfo.getStatusValue() + mergedInfo.getShortWorkfileName() + appendedPath + Integer.toString(revisionCount); break; case AbstractFileTableModel.LASTCHECKIN_COLUMN_INDEX: sortKey = Long.toString(Long.MAX_VALUE - revHeader.getCheckInDate().getTime()) + mergedInfo.getShortWorkfileName() + appendedPath; break; case AbstractFileTableModel.WORKFILEIN_COLUMN_INDEX: // Need to sort by filename also, since the workfile in value may // be an empty string. sortKey = mergedInfo.getWorkfileInLocation() + mergedInfo.getShortWorkfileName() + appendedPath + Integer.toString(revisionCount); break; case AbstractFileTableModel.FILESIZE_COLUMN_INDEX: // Need to sort by filename also, since the workfile size may // be an empty string. String sizeString; if (mergedInfo.getWorkfile() == null) { sizeString = ""; } else { long sortableSize = Long.MAX_VALUE - mergedInfo.getWorkfileSize(); sizeString = longFormatter.format(sortableSize); } sortKey = sizeString + mergedInfo.getShortWorkfileName() + appendedPath + Integer.toString(revisionCount); break; case AbstractFileTableModel.LASTEDITBY_COLUMN_INDEX: sortKey = mergedInfo.getLastEditBy() + mergedInfo.getShortWorkfileName() + appendedPath + Integer.toString(revisionCount); break; case AbstractFileTableModel.APPENDED_PATH_INDEX: sortKey = appendedPath + "/" + mergedInfo.getShortWorkfileName() + Integer.toString(revisionCount); break; default: case AbstractFileTableModel.FILENAME_COLUMN_INDEX: break; } revisionCount++; return new AscendDecendSortKey(sortKey, ascendingFlag); } private void writeFileSeparator(OutputStream outputStream) throws java.io.IOException { outputStream.write(fileSeparator.getBytes()); } }