com.google.gwt.resources.converter.Css2Gss.java Source code

Java tutorial

Introduction

Here is the source code for com.google.gwt.resources.converter.Css2Gss.java

Source

/*
 * Copyright 2014 Google Inc.
 *
 * 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.google.gwt.resources.converter;

import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.TreeLogger.Type;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.dev.util.DefaultTextOutput;
import com.google.gwt.dev.util.log.PrintWriterTreeLogger;
import com.google.gwt.resources.css.GenerateCssAst;
import com.google.gwt.resources.css.ast.CssStylesheet;
import com.google.gwt.thirdparty.guava.common.base.Predicate;
import com.google.gwt.thirdparty.guava.common.base.Predicates;
import com.google.gwt.thirdparty.guava.common.base.Splitter;
import com.google.gwt.thirdparty.guava.common.collect.FluentIterable;
import com.google.gwt.thirdparty.guava.common.collect.ImmutableSet;
import com.google.gwt.thirdparty.guava.common.io.Files;

import org.apache.commons.io.Charsets;
import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;

/**
 * Converter from Css to Gss.
 */
public class Css2Gss {
    private final URL cssFile;
    private final TreeLogger treeLogger;
    private final boolean lenient;

    private PrintWriter printWriter;
    private Map<String, String> defNameMapping;
    private Predicate<String> simpleBooleanConditionPredicate;
    private final Set<URL> scopeFiles;

    public Css2Gss(String filePath) throws MalformedURLException {
        this(new File(filePath).toURI().toURL(), false);
    }

    public Css2Gss(URL resource, boolean lenient) {
        this(resource, lenient, Predicates.<String>alwaysFalse(), new HashSet<URL>());
    }

    public Css2Gss(URL resource, boolean lenient, Predicate<String> simpleBooleanConditionPredicate,
            Set<URL> scopeFiles) {
        cssFile = resource;
        printWriter = new PrintWriter(System.err);
        PrintWriterTreeLogger printWriterTreeLogger = new PrintWriterTreeLogger(printWriter);
        printWriterTreeLogger.setMaxDetail(Type.WARN);
        this.treeLogger = printWriterTreeLogger;
        this.lenient = lenient;
        this.simpleBooleanConditionPredicate = simpleBooleanConditionPredicate;
        this.scopeFiles = scopeFiles;
    }

    public Css2Gss(URL fileUrl, TreeLogger treeLogger, boolean lenient,
            Predicate<String> simpleBooleanConditionPredicate, Set<URL> scopeFiles) {
        cssFile = fileUrl;
        this.treeLogger = treeLogger;
        this.lenient = lenient;
        this.simpleBooleanConditionPredicate = simpleBooleanConditionPredicate;
        this.scopeFiles = scopeFiles;
    }

    public Css2Gss(URL url, TreeLogger logger, boolean lenientConversion,
            Predicate<String> simpleBooleanConditionPredicate) {
        this(url, logger, lenientConversion, simpleBooleanConditionPredicate, new HashSet<URL>());
    }

    public String toGss() throws UnableToCompleteException {
        try {
            CssStylesheet sheet = GenerateCssAst.exec(treeLogger, cssFile);

            DefCollectorVisitor defCollectorVisitor = new DefCollectorVisitor(lenient, treeLogger);
            defCollectorVisitor.accept(sheet);
            defNameMapping = defCollectorVisitor.getDefMapping();

            addScopeDefs(scopeFiles, defNameMapping);

            new UndefinedConstantVisitor(new HashSet<String>(defNameMapping.values()), lenient, treeLogger)
                    .accept(sheet);

            new ElseNodeCreator().accept(sheet);

            new AlternateAnnotationCreatorVisitor().accept(sheet);

            GssGenerationVisitor gssGenerationVisitor = new GssGenerationVisitor(new DefaultTextOutput(false),
                    defNameMapping, lenient, treeLogger, simpleBooleanConditionPredicate);
            gssGenerationVisitor.accept(sheet);

            return gssGenerationVisitor.getContent();
        } finally {
            if (printWriter != null) {
                printWriter.flush();
            }
        }
    }

    private void addScopeDefs(Set<URL> scopeFiles, Map<String, String> defNameMapping)
            throws UnableToCompleteException {
        for (URL fileName : scopeFiles) {
            CssStylesheet sheet = GenerateCssAst.exec(treeLogger, fileName);
            DefCollectorVisitor defCollectorVisitor = new DefCollectorVisitor(lenient, treeLogger);
            defCollectorVisitor.accept(sheet);
            defNameMapping.putAll(defCollectorVisitor.getDefMapping());
        }
    }

    /**
     * GSS allows only uppercase letters and numbers for a name of the constant. The constants
     * need to be renamed in order to be compatible with GSS. This method returns a mapping
     * between the old name and the new name compatible with GSS.
     */
    public Map<String, String> getDefNameMapping() {
        return defNameMapping;
    }

    public static void main(String... args) {

        Options options = Options.parseOrQuit(args);

        if (options.singleFile) {
            try {
                System.out.println(
                        convertFile(options.resource, options.simpleBooleanConditions, options.scopeFiles));
                System.exit(0);
            } catch (Exception e) {
                e.printStackTrace();
                System.exit(-1);
            }
        }

        Collection<File> filesToConvert = FileUtils.listFiles(options.resource, new String[] { "css" },
                options.recurse);

        for (File cssFile : filesToConvert) {
            try {
                if (doesCorrespondingGssFileExists(cssFile)) {
                    System.out.println(
                            "GSS file already exists - will not convert, file: " + cssFile.getAbsolutePath());
                    continue;
                }
                String gss = convertFile(cssFile, options.simpleBooleanConditions, options.scopeFiles);
                writeGss(gss, cssFile);
                System.out.println("Converted " + cssFile.getAbsolutePath());
            } catch (Exception e) {
                System.err.println("Failed to convert " + cssFile.getAbsolutePath());
                e.printStackTrace();
            }
        }
    }

    private static boolean doesCorrespondingGssFileExists(File cssFile) {
        File gssFile = getCorrespondingGssFile(cssFile);
        return gssFile.exists();
    }

    private static File getCorrespondingGssFile(File cssFile) {
        String name = cssFile.getName();
        assert name.endsWith(".css");

        name = name.substring(0, name.length() - ".css".length()) + ".gss";
        return new File(cssFile.getParentFile(), name);
    }

    private static void writeGss(String gss, File cssFile) throws IOException {
        File gssFile = getCorrespondingGssFile(cssFile);
        Files.asCharSink(gssFile, Charsets.UTF_8).write(gss);
    }

    private static String convertFile(File resource, Set<String> simpleBooleanConditions, Set<URL> scopeFiles)
            throws MalformedURLException, UnableToCompleteException {
        Predicate<String> simpleConditionPredicate;

        if (simpleBooleanConditions != null) {
            simpleConditionPredicate = Predicates.in(simpleBooleanConditions);
        } else {
            simpleConditionPredicate = Predicates.alwaysFalse();
        }

        return new Css2Gss(resource.toURI().toURL(), false, simpleConditionPredicate, scopeFiles).toGss();
    }

    private static void printUsage() {
        System.err.println("Usage :");
        System.err.println("java " + Css2Gss.class.getName() + " [Options] [file or directory]");
        System.err.println("Options:");
        System.err.println(
                " -r -> Recursively convert all css files on the given directory" + "(leaves .css files in place)");
        System.err.println(" -condition list_of_condition -> Specify a comma-separated list of "
                + "variables that are used in conditionals and that will be mapped to configuration "
                + "properties. The converter will not use the is() function when it will convert these "
                + "conditions");
        System.err.println(" -scope list_of_files -> Specify a comma-separated list of "
                + "css files to be used in this conversion to determine all defined variables");
    }

    static class Options {
        static final Map<String, ArgumentConsumer> argumentConsumers;

        static {
            argumentConsumers = new LinkedHashMap<String, ArgumentConsumer>();
            argumentConsumers.put("-r", new ArgumentConsumer() {
                @Override
                public boolean consume(Options option, String nextArg) {
                    option.recurse = true;
                    return false;
                }
            });

            argumentConsumers.put("-condition", new ArgumentConsumer() {
                @Override
                public boolean consume(Options option, String nextArg) {
                    if (nextArg == null) {
                        quitEarly("-condition option must be followed by a comma separated list of conditions");
                    }

                    option.simpleBooleanConditions = FluentIterable.from(Splitter.on(',').split(nextArg)).toSet();

                    return true;
                }
            });

            argumentConsumers.put("-basedir", new ArgumentConsumer() {
                @Override
                public boolean consume(Options option, String nextArg) {
                    nextArg += nextArg.endsWith(File.separator) ? "" : File.separator;
                    option.baseDir = new File(nextArg);

                    if (!option.baseDir.exists() || !option.baseDir.isDirectory()) {
                        quitEarly("Basedir is does not exist");
                    }

                    return true;
                }
            });

            argumentConsumers.put("-scope", new ArgumentConsumer() {
                @Override
                public boolean consume(Options option, String nextArg) {
                    option.scope = nextArg;
                    return true;
                }
            });
        }

        boolean recurse;
        boolean singleFile;
        ImmutableSet<URL> scopeFiles = ImmutableSet.of();
        File resource;
        Set<String> simpleBooleanConditions;
        File baseDir;

        private String scope;

        private static Options parseOrQuit(String[] args) {
            if (!validateArgs(args)) {
                quitEarly(null);
            }

            Options options = new Options();

            int index = 0;

            // consume options
            while (index < args.length - 1) {
                String arg = args[index++];
                String nextArg = index < args.length - 1 ? args[index] : null;

                ArgumentConsumer consumer = argumentConsumers.get(arg);

                if (consumer == null) {
                    quitEarly("Unknown argument: " + arg);
                }

                boolean skipNextArg = consumer.consume(options, nextArg);

                if (skipNextArg) {
                    index++;
                }
            }

            if (index == args.length) {
                quitEarly("Missing file or directly as last parameter");
            }

            if (options.scope != null) {
                ImmutableSet<String> scopeFileSet = FluentIterable.from(Splitter.on(',').split(options.scope))
                        .toSet();
                HashSet<URL> set = new HashSet<URL>();
                for (String scopeFile : scopeFileSet) {
                    File file = null;
                    if (options.baseDir != null && !scopeFile.startsWith(File.separator)) {
                        file = new File(options.baseDir, scopeFile).getAbsoluteFile();
                    } else {
                        file = new File(scopeFile).getAbsoluteFile();
                    }
                    if (!file.exists() && !file.isFile()) {
                        quitEarly("The scope file '" + file.getAbsolutePath() + "' does not exist");
                    }
                    try {
                        set.add(file.toURI().toURL());
                    } catch (MalformedURLException e) {
                        quitEarly("Can not create url for scope file: '" + scopeFile + "'");
                    }
                }
                options.scopeFiles = ImmutableSet.copyOf(set);
            }

            // last argument is always the file or directory path
            if (options.baseDir != null && !args[index].startsWith(File.separator)) {
                options.resource = new File(options.baseDir, args[index]).getAbsoluteFile();
            } else {
                options.resource = new File(args[index]).getAbsoluteFile();
            }

            options.singleFile = !options.resource.isDirectory();

            // validate options
            if (!options.resource.exists()) {
                quitEarly("File or Directory does not exists: " + options.resource.getAbsolutePath());
            }

            if (options.recurse && !options.resource.isDirectory()) {
                quitEarly("When using -r the last parameter needs to be a directory");
            }

            return options;
        }

        private static void quitEarly(String errorMsg) {
            if (errorMsg != null) {
                System.err.println("Error: " + errorMsg);
            }

            printUsage();
            System.exit(-1);
        }

        private static boolean validateArgs(String[] args) {
            return args.length > 0 && args.length < 9;
        }
    }

    private interface ArgumentConsumer {
        /**
         *Returns true if the next argument has been consumed.
         */
        boolean consume(Options option, String nextArg);
    }
}