/*
* Copyright (C) 2014 University of Dundee & Open Microscopy Environment.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package ome.services.blitz.repo.path;
import java.util.List;
import java.util.Random;
import ome.services.util.SleepTimer;
import omero.ServerError;
/**
* Abstracts a pattern for using repository template path directories in
* {@link ome.services.blitz.repo.ManagedRepositoryI.TemplateDirectoryCreator}:
* pinpoint the next directory to use, then try to use it while watching for
* conflicts from other similar threads.
*
* @author m.t.b.carroll@dundee.ac.uk
* @since 5.0.3
*/
public abstract class MakeNextDirectory {
private Random rng = new Random();
/**
* Get the subdirectories to create for the given index,
* the first directories to be created corresponding to {@code index == 0}.
* @param index a non-negative index
* @return the subdirectories for that index
*/
public abstract List<String> getPathFor(long index);
/**
* If the circumstances (filesystem, etc.) are such that it is okay to use the given subdirectories.
* @param path the subdirectories to possibly use
* @return if the path may be used
* @throws ServerError if the path could not be tested
*/
public abstract boolean isAcceptable(List<String> path) throws ServerError;
/**
* Actually use the path. For instance, may create the directory or ensure that it exists.
* @param path the subdirectories to use
* @throws ServerError if the path could not be used
*/
public abstract void usePath(List<String> path) throws ServerError;
/**
* Use the first acceptable path (that with the lowest {@code index} for {@link #getPathFor(long)})
* and return the corresponding subdirectories.
* @return the used subdirectories
* @throws ServerError if the first acceptable path could not be found or used
*/
public List<String> useFirstAcceptable() throws ServerError {
/* highest unacceptable index found so far */
Long inclusiveLower = null;
/* lowest acceptable index found so far */
Long exclusiveHigher = null;
/* find and narrow bounds to pinpoint lowest acceptable index by binary search */
long index;
while (true) {
final long toProbe;
if (inclusiveLower == null) {
/* no bounds yet */
toProbe = 0;
final List<String> path = getPathFor(toProbe);
if (isAcceptable(path)) {
/* use the first of the directories */
index = toProbe;
break;
} else {
/* found a lower bound */
inclusiveLower = toProbe;
}
} else if (exclusiveHigher == null) {
/* only a lower bound, look further */
toProbe = inclusiveLower == 0 ? 1 : inclusiveLower << 1;
final List<String> path = getPathFor(toProbe);
if (isAcceptable(path)) {
/* found upper bound */
exclusiveHigher = toProbe;
} else {
/* moved lower bound */
inclusiveLower = toProbe;
}
} else if (exclusiveHigher - inclusiveLower < 2) {
/* the tight bounds identify the next directory */
index = exclusiveHigher;
break;
} else {
/* tighten bounds */
toProbe = (inclusiveLower + exclusiveHigher) >> 1;
final List<String> path = getPathFor(toProbe);
if (isAcceptable(path)) {
exclusiveHigher = toProbe;
} else {
inclusiveLower = toProbe;
}
}
}
while (true) {
List<String> path;
while (true) {
path = getPathFor(index);
if (isAcceptable(path)) {
/* the path is ready for using */
break;
} else {
/* the path has since been used, move on to the next increment */
index++;
}
}
try {
/* try using the next directory */
usePath(path);
} catch (ServerError e) {
/* is another thread trying to use the same directory? give it time to finish up */
SleepTimer.sleepFor(1000);
if (isAcceptable(path)) {
/* an acceptable path could not be used so something bad happened */
throw e;
} else {
/* try to fall out of synchronization with other threads in this same loop */
SleepTimer.sleepFor(rng.nextInt(500));
/* find the first non-existing directory and try again */
continue;
}
}
/* the directory that was used */
return path;
}
}
}