package com.apple.laf;
import java.awt.ComponentOrientation;
import java.beans.*;
import java.io.File;
import java.util.*;
import javax.swing.*;
import javax.swing.event.ListDataEvent;
import javax.swing.filechooser.FileSystemView;
import javax.swing.table.AbstractTableModel;
@SuppressWarnings("serial")
class AquaFileSystemModel extends AbstractTableModel implements PropertyChangeListener {
private final JTable fFileList;
private FilesLoader filesLoader = null;
private Vector<File> files = null;
JFileChooser filechooser = null;
Vector<SortableFile> fileCache = null;
Object fileCacheLock;
Vector<File> directories = null;
int fetchID = 0;
private final boolean[] fSortAscending = {true, true};
private boolean fSortNames = true;
private final String[] fColumnNames;
public static final String SORT_BY_CHANGED = "sortByChanged";
public static final String SORT_ASCENDING_CHANGED = "sortAscendingChanged";
public AquaFileSystemModel(final JFileChooser filechooser, final JTable filelist, final String[] colNames) {
fileCacheLock = new Object();
this.filechooser = filechooser;
fFileList = filelist;
fColumnNames = colNames;
validateFileCache();
updateSelectionMode();
}
void updateSelectionMode() {
final boolean b = filechooser.isMultiSelectionEnabled() && filechooser.getDialogType() != JFileChooser.SAVE_DIALOG;
fFileList.setSelectionMode(b ? ListSelectionModel.MULTIPLE_INTERVAL_SELECTION : ListSelectionModel.SINGLE_SELECTION);
}
public void propertyChange(final PropertyChangeEvent e) {
final String prop = e.getPropertyName();
if (prop == JFileChooser.DIRECTORY_CHANGED_PROPERTY || prop == JFileChooser.FILE_VIEW_CHANGED_PROPERTY || prop == JFileChooser.FILE_FILTER_CHANGED_PROPERTY || prop == JFileChooser.FILE_HIDING_CHANGED_PROPERTY) {
invalidateFileCache();
validateFileCache();
} else if (prop.equals(JFileChooser.MULTI_SELECTION_ENABLED_CHANGED_PROPERTY)) {
updateSelectionMode();
} else if (prop == JFileChooser.FILE_SELECTION_MODE_CHANGED_PROPERTY) {
invalidateFileCache();
validateFileCache();
} else if (prop.equals("componentOrientation")) {
ComponentOrientation o = (ComponentOrientation) e.getNewValue();
JFileChooser cc = (JFileChooser) e.getSource();
if (o != e.getOldValue()) {
cc.applyComponentOrientation(o);
}
fFileList.setComponentOrientation(o);
fFileList.getParent().getParent().setComponentOrientation(o);
}
if (prop == SORT_BY_CHANGED) {
fSortNames = (((Integer)e.getNewValue()).intValue() == 0);
invalidateFileCache();
validateFileCache();
fFileList.repaint();
}
if (prop == SORT_ASCENDING_CHANGED) {
final int sortColumn = (fSortNames ? 0 : 1);
fSortAscending[sortColumn] = ((Boolean)e.getNewValue()).booleanValue();
invalidateFileCache();
validateFileCache();
fFileList.repaint();
}
}
public void invalidateFileCache() {
files = null;
directories = null;
synchronized(fileCacheLock) {
if (fileCache != null) {
final int lastRow = fileCache.size();
fileCache = null;
fireTableRowsDeleted(0, lastRow);
}
}
}
public Vector<File> getDirectories() {
if (directories != null) { return directories; }
return directories;
}
public Vector<File> getFiles() {
if (files != null) { return files; }
files = new Vector<File>();
directories = new Vector<File>();
directories.addElement(filechooser.getFileSystemView().createFileObject(filechooser.getCurrentDirectory(), ".."));
synchronized(fileCacheLock) {
for (int i = 0; i < fileCache.size(); i++) {
final SortableFile sf = fileCache.elementAt(i);
final File f = sf.fFile;
if (filechooser.isTraversable(f)) {
directories.addElement(f);
} else {
files.addElement(f);
}
}
}
return files;
}
public void runWhenDone(final Runnable runnable){
synchronized (fileCacheLock) {
if (filesLoader != null) {
if (filesLoader.loadThread.isAlive()) {
filesLoader.queuedTasks.add(runnable);
return;
}
}
SwingUtilities.invokeLater(runnable);
}
}
public void validateFileCache() {
final File currentDirectory = filechooser.getCurrentDirectory();
if (currentDirectory == null) {
invalidateFileCache();
return;
}
if (filesLoader != null) {
filesLoader.loadThread.interrupt();
}
fetchID++;
invalidateFileCache();
synchronized(fileCacheLock) {
fileCache = new Vector<SortableFile>(50);
}
filesLoader = new FilesLoader(currentDirectory, fetchID);
}
public int getColumnCount() {
return 2;
}
public String getColumnName(final int col) {
return fColumnNames[col];
}
public Class<? extends Object> getColumnClass(final int col) {
if (col == 0) return File.class;
return Date.class;
}
public int getRowCount() {
synchronized(fileCacheLock) {
if (fileCache != null) {
return fileCache.size();
}
return 0;
}
}
public boolean contains(final File o) {
synchronized(fileCacheLock) {
if (fileCache != null) {
return fileCache.contains(new SortableFile(o));
}
return false;
}
}
public int indexOf(final File o) {
synchronized(fileCacheLock) {
if (fileCache != null) {
final boolean isAscending = fSortNames ? fSortAscending[0] : fSortAscending[1];
final int row = fileCache.indexOf(new SortableFile(o));
return isAscending ? row : fileCache.size() - row - 1;
}
return 0;
}
}
public Object getElementAt(final int row) {
return getValueAt(row, 0);
}
public Object getValueAt(int row, final int col) {
if (row < 0 || col < 0) return null;
final boolean isAscending = fSortNames ? fSortAscending[0] : fSortAscending[1];
synchronized(fileCacheLock) {
if (fileCache != null) {
if (!isAscending) row = fileCache.size() - row - 1;
return fileCache.elementAt(row).getValueAt(col);
}
return null;
}
}
public void intervalAdded(final ListDataEvent e) {
}
public void intervalRemoved(final ListDataEvent e) {
}
protected void sort(final Vector<Object> v) {
if (fSortNames) sSortNames.quickSort(v, 0, v.size() - 1);
else sSortDates.quickSort(v, 0, v.size() - 1);
}
abstract class QuickSort {
final void quickSort(final Vector<Object> v, final int lo0, final int hi0) {
int lo = lo0;
int hi = hi0;
SortableFile mid;
if (hi0 > lo0) {
mid = (SortableFile)v.elementAt((lo0 + hi0) / 2);
while (lo <= hi) {
while ((lo < hi0) && lt((SortableFile)v.elementAt(lo), mid)) {
++lo;
}
while ((hi > lo0) && lt(mid, (SortableFile)v.elementAt(hi))) {
--hi;
}
if (lo <= hi) {
swap(v, lo, hi);
++lo;
--hi;
}
}
if (lo0 < hi) {
quickSort(v, lo0, hi);
}
if (lo < hi0) {
quickSort(v, lo, hi0);
}
}
}
private void swap(final Vector<Object> a, final int i, final int j) {
final Object T = a.elementAt(i);
a.setElementAt(a.elementAt(j), i);
a.setElementAt(T, j);
}
protected abstract boolean lt(SortableFile a, SortableFile b);
}
class QuickSortNames extends QuickSort {
protected boolean lt(final SortableFile a, final SortableFile b) {
final String aLower = a.fName.toLowerCase();
final String bLower = b.fName.toLowerCase();
return aLower.compareTo(bLower) < 0;
}
}
class QuickSortDates extends QuickSort {
protected boolean lt(final SortableFile a, final SortableFile b) {
return a.fDateValue < b.fDateValue;
}
}
class SortableFile {
File fFile;
String fName;
long fDateValue;
Date fDate;
SortableFile(final File f) {
fFile = f;
fName = fFile.getName();
fDateValue = fFile.lastModified();
fDate = new Date(fDateValue);
}
public Object getValueAt(final int col) {
if (col == 0) return fFile;
return fDate;
}
public boolean equals(final Object other) {
final SortableFile otherFile = (SortableFile)other;
return otherFile.fFile.equals(fFile);
}
@Override
public int hashCode() {
return Objects.hashCode(fFile);
}
}
class FilesLoader implements Runnable {
Vector<Runnable> queuedTasks = new Vector<>();
File currentDirectory = null;
int fid;
Thread loadThread;
public FilesLoader(final File currentDirectory, final int fid) {
this.currentDirectory = currentDirectory;
this.fid = fid;
String name = "Aqua L&F File Loading Thread";
this.loadThread = new Thread(null, this, name, 0, false);
this.loadThread.start();
}
@Override
public void run() {
final Vector<DoChangeContents> runnables = new Vector<DoChangeContents>(10);
final FileSystemView fileSystem = filechooser.getFileSystemView();
final File[] list = fileSystem.getFiles(currentDirectory, filechooser.isFileHidingEnabled());
final Vector<Object> acceptsList = new Vector<Object>();
for (final File element : list) {
acceptsList.addElement(new SortableFile(element));
}
sort(acceptsList);
Vector<SortableFile> chunk = new Vector<SortableFile>(10);
final int listSize = acceptsList.size();
for (int i = 0; i < listSize;) {
SortableFile f;
for (int j = 0; j < 10 && i < listSize; j++, i++) {
f = (SortableFile)acceptsList.elementAt(i);
chunk.addElement(f);
}
final DoChangeContents runnable = new DoChangeContents(chunk, fid);
runnables.addElement(runnable);
SwingUtilities.invokeLater(runnable);
chunk = new Vector<SortableFile>(10);
if (loadThread.isInterrupted()) {
cancelRunnables(runnables);
return;
}
}
synchronized (fileCacheLock) {
for (final Runnable r : queuedTasks) {
SwingUtilities.invokeLater(r);
}
}
}
public void cancelRunnables(final Vector<DoChangeContents> runnables) {
for (int i = 0; i < runnables.size(); i++) {
runnables.elementAt(i).cancel();
}
}
}
class DoChangeContents implements Runnable {
private Vector<SortableFile> contentFiles;
private boolean doFire = true;
private final Object lock = new Object();
private final int fid;
public DoChangeContents(final Vector<SortableFile> files, final int fid) {
this.contentFiles = files;
this.fid = fid;
}
synchronized void cancel() {
synchronized(lock) {
doFire = false;
}
}
public void run() {
if (fetchID == fid) {
synchronized(lock) {
if (doFire) {
synchronized(fileCacheLock) {
if (fileCache != null) {
for (int i = 0; i < contentFiles.size(); i++) {
fileCache.addElement(contentFiles.elementAt(i));
fireTableRowsInserted(i, i);
}
}
}
}
contentFiles = null;
directories = null;
}
}
}
}
final QuickSortNames sSortNames = new QuickSortNames();
final QuickSortDates sSortDates = new QuickSortDates();
}