package com.aptana.rdt; import java.io.File; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.Hashtable; import java.util.Iterator; import java.util.List; import java.util.Set; import org.eclipse.core.net.proxy.IProxyService; import org.eclipse.core.runtime.FileLocator; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; 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.jobs.Job; import org.eclipse.core.runtime.preferences.DefaultScope; import org.eclipse.core.runtime.preferences.IEclipsePreferences; import org.eclipse.core.runtime.preferences.IPreferencesService; import org.eclipse.core.runtime.preferences.InstanceScope; import org.osgi.framework.BundleContext; import org.osgi.util.tracker.ServiceTracker; import org.rubypeople.rdt.core.ILoadpathEntry; import org.rubypeople.rdt.core.IRubyProject; import org.rubypeople.rdt.core.RubyCore; import org.rubypeople.rdt.core.RubyModelException; import org.rubypeople.rdt.internal.core.RubyModelManager.EclipsePreferencesListener; import org.rubypeople.rdt.internal.launching.LaunchingPlugin; import com.aptana.rdt.core.gems.Gem; import com.aptana.rdt.core.gems.GemListener; import com.aptana.rdt.core.gems.GemRequirement; import com.aptana.rdt.core.gems.IGemManager; import com.aptana.rdt.internal.core.gems.GemManager; import com.aptana.rdt.internal.core.gems.RubyGemsInitializer; import com.aptana.rdt.launching.IGemRuntime; /** * The activator class controls the plug-in life cycle */ public class AptanaRDTPlugin extends Plugin { private static final String BIN = "bin"; // The plug-in ID public static final String PLUGIN_ID = "com.aptana.rdt"; // Preferences public Set<String> optionNames = new HashSet<String>(20); public Hashtable<String, String> optionsCache; public final IEclipsePreferences[] preferencesLookup = new IEclipsePreferences[2]; private ServiceTracker gemManagersTracker; private ServiceTracker proxyTracker; static final int PREF_INSTANCE = 0; static final int PREF_DEFAULT = 1; /** * Possible configurable option ID. * * @see #getDefaultOptions() */ public static final String COMPILER_PB_UNUSED_PARAMETER = PLUGIN_ID + ".compiler.problem.unusedParameter"; //$NON-NLS-1$ /** * Possible configurable option ID. * * @see #getDefaultOptions() * @since 0.9.0 */ public static final String COMPILER_PB_UNUSED_PRIVATE_MEMBER = PLUGIN_ID + ".compiler.problem.unusedPrivateMember"; //$NON-NLS-1$ /** * Possible configurable option ID. * * @see #getDefaultOptions() * @since 0.9.0 */ public static final String COMPILER_PB_UNNECESSARY_ELSE = PLUGIN_ID + ".compiler.problem.unnecessaryElse"; //$NON-NLS-1$ /** * Possible configurable option ID. * * @see #getDefaultOptions() * @since 0.9.0 */ public static final String COMPILER_PB_LOCAL_MASKS_METHOD = PLUGIN_ID + ".compiler.problem.localVariableMasksMethod"; //$NON-NLS-1$ /** * Possible configurable option ID. * * @see #getDefaultOptions() * @since 0.9.0 */ public static final String COMPILER_PB_MISSPELLED_CONSTRUCTOR = PLUGIN_ID + ".compiler.problem.misspelledConstructor"; //$NON-NLS-1$ /** * Possible configurable option ID. * * @see #getDefaultOptions() * @since 0.9.0 */ public static final String COMPILER_PB_POSSIBLE_ACCIDENTAL_BOOLEAN_ASSIGNMENT = PLUGIN_ID + ".compiler.problem.possibleAccidentalBooleanAssignment"; //$NON-NLS-1$ /** * Possible configurable option ID. * * @see #getDefaultOptions() * @since 0.9.0 */ public static final String COMPILER_PB_CODE_COMPLEXITY_BRANCHES = PLUGIN_ID + ".compiler.problem.codeComplexityBranches"; //$NON-NLS-1$ /** * Possible configurable option ID. * * @see #getDefaultOptions() * @since 0.9.0 */ public static final String COMPILER_PB_CODE_COMPLEXITY_LINES = PLUGIN_ID + ".compiler.problem.codeComplexityLines"; //$NON-NLS-1$ /** * Possible configurable option ID. * * @see #getDefaultOptions() * @since 0.9.0 */ public static final String COMPILER_PB_CODE_COMPLEXITY_RETURNS = PLUGIN_ID + ".compiler.problem.codeComplexityReturns"; //$NON-NLS-1$ /** * Possible configurable option ID. * * @see #getDefaultOptions() * @since 0.9.0 */ public static final String COMPILER_PB_CODE_COMPLEXITY_LOCALS = PLUGIN_ID + ".compiler.problem.codeComplexityLocals"; //$NON-NLS-1$ /** * Possible configurable option ID. * * @see #getDefaultOptions() * @since 0.9.0 */ public static final String COMPILER_PB_CODE_COMPLEXITY_ARGUMENTS = PLUGIN_ID + ".compiler.problem.codeComplexityArguments"; //$NON-NLS-1$ /** * Possible configurable option ID. * * @see #getDefaultOptions() * @since 0.9.0 */ public static final String COMPILER_PB_MAX_LOCALS = PLUGIN_ID + ".compiler.problem.maxLocals"; //$NON-NLS-1$ /** * Possible configurable option ID. * * @see #getDefaultOptions() * @since 0.9.0 */ public static final String COMPILER_PB_MAX_RETURNS = PLUGIN_ID + ".compiler.problem.maxReturns"; //$NON-NLS-1$ /** * Possible configurable option ID. * * @see #getDefaultOptions() * @since 0.9.0 */ public static final String COMPILER_PB_MAX_BRANCHES = PLUGIN_ID + ".compiler.problem.maxBranches"; //$NON-NLS-1$ /** * Possible configurable option ID. * * @see #getDefaultOptions() * @since 0.9.0 */ public static final String COMPILER_PB_MAX_LINES = PLUGIN_ID + ".compiler.problem.maxLines"; //$NON-NLS-1$ /** * Possible configurable option ID. * * @see #getDefaultOptions() * @since 0.9.0 */ public static final String COMPILER_PB_MAX_ARGUMENTS = PLUGIN_ID + ".compiler.problem.maxArguments"; //$NON-NLS-1$ /** * Possible configurable option ID. * * @see #getDefaultOptions() * @since 0.9.0 */ public static final String COMPILER_PB_SIMILAR_VARIABLE_NAMES = PLUGIN_ID + ".compiler.problem.similarVariableNames"; //$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 1.0.0 */ public static final String COMPILER_PB_COMPARABLE_MISSING_METHOD = PLUGIN_ID + ".compiler.problem.comparableMissingMethod"; //$NON-NLS-1$ /** * Possible configurable option ID. * * @see #getDefaultOptions() * @since 1.0.0 */ public static final String COMPILER_PB_ENUMERABLE_MISSING_METHOD = PLUGIN_ID + ".compiler.problem.enumerableMissingMethod"; //$NON-NLS-1$ /** * Possible configurable option ID. * * @see #getDefaultOptions() * @since 1.0.0 */ public static final String COMPILER_PB_SUBCLASS_DOESNT_CALL_SUPER = PLUGIN_ID + ".compiler.problem.subclassDoesntCallSuper"; //$NON-NLS-1$ /** * Possible configurable option ID. * * @see #getDefaultOptions() * @since 1.0.0 */ public static final String COMPILER_PB_ASSIGNMENT_PRECEDENCE = PLUGIN_ID + ".compiler.problem.assignmentPrecedence"; //$NON-NLS-1$ /** * Possible configurable option ID. * * @see #getDefaultOptions() * @since 1.0.0 */ public static final String COMPILER_PB_CONSTANT_NAMING_CONVENTION = PLUGIN_ID + ".compiler.problem.constantNamingConvention"; //$NON-NLS-1$ /** * Possible configurable option ID. * * @see #getDefaultOptions() * @since 1.0.0 */ public static final String COMPILER_PB_METHOD_MISSING_NO_RESPOND_TO = PLUGIN_ID + ".compiler.problem.methodMissingWithoutRespondTo"; //$NON-NLS-1$ /** * Possible configurable option ID. * * @see #getDefaultOptions() * @since 1.0.0 */ public static final String COMPILER_PB_DYNAMIC_VARIABLE_ALIASES_LOCAL = PLUGIN_ID + ".compiler.problem.dynamicVariableAliasesLocal"; //$NON-NLS-1$ /** * Possible configurable option ID. * * @see #getDefaultOptions() * @since 1.0.0 */ public static final String COMPILER_PB_LOCAL_VARIABLE_POSSIBLE_ATTRIBUTE_ACCESS = PLUGIN_ID + ".compiler.problem.localVariablePossibleAttributeAccess"; //$NON-NLS-1$ /** * Possible configurable option ID. * * @see #getDefaultOptions() * @since 1.0.0 */ public static final String COMPILER_PB_LOCAL_METHOD_NAMING_CONVENTION = PLUGIN_ID + ".compiler.problem.methodOrLocalNamingConvention"; //$NON-NLS-1$ /** * Possible configurable option ID. * * @see #getDefaultOptions() * @since 1.0.3 */ public static final String COMPILER_PB_UNUSED_LOCAL_VARIABLE = PLUGIN_ID + ".compiler.problem.unusedLocalVariable"; //$NON-NLS-1$ /** * Possible configurable option ID. * * @see #getDefaultOptions() * @since 1.0.3 */ public static final String COMPILER_PB_DEPRECATED_REQUIRE_GEM = PLUGIN_ID + ".compiler.problem.deprecatedRequireGem"; //$NON-NLS-1$ /** * Possible configurable option ID. * * @see #getDefaultOptions() * @since 1.0.3 */ public static final String COMPILER_PB_RETRY_OUTSIDE_RESCUE = PLUGIN_ID + "compiler.problem.retryOutsideRescueBody"; //$NON-NLS-1$ /** * Possible configurable option ID. * * @see #getDefaultOptions() * @since 1.1.0 */ public static final String COMPILER_PB_DUPLICATE_HASH_KEY = PLUGIN_ID + ".compiler.problem.duplicateHashKey"; //$NON-NLS-1$ /** * Possible configurable option ID. * * @see #getDefaultOptions() * @since 1.3.0 */ public static final String COMPILER_PB_CONTROL_COUPLE = PLUGIN_ID + ".compiler.problem.controlCouple"; //$NON-NLS-1$ /** * Possible configurable option ID. * * @see #getDefaultOptions() * @since 1.3.0 */ public static final String COMPILER_PB_FEATURE_ENVY = PLUGIN_ID + ".compiler.problem.featureEnvy"; //$NON-NLS-1$ /** * Possible configurable option ID. * * @see #getDefaultOptions() * @since 1.3.0 */ public static final String COMPILER_PB_MIN_REFERENCES_FOR_ENVY = PLUGIN_ID + ".compiler.problem.minReferencesForEnvy"; //$NON-NLS-1$ /** * Possible configurable option ID. * * @see #getDefaultOptions() * @since 1.3.0 */ public static final String COMPILER_PB_UNCOMMUNICATIVE_NAME = PLUGIN_ID + ".compiler.problem.uncommunicativeName"; //$NON-NLS-1$ /** * Simple identifier constant (value <code>"gems"</code>) for the Gems extension point. * * @since 1.0.0 */ public static final String EXTENSION_POINT_GEMS = "gems"; //$NON-NLS-1$ /** * @since 1.3.1 */ public static final String DUPLICATE_CODE_CHECK_ENABLED = PLUGIN_ID + ".duplicateCodeCheckEnabled"; //$NON-NLS-1$ /** * @since 1.3.1 */ public static final String DUPLICATE_CODE_MASS_THRESHOLD = PLUGIN_ID + ".duplicateCodeMassThreshold"; //$NON-NLS-1$ // The shared instance private static AptanaRDTPlugin plugin; /** * The constructor */ public AptanaRDTPlugin() { super(); } private static class RubyDebugGemListener extends Job { private GemListener listener; public RubyDebugGemListener(GemListener listener) { super("Removing temporary gem listener"); this.listener = listener; } @Override protected IStatus run(IProgressMonitor monitor) { GemManager.getInstance().removeGemListener(listener); return Status.OK_STATUS; } } /* * (non-Javadoc) * @see org.eclipse.ui.plugin.AbstractUIPlugin#start(org.osgi.framework.BundleContext) */ public void start(final BundleContext context) throws Exception { plugin = this; super.start(context); initializePreferences(); context.registerService(IGemManager.class.getName(), getGemManager(), null); getGemManager().addGemListener(new GemListener() { public void managerInitialized() { if (getGemManager().gemInstalled("ruby-debug-ide")) { setRubyDebugUp(); } } private void setRubyDebugUp() { setRubyDebugAsDefault(); removeListener(this); } public void gemsRefreshed() { if (getGemManager().gemInstalled("ruby-debug-ide")) { setRubyDebugUp(); } } public void gemUpdated(Gem gem) { // ignore } public void gemRemoved(Gem gem) { // ignore } public void gemAdded(Gem gem) { // ignore } }); Job job = new RubyGemsInitializer(); job.setSystem(true); job.setPriority(Job.SHORT); job.schedule(); gemManagersTracker = new ServiceTracker(context, IGemManager.class.getName(), null); gemManagersTracker.open(true); } private void initializePreferences() { // Create lookups preferencesLookup[PREF_INSTANCE] = new InstanceScope().getNode(PLUGIN_ID); preferencesLookup[PREF_DEFAULT] = new DefaultScope().getNode(PLUGIN_ID); // Listen to instance preferences node removal from parent in order to refresh stored one IEclipsePreferences.INodeChangeListener listener = new IEclipsePreferences.INodeChangeListener() { public void added(IEclipsePreferences.NodeChangeEvent event) { // do nothing } public void removed(IEclipsePreferences.NodeChangeEvent event) { if (event.getChild() == preferencesLookup[PREF_INSTANCE]) { preferencesLookup[PREF_INSTANCE] = new InstanceScope().getNode(PLUGIN_ID); preferencesLookup[PREF_INSTANCE].addPreferenceChangeListener(new EclipsePreferencesListener()); } } }; ((IEclipsePreferences) preferencesLookup[PREF_INSTANCE].parent()).addNodeChangeListener(listener); preferencesLookup[PREF_INSTANCE].addPreferenceChangeListener(new EclipsePreferencesListener()); // Listen to default preferences node removal from parent in order to refresh stored one listener = new IEclipsePreferences.INodeChangeListener() { public void added(IEclipsePreferences.NodeChangeEvent event) { // do nothing } public void removed(IEclipsePreferences.NodeChangeEvent event) { if (event.getChild() == preferencesLookup[PREF_DEFAULT]) { preferencesLookup[PREF_DEFAULT] = new DefaultScope().getNode(PLUGIN_ID); } } }; ((IEclipsePreferences) preferencesLookup[PREF_DEFAULT].parent()).addNodeChangeListener(listener); } protected void setRubyDebugAsDefault() { new InstanceScope().getNode(LaunchingPlugin.PLUGIN_ID).putBoolean( org.rubypeople.rdt.internal.launching.PreferenceConstants.USE_RUBY_DEBUG, true); } protected void removeListener(GemListener listener) { Job job = new RubyDebugGemListener(listener); job.schedule(); } /* * (non-Javadoc) * @see org.eclipse.ui.plugin.AbstractUIPlugin#stop(org.osgi.framework.BundleContext) */ public void stop(BundleContext context) throws Exception { gemManagersTracker.close(); super.stop(context); plugin = null; } public IGemManager[] getGemManagers() { Object[] raw = gemManagersTracker.getServices(); if (raw == null || raw.length == 0) return new IGemManager[0]; List<IGemManager> gemManagers = new ArrayList<IGemManager>(); for (int i = 0; i < raw.length; i++) { gemManagers.add((IGemManager) raw[i]); } // Force the normal gem manager to be the first one! if (gemManagers.contains(getGemManager())) { gemManagers.remove(getGemManager()); } gemManagers.add(0, getGemManager()); return gemManagers.toArray(new IGemManager[gemManagers.size()]); } /** * Returns the shared instance * * @return the shared instance */ public static AptanaRDTPlugin getDefault() { return plugin; } public static File getFileInPlugin(IPath path) { try { URL installURL = new URL(getDefault().getBundle().getEntry("/"), path.toString()); //$NON-NLS-1$ URL localURL = FileLocator.toFileURL(installURL); return new File(localURL.getFile()); } catch (IOException ioe) { return null; } } public static void log(Throwable e) { log(new Status(IStatus.ERROR, getPluginId(), -1, AptanaRDTMessages.RubyRedPlugin_internal_error, e)); } public static void log(IStatus status) { if (getDefault() != null && getDefault().getLog() != null) { getDefault().getLog().log(status); } else { System.out.println(status.getMessage()); } } public static String getPluginId() { return PLUGIN_ID; } public Hashtable<String, String> getOptions() { // return cached options if already computed // if (this.optionsCache != null) return new Hashtable<String, String>(this.optionsCache); // init Hashtable<String, String> options = new Hashtable<String, String>(10); IPreferencesService service = Platform.getPreferencesService(); // set options using preferences service lookup Iterator<String> iterator = optionNames.iterator(); while (iterator.hasNext()) { String propertyName = (String) iterator.next(); String propertyValue = service.get(propertyName, null, this.preferencesLookup); if (propertyValue != null) { options.put(propertyName, propertyValue); } } // store built map in cache this.optionsCache = new Hashtable<String, String>(options); // return built map return options; } public IGemManager getGemManager() { return GemManager.getInstance(); } public static IPath findGem(String string) { IGemManager gemManager = getDefault().getGemManager(); if (gemManager == null) return null; IPath path = gemManager.getGemPath(string); if (path == null) return null; IPath[] rubyLibPaths = RubyCore.getLoadpathVariable(IGemRuntime.GEMLIB_VARIABLE); // Go through each rubyLibPath, find the one that is a prefix of path IPath rubyLibPath = null; for (int i = 0; i < rubyLibPaths.length; i++) { if (rubyLibPaths[i].isPrefixOf(path)) { rubyLibPath = rubyLibPaths[i]; break; } } if (rubyLibPath == null) return null; IPath resPath = new Path(IGemRuntime.GEMLIB_VARIABLE); for (int k = rubyLibPath.segmentCount(); k < path.segmentCount(); k++) { resPath = resPath.append(path.segment(k)); } return resPath; } public static void log(String string) { log(new Status(Status.INFO, PLUGIN_ID, -1, string, null)); } /** * Adds a gem and all it's dependencies to the project's loadpath * * @param rubyProject * @param originalGem * @param monitor * @throws RubyModelException */ public static void addGemLoadPath(IRubyProject rubyProject, Gem originalGem, IProgressMonitor monitor) throws RubyModelException { List<ILoadpathEntry> list = new ArrayList<ILoadpathEntry>(); ILoadpathEntry[] original = rubyProject.getRawLoadpath(); list.addAll(Arrays.asList(original)); // add the original loadpath first, then add the gems after Set<Gem> gems = new HashSet<Gem>(); gems.add(originalGem); // Grab gems meeting the requirements for dependencies Set<GemRequirement> dependencies = AptanaRDTPlugin.getDefault().getGemManager().getDependencies(originalGem); for (GemRequirement dependency : dependencies) { Gem gem = AptanaRDTPlugin.getDefault().getGemManager().findGem(dependency); if (gem != null) gems.add(gem); } // Now grab the path to these particular gems and add them to loadpath for (Gem gem : gems) { IPath gemPath = AptanaRDTPlugin.findGem(gem.getName() + "-" + gem.getVersion()); if (gemPath != null) { ILoadpathEntry newEntry = RubyCore.newVariableEntry(gemPath); if (!list.contains(newEntry)) list.add(newEntry); } } if (list.size() == original.length) return; // looks like we didn't add anything, so just return if (rubyProject.getRawLoadpath().length != original.length) { // uh oh, the project's loadpath changed while we were working! System.out.println("yuck"); // FIXME Fix this up! } rubyProject.setRawLoadpath((ILoadpathEntry[]) list.toArray(new ILoadpathEntry[list.size()]), monitor); } /** * Tries to find a bin script with the name given by command in a bin subdir of any of the gem install paths. * * @param command * Name of bin script to look for * @return IPath path to bin script, null if no script is found. */ public static IPath checkBinDir(String command) { List<IPath> paths = AptanaRDTPlugin.getDefault().getGemManager().getGemInstallPaths(); if (paths == null) return null; for (IPath path : paths) { if (path == null) continue; IPath possible = path.removeLastSegments(1).append(BIN).append(command); if (possible.toFile().exists()) return possible; } return null; } /** * Tries to find a bin script with the name given by command in a bin subdir of the gem provided by gemName. * * @param gemName * name of gem where we should look in it's bin dir * @param command * Name of bin script to look for * @return IPath path to bin script, null if no script is found. */ public static IPath checkGemBinDir(String gemName, String command) { IPath path = AptanaRDTPlugin.getDefault().getGemManager().getGemPath(gemName); if (path == null) return null; path = path.append(BIN).append(command); if (path.toFile().exists()) { return path; } return null; } public IProxyService getProxyService() { if (proxyTracker == null) { proxyTracker = new ServiceTracker(getBundle().getBundleContext(), IProxyService.class.getName(), null); proxyTracker.open(); } return (IProxyService) proxyTracker.getService(); } }