cc.alcina.framework.entity.gen.SimpleCssResourceGenerator.java Source code

Java tutorial

Introduction

Here is the source code for cc.alcina.framework.entity.gen.SimpleCssResourceGenerator.java

Source

/*
 * Copyright 2008 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 cc.alcina.framework.entity.gen;

import java.io.InputStream;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.google.gwt.core.ext.BadPropertyValueException;
import com.google.gwt.core.ext.ConfigurationProperty;
import com.google.gwt.core.ext.Generator;
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.core.ext.typeinfo.JMethod;
import com.google.gwt.dev.CompilerContext;
import com.google.gwt.dev.cfg.ModuleDef;
import com.google.gwt.dev.javac.StandardGeneratorContext;
import com.google.gwt.dev.resource.Resource;
import com.google.gwt.dev.util.Empty;
import com.google.gwt.dev.util.Util;
import com.google.gwt.resources.ext.AbstractResourceGenerator;
import com.google.gwt.resources.ext.ResourceContext;
import com.google.gwt.resources.ext.ResourceGeneratorUtil;
import com.google.gwt.resources.ext.SupportsGeneratorResultCaching;
import com.google.gwt.user.rebind.SourceWriter;
import com.google.gwt.user.rebind.StringSourceWriter;

import cc.alcina.framework.entity.ResourceUtilities;
import cc.alcina.framework.gwt.client.gen.SimpleCssResource;

/**
 * Provides implementations of SimpleCssResource.
 */
public final class SimpleCssResourceGenerator extends AbstractResourceGenerator
        implements SupportsGeneratorResultCaching {
    public static final String IGNORE_DATA_URLS = "alcina.SimpleCssResourceGenerator.ignoreMissingDataUrls";

    /**
     * Java compiler has a limit of 2^16 bytes for encoding string constants in
     * a class file. Since the max size of a character is 4 bytes, we'll limit
     * the number of characters to (2^14 - 1) to fit within one record.
     */
    private static final int MAX_STRING_CHUNK = 16383;

    private static final int MAX_DATA_URL_LENGTH = 32766;

    boolean logMissingUrlResources = true;

    @Override
    public String createAssignment(TreeLogger logger, ResourceContext context, JMethod method)
            throws UnableToCompleteException {
        try {
            ConfigurationProperty cp = context.getGeneratorContext().getPropertyOracle()
                    .getConfigurationProperty(IGNORE_DATA_URLS);
            logMissingUrlResources = !Boolean.valueOf(cp.getValues().get(0));
        } catch (BadPropertyValueException e1) {
            e1.printStackTrace();
        }
        URL[] resources = ResourceGeneratorUtil.findResources(logger, context, method);
        if (resources.length != 1) {
            logger.log(TreeLogger.ERROR, "Exactly one resource must be specified", null);
            throw new UnableToCompleteException();
        }
        URL resource = resources[0];
        SourceWriter sw = new StringSourceWriter();
        // Write the expression to create the subtype.
        sw.println("new " + SimpleCssResource.class.getName() + "() {");
        sw.indent();
        if (!AbstractResourceGenerator.STRIP_COMMENTS) {
            // Convenience when examining the generated code.
            sw.println("// " + resource.toExternalForm());
        }
        sw.println("public String getText() {");
        sw.indent();
        String toWrite = Util.readURLAsString(resource);
        if (context.supportsDataUrls()) {
            try {
                toWrite = replaceWithDataUrls(context, toWrite);
            } catch (Exception e) {
                logger.log(Type.ERROR, "css data url gen", e);
                throw new UnableToCompleteException();
            }
        }
        if (toWrite.length() > MAX_STRING_CHUNK) {
            writeLongString(sw, toWrite);
        } else {
            sw.println("return \"" + Generator.escape(toWrite) + "\";");
        }
        sw.outdent();
        sw.println("}");
        sw.println("public String getName() {");
        sw.indent();
        sw.println("return \"" + method.getName() + "\";");
        sw.outdent();
        sw.println("}");
        sw.outdent();
        sw.println("}");
        return sw.toString();
    }

    private String[] getAllPublicFiles(ModuleDef module) {
        module.refresh();
        return module.getPublicResourceOracle().getPathNames().toArray(Empty.STRINGS);
    }

    private String replaceWithDataUrls(ResourceContext context, String toWrite) throws Exception {
        Pattern urlPat = Pattern.compile("url\\s*\\((?!'?data:)(?!http:)(.+?)\\)");
        Matcher m = urlPat.matcher(toWrite);
        while (m.find()) {
            String url = m.group(1);
            int qIdx = url.indexOf('?');
            if (qIdx != -1) {
                url = url.substring(0, qIdx);
            }
            // url = url.replaceFirst("(.+?)\\?.*", "$1");
            url = url.replace("'", "").replace("\"", "");
            StandardGeneratorContext generatorContext = (StandardGeneratorContext) context.getGeneratorContext();
            Field compilerContextField = StandardGeneratorContext.class.getDeclaredField("compilerContext");
            compilerContextField.setAccessible(true);
            CompilerContext compilerContext = (CompilerContext) compilerContextField.get(generatorContext);
            Field moduleField = CompilerContext.class.getDeclaredField("module");
            moduleField.setAccessible(true);
            ModuleDef module = (ModuleDef) moduleField.get(compilerContext);
            Resource resource = module.findPublicFile(url);
            if (resource == null) {
                resource = module.findPublicFile("gwt/standard/" + url);
            }
            if (resource == null) {
                if (url.contains("://")) {
                    continue;
                } else {
                    if (logMissingUrlResources) {
                        String[] pub = getAllPublicFiles(module);
                        // System.out.println("missing url resource - " + url);
                        for (String path : pub) {
                            if (path.contains(url)) {
                                System.out.format("Maybe - %s : %s\n", url, path);
                            }
                        }
                    }
                    continue;
                }
            }
            InputStream contents = resource.openContents();
            byte[] bytes = ResourceUtilities.readStreamToByteArray(contents);
            String out = Base64.encodeBytes(bytes);
            String fileName = url.replaceFirst(".+/", "");
            String extension = fileName.replaceFirst(".+\\.", "");
            String mimeType = null;
            if (extension.toLowerCase().equals("gif")) {
                mimeType = "image/gif";
            } else if (extension.toLowerCase().equals("jpeg")) {
                mimeType = "image/jpeg";
            } else if (extension.toLowerCase().equals("jpg")) {
                mimeType = "image/jpeg";
            } else if (extension.toLowerCase().equals("png")) {
                mimeType = "image/png";
            }
            if (mimeType != null) {
                String encoded = String.format("url(data:%s;base64,%s)", mimeType, out.replace("\n", ""));
                if (encoded.length() > 5000) {
                    // System.out.println("warn - large css sprite - " + url);
                }
                if (encoded.length() < MAX_DATA_URL_LENGTH) {
                    toWrite = m.replaceFirst(encoded);
                    m = urlPat.matcher(toWrite);
                }
            } else {
                System.out.println("unable to resolve mime type - " + url);
                // continue on
            }
        }
        return toWrite;
    }

    /**
     * A single constant that is too long will crash the compiler with an out of
     * memory error. Break up the constant and generate code that appends using
     * a buffer.
     */
    private void writeLongString(SourceWriter sw, String toWrite) {
        sw.println("StringBuilder builder = new StringBuilder();");
        int offset = 0;
        int length = toWrite.length();
        while (offset < length - 1) {
            int subLength = Math.min(MAX_STRING_CHUNK, length - offset);
            sw.print("builder.append(\"");
            sw.print(Generator.escape(toWrite.substring(offset, offset + subLength)));
            sw.println("\");");
            offset += subLength;
        }
        sw.println("return builder.toString();");
    }
}