/******************************************************************************* * Copyright (c) 2008, 2015 IBM Corporation and others. * 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 * * Contributors: * IBM Corporation - initial API and implementation * Jesper S Moller - Bug 421938: [1.8] ExecutionEnvironmentDescription#getVMArguments does not preserve VM arguments *******************************************************************************/ package org.eclipse.jdt.launching.environments; import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.ListIterator; import java.util.Map; import java.util.Map.Entry; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.Status; import org.eclipse.jdt.internal.launching.EEVMType; import org.eclipse.jdt.internal.launching.LaunchingMessages; import org.eclipse.jdt.internal.launching.LaunchingPlugin; import org.eclipse.jdt.internal.launching.StandardVMType; import org.eclipse.jdt.launching.LibraryLocation; import org.eclipse.osgi.util.NLS; /** * Helper class to parse and retrieve properties from execution environment description * files. An execution environment description file can be used to define attributes relevant * the launching of a specific JRE configuration. The format of the file is defined by * code>http://wiki.eclipse.org/Execution_Environment_Descriptions</code>. * * @since 3.5 */ public final class ExecutionEnvironmentDescription { /** * Endorsed directories property name in an execution environment description file. */ public static final String ENDORSED_DIRS = "-Dee.endorsed.dirs"; //$NON-NLS-1$ /** * Boot class path property name in an execution environment description file. */ public static final String BOOT_CLASS_PATH = "-Dee.bootclasspath"; //$NON-NLS-1$ /** * Source archive property name in an execution environment description file. * Value is a path. When present, the source attachment for each library in the boot * class path will be the file specified by this property. */ public static final String SOURCE_DEFAULT = "-Dee.src"; //$NON-NLS-1$ /** * Source map property name in an execution environment description file. * <p> * Maps class libraries to source attachments. Value is one or more entries of the form * <code>libPath=sourcePath</code> separated by platform specific file separator. The paths * can use <code>{$ee.home}</code> and <code>'..'</code> as well as the wild card characters * '<code>?</code>" (any one character) and '<code>*</code>' (any number of characters). * The <code>sourcePath</code> can use the wild card characters to have the source path be based on the * wild card replacement in the <code>libPath</code>. In this case the wild card characters in the * <code>sourcePath</code> must exist in the same order as the <code>libPath</code>. * For example, <code>lib/foo*.???=source/src*foo.???</code>. * </p> */ public static final String SOURCE_MAP = "-Dee.src.map"; //$NON-NLS-1$ /** * Javadoc location property name in an execution environment description file. * <p> * Specifies javadoc location for class libraries. Must be a URL. You can use * <code>${ee.home}</code> and <code>'..'</code> segments to specify a file location * relative to the ee file. If this property is not specified in the file, * javadoc locations will be set to a default location based on the language level. * </p> */ public static final String JAVADOC_LOC = "-Dee.javadoc"; //$NON-NLS-1$ /** * Pre-built index location property in an execution environment description file. * <p> * Specifies the location for a pre-built search index. Must be a valid {@link URL}. * * You can use <code>${ee.home}</code> and <code>'..'</code> segments to specify a file location * relative to the ee file. * * If this property is not specified the default value of <code>null</code> will be used. * </p> * @since 3.7 */ public static final String INDEX_LOC = "-Dee.index"; //$NON-NLS-1$ /** * Additional directories property name in an execution environment description file. */ public static final String ADDITIONAL_DIRS = "-Dee.additional.dirs"; //$NON-NLS-1$ /** * Extension directories property name in an execution environment description file. */ public static final String EXTENSION_DIRS = "-Dee.ext.dirs"; //$NON-NLS-1$ /** * Language level property name in an execution environment description file. * For example, 1.4 or 1.5. */ public static final String LANGUAGE_LEVEL = "-Dee.language.level"; //$NON-NLS-1$ /** * OSGi profile property name in an execution environment description file. * <p> * The value is the identifier of an OSGi profile, such as <code>J2SE-1.4</code>. * </p> */ public static final String CLASS_LIB_LEVEL = "-Dee.class.library.level"; //$NON-NLS-1$ /** * Executable property name in an execution environment description file. * For example, <code>javaw.exe</code>. */ public static final String EXECUTABLE = "-Dee.executable"; //$NON-NLS-1$ /** * Console executable property name in an execution environment description file. * For example, <code>java.exe</code>. */ public static final String EXECUTABLE_CONSOLE = "-Dee.executable.console"; //$NON-NLS-1$ /** * Java home property name in an execution environment description file. * <p> * The root install directory of the runtime environment or development kit. Corresponds to a value * that could be used for <code>JAVA_HOME</code> environment variable * </p> */ public static final String JAVA_HOME = "-Djava.home"; //$NON-NLS-1$ /** * Debug arguments property name in an execution environment description file. * <p> * The arguments to use to launch the VM in debug mode. For example * <code>"-agentlib:jdwp=transport=dt_socket,suspend=y,address=localhost:${port}"</code>. * The <code>${port}</code> variable will be substituted with a free port at launch time. * When unspecified, default arguments are constructed based on the language level of the VM. * </p> */ public static final String DEBUG_ARGS = "-Dee.debug.args"; //$NON-NLS-1$ /** * VM name property name in an execution environment description file. * <p> * The name is used as the JRE name when installing an EE JRE into Eclipse. * </p> */ public static final String EE_NAME = "-Dee.name"; //$NON-NLS-1$ /** * The directory containing the execution environment description file. Relative paths are resolved * relative to this location. This property will be set if not present, it does not need to be * specified in the file. */ public static final String EE_HOME = "-Dee.home"; //$NON-NLS-1$ /** * Substitution in EE file - replaced with directory of EE file, * to support absolute path names where needed. If the value is not in the * file, it is set when properties are created. */ private static final String VAR_EE_HOME = "${ee.home}"; //$NON-NLS-1$ /** * Any line found in the description starting with this string will not be added to the * VM argument list */ private static final String EE_ARG_FILTER = "-Dee."; //$NON-NLS-1$ // Regex constants for handling the source mapping private static final Character WILDCARD_SINGLE_CHAR = new Character('?'); private static final Character WILDCARD_MULTI_CHAR = new Character('*'); private static final String REGEX_SPECIAL_CHARS = "+()^$.{}[]|\\"; //$NON-NLS-1$ /** * Execution environment description properties */ private Map<String, String> fProperties = null; /** * Creates an execution environment description based on the properties defined in the given * execution environment description file. The format of the file is defined by * <code>http://wiki.eclipse.org/Execution_Environment_Descriptions</code>. * * @param eeFile execution environment description file * @throws CoreException if unable to read or parse the file */ public ExecutionEnvironmentDescription(File eeFile) throws CoreException { initProperties(eeFile); } /** * Returns a map of properties defined in this execution environment description. * Properties in the file that do not have a value assigned to them are returned in the keys * with an empty string as the value. Variable substitutions for <code>${ee.home}</code> * have already been performed when resolving property values. * * @return properties as a map of {@link String} keys and values */ public Map<String, String> getProperties() { return fProperties; } /** * Returns the specified property from this description, or <code>null</code> * if none. * * @param property property name * @return property value or <code>null</code> */ public String getProperty(String property) { return fProperties.get(property); } /** * Returns the location of the system libraries defined in this execution environment. * Libraries are generated from the endorsed directories, boot class path, additional * directories, and extension directories specified by this description and are returned * in that order. Source attachments are configured based on <code>src</code> and * <code>src.map</code> properties. * * @return library locations, possibly empty */ public LibraryLocation[] getLibraryLocations() { List<LibraryLocation> allLibs = new ArrayList<>(); String dirs = getProperty(ENDORSED_DIRS); if (dirs != null) { // Add all endorsed libraries - they are first, as they replace allLibs.addAll(StandardVMType.gatherAllLibraries(resolvePaths(dirs))); } // next is the boot path libraries dirs = getProperty(BOOT_CLASS_PATH); if (dirs != null) { String[] bootpath = resolvePaths(dirs); List<LibraryLocation> boot = new ArrayList<>(bootpath.length); IPath src = getSourceLocation(); URL url = getJavadocLocation(); URL indexurl = getIndexLocation(); for (int i = 0; i < bootpath.length; i++) { IPath path = new Path(bootpath[i]); File lib = path.toFile(); if (lib.exists() && lib.isFile()) { LibraryLocation libraryLocation = new LibraryLocation(path, src, Path.EMPTY, url, indexurl); boot.add(libraryLocation); } } allLibs.addAll(boot); } // Add all additional libraries dirs = getProperty(ADDITIONAL_DIRS); if (dirs != null) { allLibs.addAll(StandardVMType.gatherAllLibraries(resolvePaths(dirs))); } // Add all extension libraries dirs = getProperty(EXTENSION_DIRS); if (dirs != null) { allLibs.addAll(StandardVMType.gatherAllLibraries(resolvePaths(dirs))); } //remove duplicates HashSet<String> set = new HashSet<>(); LibraryLocation lib = null; for(ListIterator<LibraryLocation> liter = allLibs.listIterator(); liter.hasNext();) { lib = liter.next(); if(!set.add(lib.getSystemLibraryPath().toOSString())) { //did not add it, duplicate liter.remove(); } } // If the ee.src.map property is specified, use it to associate source locations with the libraries addSourceLocationsToLibraries(getSourceMap(), allLibs); return allLibs.toArray(new LibraryLocation[allLibs.size()]); } /** * Returns VM arguments in this description or <code>null</code> if none. VM arguments * correspond to all properties in this description that do not begin with "-Dee." * concatenated together with spaces. Any single VM argument that contains spaces * itself is surrounded with quotes. * * @return VM arguments or <code>null</code> if none */ public String getVMArguments() { StringBuffer arguments = new StringBuffer(); Iterator<Entry<String, String>> entries = fProperties.entrySet().iterator(); while (entries.hasNext()) { Entry<String, String> entry = entries.next(); String key = entry.getKey(); String value = entry.getValue(); boolean appendArgument = !key.startsWith(EE_ARG_FILTER); if (appendArgument) { arguments.append(key); if (!value.equals("")) { //$NON-NLS-1$ arguments.append('='); value = resolveHome(value); if (value.indexOf(' ') > -1){ arguments.append('"').append(value).append('"'); } else { arguments.append(value); } } arguments.append(' '); } } if (arguments.charAt(arguments.length()-1) == ' '){ arguments.deleteCharAt(arguments.length()-1); } return arguments.toString(); } /** * Returns the executable for this description as a file or <code>null</code> if * not specified. * * @return standard (non-console) executable or <code>null</code> if none */ public File getExecutable() { String property = getProperty(ExecutionEnvironmentDescription.EXECUTABLE); if (property != null) { String[] paths = resolvePaths(property); if (paths.length == 1) { return new File(paths[0]); } } return null; } /** * Returns the console executable for this description as a file or <code>null</code> if * not specified. * * @return console executable or <code>null</code> if none */ public File getConsoleExecutable() { String property = getProperty(ExecutionEnvironmentDescription.EXECUTABLE_CONSOLE); if (property != null) { String[] paths = resolvePaths(property); if (paths.length == 1) { return new File(paths[0]); } } return null; } /** * Initializes the properties in the given execution environment * description file. * * @param eeFile the EE file * @exception CoreException if unable to read the file */ private void initProperties(File eeFile) throws CoreException { Map<String, String> properties = new LinkedHashMap<>(); String eeHome = eeFile.getParentFile().getAbsolutePath(); try (FileReader reader = new FileReader(eeFile); BufferedReader bufferedReader = new BufferedReader(reader);) { String line = bufferedReader.readLine(); while (line != null) { if (!line.startsWith("#")) { //$NON-NLS-1$ if (line.trim().length() > 0){ int eq = line.indexOf('='); if (eq > 0) { String key = line.substring(0, eq); String value = null; if (line.length() > eq + 1) { value = line.substring(eq + 1).trim(); } properties.put(key, value); } else { properties.put(line, ""); //$NON-NLS-1$ } } } line = bufferedReader.readLine(); } } catch (FileNotFoundException e) { throw new CoreException(new Status(IStatus.ERROR, LaunchingPlugin.ID_PLUGIN, NLS.bind(LaunchingMessages.ExecutionEnvironmentDescription_0,new String[]{eeFile.getPath()}), e)); } catch (IOException e) { throw new CoreException(new Status(IStatus.ERROR, LaunchingPlugin.ID_PLUGIN, NLS.bind(LaunchingMessages.ExecutionEnvironmentDescription_1,new String[]{eeFile.getPath()}), e)); } if (!properties.containsKey(EE_HOME)) { properties.put(EE_HOME, eeHome); } // resolve things with ${ee.home} in them fProperties = properties; // needs to be done to resolve Iterator<Entry<String, String>> entries = properties.entrySet().iterator(); Map<String, String> resolved = new LinkedHashMap<>(properties.size()); while (entries.hasNext()) { Entry<String, String> entry = entries.next(); String key = entry.getKey(); String value = entry.getValue(); if (value != null) { value = resolveHome(value); resolved.put(key, value); } else { resolved.put(key, ""); //$NON-NLS-1$ } } fProperties = resolved; } /** * Replaces and returns a string with all occurrences of * "${ee.home} replaced with its value. * * @param value string to process * @return resolved string */ private String resolveHome(String value) { int start = 0; int index = value.indexOf(VAR_EE_HOME, start); StringBuffer replaced = null; String eeHome = getProperty(EE_HOME); while (index >= 0) { if (replaced == null) { replaced = new StringBuffer(); } replaced.append(value.substring(start, index)); replaced.append(eeHome); start = index + VAR_EE_HOME.length(); index = value.indexOf(VAR_EE_HOME, start); } if (replaced != null) { replaced.append(value.substring(start)); return replaced.toString(); } return value; } /** * Returns all path strings contained in the given string based on system * path delimiter, resolved relative to the <code>${ee.home}</code> property. * * @param paths the paths to resolve * @return array of individual paths */ private String[] resolvePaths(String paths) { String[] strings = paths.split(File.pathSeparator, -1); String eeHome = getProperty(EE_HOME); IPath root = new Path(eeHome); for (int i = 0; i < strings.length; i++) { strings[i] = makePathAbsolute(strings[i], root); } return strings; } /** * Returns a string representing the absolute form of the given path. If the * given path is not absolute, it is appended to the given root path. The returned * path will always be the OS specific string form of the path. * * @param pathString string representing the path to make absolute * @param root root to append non-absolute paths to * @return absolute, OS specific path */ private String makePathAbsolute(String pathString, IPath root){ IPath path = new Path(pathString.trim()); if (!path.isEmpty() && !path.isAbsolute()) { IPath filePath = root.append(path); return filePath.toOSString(); } return path.toOSString(); } /** * Creates a map (regex string to regex string) mapping library locations to their * source locations. This is done by taking the ee.src.map property from the ee file * which allows a list of mappings that can use the wildcards ? (any one char) and * * (any series of chars). The property is converted to a map of regex strings used by * {@link #addSourceLocationsToLibraries(Map, List)}. * <pre> * Example property, separated onto separate lines for easier reading * -Dee.src.map=${ee.home}\lib\charconv?.zip=lib\charconv?-src.zip; * ${ee.home}\lib\jclDEE\classes.zip=lib\jclDEE\source\source.zip; * ${ee.home}\lib\jclDEE\*.zip=lib\jclDEE\source\*-src.zip; * ${ee.home}\lib\jclDEE\ext\*.???=lib\jclDEE\source\*-src.???; * </pre> * * * @return map containing regexs mapping library locations to their source locations */ private Map<String, String> getSourceMap(){ String srcMapString = getProperty(SOURCE_MAP); Map<String, String> srcMap = new HashMap<>(); if (srcMapString != null){ // Entries must be separated by the file separator and have an equals splitting the lib location from the src location String[] entries = srcMapString.split(File.pathSeparator); for (int i = 0; i < entries.length; i++) { int index = entries[i].indexOf('='); if (index > 0 && index < entries[i].length()-1){ IPath root = new Path(getProperty(EE_HOME)); String key = entries[i].substring(0,index); String value = entries[i].substring(index+1); key = makePathAbsolute(key, root); value = makePathAbsolute(value, root); List<Character> wildcards = new ArrayList<>(); StringBuffer keyBuffer = new StringBuffer(); char [] chars = key.toCharArray(); // Convert lib location to a regex, replace wildcards with grouped equivalents, keep track of used wildcards, allow '\' and '/' to be used, escape special chars for (int j = 0; j < chars.length; j++) { if (chars[j] == WILDCARD_MULTI_CHAR.charValue()) { wildcards.add(WILDCARD_MULTI_CHAR); keyBuffer.append("(.*)"); //$NON-NLS-1$ } else if (chars[j] == WILDCARD_SINGLE_CHAR.charValue()) { wildcards.add(WILDCARD_SINGLE_CHAR); keyBuffer.append("(.)"); //$NON-NLS-1$ } else if (REGEX_SPECIAL_CHARS.indexOf(chars[j]) != -1) { keyBuffer.append('\\').append(chars[j]); } else { keyBuffer.append(chars[j]); } } int currentWild = 0; StringBuffer valueBuffer = new StringBuffer(); chars = value.toCharArray(); // Convert src location to a regex, replace wildcards with their group number, allow '\' and '/' to be used, escape special chars for (int j = 0; j < chars.length; j++) { if (chars[j] == WILDCARD_MULTI_CHAR.charValue() || chars[j] == WILDCARD_SINGLE_CHAR.charValue()) { if (currentWild < wildcards.size()){ Character wild = wildcards.get(currentWild); if (chars[j] == wild.charValue()) { valueBuffer.append('$').append(currentWild+1); currentWild++; } else { LaunchingPlugin.log(NLS.bind(LaunchingMessages.EEVMType_5, new String[]{entries[i]})); break; } } else { LaunchingPlugin.log(NLS.bind(LaunchingMessages.EEVMType_5, new String[]{entries[i]})); break; } } else if (REGEX_SPECIAL_CHARS.indexOf(chars[j]) != -1) { valueBuffer.append('\\').append(chars[j]); } else { valueBuffer.append(chars[j]); } } srcMap.put(keyBuffer.toString(), valueBuffer.toString()); } else { LaunchingPlugin.log(NLS.bind(LaunchingMessages.EEVMType_6, new String[]{entries[i]})); } } } return srcMap; } /** * Uses the given src map to find source libraries that are associated with the * library locations in the list. The library locations are updated with the * found source path. * * @param srcMap mapping of library location regexs to source location regexs * @param libraries list of {@link LibraryLocation} objects to update with source locations * @see #getSourceMap() */ private void addSourceLocationsToLibraries(Map<String, String> srcMap, List<LibraryLocation> libraries){ for (Iterator<String> patternIterator = srcMap.keySet().iterator(); patternIterator.hasNext();) { // Try each library regex pattern and see what libraries apply. String currentKey = patternIterator.next(); Pattern currentPattern = Pattern.compile(currentKey); Matcher matcher = currentPattern.matcher(""); //$NON-NLS-1$ for (Iterator<LibraryLocation> locationIterator = libraries.iterator(); locationIterator.hasNext();) { LibraryLocation currentLibrary = locationIterator.next(); matcher.reset(currentLibrary.getSystemLibraryPath().toOSString()); if (matcher.find()){ // Found a file that the pattern applies to, use the map to get the source location String sourceLocation = matcher.replaceAll(srcMap.get(currentKey)); IPath sourcePath = new Path(sourceLocation); // Only add the source archive if it exists if (sourcePath.toFile().exists()){ currentLibrary.setSystemLibrarySource(sourcePath); } } } } } /** * Returns the location of the default source archive for this description or the empty * path if none. * * @return default source archive location or Path.EMPTY if none */ private IPath getSourceLocation() { String src = getProperty(ExecutionEnvironmentDescription.SOURCE_DEFAULT); if (src != null) { String eeHome = getProperty(ExecutionEnvironmentDescription.EE_HOME); src = makePathAbsolute(src, new Path(eeHome)); return new Path(src); } return Path.EMPTY; } /** * Returns the javadoc location or <code>null</code> if unable to determine one. * A default one is generated if not present, based on language level. * * @return javadoc location or <code>null</code> if none */ private URL getJavadocLocation() { return EEVMType.getJavadocLocation(fProperties); } /** * Returns the {@link URL} for the index location or <code>null</code> if one has not been set. * * @return the index {@link URL} or <code>null</code> * @since 3.7.0 */ private URL getIndexLocation() { return EEVMType.getIndexLocation(fProperties); } }