com.google.gdt.eclipse.designer.model.widgets.support.CssSupport.java Source code

Java tutorial

Introduction

Here is the source code for com.google.gdt.eclipse.designer.model.widgets.support.CssSupport.java

Source

/*******************************************************************************
 * Copyright 2011 Google Inc. All Rights Reserved.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * 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.gdt.eclipse.designer.model.widgets.support;

import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.gdt.eclipse.designer.Activator;
import com.google.gdt.eclipse.designer.util.DefaultModuleDescription;
import com.google.gdt.eclipse.designer.util.ModuleDescription;
import com.google.gdt.eclipse.designer.util.Utils;

import org.eclipse.wb.internal.core.utils.IOUtils2;
import org.eclipse.wb.internal.core.utils.execution.ExecutionUtils;
import org.eclipse.wb.internal.core.utils.execution.RunnableEx;
import org.eclipse.wb.internal.core.utils.execution.RunnableObjectEx;

import org.eclipse.core.resources.IFile;
import org.eclipse.jface.text.Document;

import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;

import java.text.MessageFormat;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Support for CSS resources in {@link GwtState}.
 * 
 * @author scheglov_ke
 * @author mitin_aa
 * @coverage gwt.model
 */
public final class CssSupport {
    private final GwtState state;
    private List<String> resources;
    private List<IFile> files;
    private long nextRequestId;
    private final Map<IFile, Long> filesStampMap = Maps.newHashMap();
    private final Set<String> waitRequestSet = Sets.newHashSet();
    private final Set<String> waitApplySet = Sets.newHashSet();

    ////////////////////////////////////////////////////////////////////////////
    //
    // Constructor
    //
    ////////////////////////////////////////////////////////////////////////////
    public CssSupport(GwtState state) {
        this.state = state;
    }

    ////////////////////////////////////////////////////////////////////////////
    //
    // Access
    //
    ////////////////////////////////////////////////////////////////////////////
    /**
     * @return the CSS resources.
     */
    public List<String> getResources() {
        return resources;
    }

    /**
     * @return the {@link IFile}s for CSS resources.
     */
    public List<IFile> getFiles() {
        return files;
    }

    ////////////////////////////////////////////////////////////////////////////
    //
    // Private access
    //
    ////////////////////////////////////////////////////////////////////////////
    /**
     * Prepares CSS resources and files.
     */
    void prepareResources() throws Exception {
        ModuleDescription moduleDescription = state.getModuleDescription();
        resources = Utils.getCssResources(moduleDescription);
        if (moduleDescription instanceof DefaultModuleDescription) {
            files = Utils.getFilesForResources(((DefaultModuleDescription) moduleDescription).getFile(), resources);
            for (IFile file : files) {
                filesStampMap.put(file, file.getModificationStamp());
            }
        }
    }

    /**
     * Adds links to the CSS resources.
     */
    void addLinkDeclarations(List<String> declarations) {
        for (String cssResource : resources) {
            declarations.add(
                    MessageFormat.format("<link rel=''stylesheet'' type=''text/css'' href=''{0}''/>", cssResource));
        }
    }

    /**
     * Adds DIVs for CSS "apply wait", see {@link #waitFor()} JavaDoc.
     */
    String addReloadingFeature(String html) {
        StringBuilder declarations = new StringBuilder();
        for (String resource : resources) {
            String name = getWaitRequestName(resource);
            declarations.append("<div class='");
            declarations.append(name);
            declarations.append("'></div>\n");
        }
        return StringUtils.replace(html, "%CSS_WAIT_DECLARATIONS%", declarations.toString());
    }

    /**
     * @return <code>true</code> if one or more CSS files were modified (and schedules them for
     *         reloading with next refresh).
     */
    boolean isModified() {
        waitRequestSet.clear();
        waitApplySet.clear();
        boolean modified = false;
        // check CSS files
        boolean hasModifiedCSSFiles = false;
        for (Map.Entry<IFile, Long> entry : filesStampMap.entrySet()) {
            IFile file = entry.getKey();
            long storedStamp = entry.getValue();
            long fileStamp = file.getModificationStamp();
            if (fileStamp != storedStamp) {
                modified = true;
                filesStampMap.put(file, fileStamp);
                hasModifiedCSSFiles = true;
            }
        }
        // schedule CSS load waiting
        if (hasModifiedCSSFiles) {
            synchronized (waitRequestSet) {
                for (String resource : resources) {
                    String waitRequestName = getWaitRequestName(resource);
                    waitRequestSet.add(waitRequestName);
                }
            }
        }
        // if has modified CSS files, ask Browser for reload
        if (modified) {
            ExecutionUtils.runLog(new RunnableEx() {
                public void run() throws Exception {
                    state.getHostModeSupport().invokeNativeVoid("__reload_css", ArrayUtils.EMPTY_CLASS_ARRAY,
                            ArrayUtils.EMPTY_OBJECT_ARRAY);
                }
            });
            waitFor();
        }
        // return final modification state
        return modified;
    }

    /**
     * Provides wait operation for CSS files to be applied.
     * 
     * CSS files waiting works as following:
     * <p>
     * When HTTP-server recognizes request for CSS resource it added into requested CSS-file fake CSS
     * class with style which applying caused browser to request some resource from HTTP-server, e.g.
     * HTTP-server adds something like that:
     * 
     * <pre> 
     * .gwt__wait_stylesheetXXXX {
     *   visibility: hidden;
     *   background: url("cssFileName_cssFileTimestamp");
     * }
     * </pre>
     * Where: XXXX is some unique identifier (hashCode for CSS file name), cssFileName is the name of
     * requested CSS file cssFileTimestamp is the timestamp of requested CSS file. So, when the
     * browser applies received CSS file with fake CSS class it requests
     * "cssFileName_cssFileTimestamp" resource from HTTP-server.
     * <p>
     * Note: browser would not request "cssFileName_cssFileTimestamp" resource without any
     * HTML-element with fake CSS class applied, thats why its needed to dynamically generate the HTML
     * element with fake CSS class for every CSS file in project (see set CSS wait declarations in
     * constructor).
     */
    void waitFor() {
        long startWait = System.currentTimeMillis();
        while (true) {
            // may be done
            boolean done = true;
            synchronized (waitRequestSet) {
                done &= waitRequestSet.isEmpty();
            }
            synchronized (waitApplySet) {
                done &= waitApplySet.isEmpty();
            }
            if (done) {
                break;
            }
            // do not wait more than 500ms
            if (System.currentTimeMillis() - startWait > 500) {
                break;
            }
            // wait more
            state.runMessagesLoop();
        }
    }

    /**
     * @return the name of CSS class to use for waiting given CSS file which is referred by
     *         {@link IFile} or public resource path.
     */
    private static String getWaitRequestName(String path) {
        String name = path;
        name = StringUtils.replace(name, "/", "_");
        name = StringUtils.removeEndIgnoreCase(name, ".css");
        return "wbp__wait_stylesheet_" + name;
    }

    ////////////////////////////////////////////////////////////////////////////
    //
    // Content access
    //
    ////////////////////////////////////////////////////////////////////////////
    /**
     * @return the empty PNG image bytes, if this resource path is "wait for CSS" marker, or
     *         <code>null</code> if some other resource for requested.
     */
    byte[] getResourceWait(String publicResourcePath) {
        synchronized (waitApplySet) {
            if (waitApplySet.remove(publicResourcePath)) {
                // IE requires the content, otherwise 'image.complete' is always false.
                // This doesn't affect other browsers though, but returning some content is the right way.
                return ExecutionUtils.runObjectIgnore(new RunnableObjectEx<byte[]>() {
                    public byte[] runObject() throws Exception {
                        return IOUtils2.readBytes(Activator.getFile("icons/empty.png"));
                    }
                }, null);
            }
        }
        return null;
    }

    /**
     * @param result
     *          the bytes of CSS file.
     * @return the updates bytes of CSS file, with "wait for loading" markers added.
     */
    byte[] getResource(String publicResourcePath, byte[] result) throws Exception {
        if (publicResourcePath.toLowerCase().endsWith(".css")) {
            if (resources.contains(publicResourcePath)) {
                // prepare "wait apply" resource, should be image
                String waitRequestName = getWaitRequestName(publicResourcePath);
                String waitApplyName = waitRequestName + "_" + nextRequestId++ + ".png";
                // prepare content
                String cssContent = new String(result, "UTF-8");
                cssContent = addRulesMarkers(publicResourcePath, cssContent);
                // add "wait apply" class to the end of CSS content
                {
                    cssContent += "\n." + waitRequestName + "{";
                    cssContent += "visibility: hidden; ";
                    cssContent += "background-image: url('";
                    cssContent += state.m_moduleBaseURL + waitApplyName;
                    cssContent += "'); }\n";
                    result = cssContent.getBytes("UTF-8");
                }
                // update request/apply sets
                synchronized (waitApplySet) {
                    waitApplySet.add(waitApplyName);
                }
                synchronized (waitRequestSet) {
                    waitRequestSet.remove(waitRequestName);
                }
            }
        }
        return result;
    }

    private String addRulesMarkers(String publicResourcePath, String content) throws Exception {
        Document sourceDocument = new Document(content);
        /*CssEditContext editContext = new CssEditContext(sourceDocument);
        CssDocument document = editContext.getCssDocument();
        for (CssRuleNode rule : document.getRules()) {
          int ruleId = m_lastTrackedRule++;
          rule.addDeclaration(CssFactory.newDeclaration("wbp-css-rule-" + ruleId, "0"));
        }
        System.out.println(sourceDocument.get());*/
        return sourceDocument.get();
    }
}