nextflow.extension.Bolts.groovy Maven / Gradle / Ivy
Show all versions of nxf-commons Show documentation
/*
* Copyright (c) 2013-2017, Centre for Genomic Regulation (CRG).
* Copyright (c) 2013-2017, Paolo Di Tommaso and the respective authors.
*
* This file is part of 'Nextflow'.
*
* Nextflow 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 3 of the License, or
* (at your option) any later version.
*
* Nextflow 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 Nextflow. If not, see .
*/
package nextflow.extension
import java.nio.file.Path
import java.util.concurrent.locks.Lock
import java.util.regex.Pattern
import groovy.transform.CompileDynamic
import groovy.transform.CompileStatic
import nextflow.file.FileHelper
import nextflow.util.CheckHelper
import nextflow.util.Duration
import nextflow.file.FileMutex
import nextflow.util.MemoryUnit
import org.apache.commons.lang.StringUtils
import org.codehaus.groovy.runtime.DefaultGroovyMethods
import org.codehaus.groovy.runtime.GStringImpl
import org.codehaus.groovy.runtime.ResourceGroovyMethods
import org.codehaus.groovy.runtime.StringGroovyMethods
import org.slf4j.Logger
/**
* Generic extensions
*
* See more about extension methods
* http://docs.codehaus.org/display/GROOVY/Creating+an+extension+module
*
* @author Paolo Di Tommaso
*/
@CompileStatic
class Bolts {
static private Pattern PATTERN_RIGHT_TRIM = ~/\s+$/
static private Pattern PATTERN_LEFT_TRIM = ~/^\s+/
static List pairs(Map self, Map opts=null) {
def flat = opts?.flat == true
def result = []
for( Map.Entry entry : self.entrySet() ) {
if( flat && entry.value instanceof Collection )
entry.value.iterator().each { result << [entry.key, it] }
else
result << [entry.key, entry.value]
}
return result
}
/**
* Remove the left side after a dot (including it) e.g.
*
* 0.10 => 0
* 10000.00 => 10000
*
*
* @param self
* @return
*/
static String trimDotZero(String self) {
int p = self?.indexOf('.')
p!=-1 ? self.substring(0,p) : self
}
/**
* Remove blank chars at the end of the string
*
* @param self The string itself
* @return The string with blanks removed
*/
static String rightTrim(String self) {
self.replaceAll(PATTERN_RIGHT_TRIM,'')
}
/**
* Remove blank chars at string beginning
*
* @param self The string itself
* @return The string with blanks removed
*/
static String leftTrim( String self ) {
self.replaceAll(PATTERN_LEFT_TRIM,'')
}
/**
* Strips any of a set of characters from the start and end of a String.
* This is similar to {@link String#trim()} but allows the characters
* to be stripped to be controlled.
*
* A null
input String returns null
.
* An empty string ("") input returns the empty string.
*
* If the stripChars String is null
, whitespace is
* stripped as defined by {@link Character#isWhitespace(char)}.
* Alternatively use {@link #strip(String)}.
*
*
* StringUtils.strip(null, *) = null
* StringUtils.strip("", *) = ""
* StringUtils.strip("abc", null) = "abc"
* StringUtils.strip(" abc", null) = "abc"
* StringUtils.strip("abc ", null) = "abc"
* StringUtils.strip(" abc ", null) = "abc"
* StringUtils.strip(" abcyx", "xyz") = " abc"
*
*
* @param str the String to remove characters from, may be null
* @param stripChars the characters to remove, null treated as whitespace
* @return the stripped String, null
if null String input
*/
static strip( String self, String stripChars = null ) {
StringUtils.strip(self, stripChars)
}
/**
* Strips any of a set of characters from the start of a String.
*
* A null
input String returns null
.
* An empty string ("") input returns the empty string.
*
* If the stripChars String is null
, whitespace is
* stripped as defined by {@link Character#isWhitespace(char)}.
*
*
* StringUtils.stripStart(null, *) = null
* StringUtils.stripStart("", *) = ""
* StringUtils.stripStart("abc", "") = "abc"
* StringUtils.stripStart("abc", null) = "abc"
* StringUtils.stripStart(" abc", null) = "abc"
* StringUtils.stripStart("abc ", null) = "abc "
* StringUtils.stripStart(" abc ", null) = "abc "
* StringUtils.stripStart("yxabc ", "xyz") = "abc "
*
*
* @param str the String to remove characters from, may be null
* @param stripChars the characters to remove, null treated as whitespace
* @return the stripped String, null
if null String input
*/
static stripStart( String self, String stripChars = null ) {
StringUtils.stripStart(self, stripChars)
}
/**
* Strips any of a set of characters from the end of a String.
*
* A null
input String returns null
.
* An empty string ("") input returns the empty string.
*
* If the stripChars String is null
, whitespace is
* stripped as defined by {@link Character#isWhitespace(char)}.
*
*
* StringUtils.stripEnd(null, *) = null
* StringUtils.stripEnd("", *) = ""
* StringUtils.stripEnd("abc", "") = "abc"
* StringUtils.stripEnd("abc", null) = "abc"
* StringUtils.stripEnd(" abc", null) = " abc"
* StringUtils.stripEnd("abc ", null) = "abc"
* StringUtils.stripEnd(" abc ", null) = " abc"
* StringUtils.stripEnd(" abcyx", "xyz") = " abc"
* StringUtils.stripEnd("120.00", ".0") = "12"
*
*
* @param str the String to remove characters from, may be null
* @param stripChars the set of characters to remove, null treated as whitespace
* @return the stripped String, null
if null String input
*/
static stripEnd( String self, String stripChars = null ) {
StringUtils.stripEnd(self, stripChars)
}
/**
* Capitalizes a String changing the first letter to title case as
* per {@link Character#toTitleCase(char)}. No other letters are changed.
*
* For a word based algorithm, see {@link WordUtils#capitalize(String)}.
* A null
input String returns null
.
*
*
* StringUtils.capitalize(null) = null
* StringUtils.capitalize("") = ""
* StringUtils.capitalize("cat") = "Cat"
* StringUtils.capitalize("cAt") = "CAt"
*
*
*/
static String capitalize(String self) {
StringUtils.capitalize(self)
}
/**
* Uncapitalizes a String changing the first letter to title case as
* per {@link Character#toLowerCase(char)}. No other letters are changed.
*
* For a word based algorithm, see {@link WordUtils#uncapitalize(String)}.
* A null
input String returns null
.
*
*
* StringUtils.uncapitalize(null) = null
* StringUtils.uncapitalize("") = ""
* StringUtils.uncapitalize("Cat") = "cat"
* StringUtils.uncapitalize("CAT") = "cAT"
*
*
*/
static String uncapitalize(String self) {
StringUtils.uncapitalize(self)
}
/**
* Check if a alphabetic characters in a string are lowercase. Non alphabetic characters are ingored
* @param self The string to check
* @return {@true} if the string contains no uppercase characters, {@code false} otherwise
*/
static boolean isLowerCase(String self) {
if( self ) for( int i=0; i T withLock( Lock self, boolean interruptible = false, Closure closure ) {
// acquire the lock
if( interruptible )
self.lockInterruptibly()
else
self.lock()
try {
return closure.call()
}
finally {
self.unlock();
}
}
/**
* Invokes the specify closure only if it is able to acquire a lock
*
* @param self
* @param interruptible
* @param closure
* @return the closure result
*/
static boolean tryLock( Lock self, Closure closure ) {
if( !self.tryLock() )
return false
try {
closure.call()
}
finally {
self.unlock()
return true
}
}
/**
* Creates a file system wide lock that prevent two or more JVM instances/process
* to work on the same file
*
* Note: this does not protected against multiple-thread accessing the file in a
* concurrent manner.
*
* @param
* self The file over which define the lock
* @param
* timeout An option timeout elapsed which the a {@link InterruptedException} is thrown
* @param
* closure The action to apply during the lock file spawn
* @return
* The user provided {@code closure} result
*
* @throws
* InterruptedException if the lock cannot be acquired within the specified {@code timeout}
*/
static withLock(File self, Duration timeout = null, Closure closure) {
def locker = new FileMutex(self)
if( timeout )
locker.setTimeout(timeout)
locker.lock(closure)
}
/**
* Creates a file system wide lock that prevent two or more JVM instances/process
* to work on the same file
*
* Note: this does not protected against multiple-thread accessing the file in a
* concurrent manner.
*
* @param
* self The file over which define the lock
* @param
* timeout An option timeout elapsed which the a {@link InterruptedException} is thrown
* @param
* closure The action to apply during the lock file spawn
* @return
* The user provided {@code closure} result
*
* @throws
* InterruptedException if the lock cannot be acquired within the specified {@code timeout}
*/
static withLock( Path self, Duration timeout, Closure closure ) {
def locker = new FileMutex(self.toFile())
if( timeout )
locker.setTimeout(timeout)
locker.lock(closure)
}
/**
* Converts a {@code String} to a {@code Duration} object
*
* @param self
* @param type
* @return
*/
static def asType( String self, Class type ) {
if( type == Duration ) {
return new Duration(self)
}
else if( Path.isAssignableFrom(type) ) {
return FileHelper.asPath(self)
}
else if( type == MemoryUnit ) {
return new MemoryUnit(self)
}
StringGroovyMethods.asType(self, type);
}
/**
* Converts a {@code GString} to a {@code Duration} object
*
* @param self
* @param type
* @return
*/
static def asType( GString self, Class type ) {
if( type == Duration ) {
return new Duration(self.toString())
}
else if( Path.isAssignableFrom(type) ) {
return FileHelper.asPath(self)
}
else if( type == MemoryUnit ) {
return new MemoryUnit(self.toString())
}
StringGroovyMethods.asType(self, type);
}
/**
* Converts a {@code Number} to a {@code Duration} object
*
* @param self
* @param type
* @return
*/
static def asType( Number self, Class type ) {
if( type == Duration ) {
return new Duration(self.longValue())
}
if( type == MemoryUnit ) {
return new MemoryUnit(self.longValue())
}
DefaultGroovyMethods.asType(self, type);
}
/**
* Converts a {@code File} to a {@code Path} object
*
* @param self
* @param type
* @return
*/
static def asType( File self, Class type ) {
if( Path.isAssignableFrom(type) ) {
return self.toPath()
}
ResourceGroovyMethods.asType(self, type);
}
static def T getOrCreate( Map self, key, factory ) {
if( self.containsKey(key) )
return (T)self.get(key)
synchronized (self) {
if( self.containsKey(key) )
return (T)self.get(key)
def result = factory instanceof Closure ? factory.call(key) : factory
self.put(key,result)
return (T)result
}
}
/**
* Navigate a map of maps traversing multiple attribute using dot notation. For example:
* {@code x.y.z }
*
* @param self The root map object
* @param key A dot separated list of keys
* @param closure An optional closure to be applied. Only if all keys exists
* @return The value associated to the specified key(s) or null on first missing entry
*/
static def navigate( Map self, String key, Closure closure = null ) {
assert key
def items = key.split(/\./)
def current = self.get(items[0])
for( int i=1; i
if( value instanceof Map ) {
result.put( key, toConfigObject((Map)value) )
}
else {
result.put( key, value )
}
}
return result
}
static private normalize0( config ) {
if( config instanceof Map ) {
Map result = new LinkedHashMap(config.size())
config.keySet().each { name ->
def value = (config as Map).get(name)
result.put(name, normalize0(value))
}
return result
}
else if( config instanceof Collection ) {
List result = new ArrayList(config.size())
for( entry in config ) {
result << normalize0(entry)
}
return result
}
else if( config instanceof GString ) {
return config.toString()
}
else {
return config
}
}
/**
* Indent each line in the given test by a specified prefix
*
* @param text
* @param prefix
* @return The string indented
*/
public static String indent( String text, String prefix = ' ' ) {
def result = new StringBuilder()
def lines = text ? text.readLines() : Collections.emptyList()
for( int i=0; i bestMatches( Collection options, String sample ) {
assert sample
assert options
// Otherwise look for the most similar
Map diffs = [:]
options.each {
diffs[it] = StringUtils.getLevenshteinDistance(sample, it)
}
// sort the Levenshtein Distance and get the fist entry
def sorted = diffs.sort { Map.Entry it -> it.value }
def nearest = (Map.Entry)sorted.find()
def min = nearest.value
def len = sample.length()
def threshold = len<=3 ? 1 : ( len > 10 ? 5 : Math.floor(len/2))
List result
if( min <= threshold ) {
result = (List)sorted.findAll { it.value==min } .collect { it.key }
}
else {
result = []
}
return result
}
static boolean isCamelCase(String str) {
if( !str ) return false
for( int i=0; i T cloneWith( T self, binding ) {
def copy = (T)self.clone()
if( binding != null ) {
copy.setDelegate(binding)
copy.setResolveStrategy( Closure.DELEGATE_FIRST )
}
return copy
}
/**
* Create a copy of a {@link GString} object cloning all values that are instance of {@link Closure}
*
* @param self The {@link GString} itself
* @param binding A {@link Map} object that is set as delegate object in the cloned closure.
* @return The cloned {@link GString} instance
*/
static GString cloneWith( GString self, binding ) {
def values = new Object[ self.valueCount ]
// clone the gstring setting the delegate for each closure argument
for( int i=0; i closest(Collection options, String sample ) {
assert sample
if( !options )
return Collections.emptyList()
// Otherwise look for the most similar
def diffs = [:]
options.each {
diffs[it] = StringUtils.getLevenshteinDistance(sample, it)
}
// sort the Levenshtein Distance and get the fist entry
def sorted = diffs.sort { it.value }
def nearest = sorted.find()
def min = nearest.value
def len = sample.length()
def threshold = len<=3 ? 1 : ( len > 10 ? 5 : Math.floor(len/2))
def result
if( min <= threshold ) {
result = sorted.findAll { it.value==min } .collect { it.key }
}
else {
result = []
}
return result
}
private static HashMap