001    /*
002     * Copyright © 2008, 2012 Pedro Agulló Soliveres.
003     * 
004     * This file is part of DirectJNgine.
005     *
006     * DirectJNgine is free software: you can redistribute it and/or modify
007     * it under the terms of the GNU Lesser General Public License as published by
008     * the Free Software Foundation, either version 3 of the License.
009     * 
010     * Commercial use is permitted to the extent that the code/component(s)
011     * do NOT become part of another Open Source or Commercially developed
012     * licensed development library or toolkit without explicit permission.
013     *
014     * DirectJNgine is distributed in the hope that it will be useful,
015     * but WITHOUT ANY WARRANTY; without even the implied warranty of
016     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
017     * GNU Lesser General Public License for more details.
018     *
019     * You should have received a copy of the GNU Lesser General Public License
020     * along with DirectJNgine.  If not, see <http://www.gnu.org/licenses/>.
021     * 
022     * This software uses the ExtJs library (http://extjs.com), which is 
023     * distributed under the GPL v3 license (see http://extjs.com/license).
024     */
025    
026    package com.softwarementors.extjs.djn.jscodegen;
027    
028    import java.io.IOException;
029    import java.io.Reader;
030    import java.io.StringReader;
031    import java.io.StringWriter;
032    import java.io.UnsupportedEncodingException;
033    import java.io.Writer;
034    
035    import org.apache.log4j.Logger;
036    import org.mozilla.javascript.ErrorReporter;
037    import org.mozilla.javascript.EvaluatorException;
038    
039    import com.softwarementors.extjs.djn.StringUtils;
040    import com.softwarementors.extjs.djn.Timer;
041    import com.yahoo.platform.yui.compressor.JavaScriptCompressor;
042    
043    import edu.umd.cs.findbugs.annotations.CheckForNull;
044    import edu.umd.cs.findbugs.annotations.NonNull;
045    
046    public class Minifier {
047    
048      @NonNull
049      private static final Logger logger = Logger.getLogger( Minifier.class);
050    
051      private Minifier() {
052        // Avoid instantation
053      }
054    
055      public static String getMinifiedFileName(String file) {
056        assert !StringUtils.isEmpty(file);
057        
058        String result = file.replace( ".js", "-min.js" );
059        return result;
060      }
061    
062      @CheckForNull public static final String minify( String input, String inputFilename, int debugCodeLength ) {
063        assert input != null;
064        assert !StringUtils.isEmpty(inputFilename);
065        assert debugCodeLength > 0;
066    
067        try {
068          Timer timer = new Timer();      
069          // logger.debug( "Starting minification for '" + inputFilename + "'...");
070          Reader in = new StringReader( input );
071          JavaScriptCompressor compressor = new JavaScriptCompressor(in, new ErrorReporter() {
072            public void warning(String message, String sourceName,
073                int line, String lineSource, int lineOffset) {
074              if (line < 0) {
075                logger.warn("Minifier Warning: " + message);
076              } else {
077                logger.warn("Minifier Warning, " + line + ':' + lineOffset + ':' + message);
078              }
079            }
080    
081            public void error(String message, String sourceName,
082                int line, String lineSource, int lineOffset) {
083              if (line < 0) {
084                logger.warn("Minifier Error: " + message);
085              } else {
086                logger.warn("Minifier Error, " + line + ':' + lineOffset + ':' + message);
087              }
088            }
089    
090            public EvaluatorException runtimeError(String message, String sourceName,
091                int line, String lineSource, int lineOffset) {
092              error(message, sourceName, line, lineSource, lineOffset);
093              return new EvaluatorException(message);
094            }
095          });
096    
097          // Close the input stream first, and then open the output stream,
098          // in case the output file should override the input file.
099          in.close();
100    
101          try {
102            Writer out = new StringWriter();
103            boolean munge = true;
104            boolean preserveAllSemiColons = false;
105            boolean disableOptimizations = false;
106            boolean verbose = false;
107            int linebreakpos = 0;
108    
109            compressor.compress(out, linebreakpos, munge, verbose,
110                preserveAllSemiColons, disableOptimizations);
111            out.close();
112            String result = out.toString();
113            if( logger.isDebugEnabled() ) {
114              timer.stop();
115              int compressionPercentage = 100 - (result.length() * 100 / debugCodeLength);
116              timer.logDebugTimeInMilliseconds( "Finished minification for '" + inputFilename + "'. Debug code length: " + debugCodeLength + ", Minified length: " + result.length() + ", Compression: " + compressionPercentage + "%. Time");
117            }
118            return result;
119          }
120          catch (UnsupportedEncodingException e) {
121            logger.warn( "Unable to minify '" + inputFilename + "'.", e );
122            return null;
123          }
124        }
125        catch( IOException e) {
126          logger.warn( "Unable to minify '" + inputFilename + "' due to an IOException.", e );
127          return null;
128        }
129        catch (EvaluatorException e) {
130          logger.warn( "Unable to minify '" + inputFilename + "' due to a problem with the Javascript evaluator.", e );
131          return null;
132        }
133      }
134    
135    }