diff options
author | John Ahlroos <john@vaadin.com> | 2013-04-23 16:00:27 +0300 |
---|---|---|
committer | Vaadin Code Review <review@vaadin.com> | 2013-04-23 13:21:16 +0000 |
commit | cda367eb0409f07ce79096319dc961cd77bc3430 (patch) | |
tree | 7c18509d72abe298e9b1f5de23c24988e8e0cb43 /server | |
parent | ccec229966d6934694f99b7c0b70c2862f4915a6 (diff) | |
download | vaadin-framework-cda367eb0409f07ce79096319dc961cd77bc3430.tar.gz vaadin-framework-cda367eb0409f07ce79096319dc961cd77bc3430.zip |
Moved SASSAddonImportFileCreator and ClassPathExplorer to vaadin-server #11591
Change-Id: I81f453d6a74a0a47c7007e0bfe20c72b866134c7
Diffstat (limited to 'server')
-rw-r--r-- | server/src/com/vaadin/server/themeutils/SASSAddonImportFileCreator.java | 178 | ||||
-rw-r--r-- | server/src/com/vaadin/server/widgetsetutils/ClassPathExplorer.java | 538 |
2 files changed, 716 insertions, 0 deletions
diff --git a/server/src/com/vaadin/server/themeutils/SASSAddonImportFileCreator.java b/server/src/com/vaadin/server/themeutils/SASSAddonImportFileCreator.java new file mode 100644 index 0000000000..5cec9bd9d5 --- /dev/null +++ b/server/src/com/vaadin/server/themeutils/SASSAddonImportFileCreator.java @@ -0,0 +1,178 @@ +/* + * Copyright 2000-2013 Vaadin Ltd. + * + * 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.vaadin.server.themeutils; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Map; + +import com.vaadin.server.widgetsetutils.ClassPathExplorer; +import com.vaadin.server.widgetsetutils.ClassPathExplorer.LocationInfo; + +/** + * Helper class for managing the addon imports and creating an a SCSS file for + * importing all your addon themes. The helper method searches the classpath for + * Vaadin addons and uses the 'Vaadin-Themes' metadata to create the imports. + * + * <p> + * The addons.scss is always overwritten when this tool is invoked. + * </p> + * + * @since 7.1 + */ +public class SASSAddonImportFileCreator { + + private static final String ADDON_IMPORTS_FILE = "addons.scss"; + + private static final String ADDON_IMPORTS_FILE_TEXT = "This file is managed by the Eclipse plug-in and " + + "will be overwritten from time to time. Do not manually edit this file."; + + /** + * + * @param args + * Theme directory where the addons.scss file should be created + */ + public static void main(String[] args) throws IOException { + if (args.length == 0) { + printUsage(); + } else { + String themeDirectory = args[0]; + updateTheme(themeDirectory); + } + } + + /** + * Updates a themes addons.scss with the addon themes found on the classpath + * + * @param themeDirectory + * The target theme directory + */ + public static void updateTheme(String themeDirectory) throws IOException { + + File addonImports = new File(themeDirectory, ADDON_IMPORTS_FILE); + + if (!addonImports.exists()) { + + // Ensure directory exists + addonImports.getParentFile().mkdirs(); + + // Ensure file exists + addonImports.createNewFile(); + } + + LocationInfo info = ClassPathExplorer + .getAvailableWidgetSetsAndStylesheets(); + + try { + PrintStream printStream = new PrintStream(new FileOutputStream( + addonImports)); + + printStream.println("/* " + ADDON_IMPORTS_FILE_TEXT + " */"); + + printStream.println(); + + Map<String, URL> addonThemes = info.getAddonStyles(); + + // Sort addon styles so that CSS imports are first and SCSS import + // last + List<String> paths = new ArrayList<String>(addonThemes.keySet()); + Collections.sort(paths, new Comparator<String>() { + + @Override + public int compare(String path1, String path2) { + if (path1.toLowerCase().endsWith(".css") + && path2.toLowerCase().endsWith(".scss")) { + return -1; + } + if (path1.toLowerCase().endsWith(".scss") + && path2.toLowerCase().endsWith(".css")) { + return 1; + } + return 0; + } + }); + + for (String path : paths) { + addImport(printStream, path, addonThemes.get(path)); + printStream.println(); + } + + } catch (FileNotFoundException e) { + // Should not happen since file is checked before this + e.printStackTrace(); + } + } + + private static void addImport(PrintStream stream, String file, URL location) { + + // Add import comment + printImportComment(stream, location); + + if (file.endsWith(".css")) { + stream.print("@import url(\"../" + file + "\");\n"); + } else { + // Assume SASS + stream.print("@import \"../" + file + "\";\n"); + + // Convention is to name the mixing after the stylesheet. Strip + // .scss from filename + String mixin = file.substring(file.lastIndexOf("/") + 1, + file.length() - ".scss".length()); + + stream.print("@include " + mixin + ";"); + } + + stream.println(); + } + + private static void printImportComment(PrintStream stream, URL location) { + + // file:/absolute/path/to/addon.jar!/ + String path = location.getPath(); + + try { + // Try to parse path for better readability + path = path.substring(path.lastIndexOf(":") + 1, + path.lastIndexOf("!")); + + // Extract jar archive filename + path = path.substring(path.lastIndexOf("/") + 1); + + } catch (Exception e) { + // Parsing failed but no worries, we then use whatever + // location.getPath() returns + } + + stream.println("/* Added by Vaadin Plug-in for Eclipse from " + path + + " */"); + } + + private static void printUsage() { + String className = SASSAddonImportFileCreator.class.getSimpleName(); + PrintStream o = System.out; + o.println(className + " usage:"); + o.println(); + o.println("./" + className + " [Path to target theme folder]"); + } +} diff --git a/server/src/com/vaadin/server/widgetsetutils/ClassPathExplorer.java b/server/src/com/vaadin/server/widgetsetutils/ClassPathExplorer.java new file mode 100644 index 0000000000..cc04e50b3c --- /dev/null +++ b/server/src/com/vaadin/server/widgetsetutils/ClassPathExplorer.java @@ -0,0 +1,538 @@ +/* + * Copyright 2000-2013 Vaadin Ltd. + * + * 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.vaadin.server.widgetsetutils; + +import java.io.File; +import java.io.FileFilter; +import java.io.IOException; +import java.net.JarURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.jar.Attributes; +import java.util.jar.JarFile; +import java.util.jar.Manifest; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Utility class to collect widgetset related information from classpath. + * Utility will seek all directories from classpaths, and jar files having + * "Vaadin-Widgetsets" key in their manifest file. + * <p> + * Used by WidgetMapGenerator and ide tools to implement some monkey coding for + * you. + * <p> + * Developer notice: If you end up reading this comment, I guess you have faced + * a sluggish performance of widget compilation or unreliable detection of + * components in your classpaths. The thing you might be able to do is to use + * annotation processing tool like apt to generate the needed information. Then + * either use that information in {@link WidgetMapGenerator} or create the + * appropriate monkey code for gwt directly in annotation processor and get rid + * of {@link WidgetMapGenerator}. Using annotation processor might be a good + * idea when dropping Java 1.5 support (integrated to javac in 6). + * + */ +public class ClassPathExplorer { + + private static final String VAADIN_ADDON_VERSION_ATTRIBUTE = "Vaadin-Package-Version"; + + /** + * File filter that only accepts directories. + */ + private final static FileFilter DIRECTORIES_ONLY = new FileFilter() { + @Override + public boolean accept(File f) { + if (f.exists() && f.isDirectory()) { + return true; + } else { + return false; + } + } + }; + + /** + * Contains information about widgetsets and themes found on the classpath + * + * @since 7.1 + */ + public static class LocationInfo { + + private final Map<String, URL> widgetsets; + + private final Map<String, URL> addonStyles; + + public LocationInfo(Map<String, URL> widgetsets, Map<String, URL> themes) { + this.widgetsets = widgetsets; + addonStyles = themes; + } + + public Map<String, URL> getWidgetsets() { + return widgetsets; + } + + public Map<String, URL> getAddonStyles() { + return addonStyles; + } + + } + + /** + * Raw class path entries as given in the java class path string. Only + * entries that could include widgets/widgetsets are listed (primarily + * directories, Vaadin JARs and add-on JARs). + */ + private static List<String> rawClasspathEntries = getRawClasspathEntries(); + + /** + * Map from identifiers (either a package name preceded by the path and a + * slash, or a URL for a JAR file) to the corresponding URLs. This is + * constructed from the class path. + */ + private static Map<String, URL> classpathLocations = getClasspathLocations(rawClasspathEntries); + + /** + * No instantiation from outside, callable methods are static. + */ + private ClassPathExplorer() { + } + + /** + * Finds the names and locations of widgetsets available on the class path. + * + * @return map from widgetset classname to widgetset location URL + * @deprecated Use {@link #getAvailableWidgetSetsAndStylesheets()} instead + */ + @Deprecated + public static Map<String, URL> getAvailableWidgetSets() { + return getAvailableWidgetSetsAndStylesheets().getWidgetsets(); + } + + /** + * Finds the names and locations of widgetsets and themes available on the + * class path. + * + * @return + */ + public static LocationInfo getAvailableWidgetSetsAndStylesheets() { + long start = System.currentTimeMillis(); + Map<String, URL> widgetsets = new HashMap<String, URL>(); + Map<String, URL> themes = new HashMap<String, URL>(); + Set<String> keySet = classpathLocations.keySet(); + for (String location : keySet) { + searchForWidgetSetsAndAddonStyles(location, widgetsets, themes); + } + long end = System.currentTimeMillis(); + + StringBuilder sb = new StringBuilder(); + sb.append("Widgetsets found from classpath:\n"); + for (String ws : widgetsets.keySet()) { + sb.append("\t"); + sb.append(ws); + sb.append(" in "); + sb.append(widgetsets.get(ws)); + sb.append("\n"); + } + + sb.append("Addon styles found from classpath:\n"); + for (String theme : themes.keySet()) { + sb.append("\t"); + sb.append(theme); + sb.append(" in "); + sb.append(themes.get(theme)); + sb.append("\n"); + } + + final Logger logger = getLogger(); + logger.info(sb.toString()); + logger.info("Search took " + (end - start) + "ms"); + return new LocationInfo(widgetsets, themes); + } + + /** + * Finds all GWT modules / Vaadin widgetsets and Addon styles in a valid + * location. + * + * If the location is a directory, all GWT modules (files with the + * ".gwt.xml" extension) are added to widgetsets. + * + * If the location is a JAR file, the comma-separated values of the + * "Vaadin-Widgetsets" attribute in its manifest are added to widgetsets. + * + * @param locationString + * an entry in {@link #classpathLocations} + * @param widgetsets + * a map from widgetset name (including package, with dots as + * separators) to a URL (see {@link #classpathLocations}) - new + * entries are added to this map + */ + private static void searchForWidgetSetsAndAddonStyles( + String locationString, Map<String, URL> widgetsets, + Map<String, URL> addonStyles) { + + URL location = classpathLocations.get(locationString); + File directory = new File(location.getFile()); + + if (directory.exists() && !directory.isHidden()) { + // Get the list of the files contained in the directory + String[] files = directory.list(); + for (int i = 0; i < files.length; i++) { + // we are only interested in .gwt.xml files + if (!files[i].endsWith(".gwt.xml")) { + continue; + } + + // remove the .gwt.xml extension + String classname = files[i].substring(0, files[i].length() - 8); + String packageName = locationString.substring(locationString + .lastIndexOf("/") + 1); + classname = packageName + "." + classname; + + if (!WidgetSetBuilder.isWidgetset(classname)) { + // Only return widgetsets and not GWT modules to avoid + // comparing modules and widgetsets + continue; + } + + if (!widgetsets.containsKey(classname)) { + String packagePath = packageName.replaceAll("\\.", "/"); + String basePath = location.getFile().replaceAll( + "/" + packagePath + "$", ""); + try { + URL url = new URL(location.getProtocol(), + location.getHost(), location.getPort(), + basePath); + widgetsets.put(classname, url); + } catch (MalformedURLException e) { + // should never happen as based on an existing URL, + // only changing end of file name/path part + getLogger().log(Level.SEVERE, + "Error locating the widgetset " + classname, e); + } + } + } + } else { + + try { + // check files in jar file, entries will list all directories + // and files in jar + + URLConnection openConnection = location.openConnection(); + if (openConnection instanceof JarURLConnection) { + JarURLConnection conn = (JarURLConnection) openConnection; + + JarFile jarFile = conn.getJarFile(); + + Manifest manifest = jarFile.getManifest(); + if (manifest == null) { + // No manifest so this is not a Vaadin Add-on + return; + } + + // Check for widgetset attribute + String value = manifest.getMainAttributes().getValue( + "Vaadin-Widgetsets"); + if (value != null) { + String[] widgetsetNames = value.split(","); + for (int i = 0; i < widgetsetNames.length; i++) { + String widgetsetname = widgetsetNames[i].trim(); + if (!widgetsetname.equals("")) { + widgetsets.put(widgetsetname, location); + } + } + } + + // Check for theme attribute + value = manifest.getMainAttributes().getValue( + "Vaadin-Stylesheets"); + if (value != null) { + String[] stylesheets = value.split(","); + for (int i = 0; i < stylesheets.length; i++) { + String stylesheet = stylesheets[i].trim(); + if (!stylesheet.equals("")) { + addonStyles.put(stylesheet, location); + } + } + } + } + } catch (IOException e) { + getLogger().log(Level.WARNING, "Error parsing jar file", e); + } + + } + } + + /** + * Splits the current class path into entries, and filters them accepting + * directories, Vaadin add-on JARs with widgetsets and Vaadin JARs. + * + * Some other non-JAR entries may also be included in the result. + * + * @return filtered list of class path entries + */ + private final static List<String> getRawClasspathEntries() { + // try to keep the order of the classpath + List<String> locations = new ArrayList<String>(); + + String pathSep = System.getProperty("path.separator"); + String classpath = System.getProperty("java.class.path"); + + if (classpath.startsWith("\"")) { + classpath = classpath.substring(1); + } + if (classpath.endsWith("\"")) { + classpath = classpath.substring(0, classpath.length() - 1); + } + + getLogger().log(Level.FINE, "Classpath: {0}", classpath); + + String[] split = classpath.split(pathSep); + for (int i = 0; i < split.length; i++) { + String classpathEntry = split[i]; + if (acceptClassPathEntry(classpathEntry)) { + locations.add(classpathEntry); + } + } + + return locations; + } + + /** + * Determine every URL location defined by the current classpath, and it's + * associated package name. + * + * See {@link #classpathLocations} for information on output format. + * + * @param rawClasspathEntries + * raw class path entries as split from the Java class path + * string + * @return map of classpath locations, see {@link #classpathLocations} + */ + private final static Map<String, URL> getClasspathLocations( + List<String> rawClasspathEntries) { + long start = System.currentTimeMillis(); + // try to keep the order of the classpath + Map<String, URL> locations = new LinkedHashMap<String, URL>(); + for (String classpathEntry : rawClasspathEntries) { + File file = new File(classpathEntry); + include(null, file, locations); + } + long end = System.currentTimeMillis(); + Logger logger = getLogger(); + if (logger.isLoggable(Level.FINE)) { + logger.fine("getClassPathLocations took " + (end - start) + "ms"); + } + return locations; + } + + /** + * Checks a class path entry to see whether it can contain widgets and + * widgetsets. + * + * All directories are automatically accepted. JARs are accepted if they + * have the "Vaadin-Widgetsets" attribute in their manifest or the JAR file + * name contains "vaadin-" or ".vaadin.". + * + * Also other non-JAR entries may be accepted, the caller should be prepared + * to handle them. + * + * @param classpathEntry + * class path entry string as given in the Java class path + * @return true if the entry should be considered when looking for widgets + * or widgetsets + */ + private static boolean acceptClassPathEntry(String classpathEntry) { + if (!classpathEntry.endsWith(".jar")) { + // accept all non jars (practically directories) + return true; + } else { + // accepts jars that comply with vaadin-component packaging + // convention (.vaadin. or vaadin- as distribution packages), + if (classpathEntry.contains("vaadin-") + || classpathEntry.contains(".vaadin.")) { + return true; + } else { + URL url; + try { + url = new URL("file:" + + new File(classpathEntry).getCanonicalPath()); + url = new URL("jar:" + url.toExternalForm() + "!/"); + JarURLConnection conn = (JarURLConnection) url + .openConnection(); + getLogger().fine(url.toString()); + JarFile jarFile = conn.getJarFile(); + Manifest manifest = jarFile.getManifest(); + if (manifest != null) { + Attributes mainAttributes = manifest + .getMainAttributes(); + if (mainAttributes.getValue("Vaadin-Widgetsets") != null) { + return true; + } + if (mainAttributes.getValue("Vaadin-Stylesheets") != null) { + return true; + } + } + } catch (MalformedURLException e) { + getLogger().log(Level.FINEST, "Failed to inspect JAR file", + e); + } catch (IOException e) { + getLogger().log(Level.FINEST, "Failed to inspect JAR file", + e); + } + + return false; + } + } + } + + /** + * Recursively add subdirectories and jar files to locations - see + * {@link #classpathLocations}. + * + * @param name + * @param file + * @param locations + */ + private final static void include(String name, File file, + Map<String, URL> locations) { + if (!file.exists()) { + return; + } + if (!file.isDirectory()) { + // could be a JAR file + includeJar(file, locations); + return; + } + + if (file.isHidden() || file.getPath().contains(File.separator + ".")) { + return; + } + + if (name == null) { + name = ""; + } else { + name += "."; + } + + // add all directories recursively + File[] dirs = file.listFiles(DIRECTORIES_ONLY); + for (int i = 0; i < dirs.length; i++) { + try { + // add the present directory + if (!dirs[i].isHidden() + && !dirs[i].getPath().contains(File.separator + ".")) { + String key = dirs[i].getCanonicalPath() + "/" + name + + dirs[i].getName(); + locations.put(key, + new URL("file://" + dirs[i].getCanonicalPath())); + } + } catch (Exception ioe) { + return; + } + include(name + dirs[i].getName(), dirs[i], locations); + } + } + + /** + * Add a jar file to locations - see {@link #classpathLocations}. + * + * @param name + * @param locations + */ + private static void includeJar(File file, Map<String, URL> locations) { + try { + URL url = new URL("file:" + file.getCanonicalPath()); + url = new URL("jar:" + url.toExternalForm() + "!/"); + JarURLConnection conn = (JarURLConnection) url.openConnection(); + JarFile jarFile = conn.getJarFile(); + if (jarFile != null) { + // the key does not matter here as long as it is unique + locations.put(url.toString(), url); + } + } catch (Exception e) { + // e.printStackTrace(); + return; + } + + } + + /** + * Find and return the default source directory where to create new + * widgetsets. + * + * Return the first directory (not a JAR file etc.) on the classpath by + * default. + * + * TODO this could be done better... + * + * @return URL + */ + public static URL getDefaultSourceDirectory() { + + final Logger logger = getLogger(); + + if (logger.isLoggable(Level.FINE)) { + logger.fine("classpathLocations values:"); + ArrayList<String> locations = new ArrayList<String>( + classpathLocations.keySet()); + for (String location : locations) { + logger.fine(String.valueOf(classpathLocations.get(location))); + } + } + + Iterator<String> it = rawClasspathEntries.iterator(); + while (it.hasNext()) { + String entry = it.next(); + + File directory = new File(entry); + if (directory.exists() && !directory.isHidden() + && directory.isDirectory()) { + try { + return new URL("file://" + directory.getCanonicalPath()); + } catch (MalformedURLException e) { + logger.log(Level.FINEST, "Ignoring exception", e); + // ignore: continue to the next classpath entry + } catch (IOException e) { + logger.log(Level.FINEST, "Ignoring exception", e); + // ignore: continue to the next classpath entry + } + } + } + return null; + } + + /** + * Test method for helper tool + */ + public static void main(String[] args) { + getLogger().info( + "Searching for available widgetsets and stylesheets..."); + + ClassPathExplorer.getAvailableWidgetSetsAndStylesheets(); + } + + private static final Logger getLogger() { + return Logger.getLogger(ClassPathExplorer.class.getName()); + } + +} |