/* * Copyright 2016 Igor Maznitsa. * * 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.igormaznitsa.sciareto.ui.tree; import java.io.File; import java.nio.file.Files; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Enumeration; import java.util.Iterator; import java.util.List; import java.util.regex.Pattern; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.swing.event.TreeModelEvent; import javax.swing.event.TreeModelListener; import javax.swing.tree.TreeModel; import javax.swing.tree.TreeNode; import javax.swing.tree.TreePath; import com.igormaznitsa.meta.annotation.MustNotContainNull; import com.igormaznitsa.meta.annotation.ReturnsOriginal; import com.igormaznitsa.meta.common.utils.ArrayUtils; import com.igormaznitsa.meta.common.utils.Assertions; import com.igormaznitsa.sciareto.Context; public class NodeFileOrFolder implements TreeNode, Comparator<NodeFileOrFolder>, Iterable<NodeFileOrFolder> { protected final NodeFileOrFolder parent; protected final List<NodeFileOrFolder> children; protected final boolean folderFlag; protected volatile String name; private final boolean readonly; public NodeFileOrFolder(@Nullable final NodeFileOrFolder parent, final boolean folder, @Nullable final String name, final boolean readOnly) { this.parent = parent; this.name = name; if (folder) { this.children = new ArrayList<>(); this.folderFlag = true; reloadSubtree(); } else { this.children = Collections.EMPTY_LIST; this.folderFlag = false; } this.readonly = readOnly; } public int size() { if (this.folderFlag) { int counter = 1; for (final NodeFileOrFolder f : this.children) { counter += f.size(); } return counter; } else { return 1; } } public boolean isReadOnly() { return this.readonly; } public boolean isProjectKnowledgeFolder() { return !this.isLeaf() && Context.KNOWLEDGE_FOLDER.equals(this.name); //NOI18N } @Nullable public NodeProject findProject() { NodeFileOrFolder path = this; while (path != null) { if (path instanceof NodeProject) { return (NodeProject) path; } path = path.getNodeParent(); } return null; } @Override public int compare(@Nonnull final NodeFileOrFolder o1, @Nonnull final NodeFileOrFolder o2) { final String name1 = o1.name; final String name2 = o2.name; if (o1.isLeaf() == o2.isLeaf()) { if (!o1.isLeaf()) { if (Context.KNOWLEDGE_FOLDER.equals(name1)) { return -1; } else if (Context.KNOWLEDGE_FOLDER.equals(name2)) { return 1; } } return name1.compareTo(name2); } else { return o1.isLeaf() ? 1 : -1; } } @Nonnull public NodeFileOrFolder addFile(@Nonnull final File file) { Assertions.assertTrue("Unexpected state!", this.folderFlag && file.getParentFile().equals(this.makeFileForNode())); //NOI18N final NodeFileOrFolder result = new NodeFileOrFolder(this, file.isDirectory(), file.getName(), !Files.isWritable(file.toPath())); this.children.add(0, result); Collections.sort(this.children, this); return result; } public void setName(@Nonnull final String name) { this.name = name; reloadSubtree(); } public void reloadSubtree() { if (this.folderFlag) { this.children.clear(); final File generatedFile = makeFileForNode(); if (generatedFile != null && generatedFile.isDirectory()) { for (final File f : generatedFile.listFiles()) { this.children.add(new NodeFileOrFolder(this, f.isDirectory(), f.getName(), !Files.isWritable(f.toPath()))); } Collections.sort(this.children, this); } } } @Nullable public File makeFileForNode() { if (this.parent == null) { return null; } else { return new File(this.parent.makeFileForNode(), this.name); } } void fireNotifySubtreeChanged(@Nonnull TreeModel model, @Nonnull @MustNotContainNull final List<TreeModelListener> listeners) { if (this.parent != null && this.folderFlag) { final Object[] childrenObject = new Object[children.size()]; final int[] indexes = new int[children.size()]; for (int i = 0; i < this.children.size(); i++) { final NodeFileOrFolder c = this.children.get(i); childrenObject[i] = c; indexes[i] = i; c.fireNotifySubtreeChanged(model, listeners); } final TreeModelEvent event = new TreeModelEvent(model, this.parent.makeTreePath(), indexes, childrenObject); for (final TreeModelListener l : listeners) { l.treeStructureChanged(event); } } } @Nonnull @MustNotContainNull @ReturnsOriginal public List<NodeFileOrFolder> findRelatedNodes(@Nonnull final File file, @Nonnull @MustNotContainNull final List<NodeFileOrFolder> list) { final File theFile = makeFileForNode(); if (theFile != null) { if (file.equals(theFile) || theFile.toPath().startsWith(file.toPath())) { list.add(this); for (final NodeFileOrFolder f : this.children) { f.findRelatedNodes(file, list); } } } return list; } @Override @Nonnull public String toString() { return this.name; } @Override @Nonnull public TreeNode getChildAt(final int childIndex) { return this.children.get(childIndex); } @Override public int getChildCount() { return this.children.size(); } @Nullable public NodeFileOrFolder getNodeParent() { return this.parent; } @Override @Nullable public TreeNode getParent() { return this.parent; } @Override public int getIndex(@Nonnull final TreeNode node) { return this.children.indexOf(node); } @Override public boolean getAllowsChildren() { return this.folderFlag; } @Override public boolean isLeaf() { return !this.folderFlag; } @Override @Nonnull public Enumeration children() { final Iterator<NodeFileOrFolder> iterator = this.children.iterator(); return new Enumeration() { @Override public boolean hasMoreElements() { return iterator.hasNext(); } @Override @Nonnull public Object nextElement() { return iterator.next(); } }; } @Nonnull public TreePath makeTreePath() { final List<Object> path = new ArrayList<>(); TreeNode node = this; while (node != null) { path.add(0, node); node = node.getParent(); } return new TreePath(path.toArray()); } public int getIndexAtParent() { int result = -1; if (this.parent != null) { result = this.parent.getIndex(this); } return result; } @Nullable public TreePath findPathToFile(@Nonnull final File file) { final File generatedFile = makeFileForNode(); if (file.equals(generatedFile)) { return new TreePath(new Object[]{this}); } if (!this.isLeaf()) { for (final NodeFileOrFolder c : this.children) { final TreePath result = c.findPathToFile(file); if (result != null) { return new TreePath(ArrayUtils.joinArrays(new Object[]{this}, result.getPath())); } } } return null; } boolean deleteChild(@Nonnull final NodeFileOrFolder child) { return this.children.remove(child); } protected void fillAllMatchNamePattern(@Nonnull final Pattern namePattern, @Nonnull @MustNotContainNull final List<NodeFileOrFolder> resultList) { if (namePattern.matcher(this.name).matches()) { resultList.add(this); } if (!this.isLeaf()) { for (final NodeFileOrFolder c : this.children) { c.fillAllMatchNamePattern(namePattern, resultList); } } } public boolean isMindMapFile() { return !this.folderFlag && this.name.endsWith(".mmd"); //NOI18N } @Override @Nonnull public Iterator<NodeFileOrFolder> iterator() { final List<NodeFileOrFolder> projects = new ArrayList<>(this.children); final Iterator<NodeFileOrFolder> result = projects.iterator(); return new Iterator<NodeFileOrFolder>() { @Override public boolean hasNext() { return result.hasNext(); } @Override public void remove() { result.remove(); } @Override @Nonnull public NodeFileOrFolder next() { return result.next(); } }; } }