summaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
authorJohn Ahlroos <john@vaadin.com>2013-04-23 16:00:27 +0300
committerVaadin Code Review <review@vaadin.com>2013-04-23 13:21:16 +0000
commitcda367eb0409f07ce79096319dc961cd77bc3430 (patch)
tree7c18509d72abe298e9b1f5de23c24988e8e0cb43 /server
parentccec229966d6934694f99b7c0b70c2862f4915a6 (diff)
downloadvaadin-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.java178
-rw-r--r--server/src/com/vaadin/server/widgetsetutils/ClassPathExplorer.java538
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());
+ }
+
+}