All Downloads are FREE. Search and download functionalities are using the official Maven repository.

uk.ac.leeds.ccg.generic.io.Generic_FileStore Maven / Gradle / Ivy

There is a newer version: 2.0
Show newest version
/*
 * Copyright 2019 Andy Turner, University of Leeds.
 *
 * 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 uk.ac.leeds.ccg.generic.io;

import java.io.IOException;
import java.io.Serializable;
import java.math.BigInteger;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.TreeMap;
import java.util.stream.Stream;
import uk.ac.leeds.ccg.generic.core.Generic_Strings;
import uk.ac.leeds.ccg.generic.math.Generic_Math;
import uk.ac.leeds.ccg.generic.util.Generic_Collections;

/**
 * For storing files on disk in file store - a form of data base where each file
 * is stored in a leaf directory. Leaf directories are found at level 0 of the
 * file store. The 1st leaf directory has the name 0, the 2nd leaf directory has
 * the name 1, the nth leaf directory has the name n where n is a positive
 * integer . A file store is comprised of a base directory in which there is a
 * root directory. The root directory indicates how many files are stored in the
 * file store using a range given in the directory name. The minimum of the
 * range is 0 and the maximum is a positive integer number. These two numbers
 * are separated with by {@link #SEP} e.g. "0_99". The root directory will
 * contain one or more subdirectories named in a similar style to the root
 * directory e.g. "0_9". The maximum number will be less than or equal to that
 * of the root directory. By comparing the range of numbers in the names of
 * directories in the root directory with the range of numbers in the names of
 * and subdirectory in the root directory it is possible to discern the range
 * for the file store. The range is a parameter that can be set when
 * initialising a file store. It controls how many subdirectories there can be
 * at each level, and ultimately this controls how many levels of directories
 * there are in the file store which is all dependent on the number of files
 * stored in the file store.
 *
 * Files are to be stored in the leaf directories. Each directory is given a
 * standardised name such that it is easy to find and infer the path to the leaf
 * directories.
 *
 * If range was set to 10, there would be at most 10 subdirectories in each
 * level of the file store.
 *
 * File stores are initialised with 3 levels and dynamically grow to store more
 * files. For range = 10 the initial tree can be represented as follows:
 *
 * {@code
 * Level
 * 2        - 1           - root
 *
 * 0        - 0_9         - 0_99
 * }
 *
 * For range = 10 and n = 100001 the tree can be represented as follows:
 *
 * {@code
 * Level
 * 6      - 5             - 4             - 3             - 2             - 1               - root
 *
 * 0      - 0_9           - 0_99          - 0_999         - 0_9999        - 0_99999        - 0_999999
 * 1
 * 2
 * 3
 * 4
 * 5
 * 6
 * 7
 * 8
 * 9
 * 10     - 10_19
 * 11
 * 12
 * ...
 * 19
 * 20     - 20_29
 * ...
 * ...
 * 99     - 90_99
 * 100    - 100_109       - 100_199
 * ...
 * ...
 * ...
 * 999    - 990_999       - 900_999
 * 1000   - 1000_1009     - 1000_1099     - 1000_1999
 * ...
 * ...
 * ...
 * ...
 * 9999   - 9990_9999     - 9900_9999     - 9000_9999
 * 10000  - 10000_10009   - 10000_10099   - 10000_10999   - 10000_19999
 * ...
 * ...
 * ...
 * ...
 * ...
 * 99999  - 99990_99999   - 99900_99999   - 99000_99999   - 90000_99999
 * 100000 - 100000_100009 - 100000_100099 - 100000_100999 - 100000_109999 - 100000_199999
 * 100001
 * }
 *
 * File stores are used for logging and may be used to store other outputs from
 * different runs of a program. They can also be used to organise caches of data
 * from a running program to help with memory management.
 *
 * Although such a file store can store many files, there are limits depending
 * on the range value set. The theoretical limit is close to Long.MAX_VALUE /
 * range. But there can be no more than Integer.MAX_VALUE levels. Perhaps a
 * bigger restriction is the size of the storage element that holds the
 * directories and files indexed by the file store.
 *
 * There is scope for developing something very similar that can store many more
 * files in the same way. This idea along with other related develop ideas are
 * outlined below:
 * 
    *
  • Refactor or develop additional code in order to store more than * Long.MAX_VALUE number of files where the identifiers of these files are * represented as {@link BigInteger} numbers. This might leverage some other * libraries which depend on the Generic library which contains this class - in * particular - Math - which provides utility for BigInteger and BigDecimal * arithmetic which might be useful in such a development. If the product of * this development were a new class, then this class could potentially be * stored in the Generic library although this might result in a cyclical * dependency albeit one using different versions (a form of recursive * enrichment). It might though be simpler to release this new class in another * library.
  • *
  • Add functionality for changing the range of a Generic_FileStore.
  • *
* * @author Andy Turner * @version 1.0.0 */ public class Generic_FileStore implements Serializable { private static final long serialVersionUID = 1L; /** * Separates the smaller and larger numbers for the range of identifiers * stored in any directory. */ protected static final String SEP = Generic_Strings.symbol_underscore; /** * For storing the base directory path of the file store. */ protected final Generic_Path baseDir; /** * For storing the root directory path of the file store. This should be a * directory in baseDir which is otherwise empty. */ protected Generic_Path root; /** * The name of the file store. Used to initialise baseDir. */ protected final String name; /** * range stored as a long. */ protected final long rangeL; /** * range stored as a BigInteger. */ protected final BigInteger rangeBI; /** * The range for the next level. */ protected long nextRange; /** * Stores the number of levels in the file store. For a new store, initially * this is 2 and increases by 1 each time the file store grows deeper. The * maximum number of files that can be stored in 2 levels is range * range. * With each subsequent level this number is increased by a factor of range. * With n levels and range r * {@code BigDecimal.valueOf(r).pow(n).longValueExact} files can be stored. * So, with range = 100 and 3 levels some 10 thousand files can be stored. * With range = 100 and 7 level some a million million files can be stored. * To calculate how many levels will be needed for a given number of files n * and a range r use {@link #getLevels(long, long)}. */ protected int levels; /** * For storing the range for each level above the root level. This grows * with {@link #levels} as the file store grows. ranges[levels - 1] is * rangeL, ranges[levels -2] is rangeL * rangeL, etc... */ protected ArrayList ranges; /** * For storing the number of directories at each level from the root level * up to the level before the leaf level. This grows with {@link #levels} as * the file store grows and is modified as new directories are added at each * level. dirCounts[0] is a count of the number of directories at the * rootLevel which is always 1; dirCounts[1] is a count of the number of * directories at level 1; etc... */ protected ArrayList dirCounts; /** * For storing the paths to the directories (at each level including the * root level) in which nextID is to be stored. This grows with * {@link #levels} as the file store grows. If the file store grows wider it * also must be modified. lps[0] is the absolute path to the root * directory; lps[1] is the path to the directory in lps[0] * of the current highest leaf; each other lps[n] is either: the * path to the directory containing the current highest leaf directory; or, * it is another subdirectory in lps[n - 1] that contains it; etc... * lps[levels - 1] is the parent of the Highest Leaf Directory. */ protected Generic_Path[] lps; /** * Stores the nextID. Initially set to 0. */ protected long nextID; /** * Initialises a file store at {@code p} called {@code name} with 3 levels * allowing to store 100 files in each directory. * * @param p The path to where the file store will be initialised. * @param name The directory file name for the {@link #baseDir} of the file * store. * @throws IOException If encountered. */ public Generic_FileStore(Path p, String name) throws IOException, Exception { this(p, name, (short) 100); } /** * Initialises a file store at {@code p} called {@code name} with 3 levels * allowing to store {@code range} number of files in each directory. * * @param p The path to where the file store will be initialised. * @param name The directory file name for the {@link #baseDir} of the file * store. * @param range The maximum number of directories in each level of the file * store. * @throws IOException If encountered. * @throws Exception If range is less than 0. */ public Generic_FileStore(Path p, String name, short range) throws IOException, Exception { if (range < 0) { throw new Exception("Range cannot be < 0."); } baseDir = new Generic_Path(Paths.get(p.toString(), name)); this.name = name; rangeL = range; rangeBI = BigInteger.valueOf(range); levels = 2; nextID = 0; lps = new Generic_Path[2]; long l; //String fn; ranges = new ArrayList<>(); BigInteger rBI; ranges.add(0, rangeBI.longValueExact()); rBI = rangeBI.multiply(rangeBI); ranges.add(0, rBI.longValueExact()); long u = 0L; l = rBI.subtract(BigInteger.ONE).longValueExact(); nextRange = rBI.multiply(rangeBI).longValueExact(); lps[0] = new Generic_Path(Paths.get(baseDir.s, getName(u, l))); l = rangeBI.subtract(BigInteger.ONE).longValueExact(); lps[1] = new Generic_Path(Paths.get(lps[0].s, getName(u, l))); Files.createDirectories(Paths.get(lps[1].s, "0")); dirCounts = new ArrayList<>(); dirCounts.add(1L); dirCounts.add(1L); root = lps[0]; } /** * Initialises a file store at {@code p} for an existing file store. * * @param p The path of the existing file store base directory. * @throws IOException If encountered. * @throws Exception If the existing file store is problematic. */ public Generic_FileStore(Path p) throws IOException, Exception { name = p.getFileName().toString(); baseDir = new Generic_Path(p); if (!Files.isDirectory(baseDir.getPath())) { throw new Exception("Path " + p.toString() + " does not appear to " + "be a file store as it does not contain one element that " + "is a directory."); } List l = Generic_IO.getList(p); if (l.size() != 1) { throw new Exception("Path " + p.toString() + " does not appear to " + "be a file store as it does not contain one element."); } root = new Generic_Path(l.get(0)); String fn = root.getFileName().toString(); if (!fn.contains(SEP)) { throw new Exception("Path " + p.toString() + " does not appear to " + "be a file store as the directory it contains does not " + "have " + SEP + " in it's filename."); } String[] split = fn.split(SEP); if (!split[0].equalsIgnoreCase("0")) { throw new Exception("Path " + p.toString() + " does not appear to " + "be a file store as the name of the directory it contains" + " does not start with \"0\"."); } if (split.length != 2) { throw new Exception("Path " + p.toString() + " contains more than " + "one \"" + SEP + "\" in the filename."); } try { long r; r = Long.valueOf(split[1]) + 1L; Path p2 = Generic_IO.getList(root.getPath()).get(0); fn = p2.getFileName().toString(); if (!fn.contains(SEP)) { throw new Exception("Path " + p2.toString() + " does not have " + SEP + " in it's filename."); } split = fn.split(SEP); if (split.length != 2) { throw new Exception("Path " + p2.toString() + " contains more " + "than one \"" + SEP + "\" in the filename."); } long r2 = Long.valueOf(split[1]) - Long.valueOf(split[0]) + 1; if (r % r2 != 0) { throw new Exception("Invalid range difference for file store."); } rangeL = r / r2; } catch (NumberFormatException ex) { ex.printStackTrace(System.err); throw new Exception(ex.getMessage() + " setting rangeL."); } if (rangeL < 0 || rangeL > Short.MAX_VALUE) { throw new Exception("range < 0 or > Short.MAX_VALUE."); } rangeBI = BigInteger.valueOf(rangeL); testIntegrity(); initLevelsAndNextID(); ranges = getRanges(nextID, rangeL); initLPs(); dirCounts = getDirCounts(nextID, rangeL); initNextRange(); } /** * * @param dir The FileStore directory. * @return The file store at {@code dir} creating it first if it does not * exist. * @throws Exception If encountered. */ public static Generic_FileStore getFileStore(Path dir) throws Exception { Generic_FileStore fs; if (Files.exists(dir)) { fs = new Generic_FileStore(dir); } else { fs = new Generic_FileStore(dir.getParent(), dir.getFileName().toString()); } return fs; } /** * @return A String description of this. */ @Override public String toString() { String r = "File store(baseDir=" + baseDir.s + ", root=" + root.s + ", name=" + name + ", range=" + rangeBI.toString() + ", nextRange=" + nextRange + ", levels=" + levels + ", ranges=(length=" + ranges.size() + ", ranges[0]=" + ranges.get(0); for (int i = 0; i < ranges.size(); i++) { r += ", ranges[" + i + "]=" + ranges.get(i); } r += "), dirCounts(length=" + dirCounts.size() + ", dirCounts[0]=" + dirCounts.get(0); for (int i = 0; i < dirCounts.size(); i++) { r += ", dirCounts[" + i + "]=" + dirCounts.get(i); } r += "), lps(length=" + lps.length + ", lps[0]=" + lps[0].s; for (int i = 0; i < lps.length; i++) { r += ", lps[" + i + "]=" + lps[i].s; } r += "), nextID=" + nextID + ")"; return r; } /** * Initialises {@link #nextRange}. */ protected final void initNextRange() { nextRange = BigInteger.valueOf(ranges.get(0)).multiply(rangeBI).longValueExact(); } /** * Calculates and returns the number of levels needed for a file store with * range of {@code range} and {@code n} total number of files to store. If * the result is larger than {@link Generic_Math#SHORT_MAXVALUE} then this * would be too deep. In such a case then it may still be possible to store * all the files but only if {@code range} is increased. * * @param n the number of files * @param range the range of the file store * @return the number of levels needed for an file store with range of r and * n total number of files to store. */ public static int getLevels(long n, long range) { int r = 0; if (n % range != 0) { r += 1; } while (n >= range) { n = n / range; r++; } if (r < 2) { r = 2; } return r; } /** * {@link #levels} is for storing the number of levels in the file store. * This is kept up to date as the file store grows. This method is a * convenience method for users that want to know how many levels will be * needed once the number of files stored reaches n. This effectively calls * {@link #getLevels(long, long)} defaulting range to rangeL. * * @param n The number of files. * @return The number of levels needed for this file store to store n total * number of files. */ public int getLevels(long n) { return getLevels(n, rangeL); } /** * For initialising levels and nextID. * * @throws java.io.IOException If there encountered in * {@link #getHighestDir()}. */ protected final void initLevelsAndNextID() throws IOException { Path p = findHighestLeaf(); nextID = Long.valueOf(p.getFileName().toString()); levels = p.getNameCount() - baseDir.getNameCount() - 1; } /** * @return {@link #levels}. */ public final long getLevels() { return levels; } /** * @return {@link ranges} updated. */ protected final ArrayList getRanges() { return ranges; } /** * Calculates and returns the ranges given the number of files to be stored * and the range. ranges[levels - 1] is range, ranges[levels -2] is range * * range, etc... * * @param n The number of files to be stored. * @param range The range. * @return {@link ranges} updated if levels has increased since it was last * updated. * @throws java.lang.Exception If n is too big for the range. If this is the * case then maybe try to specify a bigger range or look to implementing * something that can handle larger numbers of files as suggested in the * class comment documentation. */ public static final ArrayList getRanges(long n, long range) throws Exception { int lvls = getLevels(n, range); if (lvls > Integer.MAX_VALUE) { throw new Exception("n too big for the range"); } ArrayList r = new ArrayList<>(); for (int l = 0; l < lvls; l++) { r.add(0, BigInteger.valueOf(range).pow(l + 1).longValueExact()); } return r; } /** * Calculates the number of directories needed at each level to store n * files with range r. This first calculates the number of levels and the * ranges for each level. * * @param n The number of files to store. * @param range The range. * @return The number of directories needed at each level to store n files * with range range. The first element is the list is the number of * directories in the root directory. * @throws java.lang.Exception If n is too big for the range. If this is the * case then maybe try to specify a bigger range or look to implementing * something that can handle larger numbers of files as suggested in the * class comment documentation. */ public static final ArrayList getDirCounts(long n, long range) throws Exception { ArrayList dirCounts = new ArrayList<>(); long lvls = getLevels(n, range); ArrayList rngs = getRanges(n, range); /** * Can safely cast here as getLevels(long, long) already throws an * Exception if lvls is greater than Integer.MAX_VALUE */ int li = (int) lvls; ArrayList dirIndexes = getDirIndexes(n, li, rngs); for (int i = 0; i < dirIndexes.size(); i++) { dirCounts.add((long) dirIndexes.get(i) + 1L); } return dirCounts; } /** * Gets the dir indexes for the directories at each level greater than zero * for the element identified by id. * * @param id The identifier for which the dir indexes are returned. * @param levels The number of levels. * @param ranges The ranges at each level. * @return The dir indexes for the directories at each level greater than * zero for the element identified by id. */ protected static ArrayList getDirIndexes(long id, int levels, ArrayList ranges) { ArrayList r = new ArrayList<>(); for (int lvl = levels - 1; lvl >= 0; lvl--) { long id2 = id; long range = ranges.get(lvl); int c = 0; id2 -= range; while (id2 >= 0) { id2 -= range; c++; } r.add(0, c); } return r; } /** * Gets the dir indexes for the directories at each level in the current * storage of the element identified by id. * * This may result in a runtime exception if n is too big for the range. If * this is the case then maybe try to specify a bigger range or look to * implementing something that can handle larger numbers of files as * suggested in the class comment documentation. * * @param id The identifier of the element to get the dirCounts for. * @return The dir counts at each level for the current storage of the * element identified by id. */ protected final ArrayList getDirIndexes(long id) { int li = levels; return getDirIndexes(id, li, ranges); } /** * @return a copy of {@link #lps}[levels] this is the current highest leaf * directory of the file store. */ public Generic_Path getPathNext() { return new Generic_Path(Paths.get(lps[levels - 1].toString(), Long.toString(nextID))); } /** * Calculates and returns the current path of the directory for storing the * element identified by id. For efficiency, this does not check if id is * less than or equal to {@link #nextID}, but it should be and if not then * what is returned might not be useful. * * @param id The identifier of the element for which the current path is * wanted. * @return The current path of the directory for storing the element * identified by id. */ public Path getPath(long id) { Path[] paths = new Path[levels - 1]; ArrayList dirIndexes = getDirIndexes(id); Path p = root; for (int lvl = levels - 2; lvl >= 0; lvl--) { long range = ranges.get(lvl + 1); long l = range * dirIndexes.get(lvl + 1); //long l = range * dirIndexes.get(lvl); long u = l + range - 1L; paths[lvl] = Paths.get(p.toString(), getName(l, u)); p = paths[lvl]; } p = Paths.get(p.toString(), Long.toString(id)); return p; } /** * @param baseDir The baseDir of a file store for which the nextID is * returned. * @return The nextID of the file store with a baseDirectory baseDir. * @throws Exception If encountered */ public static Long getNextID(Generic_Path baseDir) throws Exception { if (baseDir == null) { return null; } return new Generic_FileStore(baseDir.getPath()).nextID; } /** * @return The nextID of the file store with a base directory baseDir. * @throws Exception If encountered */ public long getNextID() throws Exception { return nextID; } protected final String getName(long l, long u) { return Long.toString(l) + SEP + Long.toString(u); } /** * Initialises lps. This should be called when the directory structure grows * deeper. As the directory grows wider the appropriate paths in lps want * updating. */ protected final void initLPs() { lps = new Generic_Path[levels]; lps[0] = root; ArrayList dirIndexes = getDirIndexes(nextID, levels, ranges); for (int lvl = 1; lvl < levels; lvl++) { long range = ranges.get(lvl); long l = range * dirIndexes.get(lvl); long u = l + range - 1L; lps[lvl] = new Generic_Path(Paths.get(lps[lvl - 1].s, getName(l, u))); } } /** * Serializes and writes o to * {@code Paths.get(getHighestLeaf().toString(), name)}; * * @param o The Object to be serialised and written out. * @throws IOException If encountered. */ public void add(Object o) throws IOException { Path p = Paths.get(getHighestLeaf().toString(), name); Generic_IO.writeObject(o, p); } /** * Deserializes an Object from file at * {@code Paths.get(getPath(id).toString(), name)}. * * @param id The identifier for the Object to be deserialized. * @return The deserialized Object. * @throws IOException If encountered. * @throws java.lang.ClassNotFoundException If for some reason the Object * cannot otherwise be deserialized. */ public Object get(long id) throws IOException, ClassNotFoundException { Path p = Paths.get(getPath(id).toString(), name); return Generic_IO.readObject(p); } /** * Adds a new directory to the file store for storing item identified by * {@link #nextID}. * * @throws IOException If encountered. */ public void addDir() throws IOException { nextID++; if (nextID % rangeL == 0) { // Grow if (nextID == ranges.get(0)) { // Grow deeper. ranges.add(0, nextRange); root = new Generic_Path(Paths.get(baseDir.s, getName(0L, nextRange - 1))); initNextRange(); Files.createDirectory(root.getPath()); //System.out.println(root.toString()); Path target = Paths.get(root.s, lps[0].getFileName().toString()); Files.move(lps[0].getPath(), target); dirCounts.add(0, 1L); levels++; lps = new Generic_Path[levels]; lps[0] = root; // Add width. int level = levels - 2; long range = ranges.get(level); long dirCount = dirCounts.get(level); // Add directories up to the new highest leaf long l = dirCount * range; for (int lvl = 1; lvl < levels; lvl++) { long u = l + ranges.get(lvl) - 1; Path p = Paths.get(lps[lvl - 1].s, getName(l, u)); Files.createDirectory(p); //System.out.println(p.toString()); Generic_Collections.addToList(dirCounts, lvl, 1L); lps[lvl] = new Generic_Path(p); } } else { // Add width as needed. for (int lvl = 1; lvl < levels; lvl++) { long range = ranges.get(lvl); if (nextID % range == 0) { // Add a new directory. long dirCount = dirCounts.get(lvl); long l = dirCount * range; long u = l + range - 1; Path p = Paths.get(lps[lvl - 1].s, getName(l, u)); Files.createDirectory(p); //System.out.println(p.toString()); Generic_Collections.addToList(dirCounts, lvl, 1L); lps[lvl] = new Generic_Path(p); // Add other new directories up to the new highest leaf for (int lvl2 = lvl + 1; lvl2 < levels; lvl2++) { u = l + ranges.get(lvl2) - 1; p = Paths.get(lps[lvl2 - 1].s, getName(l, u)); Files.createDirectory(p); //System.out.println(p.toString()); Generic_Collections.addToList(dirCounts, lvl2, 1L); lps[lvl2] = new Generic_Path(p); } break; } } } } // Add to the currentDir Path p = Files.createDirectory( Paths.get(lps[levels - 1].s, Long.toString(nextID))); //System.out.println(p.toString()); } /** * @return a copy of {@link #baseDir}. */ public Generic_Path getBaseDir() { return new Generic_Path(baseDir); } /** * Tests the integrity of the file store from its base directory. * * @return true if the integrity seems fine. * @throws java.io.IOException If the file store lacks integrity. */ public final boolean testIntegrity() throws IOException { try (Stream paths = Files.walk(root.getPath())) { boolean ok; try { ok = paths.allMatch(path -> testPath(path)); } catch (NumberFormatException e) { throw new IOException("Some paths are not OK as they contain " + "leaf file names that cannot be converted to a " + "Long."); } if (!ok) { throw new IOException("Some paths are not OK as they are not " + "leaf files and are not not directories."); } } return true; } /** * @param p The path to test. * @return true if p is not a directory or if there is a directory at p and * the filename contains {@link #SEP} or can be readily converted to a Long. * @throws NumberFormatException if p is a directory, does not contain * {@link SEP} and cannot be readily converted to a Long. */ protected final boolean testPath(Path p) { String fn = p.getFileName().toString(); if (Files.isDirectory(p)) { if (fn.contains(SEP)) { return true; } else { Long.valueOf(fn); return true; } } else { return true; } } /** * @return The highest leaf directory for an initialised file store. * @throws IOException If encountered. */ public Path getHighestLeaf() throws IOException { return getPath(nextID); } /** * @return The highest leaf directory for a non-initialised file store. * @throws IOException If encountered. */ protected final Path findHighestLeaf() throws IOException { Path hd = getHighestDir(); List l = Generic_IO.getList(hd); if (l.size() == 1) { return l.get(0); } else { TreeMap s = new TreeMap<>(); l.forEach((p) -> { s.put(Long.valueOf(p.getFileName().toString()), p); }); return s.lastEntry().getValue(); } } /** * @return The path to the highest leaf directory. * @throws IOException If encountered. */ protected Path getHighestDir() throws IOException { Path p = getHighestDir0(baseDir.getPath()); Path p2 = getHighestDir0(p); while (p.compareTo(p2) != 0) { p = p2; p2 = getHighestDir0(p2); } return p; } /** * @param p The directory to find the highest directory in. * @return The path to the highest directory in the directory at {@code p}. * @throws IOException If encountered. */ protected Path getHighestDir0(Path p) throws IOException { List l = Generic_IO.getList(p); TreeMap m = new TreeMap<>(); l.forEach((p2) -> { String fn = p2.getFileName().toString(); if (fn.contains(SEP)) { m.put(Long.valueOf(fn.split(SEP)[1]), p2); } }); if (m.isEmpty()) { return p; } return m.lastEntry().getValue(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy