package org.rubypeople.rdt.launching;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.StringReader;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtensionPoint;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Preferences;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.variables.VariablesPlugin;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchConfigurationType;
import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
import org.eclipse.debug.core.ILaunchManager;
import org.eclipse.debug.core.IStreamListener;
import org.eclipse.debug.core.model.IProcess;
import org.eclipse.debug.core.model.IStreamMonitor;
import org.eclipse.debug.ui.IDebugUIConstants;
import org.rubypeople.rdt.core.ILoadpathContainer;
import org.rubypeople.rdt.core.ILoadpathEntry;
import org.rubypeople.rdt.core.IRubyModel;
import org.rubypeople.rdt.core.IRubyProject;
import org.rubypeople.rdt.core.RubyCore;
import org.rubypeople.rdt.core.util.Util;
import org.rubypeople.rdt.internal.launching.CompositeId;
import org.rubypeople.rdt.internal.launching.DefaultEntryResolver;
import org.rubypeople.rdt.internal.launching.DefaultProjectLoadpathEntry;
import org.rubypeople.rdt.internal.launching.LaunchingMessages;
import org.rubypeople.rdt.internal.launching.LaunchingPlugin;
import org.rubypeople.rdt.internal.launching.ListenerList;
import org.rubypeople.rdt.internal.launching.RuntimeLoadpathEntry;
import org.rubypeople.rdt.internal.launching.RuntimeLoadpathEntryResolver;
import org.rubypeople.rdt.internal.launching.RuntimeLoadpathProvider;
import org.rubypeople.rdt.internal.launching.SocketAttachConnector;
import org.rubypeople.rdt.internal.launching.VMDefinitionsContainer;
import org.rubypeople.rdt.internal.launching.VMListener;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
public class RubyRuntime {
private static final String STD_RUBY_VMTYPE = "org.rubypeople.rdt.launching.StandardVMType";
private static final String JRUBY_VMTYPE = "org.rubypeople.rdt.launching.JRubyVMType";
/**
* Loadpath container used for a project's Ruby
* (value <code>"org.rubypeople.rdt.launching.RUBY_CONTAINER"</code>). A
* container is resolved in the context of a specific Ruby project, to one
* or more system libraries contained in the Ruby std library. The container can have zero
* or two path segments following the container name. When no segments
* follow the container name, the workspace default Ruby is used to build a
* project. Otherwise the segments identify a specific Ruby used to build a
* project:
* <ol>
* <li>VM Install Type Identifier - identifies the type of Ruby VM used to build the
* project. For example, the standard VM.</li>
* <li>VM Install Name - a user defined name that identifies that a specific VM
* of the above kind. For example, <code>JRuby 1.8.4</code>. This information is
* shared in a projects loadpath file, so teams must agree on Ruby VM naming
* conventions.</li>
* </ol>
* @since 0.9.0
*/
public static final String RUBY_CONTAINER = LaunchingPlugin.getUniqueIdentifier() + ".RUBY_CONTAINER"; //$NON-NLS-1$
/**
* Preference key for the String of XML that defines all installed VMs.
*
* @since 0.9.0
*/
public static final String PREF_VM_XML = LaunchingPlugin.getUniqueIdentifier() + ".PREF_VM_XML"; //$NON-NLS-1$
/**
* Simple identifier constant (value <code>"vmInstalls"</code>) for the
* VM installs extension point.
*
* @since 0.9.0
*/
public static final String EXTENSION_POINT_VM_INSTALLS = "vmInstalls"; //$NON-NLS-1$
/**
* Classpath variable name used for the default RubyVM's library
* (value <code>"RUBY_LIB"</code>).
*/
public static final String RUBYLIB_VARIABLE= "RUBY_LIB"; //$NON-NLS-1$
/**
* Simple identifier constant (value <code>"runtimeLoadpathEntryResolvers"</code>) for the
* runtime loadpath entry resolvers extension point.
*
* @since 0.9.0
*/
public static final String EXTENSION_POINT_RUNTIME_CLASSPATH_ENTRY_RESOLVERS= "runtimeLoadpathEntryResolvers"; //$NON-NLS-1$
/**
* Simple identifier constant (value <code>"loadpathProviders"</code>) for the
* runtime loadpath providers extension point.
*
* @since 0.9.0
*/
public static final String EXTENSION_POINT_RUNTIME_CLASSPATH_PROVIDERS= "loadpathProviders"; //$NON-NLS-1$
private static IVMInstallType[] fgVMTypes= null;
private static RubyRuntime runtime;
private static Object fgVMLock = new Object();
private static boolean fgInitializingVMs;
private static String fgDefaultVMId;
private static ListenerList fgVMListeners = new ListenerList(5);
/**
* Cache of already resolved projects in container entries. Used to avoid
* cycles in project dependencies when resolving loadpath container entries.
* Counters used to know when entering/exiting to clear cache
*/
private static ThreadLocal<List<IRubyProject>> fgProjects = new ThreadLocal<List<IRubyProject>>(); // Lists
private static ThreadLocal<Integer> fgEntryCount = new ThreadLocal<Integer>(); // Integers
/**
* Default loadpath provider.
*/
private static IRuntimeLoadpathProvider fgDefaultLoadpathProvider = new StandardLoadpathProvider();
/**
* Path providers keyed by id
*/
private static Map<String, RuntimeLoadpathProvider> fgPathProviders = null;
/**
* Set of IDs of VMs contributed via vmInstalls extension point.
*/
private static Set<String> fgContributedVMs = new HashSet<String>();
/**
* Resolvers keyed by variable name, container id,
* and runtime loadpath entry id.
*/
private static Map<String, RuntimeLoadpathEntryResolver> fgVariableResolvers = null;
private static Map<String, RuntimeLoadpathEntryResolver> fgContainerResolvers = null;
private static Map<String, RuntimeLoadpathEntryResolver> fgRuntimeLoadpathEntryResolvers = null;
private static String fgDefaultVMConnectorId;
protected RubyRuntime() {
super();
}
public static RubyRuntime getDefault() {
if (runtime == null) {
runtime = new RubyRuntime();
}
return runtime;
}
public static void removeVMInstallChangedListener(IVMInstallChangedListener listener) {
fgVMListeners.remove(listener);
}
/**
* Return the default VM set with <code>setDefaultVM()</code>.
* @return Returns the default VM. May return <code>null</code> when no default
* VM was set or when the default VM has been disposed.
*/
public static IVMInstall getDefaultVMInstall() {
IVMInstall install= getVMFromCompositeId(getDefaultVMId());
if (install != null && install.getInstallLocation().exists()) {
return install;
}
// if the default Ruby VM goes missing, re-detect
if (install != null) {
install.getVMInstallType().disposeVMInstall(install.getId());
}
synchronized (fgVMLock) {
fgDefaultVMId = null;
fgVMTypes = null;
initializeVMs();
}
return getVMFromCompositeId(getDefaultVMId());
}
/**
* Return the VM corresponding to the specified composite Id. The id uniquely
* identifies a VM across all vm types.
*
* @param idString the composite id that specifies an instance of IVMInstall
*
* @since 0.9.0
*/
public static IVMInstall getVMFromCompositeId(String idString) {
if (idString == null || idString.length() == 0) {
return null;
}
CompositeId id= CompositeId.fromString(idString);
if (id.getPartCount() == 2) {
IVMInstallType vmType= getVMInstallType(id.get(0));
if (vmType != null) {
return vmType.findVMInstall(id.get(1));
}
}
return null;
}
private static String getDefaultVMId() {
initializeVMs();
return fgDefaultVMId;
}
/**
* Saves the VM configuration information to the preferences. This includes
* the following information:
* <ul>
* <li>The list of all defined IVMInstall instances.</li>
* <li>The default VM</li>
* <ul>
* This state will be read again upon first access to VM
* configuration information.
*/
public static void saveVMConfiguration() throws CoreException {
if (fgVMTypes == null) {
// if the VM types have not been instantiated, there can be no changes.
return;
}
try {
String xml = getVMsAsXML();
getPreferences().setValue(PREF_VM_XML, xml);
savePreferences();
} catch (IOException e) {
throw new CoreException(new Status(IStatus.ERROR, LaunchingPlugin.getUniqueIdentifier(), IStatus.ERROR, LaunchingMessages.RubyRuntime_exceptionsOccurred, e));
} catch (ParserConfigurationException e) {
throw new CoreException(new Status(IStatus.ERROR, LaunchingPlugin.getUniqueIdentifier(), IStatus.ERROR, LaunchingMessages.RubyRuntime_exceptionsOccurred, e));
} catch (TransformerException e) {
throw new CoreException(new Status(IStatus.ERROR, LaunchingPlugin.getUniqueIdentifier(), IStatus.ERROR, LaunchingMessages.RubyRuntime_exceptionsOccurred, e));
}
}
private static String getVMsAsXML() throws IOException, ParserConfigurationException, TransformerException {
VMDefinitionsContainer container = new VMDefinitionsContainer();
container.setDefaultVMInstallCompositeID(getDefaultVMId());
IVMInstallType[] vmTypes= getVMInstallTypes();
for (int i = 0; i < vmTypes.length; ++i) {
IVMInstall[] vms = vmTypes[i].getVMInstalls();
for (int j = 0; j < vms.length; j++) {
IVMInstall install = vms[j];
container.addVM(install);
}
}
return container.getAsXML();
}
/**
* Saves the preferences for the launching plug-in.
*
* @since 0.9.0
*/
public static void savePreferences() {
LaunchingPlugin.getDefault().savePluginPreferences();
}
public static void addVMInstallChangedListener(IVMInstallChangedListener listener) {
fgVMListeners.add(listener);
}
private static void notifyDefaultVMChanged(IVMInstall previous, IVMInstall current) {
Object[] listeners = fgVMListeners.getListeners();
for (int i = 0; i < listeners.length; i++) {
IVMInstallChangedListener listener = (IVMInstallChangedListener)listeners[i];
listener.defaultVMInstallChanged(previous, current);
}
}
/**
* Returns the VM install type with the given unique id.
* @param id the VM install type unique id
* @return The VM install type for the given id, or <code>null</code> if no
* VM install type with the given id is registered.
*/
public static IVMInstallType getVMInstallType(String id) {
IVMInstallType[] vmTypes= getVMInstallTypes();
for (int i= 0; i < vmTypes.length; i++) {
if (vmTypes[i].getId().equals(id)) {
return vmTypes[i];
}
}
return null;
}
/**
* Returns the list of registered VM types. VM types are registered via
* <code>"org.rubypeople.rdt.launching.vmTypes"</code> extension point.
* Returns an empty list if there are no registered VM types.
*
* @return the list of registered VM types
*/
public static IVMInstallType[] getVMInstallTypes() {
initializeVMs();
return fgVMTypes;
}
/**
* Perform VM type and VM install initialization. Does not hold locks
* while performing change notification.
*
* @since 0.9.0
*/
private static void initializeVMs() {
VMDefinitionsContainer vmDefs = null;
boolean setPref = false;
synchronized (fgVMLock) {
if (fgVMTypes == null) {
try {
fgInitializingVMs = true;
// 1. load VM type extensions
initializeVMTypeExtensions();
try {
vmDefs = new VMDefinitionsContainer();
// 2. add persisted VMs
setPref = addPersistedVMs(vmDefs);
// 3. if there are none, detect defaults VMs for each VM Type
if (vmDefs.getValidVMList().isEmpty()) {
// calling out to detectDefaultVMs() could allow clients to change
// VM settings (i.e. call back into change VM settings).
VMListener listener = new VMListener();
addVMInstallChangedListener(listener);
setPref = true;
VMStandin[] runtime = detectDefaultVMs();
removeVMInstallChangedListener(listener);
if (!listener.isChanged()) {
if (runtime != null && runtime.length > 0) {
for (int i = 0; i < runtime.length; i++) {
vmDefs.addVM(runtime[i]);
}
VMStandin defaultVM = chooseDefault(runtime);
vmDefs.setDefaultVMInstallCompositeID(getCompositeIdFromVM(defaultVM));
}
} else {
// VMs were changed - reflect current settings
addPersistedVMs(vmDefs);
vmDefs.setDefaultVMInstallCompositeID(fgDefaultVMId);
}
}
// 4. If JRuby VM isn't there, forcibly add JRuby VM
else {
if (noJRubyVM(vmDefs)) {
IVMInstallType type = getVMInstallType(JRUBY_VMTYPE);
VMStandin vm = detectDefaultVM(type);
if (vm != null) vmDefs.addVM(vm);
}
if (noStdRubyVM(vmDefs)) {
IVMInstallType type = getVMInstallType(STD_RUBY_VMTYPE);
VMStandin vm = detectDefaultVM(type);
if (vm != null) {
vmDefs.addVM(vm);
// if (!LaunchingPlugin.getDefault().getPluginPreferences().getBoolean(LaunchingPlugin.USING_INCLUDED_JRUBY)) {
// vmDefs.setDefaultVMInstallCompositeID(getCompositeIdFromVM(vm));
// }
}
}
}
// 5. load contributed VM installs
addVMExtensions(vmDefs);
// 6. verify default VM is valid
String defId = vmDefs.getDefaultVMInstallCompositeID();
boolean validDef = false;
if (defId != null) {
Iterator iterator = vmDefs.getValidVMList().iterator();
while (iterator.hasNext()) {
IVMInstall vm = (IVMInstall) iterator.next();
if (getCompositeIdFromVM(vm).equals(defId)) {
validDef = true;
break;
}
}
}
if (!validDef) {
// use the first as the default
setPref = true;
List list = vmDefs.getValidVMList();
if (!list.isEmpty()) {
IVMInstall vm = (IVMInstall) list.get(0);
vmDefs.setDefaultVMInstallCompositeID(getCompositeIdFromVM(vm));
}
}
fgDefaultVMId = vmDefs.getDefaultVMInstallCompositeID();
// Create the underlying VMs for each valid VM
List vmList = vmDefs.getValidVMList();
Iterator vmListIterator = vmList.iterator();
while (vmListIterator.hasNext()) {
VMStandin vmStandin = (VMStandin) vmListIterator.next();
vmStandin.convertToRealVM();
}
} catch (IOException e) {
LaunchingPlugin.log(e);
}
} finally {
fgInitializingVMs = false;
}
}
}
if (vmDefs != null) {
// notify of initial VMs for backwards compatibility
IVMInstallType[] installTypes = getVMInstallTypes();
for (int i = 0; i < installTypes.length; i++) {
IVMInstallType type = installTypes[i];
IVMInstall[] installs = type.getVMInstalls();
for (int j = 0; j < installs.length; j++) {
fireVMAdded(installs[j]);
}
}
// save settings if required
if (setPref) {
try {
String xml = vmDefs.getAsXML();
LaunchingPlugin.getDefault().getPluginPreferences().setValue(PREF_VM_XML, xml);
} catch (ParserConfigurationException e) {
LaunchingPlugin.log(e);
} catch (IOException e) {
LaunchingPlugin.log(e);
} catch (TransformerException e) {
LaunchingPlugin.log(e);
}
}
}
}
private static boolean noJRubyVM(VMDefinitionsContainer vmDefs) {
return !hasVMOfType(vmDefs, JRUBY_VMTYPE);
}
private static boolean noStdRubyVM(VMDefinitionsContainer vmDefs) {
return !hasVMOfType(vmDefs, STD_RUBY_VMTYPE);
}
private static boolean hasVMOfType(VMDefinitionsContainer vmDefs, String vmTypeId) {
List<IVMInstall> vms = vmDefs.getValidVMList();
for (IVMInstall install : vms) {
if (install.getVMInstallType() == null || install.getVMInstallType().getId() == null) continue;
if (install.getVMInstallType().getId().equals(vmTypeId)) return true;
}
return false;
}
/**
* Prefer the first standard VM install (over JRuby). Otherwise, just pick first found VM.
* @param runtime
* @return
*/
private static VMStandin chooseDefault(VMStandin[] runtime) {
for (int i = 0; i < runtime.length; i++) {
if (runtime[i].getVMInstallType().getId().equals(STD_RUBY_VMTYPE)) {
return runtime[i];
}
}
return runtime[0];
}
/**
* Detect the VM that is used by the system by default.
*
* @return a VM standin representing the VM that Eclipse is running on, or
* <code>null</code> if unable to detect the runtime VM
*/
private static VMStandin[] detectDefaultVMs() {
List<VMStandin> detected = new ArrayList<VMStandin>();
// Try to detect a VM for each declared VM type
IVMInstallType[] vmTypes= getVMInstallTypes();
for (int i = 0; i < vmTypes.length; i++) {
VMStandin standin = detectDefaultVM(vmTypes[i]);
if (standin != null) detected.add(standin);
}
return detected.toArray(new VMStandin[detected.size()]);
}
private static VMStandin detectDefaultVM(IVMInstallType vmType) {
if (vmType == null) return null;
File detectedLocation= vmType.detectInstallLocation();
if (detectedLocation != null) {
// Make sure the VM id is unique
long unique = System.currentTimeMillis();
while (vmType.findVMInstall(String.valueOf(unique)) != null) {
unique++;
}
// Create a standin for the detected VM and add it to the result collector
String vmID = String.valueOf(unique);
VMStandin detectedVMStandin = new VMStandin(vmType, vmID);
detectedVMStandin.setInstallLocation(detectedLocation);
detectedVMStandin.setName(generateDetectedVMName(detectedVMStandin));
return detectedVMStandin;
}
return null;
}
/**
* Make the name of a detected VM stand out.
*/
private static String generateDetectedVMName(IVMInstall vm) {
return vm.getInstallLocation().getName();
}
/**
* Initializes vm type extensions.
*/
private static void initializeVMTypeExtensions() {
IExtensionPoint extensionPoint= Platform.getExtensionRegistry().getExtensionPoint(LaunchingPlugin.PLUGIN_ID, "vmInstallTypes"); //$NON-NLS-1$
IConfigurationElement[] configs= extensionPoint.getConfigurationElements();
MultiStatus status= new MultiStatus(LaunchingPlugin.getUniqueIdentifier(), IStatus.OK, LaunchingMessages.RubyRuntime_exceptionOccurred, null);
fgVMTypes= new IVMInstallType[configs.length];
for (int i= 0; i < configs.length; i++) {
try {
IVMInstallType vmType= (IVMInstallType)configs[i].createExecutableExtension("class"); //$NON-NLS-1$
fgVMTypes[i]= vmType;
} catch (CoreException e) {
status.add(e.getStatus());
}
}
if (!status.isOK()) {
//only happens on a CoreException
LaunchingPlugin.log(status);
//cleanup null entries in fgVMTypes
List<IVMInstallType> temp= new ArrayList<IVMInstallType>(fgVMTypes.length);
for (int i = 0; i < fgVMTypes.length; i++) {
if(fgVMTypes[i] != null) {
temp.add(fgVMTypes[i]);
}
fgVMTypes= new IVMInstallType[temp.size()];
fgVMTypes= temp.toArray(fgVMTypes);
}
}
}
/**
* This method loads installed JREs based an existing user preference
* or old vm configurations file. The VMs found in the preference
* or vm configurations file are added to the given VM definitions container.
*
* Returns whether the user preferences should be set - i.e. if it was
* not already set when initialized.
*/
private static boolean addPersistedVMs(VMDefinitionsContainer vmDefs) throws IOException {
// Try retrieving the VM preferences from the preference store
String vmXMLString = getPreferences().getString(PREF_VM_XML);
// If the preference was found, load VMs from it into memory
if (vmXMLString.length() > 0) {
try {
ByteArrayInputStream inputStream = new ByteArrayInputStream(vmXMLString.getBytes());
VMDefinitionsContainer.parseXMLIntoContainer(inputStream, vmDefs);
return false;
} catch (IOException ioe) {
LaunchingPlugin.log(ioe);
}
} else {
// Otherwise, look for the old file that previously held the VM definitions
IPath stateLocation= LaunchingPlugin.getDefault().getStateLocation();
IPath stateFile= stateLocation.append("runtimeConfiguration.xml"); //$NON-NLS-1$
File file = new File(stateFile.toOSString());
if (file.exists()) {
// If file exists, load VM definitions from it into memory and write the definitions to
// the preference store WITHOUT triggering any processing of the new value
FileInputStream fileInputStream = new FileInputStream(file);
VMDefinitionsContainer.parseXMLIntoContainer(fileInputStream, vmDefs);
}
}
return true;
}
/**
* Returns the preference store for the launching plug-in.
*
* @return the preference store for the launching plug-in
* @since 0.9.0
*/
public static Preferences getPreferences() {
return LaunchingPlugin.getDefault().getPluginPreferences();
}
/**
* Returns a String that uniquely identifies the specified VM across all VM types.
*
* @param vm the instance of IVMInstallType to be identified
*
* @since 0.9.0
*/
public static String getCompositeIdFromVM(IVMInstall vm) {
if (vm == null) {
return null;
}
IVMInstallType vmType= vm.getVMInstallType();
String typeID= vmType.getId();
CompositeId id= new CompositeId(new String[] { typeID, vm.getId() });
return id.toString();
}
/**
* Loads contributed VM installs
* @since 0.9.0
*/
private static void addVMExtensions(VMDefinitionsContainer vmDefs) {
IExtensionPoint extensionPoint = Platform.getExtensionRegistry().getExtensionPoint(LaunchingPlugin.PLUGIN_ID, RubyRuntime.EXTENSION_POINT_VM_INSTALLS);
IConfigurationElement[] configs= extensionPoint.getConfigurationElements();
for (int i = 0; i < configs.length; i++) {
IConfigurationElement element = configs[i];
try {
if ("vmInstall".equals(element.getName())) { //$NON-NLS-1$
String vmType = element.getAttribute("vmInstallType"); //$NON-NLS-1$
if (vmType == null) {
abort(MessageFormat.format("Missing required vmInstallType attribute for vmInstall contributed by {0}", //$NON-NLS-1$
(Object[]) new String[]{element.getContributor().getName()}), null);
}
String id = element.getAttribute("id"); //$NON-NLS-1$
if (id == null) {
abort(MessageFormat.format("Missing required id attribute for vmInstall contributed by {0}", //$NON-NLS-1$
(Object[]) new String[]{element.getContributor().getName()}), null);
}
IVMInstallType installType = getVMInstallType(vmType);
if (installType == null) {
abort(MessageFormat.format("vmInstall {0} contributed by {1} references undefined VM install type {2}", //$NON-NLS-1$
(Object[]) new String[]{id, element.getContributor().getName(), vmType}), null);
}
IVMInstall install = installType.findVMInstall(id);
if (install == null) {
// only load/create if first time we've seen this VM install
String name = element.getAttribute("name"); //$NON-NLS-1$
if (name == null) {
abort(MessageFormat.format("vmInstall {0} contributed by {1} missing required attribute name", //$NON-NLS-1$
(Object[]) new String[]{id, element.getContributor().getName()}), null);
}
String home = element.getAttribute("home"); //$NON-NLS-1$
if (home == null) {
abort(MessageFormat.format("vmInstall {0} contributed by {1} missing required attribute home", //$NON-NLS-1$
(Object[]) new String[]{id, element.getContributor().getName()}), null);
}
String vmArgs = element.getAttribute("vmArgs"); //$NON-NLS-1$
VMStandin standin = new VMStandin(installType, id);
standin.setName(name);
home = substitute(home);
File homeDir = new File(home);
if (homeDir.exists()) {
try {
// adjust for relative path names
home = homeDir.getCanonicalPath();
homeDir = new File(home);
} catch (IOException e) {
}
}
IStatus status = installType.validateInstallLocation(homeDir);
if (!status.isOK()) {
abort(MessageFormat.format("Illegal install location {0} for vmInstall {1} contributed by {2}: {3}", //$NON-NLS-1$
(Object[]) new String[]{home, id, element.getContributor().getName(), status.getMessage()}), null);
}
standin.setInstallLocation(homeDir);
if (vmArgs != null) {
standin.setVMArgs(vmArgs);
}
IConfigurationElement[] libraries = element.getChildren("library"); //$NON-NLS-1$
IPath[] locations = null;
if (libraries.length > 0) {
locations = new IPath[libraries.length];
for (int j = 0; j < libraries.length; j++) {
IConfigurationElement library = libraries[j];
String libPathStr = library.getAttribute("path"); //$NON-NLS-1$
if (libPathStr == null) {
abort(MessageFormat.format("library for vmInstall {0} contributed by {1} missing required attribute libPath", //$NON-NLS-1$
(Object[]) new String[]{id, element.getContributor().getName()}), null);
}
IPath homePath = new Path(home);
IPath libPath = homePath.append(substitute(libPathStr));
locations[j] = libPath;
}
}
standin.setLibraryLocations(locations);
vmDefs.addVM(standin);
}
fgContributedVMs.add(id);
} else {
abort(MessageFormat.format("Illegal element {0} in vmInstalls extension contributed by {1}", //$NON-NLS-1$
(Object[]) new String[]{element.getName(), element.getContributor().getName()}), null);
}
} catch (CoreException e) {
LaunchingPlugin.log(e);
}
}
}
/**
* Performs string substitution on the given expression.
*
* @param expression
* @return expression after string substitution
* @throws CoreException
* @since 0.9.0
*/
private static String substitute(String expression) throws CoreException {
return VariablesPlugin.getDefault().getStringVariableManager().performStringSubstitution(expression);
}
/**
* Throws a core exception with an internal error status.
*
* @param message the status message
* @param exception lower level exception associated with the
* error, or <code>null</code> if none
*/
private static void abort(String message, Throwable exception) throws CoreException {
abort(message, IRubyLaunchConfigurationConstants.ERR_INTERNAL_ERROR, exception);
}
/**
* Throws a core exception with an internal error status.
*
* @param message the status message
* @param code status code
* @param exception lower level exception associated with the
*
* error, or <code>null</code> if none
*/
private static void abort(String message, int code, Throwable exception) throws CoreException {
throw new CoreException(new Status(IStatus.ERROR, LaunchingPlugin.getUniqueIdentifier(), code, message, exception));
}
static void fireVMAdded(IVMInstall vm) {
if (!fgInitializingVMs) {
Object[] listeners = fgVMListeners.getListeners();
for (int i = 0; i < listeners.length; i++) {
IVMInstallChangedListener listener = (IVMInstallChangedListener)listeners[i];
listener.vmAdded(vm);
}
}
}
public static void fireVMChanged(PropertyChangeEvent event) {
Object[] listeners = fgVMListeners.getListeners();
for (int i = 0; i < listeners.length; i++) {
IVMInstallChangedListener listener = (IVMInstallChangedListener)listeners[i];
listener.vmChanged(event);
}
}
/**
* Notifies all VM install changed listeners of the VM removal
*
* @param vm the VM that has been removed
* @since 0.9.0
*/
public static void fireVMRemoved(IVMInstall vm) {
Object[] listeners = fgVMListeners.getListeners();
for (int i = 0; i < listeners.length; i++) {
IVMInstallChangedListener listener = (IVMInstallChangedListener)listeners[i];
listener.vmRemoved(vm);
}
}
/**
* Evaluates library locations for a IVMInstall. If no library locations are set on the install, a default
* location is evaluated and checked if it exists.
* @return library locations with paths that exist or are empty
* @since 0.9.0
*/
public static IPath[] getLibraryLocations(IVMInstall vm) {
IPath[] locations= vm.getLibraryLocations();
if (locations != null) return locations;
IPath[] dflts= vm.getVMInstallType().getDefaultLibraryLocations(vm.getInstallLocation());
IPath[] libraryPaths = new IPath[dflts.length];
for (int i = 0; i < dflts.length; i++) {
libraryPaths[i]= dflts[i];
if (!libraryPaths[i].toFile().isDirectory()) {
libraryPaths[i]= Path.EMPTY;
}
}
return libraryPaths;
}
/**
* Returns the VM install for the given launch configuration.
* The VM install is determined in the following prioritized way:
* <ol>
* <li>The VM install is explicitly specified on the launch configuration
* via the <code>ATTR_JRE_CONTAINER_PATH</code> attribute (since 3.2).</li>
* <li>The VM install is explicitly specified on the launch configuration
* via the <code>ATTR_VM_INSTALL_TYPE</code> and <code>ATTR_VM_INSTALL_ID</code>
* attributes.</li>
* <li>If no explicit VM install is specified, the VM install associated with
* the launch configuration's project is returned.</li>
* <li>If no project is specified, or the project does not specify a custom
* VM install, the workspace default VM install is returned.</li>
* </ol>
*
* @param configuration launch configuration
* @return vm install
* @exception CoreException if unable to compute a vm install
* @since 0.9.0
*/
public static IVMInstall computeVMInstall(ILaunchConfiguration configuration) throws CoreException {
String rubyVmAttr = configuration.getAttribute(IRubyLaunchConfigurationConstants.ATTR_RUBY_CONTAINER_PATH, (String)null);
if (rubyVmAttr == null) {
String type = configuration.getAttribute(IRubyLaunchConfigurationConstants.ATTR_VM_INSTALL_TYPE, (String)null);
if (type == null) {
IRubyProject proj = getRubyProject(configuration);
if (proj != null) {
IVMInstall vm = getVMInstall(proj);
if (vm != null) {
return vm;
}
}
} else {
String name = configuration.getAttribute(IRubyLaunchConfigurationConstants.ATTR_VM_INSTALL_NAME, (String)null);
return resolveVM(type, name, configuration);
}
} else {
IPath rubyVmPath = Path.fromPortableString(rubyVmAttr);
ILoadpathEntry entry = RubyCore.newContainerEntry(rubyVmPath);
IRuntimeLoadpathEntryResolver2 resolver = getVariableResolver(rubyVmPath.segment(0));
if (resolver != null) {
return resolver.resolveVMInstall(entry);
}
resolver = getContainerResolver(rubyVmPath.segment(0));
if (resolver != null) {
return resolver.resolveVMInstall(entry);
}
}
return getDefaultVMInstall();
}
/**
* Returns the VM of the given type with the specified name.
*
* @param type vm type identifier
* @param name vm name
* @return vm install
* @exception CoreException if unable to resolve
* @since 0.9.0
*/
private static IVMInstall resolveVM(String type, String name, ILaunchConfiguration configuration) throws CoreException {
IVMInstallType vt = getVMInstallType(type);
if (vt == null) {
// error type does not exist
abort(MessageFormat.format(LaunchingMessages.JavaRuntime_Specified_VM_install_type_does_not_exist___0__2, type), null);
}
IVMInstall vm = null;
// look for a name
if (name == null) {
// error - type specified without a specific install (could be an old config that specified a VM ID)
// log the error, but choose the default VM.
IStatus status = new Status(IStatus.WARNING, LaunchingPlugin.getUniqueIdentifier(),
IRubyLaunchConfigurationConstants.ERR_UNSPECIFIED_VM_INSTALL,
MessageFormat.format(LaunchingMessages.JavaRuntime_VM_not_fully_specified_in_launch_configuration__0____missing_VM_name__Reverting_to_default_VM__1, configuration.getName()),
null);
LaunchingPlugin.log(status);
return getDefaultVMInstall();
}
vm = vt.findVMInstallByName(name);
if (vm == null) {
// error - install not found
abort(MessageFormat.format(LaunchingMessages.JavaRuntime_Specified_VM_install_not_found__type__0___name__1__2, vt.getName(), name), null);
} else {
return vm;
}
// won't reach here
return null;
}
/**
* Returns the resolver registered for the given variable, or
* <code>null</code> if none.
*
* @param variableName the variable to determine the resolver for
* @return the resolver registered for the given variable, or
* <code>null</code> if none
*/
private static IRuntimeLoadpathEntryResolver2 getVariableResolver(String variableName) {
return (IRuntimeLoadpathEntryResolver2)getVariableResolvers().get(variableName);
}
/**
* Returns the resolver registered for the given container id, or
* <code>null</code> if none.
*
* @param containerId the container to determine the resolver for
* @return the resolver registered for the given container id, or
* <code>null</code> if none
*/
private static IRuntimeLoadpathEntryResolver2 getContainerResolver(String containerId) {
return (IRuntimeLoadpathEntryResolver2)getContainerResolvers().get(containerId);
}
private static Map getVariableResolvers() {
if (fgVariableResolvers == null) {
initializeResolvers();
}
return fgVariableResolvers;
}
/**
* Returns all registered container resolvers.
*/
private static Map getContainerResolvers() {
if (fgContainerResolvers == null) {
initializeResolvers();
}
return fgContainerResolvers;
}
private static void initializeResolvers() {
IExtensionPoint point = Platform.getExtensionRegistry().getExtensionPoint(LaunchingPlugin.PLUGIN_ID, EXTENSION_POINT_RUNTIME_CLASSPATH_ENTRY_RESOLVERS);
IConfigurationElement[] extensions = point.getConfigurationElements();
fgVariableResolvers = new HashMap<String, RuntimeLoadpathEntryResolver>(extensions.length);
fgContainerResolvers = new HashMap<String, RuntimeLoadpathEntryResolver>(extensions.length);
fgRuntimeLoadpathEntryResolvers = new HashMap<String, RuntimeLoadpathEntryResolver>(extensions.length);
for (int i = 0; i < extensions.length; i++) {
RuntimeLoadpathEntryResolver res = new RuntimeLoadpathEntryResolver(extensions[i]);
String variable = res.getVariableName();
String container = res.getContainerId();
String entryId = res.getRuntimeLoadpathEntryId();
if (variable != null) {
fgVariableResolvers.put(variable, res);
}
if (container != null) {
fgContainerResolvers.put(container, res);
}
if (entryId != null) {
fgRuntimeLoadpathEntryResolvers.put(entryId, res);
}
}
}
/**
* Returns the VM assigned to build the given Java project.
* The project must exist. The VM assigned to a project is
* determined from its build path.
*
* @param project the project to retrieve the VM from
* @return the VM instance that is assigned to build the given Java project
* Returns <code>null</code> if no VM is referenced on the project's build path.
* @throws CoreException if unable to determine the project's VM install
*/
public static IVMInstall getVMInstall(IRubyProject project) throws CoreException {
// check the loadpath
IVMInstall vm = null;
ILoadpathEntry[] loadpath = project.getRawLoadpath();
IRuntimeLoadpathEntryResolver resolver = null;
for (int i = 0; i < loadpath.length; i++) {
ILoadpathEntry entry = loadpath[i];
switch (entry.getEntryKind()) {
case ILoadpathEntry.CPE_VARIABLE:
resolver = getVariableResolver(entry.getPath().segment(0));
if (resolver != null) {
vm = resolver.resolveVMInstall(entry);
}
break;
case ILoadpathEntry.CPE_CONTAINER:
resolver = getContainerResolver(entry.getPath().segment(0));
if (resolver != null) {
vm = resolver.resolveVMInstall(entry);
}
break;
}
if (vm != null) {
return vm;
}
}
return null;
}
/**
* Return the <code>IRubyProject</code> referenced in the specified configuration or
* <code>null</code> if none.
*
* @exception CoreException if the referenced Ruby project does not exist
* @since 0.9.0
*/
public static IRubyProject getRubyProject(ILaunchConfiguration configuration) throws CoreException {
String projectName = configuration.getAttribute(IRubyLaunchConfigurationConstants.ATTR_PROJECT_NAME, (String)null);
if ((projectName == null) || (projectName.trim().length() < 1)) {
return null;
}
IRubyProject javaProject = getRubyModel().getRubyProject(projectName);
if (javaProject != null && javaProject.getProject().exists() && !javaProject.getProject().isOpen()) {
abort(MessageFormat.format(LaunchingMessages.JavaRuntime_28, configuration.getName(), projectName), IRubyLaunchConfigurationConstants.ERR_PROJECT_CLOSED, null);
}
if ((javaProject == null) || !javaProject.exists()) {
abort(MessageFormat.format(LaunchingMessages.JavaRuntime_Launch_configuration__0__references_non_existing_project__1___1,configuration.getName(), projectName), IRubyLaunchConfigurationConstants.ERR_NOT_A_RUBY_PROJECT, null);
}
return javaProject;
}
/**
* Convenience method to get the ruby model.
*/
private static IRubyModel getRubyModel() {
return RubyCore.create(ResourcesPlugin.getWorkspace().getRoot());
}
/**
* Returns a new runtime loadpath entry for the given archive (possibly
* external).
*
* @param path absolute path to an archive
* @return runtime loadpath entry
* @since 0.9.0
*/
public static IRuntimeLoadpathEntry newArchiveRuntimeLoadpathEntry(IPath path) {
ILoadpathEntry cpe = RubyCore.newLibraryEntry(path);
return newRuntimeLoadpathEntry(cpe);
}
/**
* Returns a runtime loadpath entry that corresponds to the given
* loadpath entry. The loadpath entry may not be of type <code>CPE_SOURCE</code>
* or <code>CPE_CONTAINER</code>.
*
* @param entry a loadpath entry
* @return runtime loadpath entry
* @since 0.9.0
*/
private static IRuntimeLoadpathEntry newRuntimeLoadpathEntry(ILoadpathEntry entry) {
return new RuntimeLoadpathEntry(entry);
}
/**
* Returns a runtime loadpath entry for the given container path with the given
* loadpath property to be resolved in the context of the given Java project.
*
* @param path container path
* @param loadpathProperty the type of entry - one of <code>USER_CLASSES</code>,
* <code>BOOTSTRAP_CLASSES</code>, or <code>STANDARD_CLASSES</code>
* @param project Java project context used for resolution, or <code>null</code>
* if to be resolved in the context of the launch configuration this entry
* is referenced in
* @return runtime loadpath entry
* @exception CoreException if unable to construct a runtime loadpath entry
* @since 0.9.0
*/
public static IRuntimeLoadpathEntry newRuntimeContainerLoadpathEntry(IPath path, int loadpathProperty, IRubyProject project) throws CoreException {
ILoadpathEntry cpe = RubyCore.newContainerEntry(path);
RuntimeLoadpathEntry entry = new RuntimeLoadpathEntry(cpe, loadpathProperty);
entry.setRubyProject(project);
return entry;
}
/**
* Returns a new runtime loadpath entry for the loadpath
* variable with the given path.
*
* @param path variable path; first segment is the name of the variable;
* trailing segments are appended to the resolved variable value
* @return runtime loadpath entry
* @since 0.9.0
*/
public static IRuntimeLoadpathEntry newVariableRuntimeLoadpathEntry(
IPath path) {
ILoadpathEntry cpe = RubyCore.newVariableEntry(path);
return newRuntimeLoadpathEntry(cpe);
}
/**
* Computes and returns the unresolved class path for the given launch configuration.
* Variable and container entries are unresolved.
*
* @param configuration launch configuration
* @return unresolved runtime loadpath entries
* @throws CoreException
* @exception CoreException if unable to compute the loadpath
* @since 0.9.0
*/
public static IRuntimeLoadpathEntry[] computeUnresolvedRuntimeLoadpath(
ILaunchConfiguration configuration) throws CoreException {
return getLoadpathProvider(configuration).computeUnresolvedLoadpath(configuration);
}
/**
* Returns the loadpath provider for the given launch configuration.
*
* @param configuration launch configuration
* @return loadpath provider
* @exception CoreException if unable to resolve the path provider
* @since 0.9.0
*/
public static IRuntimeLoadpathProvider getLoadpathProvider(ILaunchConfiguration configuration) throws CoreException {
String providerId = configuration.getAttribute(IRubyLaunchConfigurationConstants.ATTR_LOADPATH_PROVIDER, (String)null);
IRuntimeLoadpathProvider provider = null;
if (providerId == null) {
provider = fgDefaultLoadpathProvider;
} else {
provider = (IRuntimeLoadpathProvider)getLoadpathProviders().get(providerId);
if (provider == null) {
abort(MessageFormat.format(LaunchingMessages.JavaRuntime_26, providerId), null);
}
}
return provider;
}
/**
* Returns all registered loadpath providers.
*/
private static Map getLoadpathProviders() {
if (fgPathProviders == null) {
initializeProviders();
}
return fgPathProviders;
}
private static void initializeProviders() {
IExtensionPoint point = Platform.getExtensionRegistry().getExtensionPoint(LaunchingPlugin.PLUGIN_ID, EXTENSION_POINT_RUNTIME_CLASSPATH_PROVIDERS);
IConfigurationElement[] extensions = point.getConfigurationElements();
fgPathProviders = new HashMap<String, RuntimeLoadpathProvider>(extensions.length);
for (int i = 0; i < extensions.length; i++) {
RuntimeLoadpathProvider res = new RuntimeLoadpathProvider(extensions[i]);
fgPathProviders.put(res.getIdentifier(), res);
}
}
/**
* Returns a runtime loadpath entry constructed from the given memento.
*
* @param memento a memento for a runtime loadpath entry
* @return runtime loadpath entry
* @exception CoreException if unable to construct a runtime loadpath entry
* @since 0.9.0
*/
public static IRuntimeLoadpathEntry newRuntimeLoadpathEntry(String memento) throws CoreException {
try {
Element root = null;
DocumentBuilder parser = LaunchingPlugin.getParser();
StringReader reader = new StringReader(memento);
InputSource source = new InputSource(reader);
root = parser.parse(source).getDocumentElement();
String id = root.getAttribute("id"); //$NON-NLS-1$
if (id == null || id.length() == 0) {
// assume an old format
return new RuntimeLoadpathEntry(root);
}
// get the extension & create a new one
IRuntimeLoadpathEntry2 entry = LaunchingPlugin.getDefault().newRuntimeLoadpathEntry(id);
NodeList list = root.getChildNodes();
for (int i = 0; i < list.getLength(); i++) {
Node node = list.item(i);
if (node.getNodeType() == Node.ELEMENT_NODE) {
Element element = (Element)node;
if ("memento".equals(element.getNodeName())) { //$NON-NLS-1$
entry.initializeFrom(element);
}
}
}
return entry;
} catch (SAXException e) {
abort(LaunchingMessages.JavaRuntime_31, e);
} catch (IOException e) {
abort(LaunchingMessages.JavaRuntime_32, e);
}
return null;
}
/**
* Returns a runtime loadpath entry identifying the JRE to use when launching the specified
* configuration or <code>null</code> if none is specified. The entry returned represents a
* either a loadpath variable or loadpath container that resolves to a JRE.
* <p>
* The entry is resolved as follows:
* <ol>
* <li>If the <code>ATTR_JRE_CONTAINER_PATH</code> is present, it is used to create
* a loadpath container referring to a JRE.</li>
* <li>Next, if the <code>ATTR_VM_INSTALL_TYPE</code> and <code>ATTR_VM_INSTALL_NAME</code>
* attributes are present, they are used to create a loadpath container.</li>
* <li>When none of the above attributes are specified, a default entry is
* created which refers to the JRE referenced by the build path of the configuration's
* associated Java project. This could be a loadpath variable or loadpath container.</li>
* <li>When there is no Java project associated with a configuration, the workspace
* default JRE is used to create a container path.</li>
* </ol>
* </p>
* @param configuration
* @return loadpath container path identifying a RubyVM or <code>null</code>
* @exception org.eclipse.core.runtime.CoreException if an exception occurs retrieving
* attributes from the specified launch configuration
* @since 0.9.0
*/
public static IRuntimeLoadpathEntry computeRubyVMEntry(ILaunchConfiguration configuration) throws CoreException {
String rubyVmAttr = configuration.getAttribute(IRubyLaunchConfigurationConstants.ATTR_RUBY_CONTAINER_PATH, (String)null);
IPath containerPath = null;
if (rubyVmAttr == null) {
String type = configuration.getAttribute(IRubyLaunchConfigurationConstants.ATTR_VM_INSTALL_TYPE, (String)null);
if (type == null) {
// default RubyVM for the launch configuration
IRubyProject proj = getRubyProject(configuration);
if (proj == null) {
containerPath = newDefaultRubyVMContainerPath();
} else {
return computeRubyVMEntry(proj);
}
} else {
String name = configuration.getAttribute(IRubyLaunchConfigurationConstants.ATTR_VM_INSTALL_NAME, (String)null);
if (name != null) {
containerPath = newDefaultRubyVMContainerPath().append(type).append(name);
}
}
} else {
containerPath = Path.fromPortableString(rubyVmAttr);
}
if (containerPath != null) {
return newRuntimeContainerLoadpathEntry(containerPath, IRuntimeLoadpathEntry.STANDARD_CLASSES);
}
return null;
}
/**
* Returns a runtime loadpath entry identifying the JRE referenced by the specified
* project, or <code>null</code> if none. The entry returned represents a either a
* loadpath variable or loadpath container that resolves to a JRE.
*
* @param project Java project
* @return Ruby VM runtime loadpath entry or <code>null</code>
* @exception org.eclipse.core.runtime.CoreException if an exception occurs
* accessing the project's loadpath
* @since 0.9.0
*/
public static IRuntimeLoadpathEntry computeRubyVMEntry(IRubyProject project) throws CoreException {
ILoadpathEntry[] rawClasspath = project.getRawLoadpath();
IRuntimeLoadpathEntryResolver2 resolver = null;
for (int i = 0; i < rawClasspath.length; i++) {
ILoadpathEntry entry = rawClasspath[i];
switch (entry.getEntryKind()) {
case ILoadpathEntry.CPE_VARIABLE:
resolver = getVariableResolver(entry.getPath().segment(0));
if (resolver != null) {
if (resolver.isVMInstallReference(entry)) {
return newRuntimeLoadpathEntry(entry);
}
}
break;
case ILoadpathEntry.CPE_CONTAINER:
resolver = getContainerResolver(entry.getPath().segment(0));
if (resolver != null) {
if (resolver.isVMInstallReference(entry)) {
ILoadpathContainer container = RubyCore.getLoadpathContainer(entry.getPath(), project);
if (container != null) {
switch (container.getKind()) {
case ILoadpathContainer.K_APPLICATION:
break;
case ILoadpathContainer.K_DEFAULT_SYSTEM:
return newRuntimeContainerLoadpathEntry(entry.getPath(), IRuntimeLoadpathEntry.STANDARD_CLASSES);
case ILoadpathContainer.K_SYSTEM:
return newRuntimeContainerLoadpathEntry(entry.getPath(), IRuntimeLoadpathEntry.BOOTSTRAP_CLASSES);
}
}
}
}
break;
}
}
return null;
}
/**
* Returns a path for the JRE loadpath container identifying the
* default VM install.
*
* @return loadpath container path
* @since 0.9.0
*/
public static IPath newDefaultRubyVMContainerPath() {
return new Path(RUBY_CONTAINER);
}
/**
* Returns a runtime loadpath entry for the given container path with the given
* loadpath property.
*
* @param path container path
* @param loadpathProperty the type of entry - one of <code>USER_CLASSES</code>,
* <code>BOOTSTRAP_CLASSES</code>, or <code>STANDARD_CLASSES</code>
* @return runtime loadpath entry
* @exception CoreException if unable to construct a runtime loadpath entry
* @since 0.9.0
*/
public static IRuntimeLoadpathEntry newRuntimeContainerLoadpathEntry(IPath path, int loadpathProperty) throws CoreException {
return newRuntimeContainerLoadpathEntry(path, loadpathProperty, null);
}
/**
* Computes and returns the default unresolved runtime loadpath for the
* given project.
*
* @return runtime loadpath entries
* @exception CoreException if unable to compute the runtime loadpath
* @see IRuntimeClasspathEntry
* @since 0.9.0
*/
public static IRuntimeLoadpathEntry[] computeUnresolvedRuntimeLoadpath(IRubyProject project) throws CoreException {
ILoadpathEntry[] entries = project.getRawLoadpath();
List<IRuntimeLoadpathEntry> loadpathEntries = new ArrayList<IRuntimeLoadpathEntry>(3);
for (int i = 0; i < entries.length; i++) {
ILoadpathEntry entry = entries[i];
switch (entry.getEntryKind()) {
case ILoadpathEntry.CPE_CONTAINER:
ILoadpathContainer container = RubyCore.getLoadpathContainer(entry.getPath(), project);
if (container != null) {
switch (container.getKind()) {
case ILoadpathContainer.K_APPLICATION:
// don't look at application entries
break;
case ILoadpathContainer.K_DEFAULT_SYSTEM:
loadpathEntries.add(newRuntimeContainerLoadpathEntry(container.getPath(), IRuntimeLoadpathEntry.STANDARD_CLASSES, project));
break;
case ILoadpathContainer.K_SYSTEM:
loadpathEntries.add(newRuntimeContainerLoadpathEntry(container.getPath(), IRuntimeLoadpathEntry.BOOTSTRAP_CLASSES, project));
break;
}
}
break;
case ILoadpathEntry.CPE_VARIABLE:
if (RUBYLIB_VARIABLE.equals(entry.getPath().segment(0))) {
IRuntimeLoadpathEntry jre = newVariableRuntimeLoadpathEntry(entry.getPath());
jre.setLoadpathProperty(IRuntimeLoadpathEntry.STANDARD_CLASSES);
loadpathEntries.add(jre);
}
break;
default:
break;
}
}
loadpathEntries.add(newDefaultProjectLoadpathEntry(project));
return loadpathEntries.toArray(new IRuntimeLoadpathEntry[loadpathEntries.size()]);
}
/**
* Returns a new runtime loadpath entry containing the default loadpath
* for the specified Ruby project.
*
* @param project Ruby project
* @return runtime loadpath entry
* @since 0.9.0
*/
public static IRuntimeLoadpathEntry newDefaultProjectLoadpathEntry(IRubyProject project) {
return new DefaultProjectLoadpathEntry(project);
}
/**
* Returns resolved entries for the given entry in the context of the given
* launch configuration. If the entry is of kind
* <code>VARIABLE</code> or <code>CONTAINER</code>, variable and container
* resolvers are consulted. If the entry is of kind <code>PROJECT</code>,
* and the associated Ruby project specifies non-default output locations,
* the corresponding output locations are returned. Otherwise, the given
* entry is returned.
* <p>
* If the given entry is a variable entry, and a resolver is not registered,
* the entry itself is returned. If the given entry is a container, and a
* resolver is not registered, resolved runtime loadpath entries are calculated
* from the associated container loadpath entries, in the context of the project
* associated with the given launch configuration.
* </p>
* @param entry runtime loadpath entry
* @param configuration launch configuration
* @return resolved runtime loadpath entry
* @exception CoreException if unable to resolve
* @see IRuntimeClasspathEntryResolver
* @since 2.0
*/
public static IRuntimeLoadpathEntry[] resolveRuntimeLoadpathEntry(IRuntimeLoadpathEntry entry, ILaunchConfiguration configuration) throws CoreException {
switch (entry.getType()) {
case IRuntimeLoadpathEntry.PROJECT:
// if the project has multiple output locations, they must be returned
IResource resource = entry.getResource();
if (resource instanceof IProject) {
IProject p = (IProject)resource;
IRubyProject project = RubyCore.create(p);
if (project == null || !p.isOpen() || !project.exists()) {
return new IRuntimeLoadpathEntry[0];
}
} else {
// could not resolve project
abort(MessageFormat.format(LaunchingMessages.JavaRuntime_Classpath_references_non_existant_project___0__3, entry.getPath().lastSegment()), null);
}
break;
case IRuntimeLoadpathEntry.VARIABLE:
IRuntimeLoadpathEntryResolver resolver = getVariableResolver(entry.getVariableName());
if (resolver == null) {
IRuntimeLoadpathEntry[] resolved = resolveVariableEntry(entry, null, configuration);
if (resolved != null) {
return resolved;
}
break;
}
return resolver.resolveRuntimeLoadpathEntry(entry, configuration);
case IRuntimeLoadpathEntry.CONTAINER:
resolver = getContainerResolver(entry.getVariableName());
if (resolver == null) {
return computeDefaultContainerEntries(entry, configuration);
}
return resolver.resolveRuntimeLoadpathEntry(entry, configuration);
case IRuntimeLoadpathEntry.ARCHIVE:
// verify the archive exists
String location = entry.getLocation();
if (location == null) {
abort(MessageFormat.format(LaunchingMessages.JavaRuntime_Classpath_references_non_existant_archive___0__4, entry.getPath().toString()), null);
}
File file = new File(location);
if (!file.exists()) {
abort(MessageFormat.format(LaunchingMessages.JavaRuntime_Classpath_references_non_existant_archive___0__4, entry.getPath().toString()), null);
}
break;
case IRuntimeLoadpathEntry.OTHER:
resolver = getContributedResolver(((IRuntimeLoadpathEntry2)entry).getTypeId());
return resolver.resolveRuntimeLoadpathEntry(entry, configuration);
default:
break;
}
return new IRuntimeLoadpathEntry[] {entry};
}
/**
* Performs default resolution for a container entry.
* Delegates to the Ruby model.
*/
private static IRuntimeLoadpathEntry[] computeDefaultContainerEntries(IRuntimeLoadpathEntry entry, ILaunchConfiguration config) throws CoreException {
IRubyProject project = entry.getRubyProject();
if (project == null) {
project = getRubyProject(config);
}
return computeDefaultContainerEntries(entry, project);
}
/**
* Performs default resolution for a container entry.
* Delegates to the Ruby model.
*/
private static IRuntimeLoadpathEntry[] computeDefaultContainerEntries(IRuntimeLoadpathEntry entry, IRubyProject project) throws CoreException {
if (project == null || entry == null) {
// cannot resolve without entry or project context
return new IRuntimeLoadpathEntry[0];
}
ILoadpathContainer container = RubyCore.getLoadpathContainer(entry.getPath(), project);
if (container == null) {
abort(MessageFormat.format(LaunchingMessages.JavaRuntime_Could_not_resolve_classpath_container___0__1,entry.getPath().toString()), null);
// execution will not reach here - exception will be thrown
return null;
}
ILoadpathEntry[] cpes = container.getLoadpathEntries();
int property = -1;
switch (container.getKind()) {
case ILoadpathContainer.K_APPLICATION:
property = IRuntimeLoadpathEntry.USER_CLASSES;
break;
case ILoadpathContainer.K_DEFAULT_SYSTEM:
property = IRuntimeLoadpathEntry.STANDARD_CLASSES;
break;
case ILoadpathContainer.K_SYSTEM:
property = IRuntimeLoadpathEntry.BOOTSTRAP_CLASSES;
break;
}
List<IRuntimeLoadpathEntry> resolved = new ArrayList<IRuntimeLoadpathEntry>(cpes.length);
List<IRubyProject> projects = fgProjects.get();
Integer count = fgEntryCount.get();
if (projects == null) {
projects = new ArrayList<IRubyProject>();
fgProjects.set(projects);
count = Integer.valueOf(0);
}
int intCount = count.intValue();
intCount++;
fgEntryCount.set(Integer.valueOf(intCount));
try {
for (int i = 0; i < cpes.length; i++) {
ILoadpathEntry cpe = cpes[i];
if (cpe.getEntryKind() == ILoadpathEntry.CPE_PROJECT) {
IProject p = ResourcesPlugin.getWorkspace().getRoot().getProject(cpe.getPath().segment(0));
IRubyProject rp = RubyCore.create(p);
if (!projects.contains(rp)) {
projects.add(rp);
IRuntimeLoadpathEntry loadpath = newDefaultProjectLoadpathEntry(rp);
IRuntimeLoadpathEntry[] entries = resolveRuntimeLoadpathEntry(loadpath, rp);
for (int j = 0; j < entries.length; j++) {
IRuntimeLoadpathEntry e = entries[j];
if (!resolved.contains(e)) {
resolved.add(entries[j]);
}
}
}
} else {
IRuntimeLoadpathEntry e = newRuntimeLoadpathEntry(cpe);
if (!resolved.contains(e)) {
resolved.add(e);
}
}
}
} finally {
intCount--;
if (intCount == 0) {
fgProjects.set(null);
fgEntryCount.set(null);
} else {
fgEntryCount.set(new Integer(intCount));
}
}
// set loadpath property
IRuntimeLoadpathEntry[] result = new IRuntimeLoadpathEntry[resolved.size()];
for (int i = 0; i < result.length; i++) {
result[i] = resolved.get(i);
result[i].setLoadpathProperty(property);
}
return result;
}
/**
* Default resolution for a loadpath variable - resolve to an archive. Only
* one of project/configuration can be non-null.
*
* @param entry
* @param project the project context or <code>null</code>
* @param configuration configuration context or <code>null</code>
* @return IRuntimeLoadpathEntry[]
* @throws CoreException
*/
private static IRuntimeLoadpathEntry[] resolveVariableEntry(IRuntimeLoadpathEntry entry, IRubyProject project, ILaunchConfiguration configuration) throws CoreException {
// default resolution - an archive
IPath archPath = RubyCore.getResolvedVariablePath(entry.getPath());
if (archPath != null) {
if (entry.getPath().segmentCount() > 1) {
archPath = archPath.append(entry.getPath().removeFirstSegments(1));
}
if (archPath != null && !archPath.isEmpty()) {
// now resolve the archive (recursively)
ILoadpathEntry archEntry = RubyCore.newLibraryEntry(archPath, entry.getLoadpathEntry().isExported());
IRuntimeLoadpathEntry runtimeArchEntry = newRuntimeLoadpathEntry(archEntry);
runtimeArchEntry.setLoadpathProperty(entry.getLoadpathProperty());
if (configuration == null) {
return resolveRuntimeLoadpathEntry(runtimeArchEntry, project);
}
return resolveRuntimeLoadpathEntry(runtimeArchEntry, configuration);
}
}
return null;
}
/**
* Returns resolved entries for the given entry in the context of the given
* Ruby project. If the entry is of kind
* <code>VARIABLE</code> or <code>CONTAINER</code>, variable and container
* resolvers are consulted. If the entry is of kind <code>PROJECT</code>,
* and the associated Ruby project specifies non-default output locations,
* the corresponding output locations are returned. Otherwise, the given
* entry is returned.
* <p>
* If the given entry is a variable entry, and a resolver is not registered,
* the entry itself is returned. If the given entry is a container, and a
* resolver is not registered, resolved runtime loadpath entries are calculated
* from the associated container loadpath entries, in the context of the
* given project.
* </p>
* @param entry runtime loadpath entry
* @param project Ruby project context
* @return resolved runtime loadpath entry
* @exception CoreException if unable to resolve
* @see IRuntimeClasspathEntryResolver
* @since 0.9.0
*/
public static IRuntimeLoadpathEntry[] resolveRuntimeLoadpathEntry(IRuntimeLoadpathEntry entry, IRubyProject project) throws CoreException {
switch (entry.getType()) {
case IRuntimeLoadpathEntry.PROJECT:
// if the project has multiple output locations, they must be returned
IResource resource = entry.getResource();
if (resource instanceof IProject) {
IProject p = (IProject)resource;
IRubyProject jp = RubyCore.create(p);
if (!(jp != null && p.isOpen() && jp.exists())) {
return new IRuntimeLoadpathEntry[0];
}
}
break;
case IRuntimeLoadpathEntry.VARIABLE:
IRuntimeLoadpathEntryResolver resolver = getVariableResolver(entry.getVariableName());
if (resolver == null) {
IRuntimeLoadpathEntry[] resolved = resolveVariableEntry(entry, project, null);
if (resolved != null) {
return resolved;
}
break;
}
return resolver.resolveRuntimeLoadpathEntry(entry, project);
case IRuntimeLoadpathEntry.CONTAINER:
resolver = getContainerResolver(entry.getVariableName());
if (resolver == null) {
return computeDefaultContainerEntries(entry, project);
}
return resolver.resolveRuntimeLoadpathEntry(entry, project);
case IRuntimeLoadpathEntry.OTHER:
resolver = getContributedResolver(((IRuntimeLoadpathEntry2)entry).getTypeId());
return resolver.resolveRuntimeLoadpathEntry(entry, project);
default:
break;
}
return new IRuntimeLoadpathEntry[] {entry};
}
/**
* Returns the resolver registered for the given contributed loadpath
* entry type.
*
* @param typeId the id of the contributed loadpath entry
* @return the resolver registered for the given loadpath entry
*/
private static IRuntimeLoadpathEntryResolver getContributedResolver(String typeId) {
IRuntimeLoadpathEntryResolver resolver = (IRuntimeLoadpathEntryResolver)getEntryResolvers().get(typeId);
if (resolver == null) {
return new DefaultEntryResolver();
}
return resolver;
}
/**
* Returns all registered runtime loadpath entry resolvers.
*/
private static Map getEntryResolvers() {
if (fgRuntimeLoadpathEntryResolvers == null) {
initializeResolvers();
}
return fgRuntimeLoadpathEntryResolvers;
}
/**
* Resolves the given loadpath, returning the resolved loadpath
* in the context of the given launch configuration.
*
* @param entries unresolved loadpath
* @param configuration launch configuration
* @return resolved runtime loadpath entries
* @throws CoreException
* @exception CoreException if unable to compute the loadpath
* @since 0.9.0
*/
public static IRuntimeLoadpathEntry[] resolveRuntimeLoadpath(
IRuntimeLoadpathEntry[] entries, ILaunchConfiguration configuration) throws CoreException {
return getLoadpathProvider(configuration).resolveLoadpath(entries, configuration);
}
/**
* Sets a VM as the system-wide default VM, and notifies registered VM install
* change listeners of the change.
*
* @param vm The vm to make the default. May be <code>null</code> to clear
* the default.
* @param monitor progress monitor or <code>null</code>
* @param savePreference If <code>true</code>, update workbench preferences to reflect
* the new default VM.
* @throws CoreException
* @since 0.9.0
*/
public static void setDefaultVMInstall(IVMInstall vm, IProgressMonitor monitor, boolean savePreference) throws CoreException {
IVMInstall previous = null;
if (fgDefaultVMId != null) {
previous = getVMFromCompositeId(fgDefaultVMId);
}
fgDefaultVMId = getCompositeIdFromVM(vm);
if (savePreference) {
saveVMConfiguration();
}
IVMInstall current = null;
if (fgDefaultVMId != null) {
current = getVMFromCompositeId(fgDefaultVMId);
}
if (previous != current) {
notifyDefaultVMChanged(previous, current);
}
}
public static File getRI() {
return getBinExecutable("qri");
}
public static File getRDoc() {
return getBinExecutable("rdoc");
}
private static File getBinExecutable(String command) {
IVMInstall vm = RubyRuntime.getDefaultVMInstall();
if (vm == null) return null;
File installLocation = vm.getInstallLocation();
String path = installLocation.getAbsolutePath();
if (!installLocation.getName().equals("bin")) {
path += File.separator + "bin";
}
path += File.separator + command;
if (Platform.getOS().equals(Platform.OS_WIN32)) {
path += ".bat";
File file = new File(path);
if (file.exists()) return file;
path = path.substring(0, path.length() - 3) + "cmd";
}
File file = new File(path);
if (file.exists()) return file;
// try adding major version number to end of command for OSes like Debian that do ri1.8
String version = vm.getRubyVersion();
if (version == null || version.length() < 3) return file;
version = version.substring(0, 3);
path = installLocation.getAbsolutePath();
if (!installLocation.getName().equals("bin")) {
path += File.separator + "bin";
}
path = File.separator + command + version;
if (Platform.getOS().equals(Platform.OS_WIN32)) {
path += ".bat";
}
return new File(path);
}
public static File getIRB() {
return getBinExecutable("irb");
}
private static ILaunchManager getLaunchManager() {
return DebugPlugin.getDefault().getLaunchManager();
}
public static ILaunchConfigurationWorkingCopy createBasicLaunch(String file, String args, IProject project) throws CoreException
{
return createBasicLaunch(file, args, project, project.getLocation().toOSString());
}
public static ILaunchConfigurationWorkingCopy createBasicLaunch(String file, String args, IProject project, String workingDirectory) throws CoreException {
ILaunchConfigurationType configType = getLaunchManager().getLaunchConfigurationType(IRubyLaunchConfigurationConstants.ID_RUBY_APPLICATION);
ILaunchConfigurationWorkingCopy wc = configType.newInstance(null, generateUniqueLaunchConfigurationNameFrom(file));
wc.setAttribute(IRubyLaunchConfigurationConstants.ATTR_FILE_NAME,
file);
wc.setAttribute(
IRubyLaunchConfigurationConstants.ATTR_PROGRAM_ARGUMENTS,
args);
wc.setAttribute(
IRubyLaunchConfigurationConstants.ATTR_WORKING_DIRECTORY,
workingDirectory);
wc.setAttribute(
IRubyLaunchConfigurationConstants.ATTR_REQUIRES_REFRESH, true);
wc.setAttribute(IRubyLaunchConfigurationConstants.ATTR_PROJECT_NAME,
project.getName());
wc.setAttribute(DebugPlugin.ATTR_CONSOLE_ENCODING, "UTF-8");
return wc;
}
public static boolean currentVMIsJRuby() {
if (RubyRuntime.getDefaultVMInstall() == null) return false;
if (RubyRuntime.getDefaultVMInstall().getVMInstallType() == null) return false;
if (RubyRuntime.getDefaultVMInstall().getVMInstallType().getId() == null) return false;
return RubyRuntime.getDefaultVMInstall().getVMInstallType().getId().equals(RubyRuntime.JRUBY_VMTYPE);
}
public static IPath checkInterpreterBin(String exe)
{
IVMInstall vm = getDefaultVMInstall();
if (vm == null)
return null;
File installLocation = vm.getInstallLocation();
if (installLocation == null)
return null;
return checkAnyInterpreterBin(exe, installLocation);
}
/**
* Similar to checkInterpreterBin, but allows for checking of rdebug-ide in non default InterpreterVM (e.g. a jruby
* interpreter)
*/
public static IPath checkAnyInterpreterBin(String exe, File installLocation)
{
IPath path = new Path(installLocation.getAbsolutePath());
if (!installLocation.getName().equals("bin"))
{
path = path.append("bin");
}
path = path.append(exe);
if (path.toFile().exists())
return path;
return null;
}
public static boolean currentVMIsCygwin() {
if (RubyRuntime.getDefaultVMInstall() == null) return false;
if (RubyRuntime.getDefaultVMInstall().getPlatform() == null) return false;
return RubyRuntime.getDefaultVMInstall().getPlatform().equals(IVMInstall.CYWGIN_PLATFORM);
}
public static String launchInBackgroundAndRead(final ILaunchConfiguration config, final File file) {
try {
ILaunchConfigurationWorkingCopy wc = config.getWorkingCopy();
wc.setAttribute(IDebugUIConstants.ATTR_LAUNCH_IN_BACKGROUND, true);
wc.setAttribute(IDebugUIConstants.ATTR_CAPTURE_IN_CONSOLE, false);
wc.setAttribute(IDebugUIConstants.ATTR_CAPTURE_IN_FILE, file.getAbsolutePath());
wc.setAttribute(IDebugUIConstants.ATTR_PRIVATE, true);
wc.setAttribute(IRubyLaunchConfigurationConstants.ATTR_FORCE_NO_CONSOLE, true);
ILaunchConfiguration config2 = wc.doSave();
ILaunch launch = config2.launch(ILaunchManager.RUN_MODE, new NullProgressMonitor());
IProcess iproc = launch.getProcesses()[0];
IStreamMonitor stdOut = iproc.getStreamsProxy().getOutputStreamMonitor();
StreamListener listener = new StreamListener();
stdOut.addListener(listener);
while (!launch.isTerminated()) {
Thread.yield();
}
//if (listener.getContents().trim().length() == 0) { // if we didn't get anything out of the listener, try reading the output file
return readFile(file);
//}
//return listener.getContents();
} catch (Exception e) {
LaunchingPlugin.log(e);
}
return null;
}
private static String readFile(File file) {
try {
return new String(Util.getFileCharContent(file, null));
} catch (FileNotFoundException e) {
LaunchingPlugin.log(e);
} catch (IOException e) {
LaunchingPlugin.log(e);
}
return null;
}
private static class StreamListener implements IStreamListener {
private StringBuffer buf;
public StreamListener() {
buf = new StringBuffer();
}
public void streamAppended(final String text, IStreamMonitor monitor) {
buf.append(text);
}
public String getContents() {
return buf.toString();
}
}
/**
* Returns the VM connector defined with the specified identifier,
* or <code>null</code> if none.
*
* @param id VM connector identifier
* @return VM connector or <code>null</code> if none
* @since 2.0
*/
public static IVMConnector getVMConnector(String id) {
return LaunchingPlugin.getDefault().getVMConnector(id);
}
/**
* Returns all VM connector extensions.
*
* @return VM connectors
* @since 2.0
*/
public static IVMConnector[] getVMConnectors() {
return LaunchingPlugin.getDefault().getVMConnectors();
}
/**
* Return the default VM connector.
* @return Returns the default VM connector.
* @since 2.0
*/
public static IVMConnector getDefaultVMConnector() {
String id = getDefaultVMConnectorId();
IVMConnector connector = null;
if (id != null) {
connector = getVMConnector(id);
}
if (connector == null) {
connector = new SocketAttachConnector();
}
return connector;
}
/**
* Returns the default VM connector id determined during the initialization of the vm types
* @return the id of the default VM connector
*/
private static String getDefaultVMConnectorId() {
initializeVMs();
return fgDefaultVMConnectorId;
}
/**
* Wrap generation of launch config name from LaunchManager to avoid possible bad chars that cause the config to break in Eclipse 3.5+ (i.e. slashes)
* @param fullFileName
* @return
*/
public static String generateUniqueLaunchConfigurationNameFrom(String fullFileName)
{
// sanitize the name
fullFileName = fullFileName.replace('/', '_');
fullFileName = fullFileName.replace('\\', '_');
fullFileName = fullFileName.replace(' ', '_');
return getLaunchManager().generateUniqueLaunchConfigurationNameFrom(fullFileName);
}
}