content: /*
   * Copyright wro4j@2011
   */
  package ro.isdc.wro.extensions.processor.support.linter;
  
  import java.io.IOException;
  import java.io.InputStream;
  import java.lang.reflect.Type;
  import java.util.List;
  
  import org.apache.commons.lang3.StringUtils;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  import ro.isdc.wro.WroRuntimeException;
  import ro.isdc.wro.extensions.locator.WebjarUriLocator;
  import ro.isdc.wro.extensions.processor.support.csslint.CssLint;
  import ro.isdc.wro.extensions.script.RhinoScriptBuilder;
  import ro.isdc.wro.util.StopWatch;
  import ro.isdc.wro.util.WroUtil;
  
  import com.google.gson.Gson;
  import com.google.gson.reflect.TypeToken;
  
  
  /**
   * Encapsulates common code for utilities like jsHint or jsLint.
   *
   * @author Alex Objelean
   * @created 19 Sept 2011
   * @since 1.4.2
   */
  public abstract class AbstractLinter {
    private static final Logger LOG = LoggerFactory.getLogger(AbstractLinter.class);
    /**
     * Required to make jshint not complain.
     */
    private static final String DEFINE_WINDOW = "var window = {};";
    private WebjarUriLocator webjarLocator;
    private final OptionsBuilder optionsBuilder = new OptionsBuilder();
    /**
     * Options to apply to js hint processing
     */
    private String options;
    private RhinoScriptBuilder builder;
  
    /**
     * Initialize script builder for evaluation.
     */
    private RhinoScriptBuilder initScriptBuilder() {
      try {
        if (builder == null) {
          builder = RhinoScriptBuilder.newChain().evaluateChain(DEFINE_WINDOW, "window").evaluateChain(
              getScriptAsStream(), "linter.js");
        }
        return builder;
      } catch (final IOException e) {
        throw new WroRuntimeException("Failed reading init script", e);
      }
    }
  
    /**
     * @return {@link WebjarUriLocator} instance to retrieve webjars.
     */
    protected final WebjarUriLocator getWebjarLocator() {
      if (webjarLocator == null) {
        webjarLocator = new WebjarUriLocator();
      }
      return webjarLocator;
    }
  
    /**
     * @return the stream of the linter script. Override this method to provide a different script version.
     * @throws IOException
     *           if the stream is invalid or unavailable.
     */
    protected abstract InputStream getScriptAsStream()
        throws IOException;
  
    /**
     * Validates a js using jsHint and throws {@link LinterException} if the js is invalid. If no exception is thrown, the
     * js is valid.
     *
     * @param data
     *          js content to process.
     */
    public void validate(final String data)
        throws LinterException {
      final StopWatch watch = new StopWatch();
      watch.start("init");
      final RhinoScriptBuilder builder = initScriptBuilder();
      watch.stop();
      watch.start("lint");
      final String packIt = buildLinterScript(WroUtil.toJSMultiLineString(data), getOptions());
      final boolean valid = Boolean.parseBoolean(builder.evaluate(packIt, "check").toString());
      if (!valid) {
        final String json = builder.addJSON().evaluate(
            String.format("JSON.stringify(JSON.decycle(%s.errors))", getLinterName()), "stringify errors").toString();
        LOG.debug("json {}", json);
        final Type type = new TypeToken<List<LinterError>>() {}.getType();
        final List<LinterError> errors = new Gson().fromJson(json, type);
        LOG.debug("errors {}", errors);
        throw new LinterException().setErrors(errors);
      }
      LOG.debug("result: {}", valid);
      watch.stop();
      LOG.debug(watch.prettyPrint());
    }
  
    /**
     * @return the name of the function used to perform the lint operation.
     */
    protected abstract String getLinterName();
  
    /**
     * TODO this method is duplicated in {@link CssLint}. Extract and reuse it.
     *
     * @param data
     *          script to process.
     * @param options
     *          options to set as true
     * @return Script used to pack and return the packed result.
     */
    private String buildLinterScript(final String data, final String options) {
      return String.format("%s(%s,%s);", getLinterName(), data, optionsBuilder.buildFromCsv(options));
    }
  
    /**
     * @param options
     *          the options to set
     */
    public AbstractLinter setOptions(final String... options) {
      this.options = StringUtils.join(options, ',');
      LOG.debug("setOptions: {}", this.options);
      return this;
    }
  
    /**
     * @return an options as CSV.
     */
    private String getOptions() {
      if (options == null) {
        options = createDefaultOptions();
      }
      return options;
    }
  
    /**
     * @return default options to use for linting.
     */
    protected String createDefaultOptions() {
      return "";
    }
  }
  