Java tutorial
package com.data2semantics.yasgui.mgwtlinker.linker; /* * #%L * YASGUI * %% * Copyright (C) 2013 Laurens Rietveld * %% * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * #L% */ import java.io.File; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.TreeMap; import org.apache.commons.io.FileUtils; import com.data2semantics.yasgui.mgwtlinker.server.BindingProperty; import com.data2semantics.yasgui.shared.StaticConfig; import com.google.gwt.core.ext.LinkerContext; 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.linker.AbstractLinker; import com.google.gwt.core.ext.linker.ArtifactSet; import com.google.gwt.core.ext.linker.EmittedArtifact; import com.google.gwt.core.ext.linker.LinkerOrder; import com.google.gwt.core.ext.linker.Shardable; import com.google.gwt.core.ext.linker.SyntheticArtifact; import com.google.gwt.core.ext.linker.impl.SelectionInformation; @LinkerOrder(LinkerOrder.Order.POST) @Shardable public class PermutationMapLinker extends AbstractLinker { public static final String EXTERNAL_FILES_CONFIGURATION_PROPERTY_NAME = "html5manifestlinker_files"; public static final String PERMUTATION_MANIFEST_FILE_ENDING = ".appcache"; public static final String PERMUTATION_FILE_ENDING = ".perm.xml"; public static final String MANIFEST_MAP_FILE_NAME = "manifest.map"; private XMLPermutationProvider xmlPermutationProvider; public PermutationMapLinker() { xmlPermutationProvider = new XMLPermutationProvider(); manifestWriter = new ManifestWriter(); } private ManifestWriter manifestWriter; @Override public String getDescription() { return "PermutationMapLinker"; } @Override public ArtifactSet link(TreeLogger logger, LinkerContext context, ArtifactSet artifacts, boolean onePermutation) throws UnableToCompleteException { if (onePermutation) { Map<String, Set<BindingProperty>> permutationMap = buildPermutationMap(logger, context, artifacts); Set<Entry<String, Set<BindingProperty>>> entrySet = permutationMap.entrySet(); // since we are in onePermutation there should be just one // strongName // better make sure.. if (permutationMap.size() != 1) { logger.log(Type.ERROR, "There should be only one permutation right now, but there were: '" + permutationMap.size() + "'"); throw new UnableToCompleteException(); } Entry<String, Set<BindingProperty>> next = entrySet.iterator().next(); String strongName = next.getKey(); Set<BindingProperty> bindingProperties = next.getValue(); // all artifacts for this compilation Set<String> artifactsForCompilation = getArtifactsForCompilation(logger, context, artifacts); ArtifactSet toReturn = new ArtifactSet(artifacts); PermutationArtifact permutationArtifact = new PermutationArtifact(PermutationMapLinker.class, strongName, artifactsForCompilation, bindingProperties); toReturn.add(permutationArtifact); return toReturn; } ArtifactSet toReturn = new ArtifactSet(artifacts); Map<String, Set<BindingProperty>> map = buildPermutationMap(logger, context, artifacts); if (map.size() == 0) { // hosted mode return toReturn; } Map<String, PermutationArtifact> permutationArtifactAsMap = getPermutationArtifactAsMap(artifacts); //we need different file sets/manifests for our dev version (unminimized js), and our stable version List<String> stableExternalFiles = getStableExternalFiles(logger, context); List<String> devExternalFiles = getDevExternalFiles(logger, context); // build manifest html page for our stable version (included as iframe in our webapp) String appcacheService = "manifest.appcache"; String manifestHtmlPage = buildManifestHtmlPage(appcacheService); toReturn.add(emitString(logger, manifestHtmlPage, appcacheService + ".html")); // build manifest html page for our stable version (included as iframe in our webapp) String devManifestHtmlPage = buildManifestHtmlPage(appcacheService + "?type=dev"); toReturn.add(emitString(logger, devManifestHtmlPage, "manifest.dev.appcache.html")); Set<String> allPermutationFiles = getAllPermutationFiles(permutationArtifactAsMap); // get all artifacts Set<String> allArtifacts = getArtifactsForCompilation(logger, context, artifacts); for (Entry<String, PermutationArtifact> entry : permutationArtifactAsMap.entrySet()) { PermutationArtifact permutationArtifact = entry.getValue(); // make a copy of all artifacts HashSet<String> filesForCurrentPermutation = new HashSet<String>(allArtifacts); // remove all permutations filesForCurrentPermutation.removeAll(allPermutationFiles); // add files of the one permutation we are interested in // leaving the common stuff for all permutations in... filesForCurrentPermutation.addAll(entry.getValue().getPermutationFiles()); filesForCurrentPermutation = appendVersionIfNeeded(filesForCurrentPermutation); String permXml = buildPermXml(logger, permutationArtifact, filesForCurrentPermutation, stableExternalFiles); // emit permutation information file SyntheticArtifact emitString = emitString(logger, permXml, permutationArtifact.getPermutationName() + PERMUTATION_FILE_ENDING); toReturn.add(emitString); // build manifest for our stable version String manifestFile = entry.getKey() + PERMUTATION_MANIFEST_FILE_ENDING; @SuppressWarnings("serial") Map<String, String> fallbacks = new HashMap<String, String>() { { put("/", "../index.jsp"); } }; String maniFest = buildManiFest(entry.getKey(), stableExternalFiles, filesForCurrentPermutation, fallbacks); toReturn.add(emitString(logger, maniFest, manifestFile)); // build manifest for our dev version String devManifestFile = entry.getKey() + ".dev" + PERMUTATION_MANIFEST_FILE_ENDING; String devManiFest = buildManiFest(entry.getKey(), devExternalFiles, filesForCurrentPermutation); toReturn.add(emitString(logger, devManiFest, devManifestFile)); } toReturn.add(createPermutationMap(logger, map)); return toReturn; } private HashSet<String> appendVersionIfNeeded(HashSet<String> filesForCurrentPermutation) { HashSet<String> newFileSet = new HashSet<String>(); for (String file : filesForCurrentPermutation) { if (file.contains("nocache")) { file += "?" + StaticConfig.VERSION; } newFileSet.add(file); } return newFileSet; } protected String buildPermXml(TreeLogger logger, PermutationArtifact permutationArtifact, Set<String> gwtCompiledFiles, List<String> otherResources) throws UnableToCompleteException { HashSet<String> namesForPermXml = new HashSet<String>(gwtCompiledFiles); namesForPermXml.addAll(otherResources); try { return xmlPermutationProvider.writePermutationInformation(permutationArtifact.getPermutationName(), permutationArtifact.getBindingProperties(), namesForPermXml); } catch (XMLPermutationProviderException e) { logger.log(Type.ERROR, "can not build xml for permutation file", e); throw new UnableToCompleteException(); } } /** * @param permutationArtifactAsMap * @return */ protected Set<String> getAllPermutationFiles(Map<String, PermutationArtifact> permutationArtifactAsMap) { Set<String> allPermutationFiles = new HashSet<String>(); for (Entry<String, PermutationArtifact> entry : permutationArtifactAsMap.entrySet()) { allPermutationFiles.addAll(entry.getValue().getPermutationFiles()); } return allPermutationFiles; } protected Map<String, PermutationArtifact> getPermutationArtifactAsMap(ArtifactSet artifacts) { Map<String, PermutationArtifact> hashMap = new HashMap<String, PermutationArtifact>(); for (PermutationArtifact permutationArtifact : artifacts.find(PermutationArtifact.class)) { hashMap.put(permutationArtifact.getPermutationName(), permutationArtifact); } return hashMap; } protected boolean shouldArtifactBeInManifest(String pathName) { if (pathName.endsWith("symbolMap") || pathName.endsWith(".xml.gz") || pathName.endsWith("rpc.log") || pathName.endsWith("gwt.rpc") || pathName.endsWith("manifest.txt") || pathName.startsWith("rpcPolicyManifest") || pathName.startsWith("soycReport") || pathName.endsWith(".cssmap") || pathName.contains("sc/system") || pathName.contains("sc/schema") || pathName.contains("skins/Graphite") || pathName.contains(" ")) { return false; } return true; } protected Set<String> getArtifactsForCompilation(TreeLogger logger, LinkerContext context, ArtifactSet artifacts) { Set<String> artifactNames = new HashSet<String>(); for (EmittedArtifact artifact : artifacts.find(EmittedArtifact.class)) { String pathName = artifact.getPartialPath(); if (shouldArtifactBeInManifest(pathName)) { artifactNames.add(pathName); } } return artifactNames; } protected String buildManiFest(String moduleName, List<String> staticResources, Set<String> cacheResources) { return manifestWriter.writeManifest(staticResources, cacheResources, null); } protected String buildManiFest(String moduleName, List<String> staticResources, Set<String> cacheResources, Map<String, String> fallbacks) { return manifestWriter.writeManifest(staticResources, cacheResources, fallbacks); } protected String buildManifestHtmlPage(String manifestFileLocation) { String html = "<html manifest=\"" + manifestFileLocation + "\">\n"; html += "<head>" + "<script type=\"text/javascript\">" + "var appCache = window.applicationCache;\n" + "\n" + "if (appCache != undefined && window.parent != undefined) {\n" + " if (window.parent.appcacheEventCached != undefined) {\n" + " // Fired after the first cache of the manifest.\n" + " appCache.addEventListener('cached', window.parent.appcacheEventCached, false);\n" + " }\n" + " if (window.parent.appcacheEventChecking != undefined) {\n" + " // Checking for an update. Always the first event fired in the sequence.\n" + " appCache.addEventListener('checking', window.parent.appcacheEventCached, false);\n" + " }\n" + " if (window.parent.appcacheEventDownloading != undefined) {\n" + " // An update was found. The browser is fetching resources.\n" + " appCache.addEventListener('downloading', window.parent.appcacheEventDownloading, false);\n" + " }\n" + " if (window.parent.appcacheEventError != undefined) {\n" + " // The manifest returns 404 or 410, the download failed,\n" + " // or the manifest changed while the download was in progress.\n" + " appCache.addEventListener('error', window.parent.appcacheEventError, false);\n" + " }\n" + " if (window.parent.appcacheEventNoupdate != undefined) {\n" + " // Fired after the first download of the manifest.\n" + " appCache.addEventListener('noupdate', window.parent.appcacheEventNoupdate, false);\n" + " }\n" + " if (window.parent.apcacheEventObsolete != undefined) {\n" + " // Fired if the manifest file returns a 404 or 410.\n" + " // This results in the application cache being deleted.\n" + " appCache.addEventListener('obsolete', window.parent.apcacheEventObsolete, false);\n" + " }\n" + " if (window.parent.appcacheEventProgress != undefined) {\n" + " // Fired for each resource listed in the manifest as it is being fetched.\n" + " appCache.addEventListener('progress', window.parent.appcacheEventProgress, false);\n" + " }\n" + " if (window.parent.appcacheEventUpdateReady != undefined) {\n" + " // Fired when the manifest resources have been newly redownloaded.\n" + " appCache.addEventListener('updateready', window.parent.appcacheEventUpdateReady, false);\n" + " }\n" + "}" + "</script>" + "</head>\n"; html += "<body></body>\n"; html += "</html>\n"; return html; } private Set<String> getFontFiles() { HashSet<String> set = new HashSet<String>(); set.add("../fonts/fonts.css?" + StaticConfig.VERSION); set.addAll(getExternalFilesFromDir("fonts", "", "eot", "svg", "ttf", "woff"));//don't append version string here return set; } /** * Retrieves files we should add to manifest, but which are not generated by GWT (i.e. images/js files we use separately) * @param logger * @param context * @return */ protected List<String> getStableExternalFiles(TreeLogger logger, LinkerContext context) { ArrayList<String> set = new ArrayList<String>(); //all external js/css files should be minimized/aggregated by our maven plugin set.add("../static/yasgui.js?" + StaticConfig.VERSION); set.add("../static/yasgui.css?" + StaticConfig.VERSION); set.add("../index.jsp"); set.addAll(getFontFiles()); set.addAll(getExternalFilesFromDir("images", "?" + StaticConfig.VERSION.replace(".", ""), "png", "jpg", "gif")); return set; } /** * Retrieves files we should add to manifest, but which are not generated by GWT (i.e. images/js files we use separately) * @param logger * @param context * @return */ protected List<String> getDevExternalFiles(TreeLogger logger, LinkerContext context) { ArrayList<String> set = new ArrayList<String>( getExternalFilesFromDir("assets", "?" + StaticConfig.VERSION, "js", "css")); set.addAll(getExternalFilesFromDir("images", "?" + StaticConfig.VERSION.replace(".", ""), "png", "jpg", "gif")); set.addAll(getFontFiles()); set.add("../dev.jsp"); return set; } protected EmittedArtifact createPermutationMap(TreeLogger logger, Map<String, Set<BindingProperty>> map) throws UnableToCompleteException { try { String string = xmlPermutationProvider.serializeMap(map); return emitString(logger, string, MANIFEST_MAP_FILE_NAME); } catch (XMLPermutationProviderException e) { logger.log(Type.ERROR, "can not build manifest map", e); throw new UnableToCompleteException(); } } protected Map<String, Set<BindingProperty>> buildPermutationMap(TreeLogger logger, LinkerContext context, ArtifactSet artifacts) throws UnableToCompleteException { HashMap<String, Set<BindingProperty>> map = new HashMap<String, Set<BindingProperty>>(); for (SelectionInformation result : artifacts.find(SelectionInformation.class)) { Set<BindingProperty> list = new HashSet<BindingProperty>(); map.put(result.getStrongName(), list); TreeMap<String, String> propMap = result.getPropMap(); Set<Entry<String, String>> set = propMap.entrySet(); for (Entry<String, String> entry : set) { BindingProperty bindingProperty = new BindingProperty(entry.getKey(), entry.getValue()); list.add(bindingProperty); } } return map; } /** * I didnt find a proper way to get the relative location of our assets/images dir: are we in the project root, or are we in the target dir? * Therefore, use this * This depends on whether this project is deployed from eclipse, or via maven. * Therefore, this (ugly) workaround * @return */ private String getWebappPath() { String webappPath = ""; File file = new File("src/main/webapp"); if (file.exists()) { webappPath = file.getPath() + "/"; } return webappPath; } private Set<String> getExternalFilesFromDir(String dir, String append, String... includeExtensions) { HashSet<String> fileSet = new HashSet<String>(); String webappPath = getWebappPath(); @SuppressWarnings("unchecked") ArrayList<File> assetFiles = new ArrayList<File>( FileUtils.listFiles(new File(webappPath + dir), includeExtensions, true)); for (File assetFile : assetFiles) { String manifestFile = assetFile.getPath(); if (webappPath.length() > 0) { //we need to make the manifest files relative to webap dir manifestFile = manifestFile.substring(webappPath.length()); } //the manifest file is located in the Yasgui subdir of our webapp. The files we are adding are in the parent folder: fileSet.add("../" + manifestFile + append); } ; return fileSet; } }