blob: e3b48b54dce9d274ceadc9bcf05b22bd92c8dcf9 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008, 2019 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* 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 = Character.valueOf('?');
private static final Character WILDCARD_MULTI_CHAR = Character.valueOf('*');
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() {
StringBuilder arguments = new StringBuilder();
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.isEmpty()) {
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);
StringBuilder replaced = null;
String eeHome = getProperty(EE_HOME);
while (index >= 0) {
if (replaced == null) {
replaced = new StringBuilder();
}
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<>();
StringBuilder keyBuffer = new StringBuilder();
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;
StringBuilder valueBuffer = new StringBuilder();
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);
}
}