<commit_before>/* 
 * Copyright (C) 2018 <NAME>.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301  USA
 */
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.mindmap.model.logger.Logger;
import com.igormaznitsa.mindmap.model.logger.LoggerFactory;
import com.igormaznitsa.sciareto.Context;
import com.igormaznitsa.sciareto.preferences.PrefUtils;
import java.io.IOException;
import java.nio.file.DirectoryStream;
import java.nio.file.Path;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;

public class NodeFileOrFolder implements TreeNode, Comparator<NodeFileOrFolder>, Iterable<NodeFileOrFolder> {

  private static final Logger LOGGER = LoggerFactory.getLogger(NodeFileOrFolder.class);

  protected static class ForkedLoadNodeTask extends RecursiveTask<NodeFileOrFolder> {

    private final NodeFileOrFolder parent;
    private final boolean isdir;
    private final String name;
    private final boolean addhidden;
    private final boolean writable;

    protected ForkedLoadNodeTask(@Nonnull final NodeFileOrFolder parent, final boolean isdir, @Nonnull final String name, final boolean addhidden, final boolean writable) {
      this.parent = parent;
      this.name = name;
      this.addhidden = addhidden;
      this.writable = writable;
      this.isdir = isdir;
    }

    @Override
    @Nonnull
    protected NodeFileOrFolder compute() {
      try {
        return new NodeFileOrFolder(this.parent, this.isdir, this.name, this.addhidden, this.writable);
      } catch (IOException ex) {
        throw new RuntimeException(ex);
      }
    }
  }

  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 showHiddenFiles, final boolean readOnly) throws IOException {
    this.parent = parent;
    this.name = name;

    if (folder) {
      this.children = new ArrayList<>();
      this.folderFlag = true;
      reloadSubtree(showHiddenFiles);
    } 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, final boolean showHiddenFiles) throws IOException {
    Assertions.assertTrue("Unexpected state!", this.folderFlag && file.getParentFile().equals(this.makeFileForNode())); //NOI18N
    final NodeFileOrFolder result = new NodeFileOrFolder(this, file.isDirectory(), file.getName(), showHiddenFiles, !Files.isWritable(file.toPath()));
    this.children.add(0, result);
    Collections.sort(this.children, this);
    return result;
  }

  public void setName(@Nonnull final String name) throws IOException {
    this.name = name;
    reloadSubtree(PrefUtils.isShowHiddenFilesAndFolders());
  }

  public void reloadSubtree(final boolean addHiddenFilesAndFolders) throws IOException {
    if (this.folderFlag) {
      this.children.clear();
      final File generatedFile = makeFileForNode();
      if (generatedFile != null && generatedFile.isDirectory()) {
        final List<ForkedLoadNodeTask> forks = new ArrayList<>();
        try (DirectoryStream<Path> stream = Files.newDirectoryStream(generatedFile.toPath())) {
          for (final Path p : stream) {
            if (addHiddenFilesAndFolders || !Files.isHidden(p)) {
              forks.add(new ForkedLoadNodeTask(this, Files.isDirectory(p), p.getFileName().toString(), addHiddenFilesAndFolders, !Files.isWritable(p)));
            }
          }
        }

        for (final ForkedLoadNodeTask f : forks) {
          f.fork();
        }

        for (final ForkedLoadNodeTask f : forks) {
          this.children.add(f.join());
        }

        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;<commit_msg>added reverse of task list before join<commit_after>/* 
 * Copyright (C) 2018 <NAME>.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301  USA
 */
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.mindmap.model.logger.Logger;
import com.igormaznitsa.mindmap.model.logger.LoggerFactory;
import com.igormaznitsa.sciareto.Context;
import com.igormaznitsa.sciareto.preferences.PrefUtils;
import java.io.IOException;
import java.nio.file.DirectoryStream;
import java.nio.file.Path;
import java.util.concurrent.RecursiveTask;

public class NodeFileOrFolder implements TreeNode, Comparator<NodeFileOrFolder>, Iterable<NodeFileOrFolder> {

  private static final Logger LOGGER = LoggerFactory.getLogger(NodeFileOrFolder.class);

  protected static class ForkedLoadNodeTask extends RecursiveTask<NodeFileOrFolder> {

    private final NodeFileOrFolder parent;
    private final boolean isdir;
    private final String name;
    private final boolean addhidden;
    private final boolean writable;

    protected ForkedLoadNodeTask(@Nonnull final NodeFileOrFolder parent, final boolean isdir, @Nonnull final String name, final boolean addhidden, final boolean writable) {
      this.parent = parent;
      this.name = name;
      this.addhidden = addhidden;
      this.writable = writable;
      this.isdir = isdir;
    }

    @Override
    @Nonnull
    protected NodeFileOrFolder compute() {
      try {
        return new NodeFileOrFolder(this.parent, this.isdir, this.name, this.addhidden, this.writable);
      } catch (IOException ex) {
        throw new RuntimeException(ex);
      }
    }
  }

  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 showHiddenFiles, final boolean readOnly) throws IOException {
    this.parent = parent;
    this.name = name;

    if (folder) {
      this.children = new ArrayList<>();
      this.folderFlag = true;
      reloadSubtree(showHiddenFiles);
    } 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, final boolean showHiddenFiles) throws IOException {
    Assertions.assertTrue("Unexpected state!", this.folderFlag && file.getParentFile().equals(this.makeFileForNode())); //NOI18N
    final NodeFileOrFolder result = new NodeFileOrFolder(this, file.isDirectory(), file.getName(), showHiddenFiles, !Files.isWritable(file.toPath()));
    this.children.add(0, result);
    Collections.sort(this.children, this);
    return result;
  }

  public void setName(@Nonnull final String name) throws IOException {
    this.name = name;
    reloadSubtree(PrefUtils.isShowHiddenFilesAndFolders());
  }

  public void reloadSubtree(final boolean addHiddenFilesAndFolders) throws IOException {
    if (this.folderFlag) {
      this.children.clear();
      final File generatedFile = makeFileForNode();
      if (generatedFile != null && generatedFile.isDirectory()) {
        final List<ForkedLoadNodeTask> forks = new ArrayList<>();
        try (DirectoryStream<Path> stream = Files.newDirectoryStream(generatedFile.toPath())) {
          for (final Path p : stream) {
            if (addHiddenFilesAndFolders || !Files.isHidden(p)) {
              forks.add(new ForkedLoadNodeTask(this, Files.isDirectory(p), p.getFileName().toString(), addHiddenFilesAndFolders, !Files.isWritable(p)));
            }
          }
        }

        for (final ForkedLoadNodeTask f : forks) {
          f.fork();
        }

        Collections.reverse(forks);
        
        for (final ForkedLoadNodeTask f : forks) {
          this.children.add(f.join());
        }

        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;
{'fraction_non_alphanumeric': 0.09950248756218906, 'fraction_numerical': 0.005942509673852957, 'mean_word_length': 3.392412746585736, 'pattern_counts': {'":': 0, '<': 25, '<?xml version=': 0, '>': 23, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3961123', 'n_tokens_mistral': 4332, 'n_tokens_neox': 4223, 'n_words': 1142}