/*
* Copyright (C) 2012-2016 NS Solutions Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.htmlhifive.tools.jslint.engine;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.Reader;
import org.apache.commons.lang.math.NumberUtils;
import org.eclipse.core.runtime.CoreException;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.ContextAction;
import org.mozilla.javascript.ContextFactory;
import org.mozilla.javascript.Function;
import org.mozilla.javascript.NativeArray;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;
import com.htmlhifive.tools.jslint.engine.option.CheckOption;
import com.htmlhifive.tools.jslint.logger.JSLintPluginLogger;
import com.htmlhifive.tools.jslint.logger.JSLintPluginLoggerFactory;
import com.htmlhifive.tools.jslint.util.CheckJavaScriptUtils;
/**
* JSLintかJSHintを使用してjsファイルをチェックするチェッカクラスの抽象クラス.
*
* @author NS Solutions Corporation
*
*/
public abstract class AbstractJSChecker implements JSChecker {
/**
* ロガー.
*/
private static JSLintPluginLogger logger = JSLintPluginLoggerFactory.getLogger(AbstractJSChecker.class);
/**
* Rhinoの最適化レベル.(-1~9).
*/
private static final int OPTIMAZATION_LEVEL = 0;
/**
* コンテキストファクトリ.
*/
private ContextFactory factory;
/**
* スコープ.
*/
private ScriptableObject scope;
/**
* 追加されたオプション.
*/
private CheckOption[] options;
/**
* JSLint/JSHintの結果オブジェクト
*/
protected Object result;
/**
* コンストラクタ.
*
* @param jslint JSLINTのjsファイルパス
* @param options オプション
* @throws CoreException 解析例外
*/
public AbstractJSChecker(Reader jslint, CheckOption[] options) throws CoreException {
if (options != null) {
this.options = options.clone();
}
try {
factory = new ContextFactory();
Context context = factory.enterContext();
context.setOptimizationLevel(OPTIMAZATION_LEVEL);
logger.debug("optimizationLevel is " + String.valueOf(context.getOptimizationLevel()));
scope = context.initStandardObjects();
context.evaluateReader(scope, jslint, "test", 1, null);
} catch (FileNotFoundException e) {
throw new CoreException(null);
} catch (IOException e) {
throw new CoreException(null);
}
}
/**
* コンストラクタ.
*
* @param jslint JSLINTのjsファイルパス
* @throws CoreException 解析例外
*/
public AbstractJSChecker(Reader jslint) throws CoreException {
this(jslint, null);
}
@Override
public JSCheckerResult lint(final String source) {
long lintStart = System.currentTimeMillis();
factory.call(new ContextAction() {
@Override
public Object run(Context cx) {
String src = source == null ? "" : source;
logger.debug("target source : " + src.toCharArray().length);
Object[] args = new Object[] { src, optionsAsJavaScriptObject() };
Function lintFunc = (Function) scope.get(getCheckerMethodName(), scope);
long lintFuncstart = System.currentTimeMillis();
result = lintFunc.call(cx, scope, scope, args);
logger.debug("lint func time " + String.valueOf(System.currentTimeMillis() - lintFuncstart));
return null;
}
});
logger.debug("lint time " + String.valueOf(System.currentTimeMillis() - lintStart));
return builtResults();
}
/**
* JSLINTの実行結果を取得する.
*
* @return 実行エラー結果.
*/
private JSCheckerResult builtResults() {
long createResultStart = System.currentTimeMillis();
JSCheckerResult result = (JSCheckerResult) factory.call(new ContextAction() {
@Override
public Object run(Context cx) {
JSCheckerResult result = new JSCheckerResult();
NativeArray errors = getErrors();
logger.debug("error count : " + errors.getLength());
for (int i = 0; i < errors.getLength(); i++) {
Scriptable err = (Scriptable) errors.get(i, errors);
if (err != null) {
addError(result, err);
}
}
return result;
}
});
logger.debug("result build time " + String.valueOf(System.currentTimeMillis() - createResultStart));
return result;
}
/**
* 使用するチェッカメソッドを取得する.<br>
* JSLINT or JSHINT
*
* @return 使用するチェッカメソッド
*/
abstract String getCheckerMethodName();
/**
* エラー情報を取得する
*
* @return エラー情報オブジェクトの配列
*/
abstract protected NativeArray getErrors();
/**
* JavaScriptオブジェクトのエラー情報をBeanに変換する
*
* @param err
* @return
*/
abstract protected JSCheckerErrorBean convertToErrorBean(Scriptable err);
/**
* JSLintのエラーをリストに追加する.
*
* @param result 追加するリスト
* @param err エラーオブジェクト
*/
private void addError(JSCheckerResult result, Scriptable err) {
JSCheckerErrorBean e = convertToErrorBean(err);
result.addErroList(e);
}
/**
* 設定ファイルのオプション設定をJavaScriptのオブジェクトとして返す.
*
* @return オプションオブジェクト
*/
Scriptable optionsAsJavaScriptObject() {
return (Scriptable) factory.call(new ContextAction() {
public Object run(Context cx) {
Scriptable opts = cx.newObject(scope);
if (options != null && options.length != 0) {
for (CheckOption option : options) {
putOpts(opts, option);
}
}
return opts;
}
});
}
/**
* Scriptableオブジェクトにプロパティをセットする.
*
* @param opts セットするプロパティ.
* @param option オプション
*/
private void putOpts(Scriptable opts, CheckOption option) {
if (!option.isEnable()) {
return;
}
Object val = null;
Class<?> clazz = option.getClazz();
if (clazz == Boolean.class) {
val = Boolean.valueOf(option.isEnable());
} else if (clazz == Integer.class) {
val = NumberUtils.isNumber(option.getValue()) ? Integer.valueOf(option.getValue()) : null;
}
opts.put(option.getKey(), opts, val);
}
@Override
public JSCheckerResult lint(Reader reader) throws IOException {
return lint(CheckJavaScriptUtils.readerToString(reader));
}
/**
* コンテキストファクトリを取得する.
*
* @return コンテキストファクトリ
*/
ContextFactory getFactory() {
return factory;
}
/**
* スコープを取得する.
*
* @return スコープ
*/
ScriptableObject getScope() {
return scope;
}
}