fr.gael.dhus.datastore.HierarchicalDirectoryBuilder.java Source code

Java tutorial

Introduction

Here is the source code for fr.gael.dhus.datastore.HierarchicalDirectoryBuilder.java

Source

/*
 * Data Hub Service (DHuS) - For Space data distribution.
 * Copyright (C) 2013,2014,2015 GAEL Systems
 *
 * This file is part of DHuS software sources.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
package fr.gael.dhus.datastore;

import java.io.File;
import java.io.IOException;

import org.apache.commons.io.FileUtils;
import org.apache.log4j.Logger;

/**
 * Produce hierarchical directories in depth and limiting occurrences in
 * each directories.
 */
public class HierarchicalDirectoryBuilder implements DirectoryBuilder {
    public static final String DHUS_ENTRY_NAME = "dhus_entry";
    private static Logger logger = Logger.getLogger(HierarchicalDirectoryBuilder.class);
    private static Long counter = 0L;

    private File root;
    private Long maxOccurence;

    public HierarchicalDirectoryBuilder(File root, int max_occurence) {
        this.root = root;
        this.maxOccurence = new Long(max_occurence);

    }

    /**
     * Build a path from a given counter and according to a maximum number of
     * entry per level. The path steps are expressed in hexadecimal numbers with
     * an "x" prefix and are separated by a slash. All paths begin by a leading
     * slash.
     *
     * The algorithm is equivalent to the base conversion of the input <code>
     * counter</code> to a base radix equal to the <code>max_occurence</code>.
     * As a consequence, the <code>counter</code> shall not be negative and the
     * <code>max_occurence</code> shall be greater or equal to 2.
     *
     * Example, for a counter running from 0 to 100 with a max_occurrence of 3:
     *
     * <pre>
     * 0 -> "/x0"
     * 1 -> "/x1"
     * 2 -> "/x2"
     * 3 -> "/x3"
     * 4 -> "/x4"
     * 5 -> "/x5"
     * 6 -> "/x6"
     * 7 -> "/x7"
     * 8 -> "/x8"
     * 9 -> "/x9"
     * 10 -> "/xA"
     * 11 -> "/xB"
     * 12 -> "/xC"
     * 13 -> "/xD"
     * 14 -> "/xE"
     * 15 -> "/xF"
     * 16 -> "/x0/x1"
     * 17 -> "/x1/x1"
     * 18 -> "/x2/x1"
     * 19 -> "/x3/x1"
     * ...
     * 89 -> "/x9/x5"
     * 90 -> "/xA/x5"
     * 91 -> "/xB/x5"
     * 92 -> "/xC/x5"
     * 93 -> "/xD/x5"
     * 94 -> "/xE/x5"
     * 95 -> "/xF/x5"
     * 96 -> "/x0/x6"
     * 97 -> "/x1/x6"
     * 98 -> "/x2/x6"
     * 99 -> "/x3/x6"
     * </pre>
     *
     * @param counter the counter to be converted (positive or null).
     * @param max_occurrence the maximum number of entries per level (greater or
     *    equal to 2).
     * @return the hierarchical path (never null).
     */
    static String getHierarchicalPath(final long counter, final long max_occurrence)
            throws IllegalArgumentException {
        // Check that counter is positive or null
        if (counter < 0) {
            throw new IllegalArgumentException("Negative counter");
        }

        // Check that counter is strictly greater than 1
        if (max_occurrence <= 1) {
            throw new IllegalArgumentException("Maximum occurrence shall be greater than 1");
        }

        // Prepare output path
        String output_path = "";

        // Prepare a quotient, equal to the input counter
        long running_quotient = counter;

        // Loop until quotient reaches 0
        do {
            // Concatenate the remainder to the output path
            output_path += "/x" + Long.toHexString(running_quotient % max_occurrence).toUpperCase();

            // !update the quotient
            running_quotient = running_quotient / max_occurrence;
        } while (running_quotient > 0);

        // Return the built path
        return output_path;

    }

    /**
     * Returns the next available incoming folder.
     * @return unused incoming path. This path is available.
     */
    @Override
    public File getDirectory() {
        return getUnused();
    }

    /**
     * @return the root
     */
    public File getRoot() {
        return root;
    }

    /**
     * Reset the path computation counter.
     */
    public void resetCounter() {
        HierarchicalDirectoryBuilder.counter = 0L;
    }

    /**
     * Reset this hierarchical path builder algorithm counter to let the next 
     * call quickly access to the first free path.
     * This method helps to fill empty directories from the hierarchy if any. 
     */
    void recomputeCounterFirstFreePath() {
        resetCounter();
        File unused = null;
        do {
            String h_path = getHierarchicalPath(counter++, maxOccurence);
            unused = new File(new File(getRoot(), h_path), DHUS_ENTRY_NAME);
        } while (isUsed(unused));
        // place the counter to the fee position.
        counter--;
        logger.info("Computed incoming counter to " + counter);
    }

    private static boolean isHookInstalled = false;
    private static String COUNTER_FILE_NAME = ".counter";

    /**
     * The initialization of incoming consists re-computing the counter. 
     * For optimization purpose, the counter is saved/retrieved form 
     * incoming root file. A shutdown hook is installed to save the counter at
     * system exit.
     */
    void init() {
        // Try to read counter from local file
        File root = getRoot();
        File counter_file = new File(root, COUNTER_FILE_NAME);
        Long counter = null;
        if (counter_file.exists()) {
            try {
                String counter_string = FileUtils.readFileToString(counter_file);
                counter_string = counter_string.trim();
                counter = Long.parseLong(counter_string);
            } catch (IOException e) {
                logger.error("Counter file " + counter_file.getPath() + " cannot be accessed", e);
            } catch (NumberFormatException nfe) {
                logger.error("Counter file " + counter_file.getPath() + " malformed: " + nfe.getMessage());
            } catch (NullPointerException npe) {
                logger.error("Counter file " + counter_file.getPath() + "is empty.");
            }
        }
        // Counter file found: save it
        if (counter != null) {
            HierarchicalDirectoryBuilder.counter = counter;
        } else {
            // Counter file not found: compute it
            recomputeCounterFirstFreePath();
        }
        // Install shutdown hook to save the counter at the end of the 
        // system execution: shall be run only once.
        if (!isHookInstalled) {
            isHookInstalled = true;
            Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {

                @Override
                public void run() {
                    try {
                        // Recompute the counter path (case of root changed).
                        File root = getRoot();
                        File counter_file = new File(root, COUNTER_FILE_NAME);
                        // Save the counter.
                        FileUtils.writeStringToFile(counter_file, HierarchicalDirectoryBuilder.counter.toString());
                    } catch (IOException e) {
                        logger.error("Unable to save Incoming counter: " + e.getMessage());
                    }
                }
            }));
        }
    }

    /**
     * Looks for and creates a new incoming directory according to 
     * {@link #getHierarchicalPath(Long, Long)} method. If the computed path is
     * already used {@link #getUnused()} method recompute it.
     * @return available path.
     */
    private File getUnused() {
        File unused = null;
        do {
            String h_path = getHierarchicalPath(counter++, maxOccurence);
            unused = new File(new File(getRoot(), h_path), DHUS_ENTRY_NAME);
        } while (isUsed(unused));
        unused.mkdirs();
        return unused;
    }

    /**
     * Checks if the passed path is already used. The path is considered 'used'
     * if it has been already created. 
     * @param path to check.
     * @return true if the directory is used, false otherwise.
     */
    private boolean isUsed(File path) {
        return path.exists();
    }

    // End getHierarchicalPath(long, long)
}