* finish rewrite of ExpressionFormat customizations

This commit is contained in:
Reinhard Pointner 2014-04-15 12:23:58 +00:00
parent 3339dc36d1
commit 1a730c3ec6
3 changed files with 367 additions and 62 deletions

View File

@ -1,13 +1,11 @@
package net.sourceforge.filebot.format; package net.sourceforge.filebot.format;
import static net.sourceforge.filebot.util.ExceptionUtilities.*; import static net.sourceforge.filebot.util.ExceptionUtilities.*;
import static net.sourceforge.filebot.util.FileUtilities.*; import static net.sourceforge.filebot.util.FileUtilities.*;
import groovy.lang.GroovyClassLoader;
import groovy.lang.GroovyRuntimeException; import groovy.lang.GroovyRuntimeException;
import groovy.lang.MissingPropertyException; import groovy.lang.MissingPropertyException;
import java.io.InputStreamReader;
import java.security.AccessController; import java.security.AccessController;
import java.text.FieldPosition; import java.text.FieldPosition;
import java.text.Format; import java.text.Format;
@ -25,25 +23,35 @@ import javax.script.ScriptEngine;
import javax.script.ScriptException; import javax.script.ScriptException;
import javax.script.SimpleScriptContext; import javax.script.SimpleScriptContext;
import org.codehaus.groovy.control.CompilerConfiguration;
import org.codehaus.groovy.control.MultipleCompilationErrorsException; import org.codehaus.groovy.control.MultipleCompilationErrorsException;
import org.codehaus.groovy.jsr223.GroovyScriptEngineFactory; import org.codehaus.groovy.control.customizers.ImportCustomizer;
import org.codehaus.groovy.jsr223.GroovyScriptEngineImpl;
public class ExpressionFormat extends Format { public class ExpressionFormat extends Format {
private static ScriptEngine engine; private static ScriptEngine engine;
private static Map<String, CompiledScript> scriptletCache = new HashMap<String, CompiledScript>(); private static Map<String, CompiledScript> scriptletCache = new HashMap<String, CompiledScript>();
protected static ScriptEngine createScriptEngine() {
CompilerConfiguration config = new CompilerConfiguration();
// include default functions
ImportCustomizer imports = new ImportCustomizer();
imports.addStaticStars(ExpressionFormatFunctions.class.getName());
config.addCompilationCustomizers(imports);
GroovyClassLoader classLoader = new GroovyClassLoader(Thread.currentThread().getContextClassLoader(), config);
return new GroovyScriptEngineImpl(classLoader);
}
protected static synchronized ScriptEngine getGroovyScriptEngine() throws ScriptException { protected static synchronized ScriptEngine getGroovyScriptEngine() throws ScriptException {
if (engine == null) { if (engine == null) {
engine = new GroovyScriptEngineFactory().getScriptEngine(); engine = createScriptEngine();
engine.eval(new InputStreamReader(ExpressionFormat.class.getResourceAsStream("ExpressionFormat.lib.groovy")));
} }
return engine; return engine;
} }
protected static synchronized CompiledScript compileScriptlet(String expression) throws ScriptException { protected static synchronized CompiledScript compileScriptlet(String expression) throws ScriptException {
Compilable engine = (Compilable) getGroovyScriptEngine(); Compilable engine = (Compilable) getGroovyScriptEngine();
CompiledScript scriptlet = scriptletCache.get(expression); CompiledScript scriptlet = scriptletCache.get(expression);
@ -60,18 +68,15 @@ public class ExpressionFormat extends Format {
private ScriptException lastException; private ScriptException lastException;
public ExpressionFormat(String expression) throws ScriptException { public ExpressionFormat(String expression) throws ScriptException {
this.expression = expression; this.expression = expression;
this.compilation = secure(compile(expression)); this.compilation = secure(compile(expression));
} }
public String getExpression() { public String getExpression() {
return expression; return expression;
} }
protected Object[] compile(String expression) throws ScriptException { protected Object[] compile(String expression) throws ScriptException {
List<Object> compilation = new ArrayList<Object>(); List<Object> compilation = new ArrayList<Object>();
@ -145,7 +150,6 @@ public class ExpressionFormat extends Format {
return compilation.toArray(); return compilation.toArray();
} }
public Bindings getBindings(Object value) { public Bindings getBindings(Object value) {
return new ExpressionBindings(value) { return new ExpressionBindings(value) {
@ -156,13 +160,11 @@ public class ExpressionFormat extends Format {
}; };
} }
@Override @Override
public StringBuffer format(Object object, StringBuffer sb, FieldPosition pos) { public StringBuffer format(Object object, StringBuffer sb, FieldPosition pos) {
return format(getBindings(object), sb); return format(getBindings(object), sb);
} }
public StringBuffer format(Bindings bindings, StringBuffer sb) { public StringBuffer format(Bindings bindings, StringBuffer sb) {
// use privileged bindings so we are not restricted by the script sandbox // use privileged bindings so we are not restricted by the script sandbox
Bindings priviledgedBindings = PrivilegedInvocation.newProxy(Bindings.class, bindings, AccessController.getContext()); Bindings priviledgedBindings = PrivilegedInvocation.newProxy(Bindings.class, bindings, AccessController.getContext());
@ -193,7 +195,6 @@ public class ExpressionFormat extends Format {
return sb; return sb;
} }
protected Object normalizeBindingValue(Object value) { protected Object normalizeBindingValue(Object value) {
// if the binding value is a String, remove illegal characters // if the binding value is a String, remove illegal characters
if (value instanceof CharSequence) { if (value instanceof CharSequence) {
@ -204,12 +205,10 @@ public class ExpressionFormat extends Format {
return value; return value;
} }
protected Object normalizeExpressionValue(Object value) { protected Object normalizeExpressionValue(Object value) {
return value; return value;
} }
protected void handleException(ScriptException exception) { protected void handleException(ScriptException exception) {
if (findCause(exception, MissingPropertyException.class) != null) { if (findCause(exception, MissingPropertyException.class) != null) {
lastException = new ExpressionException(new BindingException(findCause(exception, MissingPropertyException.class).getProperty(), "undefined", exception)); lastException = new ExpressionException(new BindingException(findCause(exception, MissingPropertyException.class).getProperty(), "undefined", exception));
@ -220,12 +219,10 @@ public class ExpressionFormat extends Format {
} }
} }
public ScriptException caughtScriptException() { public ScriptException caughtScriptException() {
return lastException; return lastException;
} }
private Object[] secure(Object[] compilation) { private Object[] secure(Object[] compilation) {
for (int i = 0; i < compilation.length; i++) { for (int i = 0; i < compilation.length; i++) {
Object snipped = compilation[i]; Object snipped = compilation[i];
@ -238,7 +235,6 @@ public class ExpressionFormat extends Format {
return compilation; return compilation;
} }
@Override @Override
public Object parseObject(String source, ParsePosition pos) { public Object parseObject(String source, ParsePosition pos) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();

View File

@ -0,0 +1,55 @@
package net.sourceforge.filebot.format;
import groovy.lang.Closure;
import java.util.ArrayList;
import java.util.List;
/**
* Global functions available in the {@link ExpressionFormat}
*/
public class ExpressionFormatFunctions {
/**
* General helpers and utilities
*/
public static Object c(Closure<?> c) {
try {
return c.call();
} catch (Exception e) {
return null;
}
}
public static Object any(Closure<?>... closures) {
for (Closure<?> it : closures) {
try {
Object result = it.call();
if (result != null) {
return result;
}
} catch (Exception e) {
// ignore
}
}
return null;
}
public static List<Object> allOf(Closure<?>... closures) {
List<Object> values = new ArrayList<Object>();
for (Closure<?> it : closures) {
try {
Object result = it.call();
if (result != null) {
values.add(result);
}
} catch (Exception e) {
// ignore
}
}
return values;
}
}

View File

@ -1,7 +1,21 @@
package net.sourceforge.filebot.format; package net.sourceforge.filebot.format;
import static java.util.regex.Pattern.*;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import net.sourceforge.filebot.util.FileUtilities;
import com.ibm.icu.text.Transliterator;
public class ExpressionFormatMethods { public class ExpressionFormatMethods {
/**
* Convenience methods for String.toLowerCase() and String.toUpperCase()
*/
public static String lower(String self) { public static String lower(String self) {
return self.toLowerCase(); return self.toLowerCase();
} }
@ -10,6 +24,11 @@ public class ExpressionFormatMethods {
return self.toUpperCase(); return self.toUpperCase();
} }
/**
* Pad strings or numbers with given characters ('0' by default).
*
* e.g. "1" -> "01"
*/
public static String pad(String self, int length, String padding) { public static String pad(String self, int length, String padding) {
while (self.length() < length) { while (self.length() < length) {
self = padding + self; self = padding + self;
@ -22,6 +41,241 @@ public class ExpressionFormatMethods {
} }
public static String pad(Number self, int length) { public static String pad(Number self, int length) {
return pad(self.toString(), length); return pad(self.toString(), length, "0");
}
/**
* Return a substring matching the given pattern or break.
*/
public static String match(String self, String pattern) {
return match(self, pattern, -1);
}
public static String match(String self, String pattern, int matchGroup) {
Matcher matcher = compile(pattern, CASE_INSENSITIVE | UNICODE_CASE | MULTILINE).matcher(self);
if (matcher.find()) {
return (matcher.groupCount() > 0 && matchGroup < 0 ? matcher.group(1) : matcher.group(matchGroup < 0 ? 0 : matchGroup)).trim();
} else {
throw new IllegalArgumentException("Pattern not found");
} }
} }
/**
* Return a list of all matching patterns or break.
*/
public static List<String> matchAll(String self, String pattern) {
return matchAll(self, pattern, 0);
}
public static List<String> matchAll(String self, String pattern, int matchGroup) {
List<String> matches = new ArrayList<String>();
Matcher matcher = compile(pattern, CASE_INSENSITIVE | UNICODE_CASE | MULTILINE).matcher(self);
while (matcher.find()) {
matches.add(matcher.group(matchGroup).trim());
}
if (matches.size() > 0) {
return matches;
} else {
throw new IllegalArgumentException("Pattern not found");
}
}
public static String removeAll(String self, String pattern) {
return compile(pattern, CASE_INSENSITIVE | UNICODE_CASE | MULTILINE).matcher(self).replaceAll("").trim();
}
/**
* Replace space characters with a given characters.
*
* e.g. "Doctor Who" -> "Doctor_Who"
*/
public static String space(String self, String replacement) {
return self.replaceAll("[:?._]", " ").trim().replaceAll("\\s+", replacement);
}
/**
* Upper-case all initials.
*
* e.g. "The Day a new Demon was born" -> "The Day A New Demon Was Born"
*/
public static String upperInitial(String self) {
Matcher matcher = compile("(?<=[&()+.,-;<=>?\\[\\]_{|}~ ]|^)[a-z]").matcher(self);
StringBuffer buffer = new StringBuffer();
while (matcher.find()) {
matcher.appendReplacement(buffer, matcher.group().toUpperCase());
}
matcher.appendTail(buffer);
return buffer.toString();
}
public static String sortName(String self) {
return sortName(self, "$2, $1");
}
public static String sortName(String self, String replacement) {
return compile("^(The|A|An)\\s(.+)", CASE_INSENSITIVE).matcher(self).replaceFirst(replacement).trim();
}
/**
* Get acronym, i.e. first letter of each word.
*
* e.g. "Deep Space 9" -> "DS9"
*/
public static String acronym(String self) {
String name = sortName(self, "$2");
Matcher matcher = compile("(?<=[&()+.,-;<=>?\\[\\]_{|}~ ]|^)[\\p{Alnum}]").matcher(name);
StringBuilder buffer = new StringBuilder();
while (matcher.find()) {
buffer.append(matcher.group().toUpperCase());
}
return buffer.toString();
}
/**
* Lower-case all letters that are not initials.
*
* e.g. "Gundam SEED" -> "Gundam Seed"
*/
public static String lowerTrail(String self) {
Matcher matcher = compile("\\b(\\p{Alpha})(\\p{Alpha}+)\\b").matcher(self);
StringBuffer buffer = new StringBuffer();
while (matcher.find()) {
matcher.appendReplacement(buffer, matcher.group(1) + matcher.group(2).toLowerCase());
}
matcher.appendTail(buffer);
return buffer.toString();
}
/**
* Return substring before the given pattern.
*/
public static String before(String self, String pattern) {
Matcher matcher = compile(pattern, CASE_INSENSITIVE | UNICODE_CASE).matcher(self);
// pattern was found, return leading substring, else return original value
return matcher.find() ? self.substring(0, matcher.start()).trim() : self;
}
/**
* Return substring after the given pattern.
*/
public static String after(String self, String pattern) {
Matcher matcher = compile(pattern, CASE_INSENSITIVE | UNICODE_CASE).matcher(self);
// pattern was found, return trailing substring, else return original value
return matcher.find() ? self.substring(matcher.end(), self.length()).trim() : self;
}
/**
* Replace trailing parenthesis including any leading whitespace.
*
* e.g. "The IT Crowd (UK)" -> "The IT Crowd"
*/
public static String replaceTrailingBrackets(String self) {
return replaceTrailingBrackets(self, "");
}
public static String replaceTrailingBrackets(String self, String replacement) {
return self.replaceAll("\\s*[(]([^)]*)[)]$", replacement).trim();
}
/**
* Replace 'part identifier'.
*
* e.g. "Today Is the Day: Part 1" -> "Today Is the Day, Part 1" or "Today Is the Day (1)" -> "Today Is the Day, Part 1"
*/
public static String replacePart(String self) {
return replacePart(self, "");
}
public static String replacePart(String self, String replacement) {
// handle '(n)', '(Part n)' and ': Part n' like syntax
String[] patterns = new String[] { "\\s*[(](\\w+)[)]$", "\\W+Part (\\w+)\\W*$" };
for (String pattern : patterns) {
Matcher matcher = compile(pattern, CASE_INSENSITIVE).matcher(self);
if (matcher.find()) {
return matcher.replaceAll(replacement).trim();
}
}
// no pattern matches, nothing to replace
return self;
}
/**
* Apply ICU transliteration
*
* @see http://userguide.icu-project.org/transforms/general
*/
public static String transliterate(String self, String transformIdentifier) {
return Transliterator.getInstance(transformIdentifier).transform(self);
}
/**
* Convert Unicode to ASCII as best as possible. Works with most alphabets/scripts used in the world.
*
* e.g. "Österreich" -> "Osterreich" "カタカナ" -> "katakana"
*/
public static String ascii(String self) {
return ascii(self, " ");
}
public static String ascii(String self, String fallback) {
return Transliterator.getInstance("Any-Latin;Latin-ASCII;[:Diacritic:]remove").transform(self).replaceAll("[^\\p{ASCII}]+", fallback).trim();
}
/**
* Replace multiple replacement pairs
*
* e.g. replace('ä', 'ae', 'ö', 'oe', 'ü', 'ue')
*/
public static String replace(String self, String tr0, String tr1, String... tr) {
// the first two parameters are required, the rest of the parameter sequence is optional
self = self.replace(tr0, tr1);
for (int i = 0; i < tr.length - 1; i += 2) {
String t = tr[i];
String r = tr[i + 1];
self = self.replace(t, r);
}
return self;
}
/**
* File utilities
*/
public static File getRoot(File self) {
return FileUtilities.listPath(self).get(0);
}
public static List<File> getPathList(File self) {
return FileUtilities.listPath(self);
}
public static File getRelativePathTail(File self, int tailSize) {
return FileUtilities.getRelativePathTail(self, tailSize);
}
public static long getDiskSpace(File self) {
List<File> list = FileUtilities.listPath(self);
for (int i = list.size() - 1; i >= 0; i--) {
if (list.get(i).exists()) {
long usableSpace = list.get(i).getUsableSpace();
if (usableSpace > 0) {
return usableSpace;
}
}
}
return 0;
}
}