All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.vaadin.server.widgetsetutils.ClassPathExplorer Maven / Gradle / Ivy

There is a newer version: 8.27.3
Show newest version
/*
 * Copyright (C) 2000-2024 Vaadin Ltd
 *
 * This program is available under Vaadin Commercial License and Service Terms.
 *
 * See  for the full
 * 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.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;

/**
 * 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.
 * 

* Used by WidgetMapGenerator and ide tools to implement some monkey coding for * you. *

* 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 {@code WidgetMapGenerator} or create the * appropriate monkey code for gwt directly in annotation processor and get rid * of {@code WidgetMapGenerator}. Using annotation processor might be a good * idea when dropping Java 1.5 support (integrated to javac in 6). * */ public class ClassPathExplorer { /** * File filter that only accepts directories. */ private static final FileFilter DIRECTORIES_ONLY = (File f) -> f.exists() && f.isDirectory(); /** * Contains information about widgetsets and themes found on the classpath. * * @since 7.1 */ public static class LocationInfo { private final Map widgetsets; private final Map addonStyles; public LocationInfo(Map widgetsets, Map themes) { this.widgetsets = widgetsets; addonStyles = themes; } public Map getWidgetsets() { return widgetsets; } public Map 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 final List RAW_CLASSPATH_ENTRIES = 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 final Map CLASSPATH_LOCATIONS = getClasspathLocations( RAW_CLASSPATH_ENTRIES); private static boolean debug = false; static { String debugProperty = System.getProperty("debug"); if (debugProperty != null && !debugProperty.isEmpty()) { debug = true; } } /** * 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 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 widgetsets = new HashMap<>(); Map themes = new HashMap<>(); Set keySet = CLASSPATH_LOCATIONS.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"); } log(sb.toString()); log("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 #CLASSPATH_LOCATIONS} * @param widgetsets * a map from widgetset name (including package, with dots as * separators) to a URL (see {@link #CLASSPATH_LOCATIONS}) - new * entries are added to this map */ private static void searchForWidgetSetsAndAddonStyles(String locationString, Map widgetsets, Map addonStyles) { URL location = CLASSPATH_LOCATIONS.get(locationString); File directory = new File(location.getFile()); if (directory.exists() && !directory.isHidden()) { // Get the list of the files contained in the directory for (String file : directory.list()) { // we are only interested in .gwt.xml files if (!file.endsWith(".gwt.xml")) { continue; } // remove the .gwt.xml extension String classname = file.substring(0, file.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(); if (basePath.endsWith("/" + packagePath)) { basePath = basePath.replaceAll("/" + packagePath + "$", ""); } else if (basePath.endsWith("/" + packagePath + "/")) { basePath = basePath.replaceAll("/" + packagePath + "/$", ""); } else { throw new IllegalStateException( "Error trying to find base path, location (" + location.getFile() + ") does not end in expected '/" + 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 error("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 (String widgetsetName : widgetsetNames) { String widgetsetname = widgetsetName.trim(); if (!widgetsetname.isEmpty()) { widgetsets.put(widgetsetname, location); } } } // Check for theme attribute value = manifest.getMainAttributes() .getValue("Vaadin-Stylesheets"); if (value != null) { String[] stylesheets = value.split(","); for (String stylesheet1 : stylesheets) { String stylesheet = stylesheet1.trim(); if (!stylesheet.isEmpty()) { addonStyles.put(stylesheet, location); } } } } } catch (IOException e) { error("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 static final List getRawClasspathEntries() { // try to keep the order of the classpath List locations = new ArrayList<>(); 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); } debug("Classpath: " + classpath); String[] split = classpath.split(pathSep); for (String classpathEntry : split) { 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 #CLASSPATH_LOCATIONS} 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 #CLASSPATH_LOCATIONS} */ private static final Map getClasspathLocations( List rawClasspathEntries) { long start = System.currentTimeMillis(); // try to keep the order of the classpath Map locations = new LinkedHashMap<>(); for (String classpathEntry : rawClasspathEntries) { File file = new File(classpathEntry); include(null, file, locations); } long end = System.currentTimeMillis(); if (debug) { debug("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(); debug(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) { if (debug) { error("Failed to inspect JAR file", e); } } catch (IOException e) { if (debug) { error("Failed to inspect JAR file", e); } } return false; } } } /** * Recursively add subdirectories and jar files to locations - see * {@link #CLASSPATH_LOCATIONS}. * * @param name * @param file * @param locations */ private static final void include(String name, File file, Map 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 for (File dir : file.listFiles(DIRECTORIES_ONLY)) { try { // add the present directory if (!dir.isHidden() && !dir.getPath().contains(File.separator + ".")) { String key = dir.getCanonicalPath() + "/" + name + dir.getName(); URL url = dir.getCanonicalFile().toURI().toURL(); locations.put(key, url); } } catch (Exception ioe) { return; } include(name + dir.getName(), dir, locations); } } /** * Add a jar file to locations - see {@link #CLASSPATH_LOCATIONS}. * * @param file * @param locations */ private static void includeJar(File file, Map 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() { return getWidgetsetSourceDirectory(null); } /** * Find and return the source directory which contains the given widgetset * file. * * If not applicable or widgetsetFileName is null, return the first * directory (not a JAR file etc.) on the classpath. * * TODO this could be done better... * * @since 7.6.5 * * @param widgetsetFileName * relative path for the widgetset * * @return URL */ public static URL getWidgetsetSourceDirectory(String widgetsetFileName) { if (debug) { debug("classpathLocations values:"); List locations = new ArrayList<>( CLASSPATH_LOCATIONS.keySet()); for (String location : locations) { debug(String.valueOf(CLASSPATH_LOCATIONS.get(location))); } } URL firstDirectory = null; for (String entry : RAW_CLASSPATH_ENTRIES) { File directory = new File(entry); if (directory.exists() && !directory.isHidden() && directory.isDirectory()) { try { URL directoryUrl = directory.toURI().toURL(); // Store the first directory encountered. if (firstDirectory == null) { firstDirectory = directoryUrl; } if (widgetsetFileName == null || new File(directory, widgetsetFileName) .exists()) { return directoryUrl; } } catch (MalformedURLException e) { // ignore: continue to the next classpath entry if (debug) { e.printStackTrace(); } } } } return firstDirectory; } /** * Test method for helper tool. */ public static void main(String[] args) { log("Searching for available widgetsets and stylesheets..."); ClassPathExplorer.getAvailableWidgetSetsAndStylesheets(); } private static void log(String message) { System.out.println(message); } private static void error(String message, Exception e) { System.err.println(message); e.printStackTrace(); } private static void debug(String message) { if (debug) { System.out.println(message); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy