Browse Source

Add FileTree component.

pull/214/head
weisj 4 years ago
parent
commit
996cd1dfbe
  1. 74
      core/src/main/java/com/github/weisj/darklaf/components/filetree/FileTree.java
  2. 49
      core/src/main/java/com/github/weisj/darklaf/components/filetree/FileTreeCellRenderer.java
  3. 89
      core/src/main/java/com/github/weisj/darklaf/components/filetree/FileTreeModel.java
  4. 313
      core/src/main/java/com/github/weisj/darklaf/components/filetree/FileTreeNode.java
  5. 80
      core/src/main/java/com/github/weisj/darklaf/components/filetree/WatchFileTree.java
  6. 187
      core/src/main/java/com/github/weisj/darklaf/components/filetree/WatchFileTreeModel.java
  7. 32
      core/src/test/java/ui/ComponentDemo.java
  8. 79
      core/src/test/java/ui/tree/FileTreeDemo.java
  9. 4
      core/src/test/java/util/ClassFinder.java
  10. 33
      core/src/test/java/util/ResourceWalker.java
  11. 125
      utils/src/main/java/com/github/weisj/darklaf/util/Lambdas.java

74
core/src/main/java/com/github/weisj/darklaf/components/filetree/FileTree.java

@ -0,0 +1,74 @@
/*
* MIT License
*
* Copyright (c) 2020 Jannis Weis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
* associated documentation files (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge, publish, distribute,
* sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or
* substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
* NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*/
package com.github.weisj.darklaf.components.filetree;
import java.io.File;
import javax.swing.*;
import javax.swing.filechooser.FileSystemView;
import javax.swing.tree.TreeModel;
public class FileTree extends JTree {
public FileTree() {
this(null);
}
public FileTree(final File rootFile) {
this(rootFile, false);
}
public FileTree(final File rootFile, final boolean showHiddenFiles) {
FileSystemView fileSystemView = FileSystemView.getFileSystemView();
setFileTreeModel(createModel(fileSystemView, rootFile, showHiddenFiles));
setCellRenderer(new FileTreeCellRenderer(fileSystemView));
setRootVisible(false);
}
protected FileTreeModel createModel(final FileSystemView fsv, final File rootFile, final boolean showHiddenFiles) {
return new FileTreeModel(fsv, rootFile, showHiddenFiles);
}
public boolean isShowHiddenFiles() {
return getModel().isShowHiddenFiles();
}
public void setShowHiddenFiles(final boolean showHiddenFiles) {
getModel().setShowHiddenFiles(showHiddenFiles);
}
@Override
public FileTreeModel getModel() {
return (FileTreeModel) super.getModel();
}
public void setFileTreeModel(final FileTreeModel fileTreeModel) {
super.setModel(fileTreeModel);
}
@Override
public void setModel(final TreeModel newModel) {}
public void reload() {
getModel().reload();
}
}

49
core/src/main/java/com/github/weisj/darklaf/components/filetree/FileTreeCellRenderer.java

@ -0,0 +1,49 @@
/*
* MIT License
*
* Copyright (c) 2020 Jannis Weis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
* associated documentation files (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge, publish, distribute,
* sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or
* substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
* NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*/
package com.github.weisj.darklaf.components.filetree;
import java.awt.*;
import java.nio.file.Path;
import javax.swing.*;
import javax.swing.filechooser.FileSystemView;
import javax.swing.tree.DefaultTreeCellRenderer;
public class FileTreeCellRenderer extends DefaultTreeCellRenderer {
private final FileSystemView fsv;
public FileTreeCellRenderer(final FileSystemView fileSystemView) {
this.fsv = fileSystemView;
}
@Override
public Component getTreeCellRendererComponent(final JTree tree, final Object value, final boolean selected,
final boolean expanded, final boolean leaf, final int row, final boolean hasFocus) {
Path f = ((FileTreeNode) value).getFile();
if (f != null) {
setIcon(fsv.getSystemIcon(f.toFile()));
setText(fsv.getSystemDisplayName(f.toFile()));
}
return this;
}
}

89
core/src/main/java/com/github/weisj/darklaf/components/filetree/FileTreeModel.java

@ -0,0 +1,89 @@
/*
* MIT License
*
* Copyright (c) 2020 Jannis Weis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
* associated documentation files (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge, publish, distribute,
* sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or
* substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
* NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*/
package com.github.weisj.darklaf.components.filetree;
import java.io.File;
import java.nio.file.Path;
import javax.swing.filechooser.FileSystemView;
import javax.swing.tree.DefaultTreeModel;
public class FileTreeModel extends DefaultTreeModel {
protected final FileSystemView fsv;
protected boolean showHiddenFiles;
public FileTreeModel(final FileSystemView fileSystemView) {
this(fileSystemView, (Path) null, false);
}
public FileTreeModel(final FileSystemView fileSystemView, final File root, final boolean showHiddenFiles) {
this(fileSystemView, root != null ? root.toPath() : null, showHiddenFiles);
}
public FileTreeModel(final FileSystemView fileSystemView, final Path root, final boolean showHiddenFiles) {
super(null);
init();
this.showHiddenFiles = showHiddenFiles;
this.fsv = fileSystemView;
this.root = createRoot(root);
}
protected void init() {}
protected FileTreeNode createRoot(final Path root) {
if (root == null) {
return new FileTreeNode.RootNode(this);
} else {
return createNode(null, root);
}
}
@Override
public void reload() {
getRoot().reload();
}
@Override
public FileTreeNode getRoot() {
return (FileTreeNode) super.getRoot();
}
public void setShowHiddenFiles(final boolean showHiddenFiles) {
if (showHiddenFiles == this.showHiddenFiles)
return;
this.showHiddenFiles = showHiddenFiles;
reload();
}
public boolean isShowHiddenFiles() {
return showHiddenFiles;
}
protected FileTreeNode createNode(final FileTreeNode parent, final Path file) {
return new FileTreeNode(parent, file, this);
}
protected void register(final FileTreeNode node) {}
protected void unregister(final FileTreeNode node) {}
}

313
core/src/main/java/com/github/weisj/darklaf/components/filetree/FileTreeNode.java

@ -0,0 +1,313 @@
/*
* MIT License
*
* Copyright (c) 2020 Jannis Weis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
* associated documentation files (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge, publish, distribute,
* sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or
* substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
* NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*/
package com.github.weisj.darklaf.components.filetree;
import java.io.IOException;
import java.nio.file.*;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Stream;
import javax.swing.*;
import javax.swing.tree.TreeNode;
public class FileTreeNode implements TreeNode, Comparable<FileTreeNode> {
protected final FileTreeNode parent;
protected final FileTreeModel model;
protected final Path file;
protected AtomicReference<List<FileTreeNode>> children;
protected WatchKey watchKey;
public FileTreeNode(final FileTreeNode parent, final Path file, final FileTreeModel model) {
this.model = model;
this.file = file;
this.parent = parent;
this.children = new AtomicReference<>();
}
@Override
public boolean equals(final Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
FileTreeNode that = (FileTreeNode) o;
return Objects.equals(file, that.file);
}
@Override
public int hashCode() {
return file != null ? file.hashCode() : 0;
}
public Path getFile() {
return file;
}
private int addSorted(final List<FileTreeNode> nodes, final FileTreeNode node) {
int index = Collections.binarySearch(nodes, node);
if (index < 0)
index = ~index;
nodes.add(index, node);
model.register(node);
return index;
}
private void add(final List<FileTreeNode> nodes, final FileTreeNode node) {
int index = addSorted(nodes, node);
model.nodesWereInserted(FileTreeNode.this, new int[] {index});
}
private void remove(final List<FileTreeNode> nodes, final FileTreeNode node) {
int index = nodes.indexOf(node);
if (index < 0)
return;
nodes.remove(node);
model.unregister(node);
model.nodesWereRemoved(FileTreeNode.this, new int[] {index}, new Object[] {node});
}
public void reload() {
reload(Integer.MAX_VALUE);
}
protected void reload(final int depth) {
if (depth < 0)
return;
if (children.get() == null)
return;
List<FileTreeNode> fileList = children.get();
doInBackground((Consumer<FileTreeNode> proc) -> {
traverseChildren(this::toNodes).accept(proc);
fileList.stream().filter(n -> Files.notExists(n.file)).forEach(proc);
}, chunks -> {
for (FileTreeNode node : chunks) {
if (Files.notExists(node.file)) {
remove(fileList, node);
} else {
if (model.showHiddenFiles) {
if (!fileList.contains(node)) {
add(fileList, node);
}
} else {
if (isHidden(node.file)) {
remove(fileList, node);
} else if (!fileList.contains(node)) {
add(fileList, node);
}
}
}
}
}, () -> {
if (depth > 0)
fileList.forEach(n -> n.reload(depth - 1));
});
}
private List<FileTreeNode> getChildren() {
return children.updateAndGet(list -> {
if (list != null) {
return list;
}
List<FileTreeNode> fileList = Collections.synchronizedList(new ArrayList<>());
doInBackground(
traverseChildren(asNodes(stream -> stream.filter(p -> model.showHiddenFiles || !isHidden(p)))),
chunks -> {
Collections.sort(fileList);
int[] indices = new int[chunks.size()];
int i = 0;
for (FileTreeNode node : chunks) {
indices[i] = addSorted(fileList, node);
i++;
}
model.nodesWereInserted(FileTreeNode.this, indices);
});
return fileList;
});
}
private boolean isHidden(final Path p) {
try {
return Files.isHidden(p) || p.toFile().isHidden();
} catch (IOException e) {
return false;
}
}
private Function<Stream<Path>, Stream<FileTreeNode>> asNodes(
final Function<Stream<Path>, Stream<Path>> transformer) {
return stream -> toNodes(transformer.apply(stream));
}
private Stream<FileTreeNode> toNodes(final Stream<Path> stream) {
return stream.map(p -> model.createNode(FileTreeNode.this, p));
}
private <T> Consumer<Consumer<T>> traverseChildren(final Function<Stream<Path>, Stream<T>> transformer) {
return publish -> {
if (Files.isDirectory(file)) {
try (Stream<Path> files = Files.walk(file, 1, FileVisitOption.FOLLOW_LINKS)) {
transformer.apply(files.filter(p -> !file.equals(p)).filter(Files::isReadable)).forEach(publish);
} catch (IOException ignored) {
}
}
};
}
private <T> void doInBackground(final Consumer<Consumer<T>> task, final Consumer<List<T>> processor) {
doInBackground(task, processor, () -> {
});
}
private <T> void doInBackground(final Consumer<Consumer<T>> task, final Consumer<List<T>> processor,
final Runnable doneTask) {
SwingWorker<Void, T> worker = new SwingWorker<Void, T>() {
@Override
public Void doInBackground() {
task.accept(this::publish);
return null;
}
@Override
protected void process(final List<T> chunks) {
processor.accept(chunks);
}
@Override
protected void done() {
doneTask.run();
}
};
worker.execute();
}
@Override
public TreeNode getChildAt(final int childIndex) {
return getChildren().get(childIndex);
}
@Override
public int getChildCount() {
return getChildren().size();
}
@Override
public TreeNode getParent() {
return parent;
}
@Override
public int getIndex(final TreeNode node) {
if (node == null) {
throw new IllegalArgumentException("argument is null");
}
if (!isNodeChild(node)) {
return -1;
}
return getChildren().indexOf(node);
}
@Override
public boolean getAllowsChildren() {
return Files.isDirectory(file);
}
@Override
public boolean isLeaf() {
return getChildren().size() == 0;
}
@Override
public Enumeration<? extends TreeNode> children() {
return Collections.enumeration(children.get());
}
public boolean isNodeChild(final TreeNode aNode) {
if (aNode == null)
return false;
return (aNode.getParent() == this);
}
@Override
public int compareTo(final FileTreeNode o) {
if (o == null)
return -1;
boolean thisDir = Files.isDirectory(file);
boolean oDir = Files.isDirectory(o.file);
if (thisDir == oDir) {
return file.compareTo(o.file);
} else {
return thisDir ? -1 : 1;
}
}
public static class RootNode extends FileTreeNode {
public RootNode(final FileTreeModel model) {
super(null, null, model);
List<FileTreeNode> nodes = new ArrayList<>();
FileSystems.getDefault().getRootDirectories().forEach(p -> {
FileTreeNode node = model.createNode(RootNode.this, p);
model.register(node);
nodes.add(node);
});
children.set(nodes);
}
@Override
protected void reload(final int depth) {
if (depth < 0)
return;
List<FileTreeNode> nodes = children.get();
FileSystems.getDefault().getRootDirectories().forEach(p -> {
FileTreeNode node = model.createNode(this, p);
if (!nodes.contains(node)) {
model.register(node);
nodes.add(node);
}
});
nodes.removeIf(n -> {
if (Files.notExists(n.file)) {
model.unregister(n);
return true;
}
return false;
});
if (depth > 0)
children.get().forEach(n -> n.reload(depth - 1));
}
@Override
public boolean isLeaf() {
return false;
}
@Override
public boolean getAllowsChildren() {
return true;
}
}
}

80
core/src/main/java/com/github/weisj/darklaf/components/filetree/WatchFileTree.java

@ -0,0 +1,80 @@
/*
* MIT License
*
* Copyright (c) 2020 Jannis Weis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
* associated documentation files (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge, publish, distribute,
* sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or
* substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
* NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*/
package com.github.weisj.darklaf.components.filetree;
import java.io.File;
import javax.swing.*;
import javax.swing.filechooser.FileSystemView;
public class WatchFileTree extends FileTree {
public WatchFileTree() {
super(null);
}
public WatchFileTree(final File rootFile) {
super(rootFile, false);
}
public WatchFileTree(final File rootFile, final boolean showHiddenFiles) {
super(rootFile, showHiddenFiles);
}
@Override
protected FileTreeModel createModel(final FileSystemView fsv, final File rootFile, final boolean showHiddenFiles) {
return new WatchFileTreeModel(fsv, rootFile, showHiddenFiles);
}
@Override
public void setFileTreeModel(final FileTreeModel fileTreeModel) {
if (getModel() == fileTreeModel)
return;
if (getModel() instanceof WatchFileTreeModel) {
((WatchFileTreeModel) getModel()).stopWatching();
}
if (isVisible() && fileTreeModel instanceof WatchFileTreeModel) {
((WatchFileTreeModel) fileTreeModel).startWatching();
}
super.setFileTreeModel(fileTreeModel);
}
@Override
public void addNotify() {
super.addNotify();
SwingUtilities.invokeLater(() -> {
if (getModel() instanceof WatchFileTreeModel) {
((WatchFileTreeModel) getModel()).startWatching();
}
});
}
@Override
public void removeNotify() {
super.removeNotify();
SwingUtilities.invokeLater(() -> {
if (getModel() instanceof WatchFileTreeModel) {
((WatchFileTreeModel) getModel()).stopWatching();
}
});
}
}

187
core/src/main/java/com/github/weisj/darklaf/components/filetree/WatchFileTreeModel.java

@ -0,0 +1,187 @@
/*
* MIT License
*
* Copyright (c) 2020 Jannis Weis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
* associated documentation files (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge, publish, distribute,
* sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or
* substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
* NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*/
package com.github.weisj.darklaf.components.filetree;
import java.io.File;
import java.io.IOException;
import java.nio.file.*;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger;
import javax.swing.filechooser.FileSystemView;
import com.github.weisj.darklaf.util.LogUtil;
public class WatchFileTreeModel extends FileTreeModel {
private static final Logger LOGGER = LogUtil.getLogger(WatchFileTreeModel.class);
private static final ScheduledExecutorService scheduler = createScheduler();
private WatchService watchService;
private Map<Watchable, FileTreeNode> nodeMap;
private Object lock;
private final AtomicBoolean isScheduled = new AtomicBoolean(false);
private ScheduledFuture<?> watchTask;
private static WatchService createWatchService() {
WatchService ws = null;
try {
ws = FileSystems.getDefault().newWatchService();
} catch (IOException e) {
e.printStackTrace();
}
return ws;
}
private static ScheduledExecutorService createScheduler() {
ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1, r -> {
final Thread thread = new Thread(r, "File Tree Watch Thread");
thread.setDaemon(true);
thread.setPriority(Thread.MIN_PRIORITY);
return thread;
});
executor.setContinueExistingPeriodicTasksAfterShutdownPolicy(false);
executor.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
return executor;
}
public WatchFileTreeModel(final FileSystemView fileSystemView) {
super(fileSystemView);
}
public WatchFileTreeModel(final FileSystemView fileSystemView, final File root, final boolean showHiddenFiles) {
super(fileSystemView, root, showHiddenFiles);
}
public WatchFileTreeModel(final FileSystemView fileSystemView, final Path root, final boolean showHiddenFiles) {
super(fileSystemView, root, showHiddenFiles);
}
@Override
protected void init() {
lock = new Object();
watchService = createWatchService();
nodeMap = Collections.synchronizedMap(new HashMap<>());
}
private Object getLock() {
return lock;
}
protected WatchService getWatchService() {
return watchService;
}
protected Map<Watchable, FileTreeNode> getNodeMap() {
return nodeMap;
}
public void startWatching() {
if (watchTask != null)
return;
isScheduled.set(true);
watchTask = scheduler.schedule(this::watch, 0, TimeUnit.SECONDS);
}
public void stopWatching() {
if (watchTask != null) {
isScheduled.set(false);
watchTask.cancel(true);
watchTask = null;
}
}
private void watch() {
while (isScheduled.get()) {
WatchKey key;
try {
key = watchService.take();
} catch (InterruptedException x) {
x.printStackTrace();
return;
}
FileTreeNode parent = getNodeMap().get(key.watchable());
if (parent != null) {
LOGGER.fine(() -> "Event for \"" + parent.file + "\"");
if (parent.parent != null) {
parent.parent.reload(1);
} else {
parent.reload(0);
}
}
List<WatchEvent<?>> watchEventList = key.pollEvents();
for (WatchEvent<?> event : watchEventList) {
WatchEvent.Kind<?> kind = event.kind();
Path path = (Path) event.context();
if (kind == StandardWatchEventKinds.OVERFLOW) {
continue;
}
LOGGER.finer("Event Type " + kind.name());
FileTreeNode node = getNodeMap().get(((Path) key.watchable()).resolve(path));
if (node != null) {
LOGGER.finer(() -> "Affected node \"" + node.file + "\"");
node.reload(0);
}
}
key.reset();
}
}
protected void register(final FileTreeNode node) {
synchronized (getLock()) {
WatchService ws = getWatchService();
if (ws == null || !Files.isDirectory(node.file))
return;
if (getNodeMap().containsKey(node.file))
return;
try {
LOGGER.finer(() -> "Register watch service for \"" + node.file + "\"");
node.watchKey = node.file.register(ws, StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_DELETE, StandardWatchEventKinds.ENTRY_MODIFY);
getNodeMap().put(node.file, node);
} catch (IOException ignored) {
}
}
}
protected void unregister(final FileTreeNode node) {
synchronized (getLock()) {
if (node.watchKey == null)
return;
LOGGER.finer(() -> "Unregister watch service for \"" + node.file + "\"");
getNodeMap().remove(node.file);
node.watchKey.cancel();
}
}
}

32
core/src/test/java/ui/ComponentDemo.java

@ -66,8 +66,12 @@ public interface ComponentDemo {
LafManager.setLogLevel(Level.FINE);
System.setProperty("apple.laf.useScreenMenuBar", "true");
SwingUtilities.invokeLater(() -> {
if (!LafManager.isInstalled()) {
LafManager.install(demo.createTheme());
if (demo.useDarkLaf()) {
if (!LafManager.isInstalled()) {
LafManager.install(demo.createTheme());
}
} else {
installSystemLaf();
}
Window window;
if (!asDialog) {
@ -110,6 +114,10 @@ public interface ComponentDemo {
});
}
default boolean useDarkLaf() {
return true;
}
default Dimension getDisplayDimension() {
return null;
}
@ -234,18 +242,12 @@ public interface ComponentDemo {
});
dev.add(new JCheckBoxMenuItem("Darklaf/System Laf") {
{
setSelected(true);
setSelected(LafManager.isInstalled());
addActionListener(e -> {
if (isSelected()) {
LafManager.install();
} else {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
LafManager.updateLaf();
} catch (ClassNotFoundException | UnsupportedLookAndFeelException | IllegalAccessException
| InstantiationException classNotFoundException) {
classNotFoundException.printStackTrace();
}
installSystemLaf();
}
});
}
@ -253,5 +255,15 @@ public interface ComponentDemo {
return dev;
}
static void installSystemLaf() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
LafManager.updateLaf();
} catch (ClassNotFoundException | UnsupportedLookAndFeelException | IllegalAccessException
| InstantiationException classNotFoundException) {
classNotFoundException.printStackTrace();
}
}
String getTitle();
}

79
core/src/test/java/ui/tree/FileTreeDemo.java

@ -0,0 +1,79 @@
/*
* MIT License
*
* Copyright (c) 2020 Jannis Weis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
* associated documentation files (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge, publish, distribute,
* sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or
* substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
* NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*/
package ui.tree;
import java.awt.*;
import javax.swing.*;
import ui.ComponentDemo;
import ui.DemoPanel;
import com.github.weisj.darklaf.components.OverlayScrollPane;
import com.github.weisj.darklaf.components.filetree.FileTree;
import com.github.weisj.darklaf.components.filetree.WatchFileTree;
import com.github.weisj.darklaf.ui.button.ButtonConstants;
import com.github.weisj.darklaf.ui.tree.DarkTreeUI;
public class FileTreeDemo implements ComponentDemo {
public static void main(final String[] args) {
ComponentDemo.showDemo(new FileTreeDemo());
}
@Override
public JComponent createComponent() {
FileTree tree = new WatchFileTree(null, false);
DemoPanel panel = new DemoPanel(new OverlayScrollPane(tree), new BorderLayout(), 0);
JPanel controlPanel = panel.addControls();
controlPanel.add(new JCheckBox("Show hidden files") {
{
setSelected(tree.isShowHiddenFiles());
addActionListener(e -> tree.setShowHiddenFiles(isSelected()));
}
});
controlPanel.add(new JButton("Reload") {
{
putClientProperty(ButtonConstants.KEY_THIN, true);
addActionListener(e -> tree.reload());
}
});
controlPanel = panel.addControls();
controlPanel.add(new JLabel(DarkTreeUI.KEY_LINE_STYLE + ":", JLabel.RIGHT));
controlPanel.add(new JComboBox<String>() {
{
addItem(DarkTreeUI.STYLE_LINE);
addItem(DarkTreeUI.STYLE_DASHED);
addItem(DarkTreeUI.STYLE_NONE);
setSelectedItem(DarkTreeUI.STYLE_LINE);
addItemListener(e -> tree.putClientProperty(DarkTreeUI.KEY_LINE_STYLE, e.getItem()));
}
});
return panel;
}
@Override
public String getTitle() {
return "File Tree Demo";
}
}

4
core/src/test/java/util/ClassFinder.java

@ -27,12 +27,14 @@ import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import com.github.weisj.darklaf.util.Lambdas;
public class ClassFinder {
public static <T> List<T> getInstancesOfType(final Class<T> type, final String... packages) {
try (ResourceWalker walker = ResourceWalker.walkResources(packages)) {
return walker.stream().filter(p -> p.endsWith(".class")).map(p -> p.replace('/', '.'))
.map(p -> p.substring(0, p.length() - 6)).map(ResourceWalker.orDefault(Class::forName, null))
.map(p -> p.substring(0, p.length() - 6)).map(Lambdas.orDefault(Class::forName, null))
.filter(Objects::nonNull).filter(type::isAssignableFrom).filter(cls -> !cls.isInterface())
.filter(cls -> !Modifier.isAbstract(cls.getModifiers())).map(cls -> (Class<T>) cls)
.map(ClassFinder::getInstance).filter(Objects::nonNull).map(type::cast)

33
core/src/test/java/util/ResourceWalker.java

@ -29,10 +29,11 @@ import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import com.github.weisj.darklaf.util.Lambdas;
public class ResourceWalker implements AutoCloseable {
private final List<FileSystem> fileSystemList = new ArrayList<>();
@ -74,8 +75,8 @@ public class ResourceWalker implements AutoCloseable {
String pathName = pack;
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
Stream<URL> stream = enumerationAsStream(
orDefault(classLoader::getResources, Collections.<URL>emptyEnumeration()).apply(pathName));
return stream.map(wrap(URL::toURI)).flatMap(uri -> {
Lambdas.orDefault(classLoader::getResources, Collections.<URL>emptyEnumeration()).apply(pathName));
return stream.map(Lambdas.wrap(URL::toURI)).flatMap(uri -> {
if ("jar".equals(uri.getScheme())) {
try {
FileSystem fileSystem = FileSystems.newFileSystem(uri, Collections.emptyMap());
@ -114,30 +115,4 @@ public class ResourceWalker implements AutoCloseable {
files = new File[0];
return Arrays.stream(files).flatMap(this::walkFolder);
}
public static <T, K, E extends Throwable> Function<T, K> orDefault(final CheckedFunction<T, K, E> wrappee,
final K fallback) {
return t -> {
try {
return wrappee.apply(t);
} catch (final Throwable e) {
return fallback;
}
};
}
public static <T, K, E extends Throwable> Function<T, K> wrap(final CheckedFunction<T, K, E> wrappee) {
return t -> {
try {
return wrappee.apply(t);
} catch (final Throwable e) {
throw new RuntimeException(e);
}
};
}
public interface CheckedFunction<T, K, E extends Throwable> {
K apply(final T value) throws E;
}
}

125
utils/src/main/java/com/github/weisj/darklaf/util/Lambdas.java

@ -0,0 +1,125 @@
/*
* MIT License
*
* Copyright (c) 2020 Jannis Weis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
* associated documentation files (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge, publish, distribute,
* sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or
* substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
* NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*/
package com.github.weisj.darklaf.util;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
public class Lambdas {
private Lambdas() {}
public static <T, K, E extends Throwable> Function<T, K> orDefault(final CheckedFunction<T, K, E> wrappee,
final K fallback) {
return t -> {
try {
return wrappee.apply(t);
} catch (final Throwable e) {
return fallback;
}
};
}
public static <T, E extends Throwable> Supplier<T> orDefault(final CheckedSupplier<T, E> wrappee,
final T fallback) {
return () -> {
try {
return wrappee.get();
} catch (final Throwable e) {
return fallback;
}
};
}
public static <T, E extends Throwable> Predicate<T> orDefault(final CheckedPredicate<T, E> wrappee,
final boolean fallback) {
return t -> {
try {
return wrappee.test(t);
} catch (final Throwable e) {
return fallback;
}
};
}
public static <T, K, E extends Throwable> Function<T, K> wrap(final CheckedFunction<T, K, E> wrappee) {
return t -> {
try {
return wrappee.apply(t);
} catch (final Throwable e) {
throw new RuntimeException(e);
}
};
}
public static <T, E extends Throwable> Consumer<T> wrap(final CheckedConsumer<T, E> wrappee) {
return t -> {
try {
wrappee.accept(t);
} catch (final Throwable e) {
throw new RuntimeException(e);
}
};
}
public static <T, E extends Throwable> Supplier<T> wrap(final CheckedSupplier<T, E> wrappee) {
return () -> {
try {
return wrappee.get();
} catch (final Throwable e) {
throw new RuntimeException(e);
}
};
}
public static <T, E extends Throwable> Predicate<T> wrap(final CheckedPredicate<T, E> wrappee) {
return t -> {
try {
return wrappee.test(t);
} catch (final Throwable e) {
throw new RuntimeException(e);
}
};
}
public interface CheckedFunction<T, K, E extends Throwable> {
K apply(final T value) throws E;
}
public interface CheckedConsumer<T, E extends Throwable> {
void accept(final T value) throws E;
}
public interface CheckedSupplier<T, E extends Throwable> {
T get() throws E;
}
public interface CheckedPredicate<T, E extends Throwable> {
boolean test(final T value) throws E;
}
}
Loading…
Cancel
Save