/* 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.ParentChildProgressDialog;
import com.qumasoft.guitools.qwin.operation.OperationBaseClass;
import com.qumasoft.qvcslib.ClientTransactionManager;
import com.qumasoft.qvcslib.DirectoryManagerInterface;
import com.qumasoft.qvcslib.MergedInfo;
import com.qumasoft.qvcslib.MergedInfoInterface;
import com.qumasoft.qvcslib.QVCSRuntimeException;
import com.qumasoft.qvcslib.Utility;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;
import java.util.logging.Level;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;
/**
* File table model.
*
* @author Jim Voris
*/
public class FileTableModel extends AbstractFileTableModel {
private static final long serialVersionUID = 5105921748667131281L;
private final DecimalFormat longFormatter;
private final DecimalFormat sizeFormatter;
private final ImageIcon[] fileIcons;
private final ImageIcon[] lockIcons;
private final ImageIcon[] workfileIcons;
private final JLabel jLabel;
private Map<Comparable, MergedInfoInterface> sortedMap;
private ArrayList<MergedInfoInterface> arrayList;
FileTableModel() {
this.jLabel = new JLabel();
this.workfileIcons = new ImageIcon[]{new ImageIcon(ClassLoader.getSystemResource("images/workfile.png"), "Not controlled file")};
this.lockIcons = new ImageIcon[] {
new ImageIcon(ClassLoader.getSystemResource("images/lock.png"), "Locked file"),
new ImageIcon(ClassLoader.getSystemResource("images/lockgreen.png"), "Locked file"),
new ImageIcon(ClassLoader.getSystemResource("images/lockyellow.png"), "Locked file"),
new ImageIcon(ClassLoader.getSystemResource("images/lockdelta.png"), "Locked file")
};
this.fileIcons = new ImageIcon[] {
new ImageIcon(ClassLoader.getSystemResource("images/file.png"), "Normal file"),
new ImageIcon(ClassLoader.getSystemResource("images/filegreen.png"), "Normal file"),
new ImageIcon(ClassLoader.getSystemResource("images/fileyellow.png"), "Normal file"),
new ImageIcon(ClassLoader.getSystemResource("images/filedelta.png"), "Normal file")
};
this.sizeFormatter = new DecimalFormat("###,###,###,###,###");
this.longFormatter = new DecimalFormat("000000000000");
}
/**
* Returns the number of records managed by the data source object. A <B>JTable</B> uses this method to determine how many rows it should create and display. This method should
* be quick, as it is call by <B>JTable</B> quite frequently.
*
* @return the number or rows in the model
* @see #getColumnCount
*/
@Override
public synchronized int getRowCount() {
int rowCount = 0;
if (sortedMap != null) {
rowCount = sortedMap.size();
}
return rowCount;
}
/**
* Returns the number of columns managed by the data source object. A <B>JTable</B> uses this method to determine how many columns it should create and display on
* initialization.
*
* @return the number or columns in the model
* @see #getRowCount
*/
@Override
public int getColumnCount() {
return getColumnTitleStrings().length;
}
/**
* Returns the name of the column at <i>columnIndex</i>. This is used to initialize the table's column header name. Note, this name does not need to be unique. Two columns on a
* table can have the same name.
*
* @param columnIndex the index of column
* @return the name of the column
*/
@Override
public String getColumnName(int columnIndex) {
return getColumnTitleStrings()[columnIndex];
}
/**
* Returns the lowest common denominator Class in the column. This is used by the table to set up a default renderer and editor for the column.
* @param columnIndex the column that we're interested in.
* @return the common ancestor class of the object values in the model.
*/
@Override
public Class getColumnClass(int columnIndex) {
return javax.swing.JLabel.class;
}
/**
* Returns an attribute value for the cell at <I>columnIndex</I> and <I>rowIndex</I>.
*
* @param rowIndex the row whose value is to be looked up
* @param columnIndex the column whose value is to be looked up
* @return the value Object at the specified cell
*/
@Override
public synchronized Object getValueAt(int rowIndex, int columnIndex) {
jLabel.setText("");
jLabel.setIcon(null);
if (getDirectoryManagers() == null) {
return jLabel;
}
if (getIsDirectoryManagersChanged()) {
setDirectoryManagers(getDirectoryManagers(), false, false);
}
if (rowIndex >= arrayList.size()) {
return jLabel;
}
MergedInfoInterface mergedInfo = getMergedInfo(rowIndex);
// The following line needs to stay.
FilteredFileTableModel filteredFileTableModel = (FilteredFileTableModel) QWinFrame.getQWinFrame().getRightFilePane().getModel();
switch (columnIndex) {
case FILENAME_COLUMN_INDEX:
jLabel.setText(mergedInfo.getShortWorkfileName());
jLabel.setIcon(deduceFileGraphic(mergedInfo));
break;
case FILE_STATUS_COLUMN_INDEX:
jLabel.setText(mergedInfo.getStatusString());
jLabel.setIcon(null);
break;
case LOCKEDBY_COLUMN_INDEX:
jLabel.setText(mergedInfo.getLockedByString());
jLabel.setIcon(null);
break;
case LASTCHECKIN_COLUMN_INDEX:
String lastCheckInDateString;
if (mergedInfo.getArchiveInfo() != null) {
Date lastCheckInDate = mergedInfo.getLastCheckInDate();
lastCheckInDateString = lastCheckInDate.toString();
} else {
lastCheckInDateString = "";
}
jLabel.setText(lastCheckInDateString);
jLabel.setIcon(null);
break;
case WORKFILEIN_COLUMN_INDEX:
jLabel.setText(mergedInfo.getWorkfileInLocation());
jLabel.setIcon(null);
break;
case FILESIZE_COLUMN_INDEX:
if (mergedInfo.getWorkfile() != null) {
String formattedSize = sizeFormatter.format(mergedInfo.getWorkfileSize());
jLabel.setText(formattedSize);
} else {
jLabel.setText("");
}
jLabel.setIcon(null);
break;
case LASTEDITBY_COLUMN_INDEX:
jLabel.setText(mergedInfo.getLastEditBy());
jLabel.setIcon(null);
break;
case APPENDED_PATH_INDEX:
jLabel.setText(mergedInfo.getArchiveDirManager().getAppendedPath());
jLabel.setIcon(null);
break;
default:
throw new QVCSRuntimeException("Invalid column index: [" + columnIndex + "]");
}
return jLabel;
}
@Override
public synchronized MergedInfoInterface getMergedInfo(int index) {
MergedInfoInterface mergedInfo = null;
if (index < arrayList.size()) {
mergedInfo = arrayList.get(index);
}
return mergedInfo;
}
ArrayList getSortedCollection() {
return arrayList;
}
private boolean getIsDirectoryManagersChanged() {
boolean retVal = false;
if (getDirectoryManagers() != null) {
for (DirectoryManagerInterface directoryManager : getDirectoryManagers()) {
if (directoryManager.getHasChanged()) {
retVal = true;
break;
}
}
}
return retVal;
}
static MergedInfo createBogusMergedInfo(MergedInfoInterface mergedInfo) {
MergedInfo bogusMergedInfo = new MergedInfo(mergedInfo.getArchiveInfo(), mergedInfo.getArchiveDirManager(), mergedInfo.getProjectProperties(), mergedInfo.getUserName());
bogusMergedInfo.setWorkfileInfo(mergedInfo.getWorkfileInfo());
bogusMergedInfo.setArchiveInfo(null);
return bogusMergedInfo;
}
private javax.swing.ImageIcon deduceFileGraphic(MergedInfoInterface mergedInfo) {
if (QWinFrame.getQWinFrame().getUserProperties().getUseColoredFileIconsFlag()) {
if (mergedInfo.getArchiveInfo() == null) {
return workfileIcons[0];
} else if (mergedInfo.getLockCount() == 0) {
javax.swing.ImageIcon imageIcon = fileIcons[0];
switch (mergedInfo.getStatusIndex()) {
case MergedInfoInterface.CURRENT_STATUS_INDEX:
imageIcon = fileIcons[1];
break;
case MergedInfoInterface.DIFFERENT_STATUS_INDEX:
case MergedInfoInterface.MERGE_REQUIRED_STATUS_INDEX:
case MergedInfoInterface.STALE_STATUS_INDEX:
case MergedInfoInterface.MISSING_STATUS_INDEX:
imageIcon = fileIcons[2];
break;
case MergedInfoInterface.YOUR_COPY_CHANGED_STATUS_INDEX:
// <editor-fold> TODO
imageIcon = fileIcons[3];
// </editor-fold>
break;
default:
break;
}
return imageIcon;
} else {
javax.swing.ImageIcon imageIcon = lockIcons[0];
switch (mergedInfo.getStatusIndex()) {
case MergedInfoInterface.CURRENT_STATUS_INDEX:
imageIcon = lockIcons[1];
break;
case MergedInfoInterface.DIFFERENT_STATUS_INDEX:
case MergedInfoInterface.MERGE_REQUIRED_STATUS_INDEX:
case MergedInfoInterface.STALE_STATUS_INDEX:
imageIcon = lockIcons[2];
break;
case MergedInfoInterface.YOUR_COPY_CHANGED_STATUS_INDEX:
// <editor-fold> TODO
imageIcon = lockIcons[3];
// </editor-fold>
break;
default:
break;
}
return imageIcon;
}
} else {
if (mergedInfo.getArchiveInfo() == null) {
return workfileIcons[0];
} else if (mergedInfo.getLockCount() == 0) {
return fileIcons[0];
} else {
return lockIcons[0];
}
}
}
@Override
public void setDirectoryManagers(final DirectoryManagerInterface[] managers, final boolean showProgressFlag, final boolean columnHeaderClickedFlag) {
// Don't bother to do anything here while there is stuff in-progress.
if (ClientTransactionManager.getInstance().getOpenTransactionCount() > 0) {
return;
}
// Make sure we are not listeners to the old directory managers
if (getDirectoryManagers() != null) {
for (DirectoryManagerInterface m_DirectoryManager : getDirectoryManagers()) {
if (m_DirectoryManager != null) {
m_DirectoryManager.removeChangeListener(this);
}
}
}
final FileTableModel finalThis = this;
// Maybe display the progress dialog.
ParentChildProgressDialog progressDialog = null;
if (showProgressFlag) {
progressDialog = OperationBaseClass.createParentProgressDialog("Updating file information...", 10);
progressDialog.setAutoClose(false);
}
final ParentChildProgressDialog progressMonitor = progressDialog;
final String fServerName = QWinFrame.getQWinFrame().getServerName();
int transactionID = 0;
// Show busy
if (columnHeaderClickedFlag) {
transactionID = ClientTransactionManager.getInstance().beginTransaction(fServerName);
}
final int fTransactionID = transactionID;
Runnable worker = new Runnable() {
@Override
public void run() {
try {
// Create a TreeMap that is sorted in the current sort order.
sortedMap = Collections.synchronizedMap(new TreeMap<Comparable, MergedInfoInterface>());
if (managers != null) {
if (showProgressFlag) {
// Run this on the Swing thread.
Runnable fireChange = new Runnable() {
@Override
public void run() {
progressMonitor.initParentProgressBar(0, managers.length);
}
};
SwingUtilities.invokeLater(fireChange);
}
for (int i = 0; i < managers.length; i++) {
if (showProgressFlag && progressMonitor.getIsCancelled()) {
break;
}
DirectoryManagerInterface manager = managers[i];
synchronized (manager) {
manager.setHasChanged(false);
// Get the collection of merged info.
Collection collection = manager.getMergedInfoCollection();
// If there is anything in the collection yet... (at initialization
// the collection may not exist yet...).
if (collection != null) {
int max = collection.size();
int count = 0;
if (showProgressFlag) {
OperationBaseClass.updateParentChildProgressDialog(i, "Updating directory: " + manager.getAppendedPath(), progressMonitor);
OperationBaseClass.initProgressDialog("Updating: " + manager.getAppendedPath(), 0, max, progressMonitor);
}
Iterator it = collection.iterator();
while (it.hasNext()) {
MergedInfoInterface mergedInfo = (MergedInfoInterface) it.next();
if (passesFileFilters(mergedInfo)) {
sortedMap.put(getSortKey(mergedInfo), mergedInfo);
}
if (showProgressFlag) {
OperationBaseClass.updateProgressDialog(count++, "Updating information for: " + mergedInfo.getShortWorkfileName(), progressMonitor);
}
}
}
manager.addChangeListener(finalThis);
}
}
}
} catch (Exception e) {
QWinUtility.logProblem(Level.WARNING, "Caught exception when updating file information: " + e.getClass().toString() + ": " + e.getLocalizedMessage());
QWinUtility.logProblem(Level.WARNING, Utility.expandStackTraceToString(e));
} finally {
synchronized (finalThis) {
setDirectoryManagers(managers);
// And put this in the array we associate with the screen display
arrayList = new ArrayList<>(sortedMap.values());
// Other threads can now proceed.
finalThis.notifyAll();
}
// Run the update on the Swing thread.
Runnable fireChange = new Runnable() {
@Override
public void run() {
QWinFrame.getQWinFrame().getStatusBar().updateStatusInfo();
if (showProgressFlag) {
progressMonitor.close();
}
if (columnHeaderClickedFlag) {
ClientTransactionManager.getInstance().endTransaction(fServerName, fTransactionID);
}
fireTableChanged(new javax.swing.event.TableModelEvent(FileTableModel.this));
}
};
SwingUtilities.invokeLater(fireChange);
}
}
};
// Put all this on a separate worker thread.
Thread workerThread = new Thread(worker);
// Wait for the bulk of the work to get done here
synchronized (finalThis) {
try {
workerThread.start();
finalThis.wait();
} catch (InterruptedException e) {
QWinUtility.logProblem(Level.WARNING, "Caught Interrupted exception waiting on setDirectoryManagers worker thread: " + e.getLocalizedMessage());
}
}
}
private Comparable getSortKey(MergedInfoInterface mergedInfo) {
int column = getSortColumnInteger();
String appendedPath = mergedInfo.getArchiveDirManager().getAppendedPath();
String sortKey = mergedInfo.getMergedInfoKey() + appendedPath;
FilteredFileTableModel filteredFileTableModel = (FilteredFileTableModel) QWinFrame.getQWinFrame().getRightFilePane().getModel();
FilterCollection filterCollection = filteredFileTableModel.getFilterCollection();
switch (column) {
case LOCKEDBY_COLUMN_INDEX:
// Sort by locked by, then status, then filename
if (mergedInfo.getLockedByString().length() > 0) {
sortKey = "000000" + mergedInfo.getLockedByString() + mergedInfo.getStatusString() + mergedInfo.getMergedInfoKey() + appendedPath;
} else {
sortKey = mergedInfo.getStatusValue() + mergedInfo.getMergedInfoKey() + appendedPath;
}
break;
case FILE_STATUS_COLUMN_INDEX:
sortKey = mergedInfo.getStatusValue() + mergedInfo.getMergedInfoKey() + appendedPath;
break;
case LASTCHECKIN_COLUMN_INDEX:
sortKey = Long.toString(Long.MAX_VALUE - mergedInfo.getLastCheckInDate().getTime()) + mergedInfo.getMergedInfoKey() + appendedPath;
break;
case WORKFILEIN_COLUMN_INDEX:
// Need to sort by filename also, since the workfile in value may
// be an empty string.
sortKey = mergedInfo.getWorkfileInLocation() + mergedInfo.getMergedInfoKey() + appendedPath;
break;
case 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.getMergedInfoKey() + appendedPath;
break;
case LASTEDITBY_COLUMN_INDEX:
sortKey = mergedInfo.getLastEditBy() + mergedInfo.getMergedInfoKey() + appendedPath;
break;
case APPENDED_PATH_INDEX:
sortKey = appendedPath + "/" + mergedInfo.getMergedInfoKey();
break;
default:
case FILENAME_COLUMN_INDEX:
break;
}
return new AscendDecendSortKey(sortKey, getAscendingSortFlag());
}
}