Package org.lesscss

Source Code of org.lesscss.LessCompiler

/* Copyright 2011-2012 The Apache Software Foundation.
*
* 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 org.lesscss;

// If less.js 1.3.3 is supported again, remove this package and re-activate the pom.xml entry.

import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.apache.commons.io.FileUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.Function;
import org.mozilla.javascript.JavaScriptException;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;
import org.mozilla.javascript.tools.shell.Global;

/**
* The LESS compiler to compile LESS sources to CSS stylesheets.
* <p>
* The compiler uses Rhino (JavaScript implementation written in Java), Envjs
* (simulated browser environment written in JavaScript), and the official LESS
* JavaScript compiler.<br />
* Note that the compiler is not a Java implementation of LESS itself, but rather
* integrates the LESS JavaScript compiler within a Java/JavaScript browser
* environment provided by Rhino and Envjs.
* </p>
* <p>
* The compiler comes bundled with the Envjs and LESS JavaScript, so there is
* no need to include them yourself. But if needed they can be overridden.
* </p>
* <h4>Basic code example:</h4>
* <pre>
* LessCompiler lessCompiler = new LessCompiler();
* String css = lessCompiler.compile("@color: #4D926F; #header { color: @color; }");
* </pre>
*
* @author Marcel Overdijk
* @see <a href="http://lesscss.org/">LESS - The Dynamic Stylesheet language</a>
* @see <a href="http://www.mozilla.org/rhino/">Rhino - JavaScript for Java</a>
* @see <a href="http://www.envjs.com/">Envjs - Bringing the Browser</a>
*/
public class LessCompiler {

  private static final String COMPILE_STRING = "function doIt(input, compress) { var result; var parser = new less.Parser(); parser.parse(input, function(e, tree) { if (e instanceof Object) { throw e; } ; result = tree.toCSS({compress: compress}); }); return result; }";

  private static final Log log = LogFactory.getLog(LessCompiler.class);

  private URL envJs = LessCompiler.class.getClassLoader().getResource("lesscss/env.rhino.js");
  private URL lessJs = LessCompiler.class.getClassLoader().getResource("lesscss/less-1.3.3.min.js");
  private List<URL> customJs = Collections.emptyList();
  private boolean compress = false;
  private String encoding = null;

  private Function doIt;

  private Scriptable scope;

  /**
   * Constructs a new <code>LessCompiler</code>.
   */
  public LessCompiler() {
  }

  /**
   * Returns the Envjs JavaScript file used by the compiler.
   *
   * @return The Envjs JavaScript file used by the compiler.
   */
  public URL getEnvJs() {
    return envJs;
  }

  /**
   * Sets the Envjs JavaScript file used by the compiler.
   * Must be set before {@link #init()} is called.
   *
   * @param envJs The Envjs JavaScript file used by the compiler.
   */
  public synchronized void setEnvJs(final URL envJs) {
    if (scope != null) {
      throw new IllegalStateException("This method can only be called before init()");
    }
    this.envJs = envJs;
  }

  /**
   * Returns the LESS JavaScript file used by the compiler.
   * COMPILE_STRING
   * @return The LESS JavaScript file used by the compiler.
   */
  public URL getLessJs() {
    return lessJs;
  }

  /**
   * Sets the LESS JavaScript file used by the compiler.
   * Must be set before {@link #init()} is called.
   *
   * @param The LESS JavaScript file used by the compiler.
   */
  public synchronized void setLessJs(final URL lessJs) {
    if (scope != null) {
      throw new IllegalStateException("This method can only be called before init()");
    }
    this.lessJs = lessJs;
  }

  /**
   * Returns the custom JavaScript files used by the compiler.
   *
   * @return The custom JavaScript files used by the compiler.
   */
  public List<URL> getCustomJs() {
    return Collections.unmodifiableList(customJs);
  }

  /**
   * Sets a single custom JavaScript file used by the compiler.
   * Must be set before {@link #init()} is called.
   *
   * @param customJs A single custom JavaScript file used by the compiler.
   */
  public synchronized void setCustomJs(final URL customJs) {
    if (scope != null) {
      throw new IllegalStateException("This method can only be called before init()");
    }
    this.customJs = Collections.singletonList(customJs);
  }

  /**
   * Sets the custom JavaScript files used by the compiler.
   * Must be set before {@link #init()} is called.
   *
   * @param customJs The custom JavaScript files used by the compiler.
   */
  public synchronized void setCustomJs(final List<URL> customJs) {
    if (scope != null) {
      throw new IllegalStateException("This method can only be called before init()");
    }
    // copy the list so there's no way for anyone else who holds a reference to the list to modify it
    this.customJs = new ArrayList<URL>(customJs);
  }

  /**
   * Returns whether the compiler will compress the CSS.
   *
   * @return Whether the compiler will compress the CSS.
   */
  public boolean isCompress() {
    return compress;
  }

  /**
   * Sets the compiler to compress the CSS.
   * Must be set before {@link #init()} is called.
   *
   * @param compress If <code>true</code>, sets the compiler to compress the CSS.
   */
  public synchronized void setCompress(final boolean compress) {
    if (scope != null) {
      throw new IllegalStateException("This method can only be called before init()");
    }
    this.compress = compress;
  }

  /**
   * Returns the character encoding used by the compiler when writing the output <code>File</code>.
   *
   * @return The character encoding used by the compiler when writing the output <code>File</code>.
   */
  public String getEncoding() {
    return encoding;
  }

  /**
   * Sets the character encoding used by the compiler when writing the output <code>File</code>.
   * If not set the platform default will be used.
   * Must be set before {@link #init()} is called.
   *
   * @param The character encoding used by the compiler when writing the output <code>File</code>.
   */
  public synchronized void setEncoding(final String encoding) {
    if (scope != null) {
      throw new IllegalStateException("This method can only be called before init()");
    }
    this.encoding = encoding;
  }

  /**
   * Initializes this <code>LessCompiler</code>.
   * <p>
   * It is not needed to call this method manually, as it is called implicitly by the compile methods if needed.
   * </p>
   */
  public synchronized void init() {
    final long start = System.currentTimeMillis();

    try {
      final Context cx = Context.enter();
      cx.setOptimizationLevel(-1);
      cx.setLanguageVersion(Context.VERSION_1_7);

      final Global global = new Global();
      global.init(cx);

      scope = cx.initStandardObjects(global);

      final List<URL> jsUrls = new ArrayList<URL>(2 + customJs.size());
      jsUrls.add(envJs);
      jsUrls.add(lessJs);
      jsUrls.addAll(customJs);

      for(final URL url : jsUrls){
        final InputStreamReader inputStreamReader = new InputStreamReader(url.openConnection().getInputStream());
        try{
          cx.evaluateReader(scope, inputStreamReader, url.toString(), 1, null);
        }finally{
          inputStreamReader.close();
        }
      }
      doIt = cx.compileFunction(scope, COMPILE_STRING, "doIt.js", 1, null);
    }
    catch (final Exception e) {
      final String message = "Failed to initialize LESS compiler.";
      log.error(message, e);
      throw new IllegalStateException(message, e);
    }finally{
      Context.exit();
    }

    if (log.isDebugEnabled()) {
      log.debug("Finished initialization of LESS compiler in " + (System.currentTimeMillis() - start) + " ms.");
    }
  }

  /**
   * Compiles the LESS input <code>String</code> to CSS.
   *
   * @param input The LESS input <code>String</code> to compile.
   * @return The CSS.
   */
  public String compile(final String input) throws LessException {
    synchronized(this){
      if (scope == null) {
        init();
      }
    }

    final long start = System.currentTimeMillis();

    try {
      final Context cx = Context.enter();
      final Object result = doIt.call(cx, scope, null, new Object[]{input, compress});

      if (log.isDebugEnabled()) {
        log.debug("Finished compilation of LESS source in " + (System.currentTimeMillis() - start) + " ms.");
      }

      return result.toString();
    }
    catch (final Exception e) {
      if (e instanceof JavaScriptException) {
        final Scriptable value = (Scriptable)((JavaScriptException)e).getValue();
        if (value != null && ScriptableObject.hasProperty(value, "message")) {
          final String message = ScriptableObject.getProperty(value, "message").toString();
          throw new LessException(message, e);
        }
      }
      throw new LessException(e);
    }finally{
      Context.exit();
    }
  }

  /**
   * Compiles the LESS input <code>File</code> to CSS.
   *
   * @param input The LESS input <code>File</code> to compile.
   * @return The CSS.
   * @throws IOException If the LESS file cannot be read.
   */
  public String compile(final File input) throws IOException, LessException {
    final LessSource lessSource = new LessSource(input);
    return compile(lessSource);
  }

  /**
   * Compiles the LESS input <code>File</code> to CSS and writes it to the specified output <code>File</code>.
   *
   * @param input The LESS input <code>File</code> to compile.
   * @param output The output <code>File</code> to write the CSS to.
   * @throws IOException If the LESS file cannot be read or the output file cannot be written.
   */
  public void compile(final File input, final File output) throws IOException, LessException {
    this.compile(input, output, true);
  }

  /**
   * Compiles the LESS input <code>File</code> to CSS and writes it to the specified output <code>File</code>.
   *
   * @param input The LESS input <code>File</code> to compile.
   * @param output The output <code>File</code> to write the CSS to.
   * @param force 'false' to only compile the LESS input file in case the LESS source has been modified (including imports) or the output file does not exists.
   * @throws IOException If the LESS file cannot be read or the output file cannot be written.
   */
  public void compile(final File input, final File output, final boolean force) throws IOException, LessException {
    final LessSource lessSource = new LessSource(input);
    compile(lessSource, output, force);
  }

  /**
   * Compiles the input <code>LessSource</code> to CSS.
   *
   * @param input The input <code>LessSource</code> to compile.
   * @return The CSS.
   */
  public String compile(final LessSource input) throws LessException {
    return compile(input.getNormalizedContent());
  }

  /**
   * Compiles the input <code>LessSource</code> to CSS and writes it to the specified output <code>File</code>.
   *
   * @param input The input <code>LessSource</code> to compile.
   * @param output The output <code>File</code> to write the CSS to.
   * @throws IOException If the LESS file cannot be read or the output file cannot be written.
   */
  public void compile(final LessSource input, final File output) throws IOException, LessException {
    compile(input, output, true);
  }

  /**
   * Compiles the input <code>LessSource</code> to CSS and writes it to the specified output <code>File</code>.
   *
   * @param input The input <code>LessSource</code> to compile.
   * @param output The output <code>File</code> to write the CSS to.
   * @param force 'false' to only compile the input <code>LessSource</code> in case the LESS source has been modified (including imports) or the output file does not exists.
   * @throws IOException If the LESS file cannot be read or the output file cannot be written.
   */
  public void compile(final LessSource input, final File output, final boolean force) throws IOException, LessException {
    if (force || !output.exists() || output.lastModified() < input.getLastModifiedIncludingImports()) {
      final String data = compile(input);
      FileUtils.writeStringToFile(output, data, encoding);
    }
  }
}
TOP

Related Classes of org.lesscss.LessCompiler

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.