/*
* Author:
*
* Copyright (c) 2003-2005 RubyPeople.
*
* This file is part of the Ruby Development Tools (RDT) plugin for eclipse.
* RDT is subject to the "Common Public License (CPL) v 1.0". You may not use
* RDT except in compliance with the License. For further information see
* org.rubypeople.rdt/rdt.license.
*/
package org.rubypeople.rdt.core;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IProjectDescription;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IResourceProxy;
import org.eclipse.core.resources.IResourceProxyVisitor;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.IWorkspaceRunnable;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtension;
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.OperationCanceledException;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Plugin;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.core.runtime.jobs.ISchedulingRule;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.rubypeople.rdt.core.search.IRubySearchConstants;
import org.rubypeople.rdt.core.search.IRubySearchScope;
import org.rubypeople.rdt.core.search.SearchEngine;
import org.rubypeople.rdt.core.search.SearchPattern;
import org.rubypeople.rdt.core.search.TypeNameRequestor;
import org.rubypeople.rdt.internal.core.BatchOperation;
import org.rubypeople.rdt.internal.core.DefaultWorkingCopyOwner;
import org.rubypeople.rdt.internal.core.LoadpathAttribute;
import org.rubypeople.rdt.internal.core.LoadpathEntry;
import org.rubypeople.rdt.internal.core.Region;
import org.rubypeople.rdt.internal.core.RubyCorePreferenceInitializer;
import org.rubypeople.rdt.internal.core.RubyModel;
import org.rubypeople.rdt.internal.core.RubyModelManager;
import org.rubypeople.rdt.internal.core.RubyProject;
import org.rubypeople.rdt.internal.core.SetExecutableBits;
import org.rubypeople.rdt.internal.core.SetLoadpathOperation;
import org.rubypeople.rdt.internal.core.util.MementoTokenizer;
import org.rubypeople.rdt.internal.core.util.Messages;
import org.rubypeople.rdt.internal.core.util.Util;
import org.rubypeople.rdt.internal.ti.DataFlowTypeInferrer;
import org.rubypeople.rdt.internal.ti.ITypeInferrer;
public class RubyCore extends Plugin
{
private static RubyCore RUBY_CORE_PLUGIN = null;
public final static String PLUGIN_ID = "org.rubypeople.rdt.core";//$NON-NLS-1$
public final static String NATURE_ID = PLUGIN_ID + ".rubynature";//$NON-NLS-1$
/**
* New Preferences API
*
* @since 0.6.0
*/
public static final IEclipsePreferences[] preferencesLookup = new IEclipsePreferences[2];
static final int PREF_INSTANCE = 0;
static final int PREF_DEFAULT = 1;
/**
* Default task tag
*
* @since 0.6.0
*/
public static final String DEFAULT_TASK_TAGS = "TODO,FIXME,XXX,OPTIMIZE"; //$NON-NLS-1$
/**
* The identifier for the Ruby builder (value <code>"org.rubypeople.rdt.core.rubybuilder"</code>).
*/
public static final String BUILDER_ID = PLUGIN_ID + ".rubybuilder"; //$NON-NLS-1$
/**
* Default task priority
*
* @since 0.6.0
*/
public static final String DEFAULT_TASK_PRIORITIES = "NORMAL,HIGH,NORMAL,NORMAL"; //$NON-NLS-1$
/**
* Possible configurable option ID.
*
* @see #getDefaultOptions()
* @since 0.6.0
*/
public static final String COMPILER_TASK_PRIORITIES = PLUGIN_ID + ".compiler.taskPriorities"; //$NON-NLS-1$
/**
* Possible configurable option value for COMPILER_TASK_PRIORITIES.
*
* @see #getDefaultOptions()
* @since 0.6.0
*/
public static final String COMPILER_TASK_PRIORITY_HIGH = "HIGH"; //$NON-NLS-1$
/**
* Possible configurable option value for COMPILER_TASK_PRIORITIES.
*
* @see #getDefaultOptions()
* @since 0.6.0
*/
public static final String COMPILER_TASK_PRIORITY_LOW = "LOW"; //$NON-NLS-1$
/**
* Possible configurable option value for COMPILER_TASK_PRIORITIES.
*
* @see #getDefaultOptions()
* @since 0.6.0
*/
public static final String COMPILER_TASK_PRIORITY_NORMAL = "NORMAL"; //$NON-NLS-1$
/**
* Possible configurable option ID.
*
* @see #getDefaultOptions()
* @since 0.6.0
*/
public static final String COMPILER_TASK_TAGS = PLUGIN_ID + ".compiler.taskTags"; //$NON-NLS-1$
/**
* Possible configurable option ID.
*
* @see #getDefaultOptions()
* @since 0.7.0
*/
public static final String COMPILER_TASK_CASE_SENSITIVE = PLUGIN_ID + ".compiler.taskCaseSensitive"; //$NON-NLS-1$
/**
* Possible configurable option value.
*
* @see #getDefaultOptions()
* @since 0.7.0
*/
public static final String ENABLED = "enabled"; //$NON-NLS-1$
/**
* Possible configurable option value.
*
* @see #getDefaultOptions()
* @since 0.7.0
*/
public static final String DISABLED = "disabled"; //$NON-NLS-1$
/**
* Possible configurable option value.
*
* @see #getDefaultOptions()
*/
public static final String ERROR = "error"; //$NON-NLS-1$
/**
* Possible configurable option value.
*
* @see #getDefaultOptions()
*/
public static final String WARNING = "warning"; //$NON-NLS-1$
/**
* Possible configurable option value.
*
* @see #getDefaultOptions()
*/
public static final String IGNORE = "ignore"; //$NON-NLS-1$
/**
* Possible configurable option value.
*
* @see #getDefaultOptions()
* @since 0.7.0
*/
public static final String TAB = "tab"; //$NON-NLS-1$
/**
* Possible configurable option value.
*
* @see #getDefaultOptions()
* @since 0.7.0
*/
public static final String SPACE = "space"; //$NON-NLS-1$
/**
* Possible configurable option ID.
*
* @see #getDefaultOptions()
* @since 0.7.0
*/
public static final String CORE_ENCODING = PLUGIN_ID + ".encoding"; //$NON-NLS-1$
/**
* Possible configurable option value.
*
* @see #getDefaultOptions()
* @since 0.8.0
*/
public static final String INSERT = "insert"; //$NON-NLS-1$
/**
* Possible configurable option value.
*
* @see #getDefaultOptions()
* @since 0.8.0
*/
public static final String DO_NOT_INSERT = "do not insert"; //$NON-NLS-1$
/**
* Value of the content-type for Ruby source files. Use this value to retrieve the Ruby content type from the
* content type manager, and to add new Ruby-like extensions to this content type.
*
* @see org.eclipse.core.runtime.content.IContentTypeManager#getContentType(String)
* @see #getRubyLikeExtensions()
* @since 0.8.0
*/
public static final String RUBY_SOURCE_CONTENT_TYPE = RubyCore.PLUGIN_ID + ".rubySource"; //$NON-NLS-1$
/**
* Possible configurable option ID.
*
* @see #getDefaultOptions()
* @since 0.9.0
*/
public static final String COMPILER_PB_EMPTY_STATEMENT = PLUGIN_ID + ".compiler.problem.emptyStatement"; //$NON-NLS-1$
/**
* Possible configurable option ID.
*
* @see #getDefaultOptions()
* @since 0.9.0
*/
public static final String COMPILER_PB_CONSTANT_REASSIGNMENT = PLUGIN_ID + ".compiler.problem.constantReassignment"; //$NON-NLS-1$
/**
* Possible configurable option ID.
*
* @see #getDefaultOptions()
* @since 0.9.0
*/
public static final String COMPILER_PB_UNREACHABLE_CODE = PLUGIN_ID + ".compiler.problem.unreachableCode"; //$NON-NLS-1$
/**
* Possible configurable option ID.
*
* @see #getDefaultOptions()
* @since 0.9.0
*/
public static final String COMPILER_PB_REDEFINITION_CORE_CLASS_METHOD = PLUGIN_ID
+ ".compiler.problem.redefinition.core.class.method"; //$NON-NLS-1$
/**
* Possible configurable option ID.
*
* @see #getDefaultOptions()
* @since 1.0.0
*/
public static final String COMPILER_PB_RUBY_19_WHEN_STATEMENTS = PLUGIN_ID
+ ".compiler.problem.ruby19WhenStatements"; //$NON-NLS-1$
/**
* Possible configurable option ID.
*
* @see #getDefaultOptions()
* @since 1.0.0
*/
public static final String COMPILER_PB_RUBY_19_HASH_COMMA_SYTNAX = PLUGIN_ID
+ ".compiler.problem.ruby19HashCommaSyntax"; //$NON-NLS-1$
/**
* Possible configurable option ID.
*
* @see #getDefaultOptions()
* @since 0.9.0
*/
public static final String CODEASSIST_CAMEL_CASE_MATCH = PLUGIN_ID + ".codeComplete.camelCaseMatch"; //$NON-NLS-1$
// FIXME Rename to CORE_INCOMPLETE_LOADPATH
/**
* Possible configurable option ID.
*
* @see #getDefaultOptions()
* @since 0.9.0
*/
public static final String CORE_INCOMPLETE_CLASSPATH = PLUGIN_ID + ".incompleteClasspath"; //$NON-NLS-1$
// FIXME Rename to CORE_INCOMPATIBLE_RUBY_VERSION
/**
* Possible configurable option ID.
*
* @see #getDefaultOptions()
* @since 3.0
*/
public static final String CORE_INCOMPATIBLE_JDK_LEVEL = PLUGIN_ID + ".incompatibleJDKLevel"; //$NON-NLS-1$
// FIXME Rename to CORE_CIRCULAR_LOADPATH
/**
* Possible configurable option ID.
*
* @see #getDefaultOptions()
* @since 2.1
*/
public static final String CORE_CIRCULAR_CLASSPATH = PLUGIN_ID + ".circularClasspath"; //$NON-NLS-1$
/**
* Name of the User Library Container id.
*
* @since 1.0.0
*/
public static final String USER_LIBRARY_CONTAINER_ID = "org.rubypeople.rdt.USER_LIBRARY"; //$NON-NLS-1$
private static final boolean VERBOSE = false;
private RubyProjectListener fProjectListener;
public RubyCore()
{
super();
}
/**
* Returns the single instance of the Ruby core plug-in runtime class.
*
* @return the single instance of the Ruby core plug-in runtime class
*/
public static RubyCore getPlugin()
{
return RUBY_CORE_PLUGIN;
}
public static IWorkspace getWorkspace()
{
return ResourcesPlugin.getWorkspace();
}
/*
* (non-Javadoc)
* @see org.eclipse.core.runtime.Plugin#start(org.osgi.framework.BundleContext)
*/
public void start(BundleContext context) throws Exception
{
RUBY_CORE_PLUGIN = this;
super.start(context);
fProjectListener = new RubyProjectListener();
ResourcesPlugin.getWorkspace().addResourceChangeListener(fProjectListener, IResourceChangeEvent.POST_CHANGE);
RubyModelManager.getRubyModelManager().startup();
new SetExecutableBits().schedule();
}
/*
* (non-Javadoc) Shutdown the RubyCore plug-in. <p> De-registers the RubyModelManager as a resource changed listener
* and save participant. <p>
* @see org.eclipse.core.runtime.Plugin#stop(BundleContext)
*/
public void stop(BundleContext context) throws Exception
{
try
{
ResourcesPlugin.getWorkspace().removeResourceChangeListener(fProjectListener);
RubyModelManager.getRubyModelManager().shutdown();
}
finally
{
// ensure we call super.stop as the last thing
super.stop(context);
}
}
public static void trace(String message)
{
if (getPlugin().isDebugging())
System.out.println(message);
}
public static void log(Exception e)
{
String msg = e.getMessage();
if (msg == null)
msg = "";
log(Status.ERROR, msg, e);
}
/**
* @param string
*/
public static void log(String string)
{
log(IStatus.INFO, string);
}
public static void log(int severity, String string)
{
log(severity, string, null);
}
public static void log(int severity, String string, Throwable e)
{
getPlugin().getLog().log(new Status(severity, PLUGIN_ID, IStatus.OK, string, e));
if (severity == Status.ERROR)
{
// send stack trace to Trac!
}
if (VERBOSE)
{
System.out.println(string);
if (e != null)
e.printStackTrace();
}
}
public static String getOSDirectory(Plugin plugin)
{
final Bundle bundle = plugin.getBundle();
String location = bundle.getLocation();
int prefixLength = location.indexOf('@');
if (prefixLength == -1)
{
throw new RuntimeException("Location of launching bundle does not contain @: " + location);
}
String pluginDir = location.substring(prefixLength + 1);
File pluginDirFile = new File(pluginDir);
if (!pluginDirFile.exists())
{
// pluginDirFile is a relative path, if the working directory is
// different from
// the location of the eclipse executable, we try this ...
String installArea = System.getProperty("osgi.install.area");
if (installArea.startsWith("file:"))
{
installArea = installArea.substring("file:".length());
}
// Path.toOSString() removes a leading slash if on windows, e.g.
// /D:/Eclipse => D:/Eclipse
File installFile = new File(new Path(installArea).toOSString());
pluginDirFile = new File(installFile, pluginDir);
if (!pluginDirFile.exists())
throw new RuntimeException("Unable to find (" + pluginDirFile + ") directory for " + plugin.getClass());
}
return pluginDirFile.getAbsolutePath() + "/";
}
public static IProject[] getRubyProjects()
{
List<IProject> rubyProjectsList = new ArrayList<IProject>();
IProject[] workspaceProjects = RubyCore.getWorkspace().getRoot().getProjects();
for (int i = 0; i < workspaceProjects.length; i++)
{
IProject iProject = workspaceProjects[i];
if (isRubyProject(iProject))
rubyProjectsList.add(iProject);
}
IProject[] rubyProjects = new IProject[rubyProjectsList.size()];
return rubyProjectsList.toArray(rubyProjects);
}
public static boolean isRubyProject(IProject aProject)
{
try
{
return aProject.hasNature(RubyCore.NATURE_ID);
}
catch (CoreException e)
{
}
return false;
}
public static IRubyScript create(IFile file)
{
return RubyModelManager.create(file, null/* unknown ruby project */);
}
public static IRubyProject create(IProject project)
{
if (project == null)
{
return null;
}
RubyModel rubyModel = RubyModelManager.getRubyModelManager().getRubyModel();
return rubyModel.getRubyProject(project);
}
public static void addRubyNature(IProject project, IProgressMonitor monitor) throws CoreException
{
if (!project.hasNature(RubyCore.NATURE_ID))
{
IProjectDescription description = project.getDescription();
String[] prevNatures = description.getNatureIds();
String[] newNatures = new String[prevNatures.length + 1];
System.arraycopy(prevNatures, 0, newNatures, 0, prevNatures.length);
newNatures[prevNatures.length] = RubyCore.NATURE_ID;
description.setNatureIds(newNatures);
project.setDescription(description, monitor);
}
}
public static IRubyElement create(IResource resource)
{
return RubyModelManager.create(resource, null/* unknown ruby project */);
}
/**
* Returns the Ruby model.
*
* @param root
* the given root
* @return the Ruby model, or <code>null</code> if the root is null
*/
public static IRubyModel create(IWorkspaceRoot root)
{
if (root == null)
{
return null;
}
return RubyModelManager.getRubyModelManager().getRubyModel();
}
/**
* Runs the given action as an atomic Java model operation.
* <p>
* After running a method that modifies java elements, registered listeners receive after-the-fact notification of
* what just transpired, in the form of a element changed event. This method allows clients to call a number of
* methods that modify java elements and only have element changed event notifications reported at the end of the
* entire batch.
* </p>
* <p>
* If this method is called outside the dynamic scope of another such call, this method runs the action and then
* reports a single element changed event describing the net effect of all changes done to java elements by the
* action.
* </p>
* <p>
* If this method is called in the dynamic scope of another such call, this method simply runs the action.
* </p>
* <p>
* The supplied scheduling rule is used to determine whether this operation can be run simultaneously with workspace
* changes in other threads. See <code>IWorkspace.run(...)</code> for more details.
* </p>
*
* @param action
* the action to perform
* @param rule
* the scheduling rule to use when running this operation, or <code>null</code> if there are no
* scheduling restrictions for this operation.
* @param monitor
* a progress monitor, or <code>null</code> if progress reporting and cancellation are not desired
* @exception CoreException
* if the operation failed.
* @since 3.0
*/
public static void run(IWorkspaceRunnable action, ISchedulingRule rule, IProgressMonitor monitor)
throws CoreException
{
IWorkspace workspace = ResourcesPlugin.getWorkspace();
if (workspace.isTreeLocked())
{
new BatchOperation(action).run(monitor);
}
else
{
// use IWorkspace.run(...) to ensure that a build will be done in
// autobuild mode
workspace.run(new BatchOperation(action), rule, IWorkspace.AVOID_UPDATE, monitor);
}
}
/**
* Helper method for returning one option value only. Equivalent to
* <code>(String)JavaCore.getOptions().get(optionName)</code> Note that it may answer <code>null</code> if this
* option does not exist.
* <p>
* For a complete description of the configurable options, see <code>getDefaultOptions</code>.
* </p>
*
* @param optionName
* the name of an option
* @return the String value of a given option
* @see RubyCore#getDefaultOptions()
* @see RubyCorePreferenceInitializer for changing default settings
*/
public static String getOption(String optionName)
{
return RubyModelManager.getRubyModelManager().getOption(optionName);
}
/**
* Returns the workspace root default charset encoding.
*
* @return the name of the default charset encoding for workspace root.
* @see IContainer#getDefaultCharset()
* @see ResourcesPlugin#getEncoding()
*/
public static String getEncoding()
{
// Verify that workspace is not shutting down (see bug
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=60687)
IWorkspace workspace = ResourcesPlugin.getWorkspace();
if (workspace != null)
{
try
{
return workspace.getRoot().getDefaultCharset();
}
catch (CoreException e)
{
// fails silently and return plugin global encoding if core
// exception occurs
}
}
return ResourcesPlugin.getEncoding();
}
public static ITypeInferrer getTypeInferrer()
{
return new DataFlowTypeInferrer();
}
/**
* Returns the table of the current options. Initially, all options have their default values, and this method
* returns a table that includes all known options.
* <p>
* For a complete description of the configurable options, see <code>getDefaultOptions</code>.
* </p>
*
* @return table of current settings of all options (key type: <code>String</code>; value type: <code>String</code>)
* @see #getDefaultOptions()
* @see RubyCorePreferenceInitializer for changing default settings
*/
public static Hashtable<String, String> getOptions()
{
return RubyModelManager.getRubyModelManager().getOptions();
}
/**
* Adds the given listener for changes to Java elements. Has no effect if an identical listener is already
* registered. This listener will only be notified during the POST_CHANGE resource change notification and any
* reconcile operation (POST_RECONCILE). For finer control of the notification, use
* <code>addElementChangedListener(IElementChangedListener,int)</code>, which allows to specify a different
* eventMask.
*
* @param listener
* the listener
* @see ElementChangedEvent
*/
public static void addElementChangedListener(IElementChangedListener listener)
{
addElementChangedListener(listener, ElementChangedEvent.POST_CHANGE | ElementChangedEvent.POST_RECONCILE);
}
/**
* Adds the given listener for changes to Java elements. Has no effect if an identical listener is already
* registered. After completion of this method, the given listener will be registered for exactly the specified
* events. If they were previously registered for other events, they will be deregistered.
* <p>
* Once registered, a listener starts receiving notification of changes to java elements in the model. The listener
* continues to receive notifications until it is replaced or removed.
* </p>
* <p>
* Listeners can listen for several types of event as defined in <code>ElementChangeEvent</code>. Clients are free
* to register for any number of event types however if they register for more than one, it is their responsibility
* to ensure they correctly handle the case where the same java element change shows up in multiple notifications.
* Clients are guaranteed to receive only the events for which they are registered.
* </p>
*
* @param listener
* the listener
* @param eventMask
* the bit-wise OR of all event types of interest to the listener
* @see IElementChangedListener
* @see ElementChangedEvent
* @see #removeElementChangedListener(IElementChangedListener)
* @since 0.7.0
*/
public static void addElementChangedListener(IElementChangedListener listener, int eventMask)
{
RubyModelManager.getRubyModelManager().deltaState.addElementChangedListener(listener, eventMask);
}
/**
* Removes the given element changed listener. Has no affect if an identical listener is not registered.
*
* @param listener
* the listener
*/
public static void removeElementChangedListener(IElementChangedListener listener)
{
RubyModelManager.getRubyModelManager().deltaState.removeElementChangedListener(listener);
}
/**
* Returns the single instance of the Ruby core plug-in runtime class. Equivalent to
* <code>(RubyCore) getPlugin()</code>.
*
* @return the single instance of the Ruby core plug-in runtime class
*/
public static RubyCore getRubyCore()
{
return getPlugin();
}
public static boolean isRubyLikeFileName(String name)
{
return Util.isRubyLikeFileName(name);
}
/**
* Creates and returns a new classpath entry of kind <code>CPE_SOURCE</code> for all files in the project's source
* folder identified by the given absolute workspace-relative path.
* <p>
* The convenience method is fully equivalent to:
*
* <pre>
* newSourceEntry(path, new IPath[] {}, new IPath[] {}, null);
* </pre>
*
* </p>
*
* @param path
* the absolute workspace-relative path of a source folder
* @return a new source classpath entry
* @see #newSourceEntry(IPath, IPath[], IPath[])
*/
public static ILoadpathEntry newSourceEntry(IPath path)
{
return newSourceEntry(path, LoadpathEntry.INCLUDE_ALL, LoadpathEntry.EXCLUDE_NONE,
LoadpathEntry.NO_EXTRA_ATTRIBUTES);
}
/**
* Creates and returns a new classpath entry of kind <code>CPE_SOURCE</code> for the project's source folder
* identified by the given absolute workspace-relative path but excluding all source files with paths matching any
* of the given patterns, and associated with a specific output location (that is, ".class" files are not going to
* the project default output location).
* <p>
* The convenience method is fully equivalent to:
*
* <pre>
* newSourceEntry(path, new IPath[] {}, exclusionPatterns);
* </pre>
*
* </p>
*
* @param path
* the absolute workspace-relative path of a source folder
* @param inclusionPatterns
* the possibly empty list of inclusion patterns represented as relative paths
* @param exclusionPatterns
* the possibly empty list of exclusion patterns represented as relative paths
* @return a new source classpath entry
* @since 3.0
*/
public static ILoadpathEntry newSourceEntry(IPath path, IPath[] inclusionPatterns, IPath[] exclusionPatterns,
ILoadpathAttribute[] extraAttributes)
{
if (path == null)
Assert.isTrue(false, "Source path cannot be null"); //$NON-NLS-1$
if (!path.isAbsolute())
Assert.isTrue(false, "Path for ILoadpathEntry must be absolute"); //$NON-NLS-1$
if (exclusionPatterns == null)
Assert.isTrue(false, "Exclusion pattern set cannot be null"); //$NON-NLS-1$
if (inclusionPatterns == null)
Assert.isTrue(false, "Inclusion pattern set cannot be null"); //$NON-NLS-1$
return new LoadpathEntry(ILoadpathEntry.CPE_SOURCE, path, inclusionPatterns, exclusionPatterns,
extraAttributes, false);
}
public static ILoadpathEntry newLibraryEntry(IPath path, ILoadpathAttribute[] extraAttributes, boolean isExported)
{
if (path == null)
Assert.isTrue(false, "Library path cannot be null"); //$NON-NLS-1$
if (!path.isAbsolute())
Assert.isTrue(false, "Path for ILoadpathEntry must be absolute"); //$NON-NLS-1$
return new LoadpathEntry(ILoadpathEntry.CPE_LIBRARY, RubyProject.canonicalizedPath(path),
LoadpathEntry.INCLUDE_ALL, // inclusion patterns
LoadpathEntry.EXCLUDE_NONE, // exclusion patterns
extraAttributes, isExported);
}
public static ILoadpathEntry newProjectEntry(IPath path, ILoadpathAttribute[] extraAttributes, boolean isExported)
{
if (!path.isAbsolute())
Assert.isTrue(false, "Path for ILoadpathEntry must be absolute"); //$NON-NLS-1$
return new LoadpathEntry(ILoadpathEntry.CPE_PROJECT, path, LoadpathEntry.INCLUDE_ALL, // inclusion patterns
LoadpathEntry.EXCLUDE_NONE, // exclusion patterns
extraAttributes, isExported);
}
public static ILoadpathEntry newVariableEntry(IPath variablePath, ILoadpathAttribute[] extraAttributes,
boolean isExported)
{
if (variablePath == null)
Assert.isTrue(false, "Variable path cannot be null"); //$NON-NLS-1$
if (variablePath.segmentCount() < 1)
{
Assert
.isTrue(
false,
"Illegal loadpath variable path: \'" + variablePath.makeRelative().toString() + "\', must have at least one segment"); //$NON-NLS-1$//$NON-NLS-2$
}
return new LoadpathEntry(ILoadpathEntry.CPE_VARIABLE, variablePath, LoadpathEntry.INCLUDE_ALL, // inclusion
// patterns
LoadpathEntry.EXCLUDE_NONE, // exclusion patterns
extraAttributes, isExported);
}
public static ILoadpathEntry newContainerEntry(IPath containerPath, ILoadpathAttribute[] extraAttributes,
boolean isExported)
{
if (containerPath == null)
{
Assert.isTrue(false, "Container path cannot be null"); //$NON-NLS-1$
}
else if (containerPath.segmentCount() < 1)
{
Assert
.isTrue(
false,
"Illegal loadpath container path: \'" + containerPath.makeRelative().toString() + "\', must have at least one segment (containerID+hints)"); //$NON-NLS-1$//$NON-NLS-2$
}
return new LoadpathEntry(ILoadpathEntry.CPE_CONTAINER, containerPath, LoadpathEntry.INCLUDE_ALL, // inclusion
// patterns
LoadpathEntry.EXCLUDE_NONE, // exclusion patterns
extraAttributes, isExported);
}
public static ILoadpathEntry getResolvedLoadpathEntry(ILoadpathEntry entry)
{
if (entry.getEntryKind() != ILoadpathEntry.CPE_VARIABLE)
return entry;
IPath resolvedPath = RubyCore.getResolvedVariablePath(entry.getPath());
if (resolvedPath == null)
return null;
Object target = RubyModel.getTarget(resolvedPath, false);
if (target == null)
return null;
// inside the workspace
if (target instanceof IResource)
{
IResource resolvedResource = (IResource) target;
if (resolvedResource != null)
{
switch (resolvedResource.getType())
{
case IResource.PROJECT:
// internal project
return RubyCore.newProjectEntry(resolvedPath, entry.getExtraAttributes(), entry.isExported());
case IResource.FOLDER:
// internal binary folder
return RubyCore.newLibraryEntry(resolvedPath, entry.getExtraAttributes(), entry.isExported());
}
}
}
// outside the workspace
if (target instanceof File)
{
File externalFile = RubyModel.getFolder(target);
if (externalFile != null)
{
return RubyCore.newLibraryEntry(resolvedPath, entry.getExtraAttributes(), entry.isExported());
}
else
{ // external binary folder
if (resolvedPath.isAbsolute())
{
return RubyCore.newLibraryEntry(resolvedPath, entry.getExtraAttributes(), entry.isExported());
}
}
}
return null;
}
/**
* Resolve a variable path (helper method).
*
* @param variablePath
* the given variable path
* @return the resolved variable path or <code>null</code> if none
*/
public static IPath getResolvedVariablePath(IPath variablePath)
{
if (variablePath == null)
return null;
int count = variablePath.segmentCount();
if (count == 0)
return null;
String variableName = variablePath.segment(0);
IPath[] resolvedPaths = RubyCore.getLoadpathVariable(variableName);
if (resolvedPaths == null)
{
return null;
}
// append path suffix
if (count > 1)
{
for (int i = 0; i < resolvedPaths.length; i++)
{
resolvedPaths[i] = resolvedPaths[i].append(variablePath.removeFirstSegments(1));
}
}
IPath resolvedPath = null;
Object target = null;
for (int i = 0; i < resolvedPaths.length; i++)
{
target = RubyModel.getTarget(resolvedPaths[i], false);
if (target instanceof File)
{
File targetFile = (File) target;
if (!targetFile.exists())
{
target = null;
}
}
if (target != null)
{
resolvedPath = resolvedPaths[i];
break;
}
}
if (target == null)
{
// TODO This is routine for GEM_LIB variables until it registers, but for others we should probably log it (or if we get the same GEM_LIB ones many times)
// log(Status.INFO, "Was unable to resolve path: " + variablePath.toPortableString());
return null;
}
return resolvedPath;
}
/**
* Returns the path held in the given loadpath variable. Returns <code>null</code> if unable to bind.
* <p>
* Loadpath variable values are persisted locally to the workspace, and are preserved from session to session.
* <p>
* Note that loadpath variables can be contributed registered initializers for, using the extension point
* "org.rubypeople.rdt.core.loadpathVariableInitializer". If an initializer is registered for a variable, its
* persisted value will be ignored: its initializer will thus get the opportunity to rebind the variable differently
* on each session.
*
* @param variableName
* the name of the loadpath variable
* @return the path, or <code>null</code> if none
* @see #setLoadpathVariable(String, IPath)
*/
public static IPath[] getLoadpathVariable(final String variableName)
{
RubyModelManager manager = RubyModelManager.getRubyModelManager();
IPath[] variablePath = manager.variableGet(variableName);
if (variablePath == RubyModelManager.VARIABLE_INITIALIZATION_IN_PROGRESS)
{
return manager.getPreviousSessionVariable(variableName);
}
if (variablePath != null)
{
if (variablePath == RubyModelManager.CP_ENTRY_IGNORE_PATH)
return null;
return variablePath;
}
// even if persisted value exists, initializer is given priority, only if no initializer is found the persisted
// value is reused
final LoadpathVariableInitializer initializer = RubyCore.getLoadpathVariableInitializer(variableName);
if (initializer != null)
{
if (RubyModelManager.CP_RESOLVE_VERBOSE)
{
Util.verbose("CPVariable INIT - triggering initialization\n" + //$NON-NLS-1$
" variable: " + variableName + '\n' + //$NON-NLS-1$
" initializer: " + initializer + '\n' + //$NON-NLS-1$
" invocation stack trace:"); //$NON-NLS-1$
new Exception("<Fake exception>").printStackTrace(System.out); //$NON-NLS-1$
}
manager.variablePut(variableName, RubyModelManager.VARIABLE_INITIALIZATION_IN_PROGRESS); // avoid
// initialization
// cycles
boolean ok = false;
try
{
// let OperationCanceledException go through
// (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=59363)
initializer.initialize(variableName);
variablePath = manager.variableGet(variableName); // initializer should have performed side-effect
if (variablePath == RubyModelManager.VARIABLE_INITIALIZATION_IN_PROGRESS)
return null; // break cycle (initializer did not init or reentering call)
if (RubyModelManager.CP_RESOLVE_VERBOSE)
{
Util.verbose("CPVariable INIT - after initialization\n" + //$NON-NLS-1$
" variable: " + variableName + '\n' + //$NON-NLS-1$
" variable path: " + variablePath); //$NON-NLS-1$
}
manager.variablesWithInitializer.add(variableName);
ok = true;
}
catch (RuntimeException e)
{
if (RubyModelManager.CP_RESOLVE_VERBOSE)
{
e.printStackTrace();
}
throw e;
}
catch (Error e)
{
if (RubyModelManager.CP_RESOLVE_VERBOSE)
{
e.printStackTrace();
}
throw e;
}
finally
{
if (!ok)
RubyModelManager.getRubyModelManager().variablePut(variableName, null); // flush cache
}
}
else
{
if (RubyModelManager.CP_RESOLVE_VERBOSE)
{
Util.verbose("CPVariable INIT - no initializer found\n" + //$NON-NLS-1$
" variable: " + variableName); //$NON-NLS-1$
}
}
return variablePath;
}
/**
* Helper method finding the loadpath variable initializer registered for a given loadpath variable name or
* <code>null</code> if none was found while iterating over the contributions to extension point to the extension
* point "org.rubypeople.rdt.core.loadpathVariableInitializer".
* <p>
*
* @param variable
* the given variable
* @return LoadpathVariableInitializer - the registered loadpath variable initializer or <code>null</code> if none
* was found.
* @since 0.9.0
*/
public static LoadpathVariableInitializer getLoadpathVariableInitializer(String variable)
{
Plugin jdtCorePlugin = RubyCore.getPlugin();
if (jdtCorePlugin == null)
return null;
IExtensionPoint extension = Platform.getExtensionRegistry().getExtensionPoint(RubyCore.PLUGIN_ID,
RubyModelManager.CPVARIABLE_INITIALIZER_EXTPOINT_ID);
if (extension != null)
{
IExtension[] extensions = extension.getExtensions();
for (int i = 0; i < extensions.length; i++)
{
IConfigurationElement[] configElements = extensions[i].getConfigurationElements();
for (int j = 0; j < configElements.length; j++)
{
try
{
String varAttribute = configElements[j].getAttribute("variable"); //$NON-NLS-1$
if (variable.equals(varAttribute))
{
if (RubyModelManager.CP_RESOLVE_VERBOSE)
{
Util.verbose("CPVariable INIT - found initializer\n" + //$NON-NLS-1$
" variable: " + variable + '\n' + //$NON-NLS-1$
" class: " + configElements[j].getAttribute("class")); //$NON-NLS-1$ //$NON-NLS-2$
}
Object execExt = configElements[j].createExecutableExtension("class"); //$NON-NLS-1$
if (execExt instanceof LoadpathVariableInitializer)
{
return (LoadpathVariableInitializer) execExt;
}
}
}
catch (CoreException e)
{
// executable extension could not be created: ignore this initializer
if (RubyModelManager.CP_RESOLVE_VERBOSE)
{
Util.verbose("CPContainer INIT - failed to instanciate initializer\n" + //$NON-NLS-1$
" variable: " + variable + '\n' + //$NON-NLS-1$
" class: " + configElements[j].getAttribute("class"), //$NON-NLS-1$ //$NON-NLS-2$
System.err);
e.printStackTrace();
}
}
}
}
}
return null;
}
public static ILoadpathContainer getLoadpathContainer(IPath containerPath, IRubyProject project)
throws RubyModelException
{
RubyModelManager manager = RubyModelManager.getRubyModelManager();
ILoadpathContainer container = manager.getLoadpathContainer(containerPath, project);
if (container == RubyModelManager.CONTAINER_INITIALIZATION_IN_PROGRESS)
{
return manager.getPreviousSessionContainer(containerPath, project);
}
return container;
}
/**
* Helper method finding the classpath container initializer registered for a given classpath container ID or
* <code>null</code> if none was found while iterating over the contributions to extension point to the extension
* point "org.eclipse.jdt.core.classpathContainerInitializer".
* <p>
* A containerID is the first segment of any container path, used to identify the registered container initializer.
* <p>
*
* @param containerID
* - a containerID identifying a registered initializer
* @return ClasspathContainerInitializer - the registered classpath container initializer or <code>null</code> if
* none was found.
* @since 0.9.0
*/
public static LoadpathContainerInitializer getLoadpathContainerInitializer(String containerID)
{
HashMap<String, LoadpathContainerInitializer> containerInitializersCache = RubyModelManager
.getRubyModelManager().containerInitializersCache;
LoadpathContainerInitializer initializer = (LoadpathContainerInitializer) containerInitializersCache
.get(containerID);
if (initializer == null)
{
initializer = computeLoadpathContainerInitializer(containerID);
if (initializer == null)
return null;
containerInitializersCache.put(containerID, initializer);
}
return initializer;
}
private static LoadpathContainerInitializer computeLoadpathContainerInitializer(String containerID)
{
Plugin jdtCorePlugin = RubyCore.getPlugin();
if (jdtCorePlugin == null)
return null;
IExtensionPoint extension = Platform.getExtensionRegistry().getExtensionPoint(RubyCore.PLUGIN_ID,
RubyModelManager.CPCONTAINER_INITIALIZER_EXTPOINT_ID);
if (extension != null)
{
IExtension[] extensions = extension.getExtensions();
for (int i = 0; i < extensions.length; i++)
{
IConfigurationElement[] configElements = extensions[i].getConfigurationElements();
for (int j = 0; j < configElements.length; j++)
{
String initializerID = configElements[j].getAttribute("id"); //$NON-NLS-1$
if (initializerID != null && initializerID.equals(containerID))
{
if (RubyModelManager.CP_RESOLVE_VERBOSE)
{
Util.verbose("CPContainer INIT - found initializer\n" + //$NON-NLS-1$
" container ID: " + containerID + '\n' + //$NON-NLS-1$
" class: " + configElements[j].getAttribute("class")); //$NON-NLS-1$ //$NON-NLS-2$
}
try
{
Object execExt = configElements[j].createExecutableExtension("class"); //$NON-NLS-1$
if (execExt instanceof LoadpathContainerInitializer)
{
return (LoadpathContainerInitializer) execExt;
}
}
catch (CoreException e)
{
// executable extension could not be created: ignore this initializer
if (RubyModelManager.CP_RESOLVE_VERBOSE)
{
Util.verbose("CPContainer INIT - failed to instanciate initializer\n" + //$NON-NLS-1$
" container ID: " + containerID + '\n' + //$NON-NLS-1$
" class: " + configElements[j].getAttribute("class"), //$NON-NLS-1$ //$NON-NLS-2$
System.err);
e.printStackTrace();
}
}
}
}
}
}
return null;
}
public static void setLoadpathContainer(final IPath containerPath, IRubyProject[] affectedProjects,
ILoadpathContainer[] respectiveContainers, IProgressMonitor monitor) throws RubyModelException
{
if (affectedProjects.length != respectiveContainers.length)
Assert.isTrue(false, "Projects and containers collections should have the same size"); //$NON-NLS-1$
if (monitor != null && monitor.isCanceled())
return;
if (RubyModelManager.CP_RESOLVE_VERBOSE)
{
Util.verbose("CPContainer SET - setting container\n" + //$NON-NLS-1$
" container path: "
+ containerPath
+ '\n'
+ //$NON-NLS-1$
" projects: {"
+ //$NON-NLS-1$
org.rubypeople.rdt.core.util.Util.toString(affectedProjects,
new org.rubypeople.rdt.core.util.Util.Displayable()
{
public String displayString(Object o)
{
return ((IRubyProject) o).getElementName();
}
})
+ "}\n values: {\n" + //$NON-NLS-1$
org.rubypeople.rdt.core.util.Util.toString(respectiveContainers,
new org.rubypeople.rdt.core.util.Util.Displayable()
{
public String displayString(Object o)
{
StringBuffer buffer = new StringBuffer(" "); //$NON-NLS-1$
if (o == null)
{
buffer.append("<null>"); //$NON-NLS-1$
return buffer.toString();
}
ILoadpathContainer container = (ILoadpathContainer) o;
buffer.append(container.getDescription());
buffer.append(" {\n"); //$NON-NLS-1$
ILoadpathEntry[] entries = container.getLoadpathEntries();
if (entries != null)
{
for (int i = 0; i < entries.length; i++)
{
buffer.append(" "); //$NON-NLS-1$
buffer.append(entries[i]);
buffer.append('\n');
}
}
buffer.append(" }"); //$NON-NLS-1$
return buffer.toString();
}
}) + "\n }\n invocation stack trace:"); //$NON-NLS-1$
new Exception("<Fake exception>").printStackTrace(System.out); //$NON-NLS-1$
}
RubyModelManager manager = RubyModelManager.getRubyModelManager();
if (manager.containerPutIfInitializingWithSameEntries(containerPath, affectedProjects, respectiveContainers))
return;
final int projectLength = affectedProjects.length;
final IRubyProject[] modifiedProjects;
System.arraycopy(affectedProjects, 0, modifiedProjects = new IRubyProject[projectLength], 0, projectLength);
final ILoadpathEntry[][] oldResolvedPaths = new ILoadpathEntry[projectLength][];
// filter out unmodified project containers
int remaining = 0;
for (int i = 0; i < projectLength; i++)
{
if (monitor != null && monitor.isCanceled())
return;
RubyProject affectedProject = (RubyProject) affectedProjects[i];
ILoadpathContainer newContainer = respectiveContainers[i];
if (newContainer == null)
newContainer = RubyModelManager.CONTAINER_INITIALIZATION_IN_PROGRESS; // 30920 - prevent infinite loop
boolean found = false;
if (RubyProject.hasRubyNature(affectedProject.getProject()))
{
ILoadpathEntry[] rawClasspath = affectedProject.getRawLoadpath();
for (int j = 0, cpLength = rawClasspath.length; j < cpLength; j++)
{
ILoadpathEntry entry = rawClasspath[j];
if (entry.getEntryKind() == ILoadpathEntry.CPE_CONTAINER && entry.getPath().equals(containerPath))
{
found = true;
break;
}
}
}
if (!found)
{
modifiedProjects[i] = null; // filter out this project - does not reference the container path, or
// isnt't yet Java project
manager.containerPut(affectedProject, containerPath, newContainer);
continue;
}
ILoadpathContainer oldContainer = manager.containerGet(affectedProject, containerPath);
if (oldContainer == RubyModelManager.CONTAINER_INITIALIZATION_IN_PROGRESS)
{
oldContainer = null;
}
if (oldContainer != null && oldContainer.equals(respectiveContainers[i]))
{
modifiedProjects[i] = null; // filter out this project - container did not change
continue;
}
remaining++;
oldResolvedPaths[i] = affectedProject.getResolvedLoadpath(true/* ignoreUnresolvedEntry */, false/*
* don't
* generateMarkerOnError
*/, false/*
* don't
* returnResolutionInProgress
*/);
manager.containerPut(affectedProject, containerPath, newContainer);
}
if (remaining == 0)
return;
// trigger model refresh
try
{
final boolean canChangeResources = !ResourcesPlugin.getWorkspace().isTreeLocked();
RubyCore.run(new IWorkspaceRunnable()
{
public void run(IProgressMonitor progressMonitor) throws CoreException
{
for (int i = 0; i < projectLength; i++)
{
if (progressMonitor != null && progressMonitor.isCanceled())
return;
RubyProject affectedProject = (RubyProject) modifiedProjects[i];
if (affectedProject == null)
continue; // was filtered out
if (RubyModelManager.CP_RESOLVE_VERBOSE)
{
Util.verbose("CPContainer SET - updating affected project due to setting container\n" + //$NON-NLS-1$
" project: " + affectedProject.getElementName() + '\n' + //$NON-NLS-1$
" container path: " + containerPath); //$NON-NLS-1$
}
// force a refresh of the affected project (will compute deltas)
affectedProject.setRawLoadpath(affectedProject.getRawLoadpath(),
SetLoadpathOperation.DO_NOT_SET_OUTPUT, progressMonitor, canChangeResources,
oldResolvedPaths[i], false, // updating - no need for early validation
false); // updating - no need to save
}
}
}, null/* no need to lock anything */, monitor);
}
catch (CoreException e)
{
if (RubyModelManager.CP_RESOLVE_VERBOSE)
{
Util.verbose("CPContainer SET - FAILED DUE TO EXCEPTION\n" + //$NON-NLS-1$
" container path: " + containerPath, //$NON-NLS-1$
System.err);
e.printStackTrace();
}
if (e instanceof RubyModelException)
{
throw (RubyModelException) e;
}
else
{
throw new RubyModelException(e);
}
}
finally
{
for (int i = 0; i < projectLength; i++)
{
if (respectiveContainers[i] == null)
{
manager.containerPut(affectedProjects[i], containerPath, null); // reset init in progress marker
}
}
}
}
/**
* Sets the value of the given loadpath variable. The path must not be null.
* <p>
* This functionality cannot be used while the resource tree is locked.
* <p>
* Loadpath variable values are persisted locally to the workspace, and are preserved from session to session.
* <p>
* Updating a variable with the same value has no effect.
*
* @param variableName
* the name of the loadpath variable
* @param path
* the path
* @param monitor
* a monitor to report progress
* @throws RubyModelException
* @see #getLoadpathVariable(String)
*/
public static void setLoadpathVariable(String variableName, IPath[] path, IProgressMonitor monitor)
throws RubyModelException
{
if (path == null)
Assert.isTrue(false, "Variable path cannot be null"); //$NON-NLS-1$
setLoadpathVariables(new String[] { variableName }, new IPath[][] { path }, monitor);
}
/**
* Sets the values of all the given loadpath variables at once. Null paths can be used to request corresponding
* variable removal.
* <p>
* A combined Ruby element delta will be notified to describe the corresponding loadpath changes resulting from the
* variables update. This operation is batched, and automatically eliminates unnecessary updates (new variable is
* same as old one). This operation acquires a lock on the workspace's root.
* <p>
* This functionality cannot be used while the workspace is locked, since it may create/remove some resource
* markers.
* <p>
* Loadpath variable values are persisted locally to the workspace, and are preserved from session to session.
* <p>
* Updating a variable with the same value has no effect.
*
* @param variableNames
* an array of names for the updated loadpath variables
* @param paths
* an array of path updates for the modified loadpath variables (null meaning that the corresponding
* value will be removed
* @param monitor
* a monitor to report progress
* @throws RubyModelException
* @see #getLoadpathVariable(String)
* @since 0.9.0
*/
public static void setLoadpathVariables(String[] variableNames, IPath[][] paths, IProgressMonitor monitor)
throws RubyModelException
{
if (variableNames.length != paths.length)
Assert.isTrue(false, "Variable names and paths collections should have the same size"); //$NON-NLS-1$
RubyModelManager.getRubyModelManager().updateVariableValues(variableNames, paths, true/* update preferences */,
monitor);
}
public static ILoadpathEntry newProjectEntry(IPath fullPath)
{
return newProjectEntry(fullPath, LoadpathEntry.NO_EXTRA_ATTRIBUTES, false);
}
public static ILoadpathEntry newVariableEntry(IPath path)
{
return newVariableEntry(path, LoadpathEntry.NO_EXTRA_ATTRIBUTES, false);
}
public static ILoadpathEntry newContainerEntry(IPath path)
{
return newContainerEntry(path, LoadpathEntry.NO_EXTRA_ATTRIBUTES, false);
}
public static ILoadpathEntry newLibraryEntry(IPath p)
{
return newLibraryEntry(p, LoadpathEntry.NO_EXTRA_ATTRIBUTES, false);
}
/**
* Creates and returns a new loadpath attribute with the given name and the given value.
*
* @return a new loadpath attribute
* @since 0.9.0
*/
public static ILoadpathAttribute newLoadpathAttribute(String name, String value)
{
return new LoadpathAttribute(name, value);
}
public static ILoadpathEntry newLibraryEntry(IPath path, boolean isExported)
{
return newLibraryEntry(path, LoadpathEntry.NO_EXTRA_ATTRIBUTES, isExported);
}
public static ILoadpathEntry newVariableEntry(IPath path, boolean isExported)
{
return newVariableEntry(path, LoadpathEntry.NO_EXTRA_ATTRIBUTES, isExported);
}
public static ILoadpathEntry newProjectEntry(IPath path, boolean isExported)
{
return newProjectEntry(path, LoadpathEntry.NO_EXTRA_ATTRIBUTES, isExported);
}
public static ILoadpathEntry newContainerEntry(IPath path, boolean isExported)
{
return newContainerEntry(path, LoadpathEntry.NO_EXTRA_ATTRIBUTES, isExported);
}
/**
* Returns the Ruby model element corresponding to the given handle identifier generated by
* <code>IRubyElement.getHandleIdentifier()</code>, or <code>null</code> if unable to create the associated element.
*
* @param handleIdentifier
* the given handle identifier
* @return the Ruby element corresponding to the handle identifier
*/
public static IRubyElement create(String handleIdentifier)
{
return create(handleIdentifier, DefaultWorkingCopyOwner.PRIMARY);
}
/**
* Returns the Ruby model element corresponding to the given handle identifier generated by
* <code>IRubyElement.getHandleIdentifier()</code>, or <code>null</code> if unable to create the associated element.
* If the returned Ruby element is an <code>ICompilationUnit</code>, its owner is the given owner if such a working
* copy exists, otherwise the compilation unit is a primary compilation unit.
*
* @param handleIdentifier
* the given handle identifier
* @param owner
* the owner of the returned compilation unit, ignored if the returned element is not a compilation unit
* @return the Ruby element corresponding to the handle identifier
* @since 3.0
*/
public static IRubyElement create(String handleIdentifier, WorkingCopyOwner owner)
{
if (handleIdentifier == null)
{
return null;
}
MementoTokenizer memento = new MementoTokenizer(handleIdentifier);
RubyModel model = RubyModelManager.getRubyModelManager().getRubyModel();
return model.getHandleFromMemento(memento, owner);
}
/**
* Creates and returns a new loadpath entry of kind <code>CPE_SOURCE</code> for the project's source folder
* identified by the given absolute workspace-relative path but excluding all source files with paths matching any
* of the given patterns.
* <p>
* The convenience method is fully equivalent to:
*
* <pre>
* newSourceEntry(path, new IPath[] {}, exclusionPatterns, null);
* </pre>
*
* </p>
*
* @param path
* the absolute workspace-relative path of a source folder
* @param exclusionPatterns
* the possibly empty list of exclusion patterns represented as relative paths
* @return a new source loadpath entry
* @see #newSourceEntry(IPath, IPath[], IPath[], IPath)
* @since 2.1
*/
public static ILoadpathEntry newSourceEntry(IPath path, IPath[] exclusionPatterns)
{
return newSourceEntry(path, LoadpathEntry.INCLUDE_ALL, exclusionPatterns, LoadpathEntry.NO_EXTRA_ATTRIBUTES);
}
/**
* Creates and returns a compilation unit element for the given source file (i.e. a file with one of the
* {@link RubyCore#getRubyLikeExtensions() Ruby-like extensions}). Returns <code>null</code> if unable to recognize
* the compilation unit.
*
* @param file
* the given source file
* @return a compilation unit element for the given source file, or <code>null</code> if unable to recognize the
* compilation unit
*/
public static IRubyScript createRubyScriptFrom(IFile file)
{
return RubyModelManager.createRubyScriptFrom(file, null/* unknown ruby project */);
}
private static class RubyProjectListener implements IResourceChangeListener
{
public void resourceChanged(IResourceChangeEvent event)
{
if (event == null)
return;
IResourceDelta delta = event.getDelta();
checkDelta(delta);
}
private void checkDelta(IResourceDelta delta)
{
if (delta == null)
return;
IResource resource = delta.getResource();
if (resource instanceof IProject)
{
// if (IResourceDelta.ADDED != delta.getKind()) //
// FIXME Try to narrow down which deltas we actually
// check
// return;
final IProject project = (IProject) resource;
if (!RubyProject.hasRubyNature(project))
{
// check for ruby scripts, and if it has any,
// add the nature.
IResourceProxyVisitor visitor = new IResourceProxyVisitor()
{
private boolean added = false;
public boolean visit(IResourceProxy proxy) throws CoreException
{
if (proxy.getType() == IResource.FILE)
{
if (RubyCore.isRubyLikeFileName(proxy.getName()))
{
Job job = new Job("Add Ruby Nature")
{
@Override
protected IStatus run(IProgressMonitor monitor)
{
try
{
RubyCore.addRubyNature(project, monitor);
}
catch (CoreException e)
{
RubyCore.log(e);
}
return Status.OK_STATUS;
}
};
job.schedule(500);
added = true;
}
}
return !added;
}
};
try
{
project.accept(visitor, IResource.NONE);
}
catch (CoreException e)
{
RubyCore.log(e);
}
}
IResourceDelta[] children = delta.getAffectedChildren();
for (int i = 0; i < children.length; i++)
{
checkDelta(children[i]);
}
}
}
}
/**
* Returns a new empty region.
*
* @return a new empty region
*/
public static IRegion newRegion()
{
return new Region();
}
/**
* Returns the names of all known loadpath variables.
* <p>
* Loadpath variable values are persisted locally to the workspace, and are preserved from session to session.
* <p>
*
* @return the list of loadpath variable names
* @see #setLoadpathVariable(String, IPath)
*/
public static String[] getLoadpathVariableNames()
{
return RubyModelManager.getRubyModelManager().variableNames();
}
/**
* Initializes RubyCore internal structures to allow subsequent operations (such as the ones that need a resolved
* classpath) to run full speed. A client may choose to call this method in a background thread early after the
* workspace has started so that the initialization is transparent to the user.
* <p>
* However calling this method is optional. Services will lazily perform initialization when invoked. This is only a
* way to reduce initialization overhead on user actions, if it can be performed before at some appropriate moment.
* </p>
* <p>
* This initialization runs accross all Ruby projects in the workspace. Thus the workspace root scheduling rule is
* used during this operation.
* </p>
* <p>
* This method may return before the initialization is complete. The initialization will then continue in a
* background thread.
* </p>
* <p>
* This method can be called concurrently.
* </p>
*
* @param monitor
* a progress monitor, or <code>null</code> if progress reporting and cancellation are not desired
* @exception CoreException
* if the initialization fails, the status of the exception indicates the reason of the failure
* @since 3.1
*/
public static void initializeAfterLoad(IProgressMonitor monitor) throws CoreException
{
try
{
if (monitor != null)
monitor.beginTask(Messages.javamodel_initialization, 100);
// dummy query for waiting until the indexes are ready and classpath containers/variables are initialized
SearchEngine engine = new SearchEngine();
IRubySearchScope scope = SearchEngine.createWorkspaceScope(); // initialize all containers and variables
try
{
engine.searchAllTypeNames(null,
"!@$#!@".toCharArray(), //$NON-NLS-1$
SearchPattern.R_PATTERN_MATCH | SearchPattern.R_CASE_SENSITIVE, IRubySearchConstants.CLASS,
scope, new TypeNameRequestor()
{
public void acceptType(boolean isModule, char[] packageName, char[] simpleTypeName,
char[][] enclosingTypeNames, String path)
{
// no type to accept
}
},
// will not activate index query caches if indexes are not ready, since it would take to long
// to wait until indexes are fully rebuild
IRubySearchConstants.CANCEL_IF_NOT_READY_TO_SEARCH, monitor == null ? null
: new SubProgressMonitor(monitor, 99) // 99% of the time is spent in the dummy search
);
}
catch (RubyModelException e)
{
// /search failed: ignore
}
catch (OperationCanceledException e)
{
if (monitor != null && monitor.isCanceled())
throw e;
// else indexes were not ready: catch the exception so that jars are still refreshed
}
final RubyModel model = RubyModelManager.getRubyModelManager().getRubyModel();
// ensure external jars are refreshed (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=93668)
try
{
model.refreshExternalArchives(null/* refresh all projects */, monitor == null ? null
: new SubProgressMonitor(monitor, 1) // 1% of the time is spent in jar refresh
);
}
catch (RubyModelException e)
{
// refreshing failed: ignore
}
}
finally
{
if (monitor != null)
monitor.done();
}
}
/**
* Iterate through system path and try to find matching executable
*
* @param exe
* @return IPath to executable if found, null otherwise
*/
public static IPath checkSystemPath(String exe)
{
String systemPath = System.getenv("PATH");
if (systemPath == null)
return null;
String[] paths = systemPath.split(File.pathSeparator);
return checkDirs(exe, paths);
}
/**
* Check common bin/exe locations on non-win systems
*
* @param exe
* @return
*/
public static IPath checkCommonBinLocations(String exe)
{
if (Platform.getOS().equals(Platform.OS_WIN32))
return null;
String[] paths = new String[] { "/opt/local/bin", "/usr/local/bin", "/usr/sbin", "/usr/bin", "/sbin", "/bin" };
return checkDirs(exe, paths);
}
/**
* Iterate through paths and try to find matching executable inside the directory
*
* @param exe
* @return IPath to executable if found, null otherwise
*/
private static IPath checkDirs(String exe, String[] paths)
{
for (int i = 0; i < paths.length; i++)
{
IPath path = new Path(paths[i]).append(exe);
if (path.toFile().exists())
return path;
}
return null;
}
/**
* Copy over a file contained in plugin to plugin's state location.
*
* @param plugin
* @param fileName
*/
public static File copyToStateLocation(Plugin plugin, IPath fileName)
{
return copyToStateLocation(plugin, fileName, false);
}
public static File copyToStateLocation(Plugin plugin, IPath fileName, boolean force)
{
IPath path = plugin.getStateLocation().append(fileName);
if (!force && path.toFile().exists())
return path.toFile();
// copy original over
InputStream stream = null;
OutputStream out = null;
try
{
URL url = getFileURL(plugin, fileName);
stream = url.openStream();
if (!path.toFile().getParentFile().exists() && !path.toFile().getParentFile().mkdirs())
{
return null;
}
path.toFile().createNewFile();
byte[] bytes = org.rubypeople.rdt.core.util.Util.getInputStreamAsByteArray(stream, -1);
out = new FileOutputStream(path.toFile());
out.write(bytes);
}
catch (IOException e)
{
RubyCore.log(e);
}
finally
{
try
{
if (stream != null)
stream.close();
}
catch (IOException e)
{
// ignore
}
try
{
if (out != null)
out.close();
}
catch (IOException e)
{
// ignore
}
}
return path.toFile();
}
private static URL getFileURL(Plugin plugin, IPath fileName)
{
URL url = FileLocator.find(plugin.getBundle(), fileName, null);
if (url == null)
throw new RuntimeException("Expected file " + fileName + " does not exist: " + fileName.toPortableString());
return url;
}
}