Repository: redpen-cc/redpen
Pull request #703 (2016-08-30 07:35:30)

ability to load embedded JavaScript Validators located in resources/cc/redpen/validator/(section|sentence)
ValidatorFactory to load embedded JavaScript validators located in resources/cc/redpen/validator/(section|sentence)



Patch:
diff --git a/redpen-core/src/main/java/cc/redpen/RedPen.java b/redpen-core/src/main/java/cc/redpen/RedPen.java
index 196d2bf8..b30776de 100644
--- a/redpen-core/src/main/java/cc/redpen/RedPen.java
+++ b/redpen-core/src/main/java/cc/redpen/RedPen.java
@@ -40,7 +40,7 @@ import java.util.*;
 public class RedPen {
     private static final Logger LOG = LoggerFactory.getLogger(RedPen.class);
 
-    public static final String VERSION = "1.7.1";
+    public static final String VERSION = "1.7.0";
 
     private final Configuration configuration;
     private final SentenceExtractor sentenceExtractor;
@@ -190,7 +190,7 @@ public class RedPen {
                 for (PreprocessorRule rule : document.getPreprocessorRules()) {
                     switch (rule.getRuleType()) {
                         case SUPPRESS:
-                            if (rule.isTriggeredBy(document, error)) {
+                            if (rule.isTriggeredBy(document, error.getLineNumber(), error.getValidatorName())){
                                 suppressedErrors.add(error);
                             }
                             break;
diff --git a/redpen-core/src/main/java/cc/redpen/parser/PreprocessorRule.java b/redpen-core/src/main/java/cc/redpen/parser/PreprocessorRule.java
index ba6944e2..ee708d53 100644
--- a/redpen-core/src/main/java/cc/redpen/parser/PreprocessorRule.java
+++ b/redpen-core/src/main/java/cc/redpen/parser/PreprocessorRule.java
@@ -74,38 +74,13 @@ public class PreprocessorRule {
      * of the supplied document
      *
      * @param document input document
-     * @param error
-     * @return
-     */
-    public boolean isTriggeredBy(Document document, ValidationError error) {
-        int errorLineNumber = error.getLineNumber();
-        String validatorName = error.getValidatorName();
-        if (validatorName.equals("JavaScript")) {
-            validatorName = extractJSValidatorName(error.getMessage());
-        }
-        return isTriggeredBy(document, errorLineNumber, validatorName);
-    }
-
-    private String extractJSValidatorName(String message) {
-        String[] segment = message.split(" ");
-        String jsValidatorName = segment[0].replaceAll("\\[", "")
-                .replaceAll(".js\\]", "")
-                .trim();
-        return jsValidatorName;
-    }
-
-    /**
-     * Return true if the rule is triggered by the given line and name given the structure
-     * of the supplied document
-     *
-     * @param document input document
-     * @param errorLineNumber
-     * @param validatorName
-     * @return
+     * @param errorLineNumber line number
+     * @param validatorName validator name
+     * @return true if the error is triggered by the specified validator
      */
     public boolean isTriggeredBy(Document document, int errorLineNumber, String validatorName) {
         if ((lineNumberLimit < lineNumber) || (errorLineNumber < lineNumberLimit)) {
-            if (parameters.isEmpty() || parameters.contains(validatorName.toLowerCase())) {
+            if (parameters.isEmpty() || parameters.contains(validatorName.toLowerCase().replaceAll("\\.js$",""))) {
                 // find out if the rule line number is in the same section as the other line number
                 for (Section section : document) {
                     List<Sentence> allBlockSentences = new ArrayList<>();
diff --git a/redpen-core/src/main/java/cc/redpen/validator/JavaScriptLoader.java b/redpen-core/src/main/java/cc/redpen/validator/JavaScriptLoader.java
new file mode 100644
index 00000000..492cc5ec
--- /dev/null
+++ b/redpen-core/src/main/java/cc/redpen/validator/JavaScriptLoader.java
@@ -0,0 +1,190 @@
+package cc.redpen.validator;
+
+import cc.redpen.RedPenException;
+import cc.redpen.config.SymbolTable;
+import cc.redpen.model.Document;
+import cc.redpen.model.Section;
+import cc.redpen.model.Sentence;
+import cc.redpen.tokenizer.TokenElement;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.script.*;
+import java.text.MessageFormat;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+public class JavaScriptLoader extends Validator {
+    private static final Logger LOG = LoggerFactory.getLogger(JavaScriptLoader.class);
+    private final String name;
+    private final Invocable invocable;
+    private final String message;
+    private static final ScriptEngineManager manager = new ScriptEngineManager();
+
+    private static final String[] methodsToBeExposedToJS = {"getInt", "getFloat", "getString", "getBoolean", "getSet",
+            "getConfigAttribute", "getSymbolTable", "addError", "addErrorWithPosition",
+            "addLocalizedError", "addLocalizedErrorFromToken", "addLocalizedErrorWithPosition"};
+
+    JavaScriptLoader(String name, String script) throws RedPenException {
+        this.name = name;
+        setValidatorName(name);
+        ScriptEngine engine = manager.getEngineByName("nashorn");
+        try {
+            engine.put("redpenToBeBound", this);
+
+
+            for (String methodToBeExposed : methodsToBeExposedToJS) {
+                engine.eval(String.format(
+                        "var %s = Function.prototype.bind.call(redpenToBeBound.%s, redpenToBeBound);",
+                        methodToBeExposed, methodToBeExposed));
+            }
+            try {
+                engine.eval("var _JavaScriptValidatorTest = Java.type('cc.redpen.validator.JavaScriptValidatorTest');");
+            } catch (RuntimeException e) {
+                if (!(e.getCause() instanceof ClassNotFoundException)) {
+                    throw e;
+                }
+            }
+
+            engine.eval("java = undefined; javax = undefined; Java = undefined; load = undefined; redpenToBeBound = undefined;");
+
+            CompiledScript compiledScript = ((Compilable) engine).compile(script);
+            compiledScript.eval();
+            this.message = (String) engine.get("message");
+            this.invocable = (Invocable) engine;
+        } catch (ScriptException e) {
+            throw new RedPenException(e);
+        }
+    }
+
+    @Override
+    public void preValidate(Sentence sentence) {
+        call("preValidateSentence", sentence);
+    }
+
+    @Override
+    public void preValidate(Section section) {
+        call("preValidateSection", section);
+    }
+
+    @Override
+    public void validate(Document document) {
+        call("validateDocument", document);
+    }
+
+    @Override
+    public void validate(Sentence sentence) {
+        call("validateSentence", sentence);
+    }
+
+    @Override
+    public void validate(Section section) {
+        call("validateSection", section);
+    }
+
+    private Map<String, Boolean> functionExistenceMap = new HashMap<>();
+
+    private void call(String functionName, Object... args) {
+        Boolean functionExists = functionExistenceMap.getOrDefault(functionName, true);
+        if (functionExists) {
+            try {
+                invocable.invokeFunction(functionName, args);
+            } catch (ScriptException e) {
+                LOG.error("failed to invoke {}", functionName, e);
+            } catch (NoSuchMethodException ignore) {
+                functionExistenceMap.put(functionName, false);
+            }
+        }
+    }
+
+    // give Validator methods public access so that they can be bound with JavaScript
+    @Override
+    public int getInt(String name) {
+        return super.getInt(name);
+    }
+
+    @Override
+    public float getFloat(String name) {
+        return super.getFloat(name);
+    }
+
+    @Override
+    public String getString(String name) {
+        return super.getString(name);
+    }
+
+    @Override
+    public boolean getBoolean(String name) {
+        return super.getBoolean(name);
+    }
+
+    @Override
+    public Set<String> getSet(String name) {
+        return super.getSet(name);
+    }
+
+    @Override
+    public Optional<String> getConfigAttribute(String name) {
+        return super.getConfigAttribute(name);
+    }
+
+    @Override
+    public SymbolTable getSymbolTable() {
+        return super.getSymbolTable();
+    }
+
+    @Override
+    public void addError(String message, Sentence sentenceWithError) {
+        super.addError(message, sentenceWithError);
+    }
+
+    @Override
+    Object getOrDefault(String name) {
+        // script specific parameter wins
+        Object value = super.getOrDefault(this.name.replaceAll("\\.js$", "") + "-" + name);
+        if (value == null) {
+            // fallback to normal parameter
+            value = super.getOrDefault(name);
+        }
+        return value;
+    }
+
+
+    @Override
+    public void addErrorWithPosition(String message, Sentence sentenceWithError,
+                                     int start, int end) {
+        super.addLocalizedErrorWithPosition(message, sentenceWithError, start, end);
+    }
+
+    @Override
+    public void addLocalizedError(Sentence sentenceWithError, Object... args) {
+        super.addLocalizedError(sentenceWithError, args);
+    }
+
+    @Override
+    public void addLocalizedError(String messageKey, Sentence sentenceWithError, Object... args) {
+        super.addLocalizedError(messageKey, sentenceWithError, args);
+    }
+
+    @Override
+    public void addLocalizedErrorFromToken(Sentence sentenceWithError, TokenElement token) {
+        super.addLocalizedErrorFromToken(sentenceWithError, token);
+    }
+
+    @Override
+    public void addLocalizedErrorWithPosition(Sentence sentenceWithError,
+                                              int start, int end, Object... args) {
+        super.addLocalizedErrorWithPosition(sentenceWithError, start, end, args);
+    }
+
+    @Override
+    protected String getLocalizedErrorMessage(String key, Object... args) {
+        if (message != null) {
+            return MessageFormat.format(message, args);
+        } else {
+            return super.getLocalizedErrorMessage(key, args);
+        }
+    }
+}
diff --git a/redpen-core/src/main/java/cc/redpen/validator/JavaScriptValidator.java b/redpen-core/src/main/java/cc/redpen/validator/JavaScriptValidator.java
index b7462af8..73b1e585 100644
--- a/redpen-core/src/main/java/cc/redpen/validator/JavaScriptValidator.java
+++ b/redpen-core/src/main/java/cc/redpen/validator/JavaScriptValidator.java
@@ -18,21 +18,19 @@
 package cc.redpen.validator;
 
 import cc.redpen.RedPenException;
-import cc.redpen.config.SymbolTable;
+import cc.redpen.config.Configuration;
+import cc.redpen.config.ValidatorConfiguration;
 import cc.redpen.model.Document;
 import cc.redpen.model.Section;
 import cc.redpen.model.Sentence;
-import cc.redpen.tokenizer.TokenElement;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import javax.script.*;
 import java.io.File;
 import java.io.IOException;
 import java.nio.charset.Charset;
 import java.nio.file.Files;
 import java.nio.file.Paths;
-import java.text.MessageFormat;
 import java.util.*;
 
 /**
@@ -58,7 +56,7 @@ import java.util.*;
  */
 public class JavaScriptValidator extends Validator {
     private static final Logger LOG = LoggerFactory.getLogger(JavaScriptValidator.class);
-    final List<Script> scripts = new ArrayList<>();
+    final List<JavaScriptLoader> scripts = new ArrayList<>();
 
     public JavaScriptValidator() {
         super("script-path", "js");
@@ -75,19 +73,27 @@ public class JavaScriptValidator extends Validator {
                 for (File file : jsValidatorFiles) {
                     if (file.isFile() && file.getName().endsWith(".js")) {
                         try {
-                            scripts.add(new Script(this, file.getName(), loadCached(file)));
+                            JavaScriptLoader valid = new JavaScriptLoader(file.getName(), loadCached(file));
+                            scripts.add(valid);
                         } catch (IOException e) {
                             LOG.error("Exception while reading js file", e);
                         }
                     }
                 }
             }
-        }
-        catch (RedPenException e) {
+        } catch (RedPenException e) {
             LOG.warn("JavaScript validators directory is missing: {}", e.toString());
         }
     }
 
+    @Override
+    public void preInit(ValidatorConfiguration config, Configuration globalConfig) throws RedPenException {
+        super.preInit(config, globalConfig);
+        for (JavaScriptLoader js : scripts) {
+            js.preInit(config, globalConfig);
+        }
+    }
+
     private static final Map<File, String> fileCache = new HashMap<>();
     private static final Map<File, Long> loadTime = new HashMap<>();
 
@@ -111,192 +117,50 @@ public class JavaScriptValidator extends Validator {
         return read;
     }
 
+    List<ValidationError> errors;
+
+    @Override
+    public void setErrorList(List<ValidationError> errors) {
+        this.errors = errors;
+    }
+
     @Override
     public void preValidate(Sentence sentence) {
-        for (Script js : scripts) {
-            call(js, "preValidateSentence", sentence);
+        for (JavaScriptLoader js : scripts) {
+            js.setErrorList(errors);
+            js.preValidate(sentence);
         }
     }
 
     @Override
     public void preValidate(Section section) {
-        for (Script js : scripts) {
-            call(js, "preValidateSection", section);
+        for (JavaScriptLoader js : scripts) {
+            js.setErrorList(errors);
+            js.preValidate(section);
         }
     }
 
     @Override
     public void validate(Document document) {
-        for (Script js : scripts) {
-            call(js, "validateDocument", document);
+        for (JavaScriptLoader js : scripts) {
+            js.setErrorList(errors);
+            js.validate(document);
         }
     }
 
     @Override
     public void validate(Sentence sentence) {
-        for (Script js : scripts) {
-            call(js, "validateSentence", sentence);
+        for (JavaScriptLoader js : scripts) {
+            js.setErrorList(errors);
+            js.validate(sentence);
         }
     }
 
     @Override
     public void validate(Section section) {
-        for (Script js : scripts) {
-            call(js, "validateSection", section);
-        }
-    }
-
-    private Map<Script, Map<String, Boolean>> functionExistenceMap = new HashMap<>();
-
-   private Script currentJS;
-
-    private void call(Script js, String functionName, Object... args) {
-        this.currentJS = js;
-        Map<String, Boolean> map = functionExistenceMap.computeIfAbsent(js, e -> new HashMap<>());
-        Boolean functionExists = map
-                .getOrDefault(functionName, true);
-        if (functionExists) {
-            try {
-                js.invocable.invokeFunction(functionName, args);
-            } catch (ScriptException e) {
-                LOG.error("failed to invoke {}", functionName, e);
-            } catch (NoSuchMethodException ignore) {
-                map.put(functionName, false);
-            }
-        }
-    }
-
-    // give Validator methods public access so that they can be bound with JavaScript
-    @Override
-    public int getInt(String name) {
-        return super.getInt(name);
-    }
-
-    @Override
-    public float getFloat(String name) {
-        return super.getFloat(name);
-    }
-
-    @Override
-    public String getString(String name) {
-        return super.getString(name);
-    }
-
-    @Override
-    public boolean getBoolean(String name) {
-        return super.getBoolean(name);
-    }
-
-    @Override
-    public Set<String> getSet(String name) {
-        return super.getSet(name);
-    }
-
-    @Override
-    public Optional<String> getConfigAttribute(String name) {
-        return super.getConfigAttribute(name);
-    }
-
-    @Override
-    public SymbolTable getSymbolTable() {
-        return super.getSymbolTable();
-    }
-
-    @Override
-    public void addError(String message, Sentence sentenceWithError) {
-        super.addError(String.format("[%s] %s", currentJS.name, message), sentenceWithError);
-    }
-
-    @Override
-    Object getOrDefault(String name){
-        // script specific parameter wins
-        Object value = super.getOrDefault(currentJS.name.replaceAll("\\.js$","") + "-" + name);
-        if (value == null) {
-            // fallback to normal parameter
-            value = super.getOrDefault(name);
-        }
-        return value;
-    }
-
-
-    @Override
-    public void addErrorWithPosition(String message, Sentence sentenceWithError,
-                                     int start, int end) {
-        super.addLocalizedErrorWithPosition(String.format("[%s] %s", currentJS.name, message),
-                sentenceWithError, start, end);
-    }
-
-    @Override
-    public void addLocalizedError(Sentence sentenceWithError, Object... args) {
-        super.addLocalizedError(sentenceWithError, args);
-    }
-
-    @Override
-    public void addLocalizedError(String messageKey, Sentence sentenceWithError, Object... args) {
-        super.addLocalizedError(messageKey, sentenceWithError, args);
-    }
-
-    @Override
-    public void addLocalizedErrorFromToken(Sentence sentenceWithError, TokenElement token) {
-        super.addLocalizedErrorFromToken(sentenceWithError, token);
-    }
-
-    @Override
-    public void addLocalizedErrorWithPosition(Sentence sentenceWithError,
-                                              int start, int end, Object... args) {
-        super.addLocalizedErrorWithPosition(sentenceWithError, start, end, args);
-    }
-
-    @Override
-    protected String getLocalizedErrorMessage(String key, Object... args) {
-        String formatted;
-        if (currentJS.message != null) {
-            formatted = MessageFormat.format(currentJS.message, args);
-        } else {
-            formatted = super.getLocalizedErrorMessage(key, args);
+        for (JavaScriptLoader js : scripts) {
+            js.setErrorList(errors);
+            js.validate(section);
         }
-        return MessageFormat.format("[{0}] {1}", currentJS.name, formatted);
     }
-
-    class Script {
-        final String name;
-        final Invocable invocable;
-        final String message;
-        ScriptEngineManager manager = new ScriptEngineManager();
-
-        Script(JavaScriptValidator validator, String name, String script) throws RedPenException {
-            this.name = name;
-            ScriptEngine engine = manager.getEngineByName("nashorn");
-            try {
-                engine.put("redpenToBeBound", validator);
-
-                String[] methodsToBeExposedToJS = {"getInt", "getFloat", "getString", "getBoolean", "getSet",
-                        "getConfigAttribute", "getSymbolTable", "addError", "addErrorWithPosition",
-                        "addLocalizedError", "addLocalizedErrorFromToken", "addLocalizedErrorWithPosition"};
-
-                for (String methodToBeExposed : methodsToBeExposedToJS) {
-                    engine.eval(String.format(
-                            "var %s = Function.prototype.bind.call(redpenToBeBound.%s, redpenToBeBound);",
-                            methodToBeExposed, methodToBeExposed));
-                }
-                try {
-                    engine.eval("var _JavaScriptValidatorTest = Java.type('cc.redpen.validator.JavaScriptValidatorTest');");
-                } catch (RuntimeException e) {
-                    if (!(e.getCause() instanceof ClassNotFoundException)) {
-                        throw e;
-                    }
-                }
-
-                engine.eval("java = undefined; javax = undefined; Java = undefined; load = undefined; redpenToBeBound = undefined;");
-
-                CompiledScript compiledScript = ((Compilable) engine).compile(script);
-                compiledScript.eval();
-                this.message = (String)engine.get("message");
-                this.invocable = (Invocable) engine;
-            } catch (ScriptException e) {
-                throw new RedPenException(e);
-            }
-        }
-    }
-
 }
diff --git a/redpen-core/src/main/java/cc/redpen/validator/ValidationError.java b/redpen-core/src/main/java/cc/redpen/validator/ValidationError.java
index 355beaf9..cd1df5fe 100644
--- a/redpen-core/src/main/java/cc/redpen/validator/ValidationError.java
+++ b/redpen-core/src/main/java/cc/redpen/validator/ValidationError.java
@@ -38,15 +38,15 @@ public class ValidationError implements Serializable {
     /**
      * Constructor.
      *
-     * @param validatorClass    validator class
+     * @param validatorName    validator name
      * @param errorMessage      error message
      * @param sentenceWithError sentence containing validation error
      */
-    public ValidationError(Class validatorClass,
+    public ValidationError(String validatorName,
                            String errorMessage,
                            Sentence sentenceWithError) {
         this.message = errorMessage;
-        this.validatorName = validatorClass.getSimpleName();
+        this.validatorName = validatorName;;
         this.sentence = sentenceWithError;
         this.startPosition = null;
         this.endPosition = null;
@@ -55,16 +55,16 @@ public class ValidationError implements Serializable {
     /**
      * Constructor.
      *
-     * @param validatorClass    validator class
+     * @param validatorName    validator name
      * @param errorMessage      error message
      * @param sentenceWithError sentence containing validation error
      * @param startPosition     position where error starts
      * @param endPosition       position where error ends
      */
-    ValidationError(Class validatorClass, String errorMessage, Sentence sentenceWithError,
+    ValidationError(String validatorName, String errorMessage, Sentence sentenceWithError,
             int startPosition, int endPosition) {
         this.message = errorMessage;
-        this.validatorName = validatorClass.getSimpleName();
+        this.validatorName = validatorName;
         this.sentence = sentenceWithError;
         this.startPosition = sentenceWithError.getOffset(startPosition).get();
         this.endPosition = sentenceWithError.getOffset(endPosition).get();
diff --git a/redpen-core/src/main/java/cc/redpen/validator/Validator.java b/redpen-core/src/main/java/cc/redpen/validator/Validator.java
index 3cb87845..e8e2ba73 100644
--- a/redpen-core/src/main/java/cc/redpen/validator/Validator.java
+++ b/redpen-core/src/main/java/cc/redpen/validator/Validator.java
@@ -51,9 +51,10 @@ public abstract class Validator {
 
     private Map<String, Object> defaultProps;
     private ResourceBundle errorMessages = null;
-    private ValidatorConfiguration config;
-    private Configuration globalConfig;
+    protected ValidatorConfiguration config;
+    protected Configuration globalConfig;
     private Locale locale;
+    private String validatorName = this.getClass().getSimpleName();
 
     public Validator() {
         this(new Object[0]);
@@ -140,7 +141,7 @@ public abstract class Validator {
         return Collections.emptyList();
     }
 
-    public final void preInit(ValidatorConfiguration config, Configuration globalConfig) throws RedPenException {
+    public void preInit(ValidatorConfiguration config, Configuration globalConfig) throws RedPenException {
         this.config = config;
         this.globalConfig = globalConfig;
         init();
@@ -304,6 +305,10 @@ public abstract class Validator {
         return Optional.ofNullable(config.getProperty(name));
     }
 
+    protected void setValidatorName(String validatorName){
+        this.validatorName = validatorName;
+    }
+
     /** @deprecated Please use constructor with default properties instead, and then getXXX() methods */
     @Deprecated
     protected String getConfigAttribute(String name, String defaultValue) {
@@ -340,7 +345,7 @@ public abstract class Validator {
      * @param sentenceWithError sentence
      */
     protected void addError(String message, Sentence sentenceWithError) {
-        errors.add(new ValidationError(this.getClass(), message, sentenceWithError));
+        errors.add(new ValidationError(this.validatorName, message, sentenceWithError));
     }
 
     /**
@@ -353,7 +358,7 @@ public abstract class Validator {
      */
     protected void addErrorWithPosition(String message, Sentence sentenceWithError,
                                         int start, int end) {
-        errors.add(new ValidationError(this.getClass(), message, sentenceWithError, start, end));
+        errors.add(new ValidationError(this.validatorName, message, sentenceWithError, start, end));
     }
 
 
@@ -365,7 +370,7 @@ public abstract class Validator {
      * @param args              objects to format
      */
     protected void addLocalizedError(Sentence sentenceWithError, Object... args) {
-        errors.add(new ValidationError(this.getClass(), getLocalizedErrorMessage(null, args), sentenceWithError));
+        errors.add(new ValidationError(this.validatorName, getLocalizedErrorMessage(null, args), sentenceWithError));
     }
 
     /**
@@ -376,7 +381,7 @@ public abstract class Validator {
      * @param args              objects to format
      */
     protected void addLocalizedError(String messageKey, Sentence sentenceWithError, Object... args) {
-        errors.add(new ValidationError(this.getClass(), getLocalizedErrorMessage(messageKey, args), sentenceWithError));
+        errors.add(new ValidationError(this.validatorName, getLocalizedErrorMessage(messageKey, args), sentenceWithError));
     }
 
     /**
@@ -418,7 +423,7 @@ public abstract class Validator {
      */
     protected void addLocalizedErrorWithPosition(String messageKey, Sentence sentenceWithError,
                                                  int start, int end, Object... args) {
-        errors.add(new ValidationError(this.getClass(), getLocalizedErrorMessage(messageKey, args), sentenceWithError, start, end));
+        errors.add(new ValidationError(this.validatorName, getLocalizedErrorMessage(messageKey, args), sentenceWithError, start, end));
     }
 
     /**
diff --git a/redpen-core/src/main/java/cc/redpen/validator/ValidatorFactory.java b/redpen-core/src/main/java/cc/redpen/validator/ValidatorFactory.java
index 00191893..a4200ade 100644
--- a/redpen-core/src/main/java/cc/redpen/validator/ValidatorFactory.java
+++ b/redpen-core/src/main/java/cc/redpen/validator/ValidatorFactory.java
@@ -22,7 +22,12 @@ import cc.redpen.config.Configuration;
 import cc.redpen.config.ValidatorConfiguration;
 import org.reflections.Reflections;
 
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
 import java.lang.reflect.Modifier;
+import java.nio.charset.Charset;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
@@ -37,6 +42,7 @@ import static org.apache.commons.lang3.StringUtils.join;
 public class ValidatorFactory {
     private static final String validatorPackage = Validator.class.getPackage().getName();
     private static final List<String> VALIDATOR_PACKAGES = asList(validatorPackage, validatorPackage + ".sentence", validatorPackage + ".section");
+    private static final List<String> JS_VALIDATOR_DIRECTORIES = VALIDATOR_PACKAGES.stream().map(e -> "/" + e.replaceAll("\\.", "/") + "/").collect(toList());
     static final Map<String, Validator> validators = new LinkedHashMap<>();
 
     static void registerValidator(Class<? extends Validator> clazz) {
@@ -45,7 +51,7 @@ public class ValidatorFactory {
 
     static {
         Reflections reflections = new Reflections("cc.redpen.validator");
-        // register Validator implimentations under cc.redpen.validator package
+        // register Validator implementations under cc.redpen.validator package
         reflections.getSubTypesOf(Validator.class).stream()
                 .filter(validator -> !Modifier.isAbstract(validator.getModifiers()))
                 .forEach(validator -> {
@@ -82,8 +88,30 @@ public class ValidatorFactory {
     }
 
     public static Validator getInstance(ValidatorConfiguration config, Configuration globalConfig) throws RedPenException {
+        String validatorName = config.getConfigurationName();
+        // lookup JavaScript validators
+        for (String p : JS_VALIDATOR_DIRECTORIES) {
+                InputStream inputStream = ValidatorFactory.class.getResourceAsStream(p + validatorName + ".js");
+                if (inputStream != null) {
+                    try (InputStreamReader isr = new InputStreamReader(inputStream, Charset.forName("UTF-8"));
+                         BufferedReader br = new BufferedReader(isr)) {
+                        StringBuilder sb = new StringBuilder(1024);
+                        String str;
+                        while ((str = br.readLine()) != null) {
+                            sb.append(str);
+                        }
+                        JavaScriptLoader javaScriptValidator = new JavaScriptLoader(validatorName, sb.toString());
+                        javaScriptValidator.preInit(config, globalConfig);
+                        return javaScriptValidator;
+                    } catch (IOException ignored) {
+                    }
+
+                }
+        }
+
+        // fallback to Java validators
         Validator prototype = validators.get(config.getConfigurationName());
-        Class<? extends Validator> validatorClass = prototype != null ? prototype.getClass() : loadPlugin(config.getConfigurationName());
+        Class<? extends Validator> validatorClass = prototype != null ? prototype.getClass() : loadPlugin(validatorName);
         Validator validator = createValidator(validatorClass);
         validator.preInit(config, globalConfig);
         return validator;
diff --git a/redpen-server/bin/redpen-server.bat b/redpen-server/bin/redpen-server.bat
index 93ad6ea4..c4d49bf6 100644
--- a/redpen-server/bin/redpen-server.bat
+++ b/redpen-server/bin/redpen-server.bat
@@ -78,7 +78,7 @@ set REDPEN_LOG_FILE=%REDPEN_LOG_DIR%\%REDPEN_LOG_FILENAME%
 
 if ["%COMMAND%"] == ["start"] (
     echo starting RedPen server [Ctrl-C to stop]...
-    if not exist "%REDPEN_CONF_FILE%" (
+    if ["%REDPEN_CONF_FILE%"] == [] (
         echo starting RedPen server without specified configuration file...
         "%JAVA_CMD%" -jar %JAVA_OPTS% "%REDPEN_WAR_FILE%" -p %REDPEN_PORT% >> "%REDPEN_LOG_FILE%"
     ) else (


Test patch:
diff --git a/redpen-core/src/test/java/cc/redpen/validator/EmbeddedJavaScriptValidatorTest.java b/redpen-core/src/test/java/cc/redpen/validator/EmbeddedJavaScriptValidatorTest.java
new file mode 100644
index 00000000..d48ddee1
--- /dev/null
+++ b/redpen-core/src/test/java/cc/redpen/validator/EmbeddedJavaScriptValidatorTest.java
@@ -0,0 +1,44 @@
+package cc.redpen.validator;
+
+import cc.redpen.RedPen;
+import cc.redpen.RedPenException;
+import cc.redpen.config.Configuration;
+import cc.redpen.config.ValidatorConfiguration;
+import cc.redpen.model.Document;
+import cc.redpen.model.Sentence;
+import org.junit.Test;
+
+import java.util.List;
+
+import static junit.framework.Assert.assertEquals;
+
+public class EmbeddedJavaScriptValidatorTest {
+    @Test
+    public void loadEmbeddedJS() throws RedPenException {
+        ValidatorFactory.getInstance("MyEmbeddedJS");
+        ValidatorFactory.getInstance("MyEmbeddedJSSection");
+        ValidatorFactory.getInstance("MyEmbeddedJSSentence");
+    }
+
+    @Test
+    public void embeddedJS() throws RedPenException {
+        Document document = Document.builder()
+                .addSection(1)
+                .addParagraph()
+                .addSentence(new Sentence("the good item is a good example.", 1))
+                .build();
+
+        Configuration config = Configuration.builder()
+                .addValidatorConfig(new ValidatorConfiguration("MyEmbeddedJS").addProperty("property1", "value1"))
+                .addValidatorConfig(new ValidatorConfiguration("MyEmbeddedJSSentence").addProperty("property2", "value2"))
+                .addValidatorConfig(new ValidatorConfiguration("MyEmbeddedJSSection").addProperty("property3", "value3"))
+                .build();
+        RedPen redPen = new RedPen(config);
+        List<ValidationError> errors = redPen.validate(document);
+        assertEquals(3, errors.size());
+        assertEquals("MyEmbeddedJS value1", errors.get(0).getMessage());
+        assertEquals("MyEmbeddedJSSentence value2", errors.get(2).getMessage());
+        assertEquals("MyEmbeddedJSSection value3", errors.get(1).getMessage());
+
+    }
+}
diff --git a/redpen-core/src/test/java/cc/redpen/validator/JavaScriptValidatorTest.java b/redpen-core/src/test/java/cc/redpen/validator/JavaScriptValidatorTest.java
index 5beb9616..750f0e52 100644
--- a/redpen-core/src/test/java/cc/redpen/validator/JavaScriptValidatorTest.java
+++ b/redpen-core/src/test/java/cc/redpen/validator/JavaScriptValidatorTest.java
@@ -74,7 +74,7 @@ public class JavaScriptValidatorTest extends JavaScriptValidator {
         javaScriptValidatorsDir.delete();
         javaScriptValidatorsDir.mkdirs();
         System.setProperty("REDPEN_HOME", javaScriptValidatorsDir.getAbsolutePath());
-        File validatorJS = new File(javaScriptValidatorsDir.getAbsolutePath() + File.separator + "MyValidator.js");
+        File validatorJS = new File(javaScriptValidatorsDir.getAbsolutePath() + File.separator + "MyValidationScript.js");
         String content2 = "function validateSentence(sentence) {\n" +
                 "addLocalizedError(sentence, 'validation error in JavaScript Validator');}";
         Files.write(Paths.get(validatorJS.getAbsolutePath()), content2.getBytes(UTF_8));
@@ -93,7 +93,8 @@ public class JavaScriptValidatorTest extends JavaScriptValidator {
         RedPen redPen = new RedPen(config);
         List<ValidationError> errors = redPen.validate(document);
         assertEquals(1, errors.size());
-        assertEquals("[MyValidator.js] JavaScript validator validation error in JavaScript Validator", errors.get(0).getMessage());
+        assertEquals("JavaScript validator validation error in JavaScript Validator", errors.get(0).getMessage());
+        assertEquals("MyValidationScript.js", errors.get(0).getValidatorName());
     }
 
     public static List dataFromJavaScript;
@@ -101,16 +102,7 @@ public class JavaScriptValidatorTest extends JavaScriptValidator {
     @Test
     public void testJSLiteralValidator() throws RedPenException, IOException {
         JavaScriptValidator validator = new JavaScriptValidator();
-        validator.preInit(new ValidatorConfiguration("JavaScriptValidator")
-                        .addProperty("script-path", "mypath")
-                        .addProperty("intValue", 1)
-                        .addProperty("floatValue", "2")
-                        .addProperty("testScript-floatValue", "4")
-                        .addProperty("booleanValue", "false")
-                        .addProperty("hello", "world")
-                , Configuration.builder().build());
-
-        validator.scripts.add(new Script(validator, "testScript.js",
+        validator.scripts.add(new JavaScriptLoader("testScript.js",
                 "function preValidateSentence(sentence) {" +
                         // add function names to "dataFromJavaScript" list upon function calls for the later assertions
                         // the following script is using Nashorn's lobal object "Java".type to access static member:
@@ -141,6 +133,16 @@ public class JavaScriptValidatorTest extends JavaScriptValidator {
                         "}"));
 
 
+        validator.preInit(new ValidatorConfiguration("JavaScriptValidator")
+                        .addProperty("script-path", "mypath")
+                        .addProperty("intValue", 1)
+                        .addProperty("floatValue", "2")
+                        .addProperty("testScript-floatValue", "4")
+                        .addProperty("booleanValue", "false")
+                        .addProperty("hello", "world")
+                , Configuration.builder().build());
+
+
         Document document = Document.builder()
                 .addSection(1)
                 .addParagraph()
@@ -170,16 +172,20 @@ public class JavaScriptValidatorTest extends JavaScriptValidator {
         assertEquals(SymbolTable.class, dataFromJavaScript.get(10).getClass());
 
         assertEquals(4, errors.size());
-        assertEquals("[testScript.js] validation error", errors.get(0).getMessage());
-        assertEquals("[testScript.js] JavaScript validator doc", errors.get(1).getMessage());
-        assertEquals("[testScript.js] JavaScript validator sentence", errors.get(2).getMessage());
-        assertEquals("[testScript.js] JavaScript validator section", errors.get(3).getMessage());
+        assertEquals("validation error", errors.get(0).getMessage());
+        assertEquals("testScript.js", errors.get(0).getValidatorName());
+        assertEquals("JavaScript validator doc", errors.get(1).getMessage());
+        assertEquals("testScript.js", errors.get(1).getValidatorName());
+        assertEquals("JavaScript validator sentence", errors.get(2).getMessage());
+        assertEquals("testScript.js", errors.get(2).getValidatorName());
+        assertEquals("JavaScript validator section", errors.get(3).getMessage());
+        assertEquals("testScript.js", errors.get(3).getValidatorName());
     }
 
     @Test
     public void testEmbeddedmessage() throws RedPenException, IOException {
         JavaScriptValidator validator = new JavaScriptValidator();
-        validator.scripts.add(new Script(validator, "testScript.js",
+        validator.scripts.add(new JavaScriptLoader("testScript.js",
                 "var message = 'embedded message {0}';" +
                         "function validateSentence(sentence) {" +
                         // add ValidationError
@@ -196,7 +202,8 @@ public class JavaScriptValidatorTest extends JavaScriptValidator {
         validator.setErrorList(errors);
         validator.validate(sentence);
         assertEquals(1, errors.size());
-        assertEquals("[testScript.js] embedded message [placeholder]", errors.get(0).getMessage());
+        assertEquals("embedded message [placeholder]", errors.get(0).getMessage());
+        assertEquals("testScript.js", errors.get(0).getValidatorName());
     }
 
     @Test
@@ -205,7 +212,7 @@ public class JavaScriptValidatorTest extends JavaScriptValidator {
         javaScriptValidatorsDir.delete();
         javaScriptValidatorsDir.mkdirs();
         System.setProperty("REDPEN_HOME", javaScriptValidatorsDir.getAbsolutePath());
-        File validatorJS = new File(javaScriptValidatorsDir.getAbsolutePath() + File.separator + "MyValidator.js");
+        File validatorJS = new File(javaScriptValidatorsDir.getAbsolutePath() + File.separator + "MyValidationScript.js");
         String content2 = "function validateSentence(sentence) {\n" +
                 "addLocalizedError(sentence, 'validation error in JavaScript Validator');}";
         Files.write(Paths.get(validatorJS.getAbsolutePath()), content2.getBytes(UTF_8));
@@ -230,7 +237,7 @@ public class JavaScriptValidatorTest extends JavaScriptValidator {
         javaScriptValidatorsDir.delete();
         javaScriptValidatorsDir.mkdirs();
         System.setProperty("REDPEN_HOME", javaScriptValidatorsDir.getAbsolutePath());
-        File validatorJS = new File(javaScriptValidatorsDir.getAbsolutePath() + File.separator + "MyValidator.js");
+        File validatorJS = new File(javaScriptValidatorsDir.getAbsolutePath() + File.separator + "MyJSValidation.js");
         String content2 =
                 "function validateSentence(sentence) {\n" +
                         "addLocalizedError(sentence, 'validation error in JavaScript Validator');}";
@@ -244,7 +251,7 @@ public class JavaScriptValidatorTest extends JavaScriptValidator {
                 .build();
 
         String sampleAsciiDocShortText =
-                "[suppress='MyValidator SuccessiveWord']\n" +
+                "[suppress='MyJSValidation SuccessiveWord']\n" +
                 "= Title\n" +
                 "the good item is is a good example.\n";
         Document doc = createFileContent(sampleAsciiDocShortText, DocumentParser.ASCIIDOC);
@@ -259,7 +266,7 @@ public class JavaScriptValidatorTest extends JavaScriptValidator {
         javaScriptValidatorsDir.delete();
         javaScriptValidatorsDir.mkdirs();
         System.setProperty("REDPEN_HOME", javaScriptValidatorsDir.getAbsolutePath());
-        File validatorJS = new File(javaScriptValidatorsDir.getAbsolutePath() + File.separator + "MyValidator.js");
+        File validatorJS = new File(javaScriptValidatorsDir.getAbsolutePath() + File.separator + "MyValidationScript.js");
         String content2 =
                 "function validateSentence(sentence) {\n" +
                         "if (sentence.getContent().length() > 0) {" +
@@ -290,7 +297,7 @@ public class JavaScriptValidatorTest extends JavaScriptValidator {
         javaScriptValidatorsDir.delete();
         javaScriptValidatorsDir.mkdirs();
         System.setProperty("REDPEN_HOME", javaScriptValidatorsDir.getAbsolutePath());
-        File validatorJS = new File(javaScriptValidatorsDir.getAbsolutePath() + File.separator + "MyValidator.js");
+        File validatorJS = new File(javaScriptValidatorsDir.getAbsolutePath() + File.separator + "MyJSValidation.js");
         String content2 =
                 "function validateSentence(sentence) {\n" +
                         "if (sentence.getContent().length() > 0) {" +
@@ -307,7 +314,7 @@ public class JavaScriptValidatorTest extends JavaScriptValidator {
                 .build();
 
         String sampleAsciiDocShortText =
-                "<!-- @suppress MyValidator SuccessiveWord -->\n" +
+                "<!-- @suppress MyJSValidation SuccessiveWord -->\n" +
                 "# Section title\n" +
                 "the good item is a good example.\n";
         Document doc = createFileContent(sampleAsciiDocShortText, DocumentParser.MARKDOWN);
@@ -319,7 +326,7 @@ public class JavaScriptValidatorTest extends JavaScriptValidator {
     @Test
     public void testJSValidatorIsConfinedByDefault() throws RedPenException, IOException {
         JavaScriptValidator validator = new JavaScriptValidator();
-        validator.scripts.add(new Script(validator, "testScript.js",
+        validator.scripts.add(new JavaScriptLoader("testScript.js",
                                          "function validateSentence(sentence) {"
                                          + "if (java || javax || Java || redpenToBeBound || load) {"
                                          + "addLocalizedError(sentence, 'runtime environment is NOT confined');"
@@ -339,7 +346,8 @@ public class JavaScriptValidatorTest extends JavaScriptValidator {
         validator.setErrorList(errors);
         validator.validate(sentence);
         assertEquals(1, errors.size());
-        assertEquals("[testScript.js] JavaScript validator runtime environment is confined", errors.get(0).getMessage());
+        assertEquals("JavaScript validator runtime environment is confined", errors.get(0).getMessage());
+        assertEquals("testScript.js", errors.get(0).getValidatorName());
     }
 
     ArrayList<ValidationError> errors = new ArrayList<>();
diff --git a/redpen-core/src/test/resources/cc/redpen/validator/MyEmbeddedJS.js b/redpen-core/src/test/resources/cc/redpen/validator/MyEmbeddedJS.js
new file mode 100644
index 00000000..b0c56a1f
--- /dev/null
+++ b/redpen-core/src/test/resources/cc/redpen/validator/MyEmbeddedJS.js
@@ -0,0 +1,3 @@
+function validateDocument(document) {
+    addError('MyEmbeddedJS ' + getString("property1"), document.getLastSection().getParagraph(0).getSentence(0));
+}
\ No newline at end of file
diff --git a/redpen-core/src/test/resources/cc/redpen/validator/error-messages.properties b/redpen-core/src/test/resources/cc/redpen/validator/error-messages.properties
index bfd94d79..3b9ffe74 100644
--- a/redpen-core/src/test/resources/cc/redpen/validator/error-messages.properties
+++ b/redpen-core/src/test/resources/cc/redpen/validator/error-messages.properties
@@ -1,4 +1,4 @@
 ValidationErrorMessageTest=error str\:{3} 1\:{0} 2\:{1} 3\:{2}
 ValidationErrorMessageTest.withKey=with Key \:{0}
 JavaScriptValidatorTest=js validator {0}
-JavaScriptValidator=JavaScript validator {0}
\ No newline at end of file
+JavaScriptLoader=JavaScript validator {0}
\ No newline at end of file
diff --git a/redpen-core/src/test/resources/cc/redpen/validator/error-messages_ja.properties b/redpen-core/src/test/resources/cc/redpen/validator/error-messages_ja.properties
index 18596e3a..da703a22 100644
--- a/redpen-core/src/test/resources/cc/redpen/validator/error-messages_ja.properties
+++ b/redpen-core/src/test/resources/cc/redpen/validator/error-messages_ja.properties
@@ -1,4 +1,4 @@
 ValidationErrorMessageTest=\u30A8\u30E9\u30FC \u30B9\u30C8\u30EA\u30F3\u30B0\:{3} 1\:{0} 2\:{1} 3\:{2}
 ValidationErrorMessageTest.withKey=\u30AD\u30FC\u6307\u5B9A \:{0}
 JavaScriptValidatorTest=js validator {0}
-JavaScriptValidator=JavaScript validator {0}
\ No newline at end of file
+JavaScriptLoader=JavaScript validator {0}
\ No newline at end of file
diff --git a/redpen-core/src/test/resources/cc/redpen/validator/section/MyEmbeddedJSSection.js b/redpen-core/src/test/resources/cc/redpen/validator/section/MyEmbeddedJSSection.js
new file mode 100644
index 00000000..2d67705d
--- /dev/null
+++ b/redpen-core/src/test/resources/cc/redpen/validator/section/MyEmbeddedJSSection.js
@@ -0,0 +1,3 @@
+function validateSection(section) {
+    addError('MyEmbeddedJSSection ' + getString("property3"), section.getParagraph(0).getSentence(0));
+}
\ No newline at end of file
diff --git a/redpen-core/src/test/resources/cc/redpen/validator/sentence/MyEmbeddedJSSentence.js b/redpen-core/src/test/resources/cc/redpen/validator/sentence/MyEmbeddedJSSentence.js
new file mode 100644
index 00000000..9e4ce3f3
--- /dev/null
+++ b/redpen-core/src/test/resources/cc/redpen/validator/sentence/MyEmbeddedJSSentence.js
@@ -0,0 +1,3 @@
+function validateSentence(sentence) {
+    addError('MyEmbeddedJSSentence ' + getString("property2"), sentence);
+}
\ No newline at end of file
diff --git a/redpen-server/src/test/java/cc/redpen/server/api/RedPenResourceTest.java b/redpen-server/src/test/java/cc/redpen/server/api/RedPenResourceTest.java
index f149d518..993d132c 100644
--- a/redpen-server/src/test/java/cc/redpen/server/api/RedPenResourceTest.java
+++ b/redpen-server/src/test/java/cc/redpen/server/api/RedPenResourceTest.java
@@ -107,7 +107,8 @@ public class RedPenResourceTest extends MockServletInvocationTest {
         assertTrue(errors.length() > 0);
         for (int i=0; i<errors.length(); ++i) {
             JSONObject o = errors.getJSONObject(i).getJSONArray("errors").getJSONObject(0);
-            assertEquals("[pass.js] called", o.getString("message"));
+            assertEquals("called", o.getString("message"));
+            assertEquals("pass.js", o.getString("validator"));
         }
     }