/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 1997-2010 Oracle and/or its affiliates. All rights reserved.
*
* Oracle and Java are registered trademarks of Oracle and/or its affiliates.
* Other names may be trademarks of their respective owners.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common
* Development and Distribution License("CDDL") (collectively, the
* "License"). You may not use this file except in compliance with the
* License. You can obtain a copy of the License at
* http://www.netbeans.org/cddl-gplv2.html
* or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
* specific language governing permissions and limitations under the
* License. When distributing the software, include this License Header
* Notice in each file and include the License file at
* nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the GPL Version 2 section of the License file that
* accompanied this code. If applicable, add the following below the
* License Header, with the fields enclosed by brackets [] replaced by
* your own identifying information:
* "Portions Copyrighted [year] [name of copyright owner]"
*
* The Original Software is NetBeans. The Initial Developer of the Original
* Software is Sun Microsystems, Inc. Portions Copyright 1997-2008 Sun
* Microsystems, Inc. All Rights Reserved.
*
* If you wish your version of this file to be governed by only the CDDL
* or only the GPL Version 2, indicate your decision by adding
* "[Contributor] elects to include this software in this distribution
* under the [CDDL or GPL Version 2] license." If you do not indicate a
* single choice of license, a recipient has the option to distribute
* your version of this file under either the CDDL, the GPL Version 2 or
* to extend the choice of license to its licensees as provided above.
* However, if you add GPL Version 2 code and therefore, elected the GPL
* Version 2 license, then the option applies only if the new code is
* made subject to such option by the copyright holder.
*/
package org.netbeans.api.ruby.platform;
import java.awt.Dialog;
import java.awt.EventQueue;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.util.List;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import javax.swing.JButton;
import org.netbeans.api.annotations.common.CheckForNull;
import org.netbeans.api.project.Project;
import org.netbeans.api.project.ProjectUtils;
import org.netbeans.modules.ruby.platform.Util;
import org.netbeans.modules.ruby.platform.gems.GemInfo;
import org.netbeans.modules.ruby.platform.gems.GemManager.VersionPredicate;
import org.netbeans.modules.ruby.platform.gems.GemManager;
import org.netbeans.spi.project.ui.CustomizerProvider;
import org.openide.DialogDescriptor;
import org.openide.DialogDisplayer;
import org.openide.NotifyDescriptor;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
import org.openide.modules.InstalledFileLocator;
import org.openide.util.Exceptions;
import org.openide.util.HelpCtx;
import org.openide.util.NbBundle;
import org.openide.util.Utilities;
/**
* Represents one Ruby platform, i.e. installation of a Ruby interpreter.
*<p>
* This class is thread safe.
*/
public final class RubyPlatform implements Comparable<RubyPlatform> {
private static final Logger LOGGER = Logger.getLogger(RubyPlatform.class.getName());
public static final String DEFAULT_RUBY_RELEASE = "1.8"; // NOI18N
/** Version number of the rubystubs */
public static final String RUBYSTUBS_VERSION = "1.8.7-p72"; // NOI18N
/**
* The name of the rubystubs folder.
*/
public static final String RUBYSTUBS = "rubystubs"; //NOI18N
/** Name of the Ruby Debug IDE gem. */
static final String RUBY_DEBUG_IDE_NAME;
static {
// Allow to pass different gem name for ruby-debug-ide gem. It allows
// testing of forks of official ruby-debug-ide gem. Cf. issue #157870.
String prop = System.getProperty("rubyDebugIDEName"); // NOI18N
if (prop != null) {
RUBY_DEBUG_IDE_NAME = prop;
} else {
RUBY_DEBUG_IDE_NAME = "ruby-debug-ide"; // NOI18N
}
}
private final Info info;
private final RubyPlatformValidator validator;
private final String id;
private final String interpreter;
private final PropertyChangeSupport pcs;
// the following are guarded by "this"
/** @GuardedBy("this") */
private File home;
/** @GuardedBy("this") */
private String homeUrl;
/** @GuardedBy("this") */
private FileObject libDirFO;
/** @GuardedBy("this") */
private GemManager gemManager;
/** @GuardedBy("this") */
private static FileObject stubsFO;
// XXX - see updateIndexRoots below
// private boolean indexInitialized;
// Platform tools
/** @GuardedBy("this") */
private String gemTool;
/** @GuardedBy("this") */
private String rdoc;
/** @GuardedBy("this") */
private String irb;
/**
* 'rake' executable for this platform.
* @GuardedBy("this")
*/
private String rake;
/** 'rails' executable for this platform.
* @GuardedBy("this")
*/
private String rails;
/** 'autotest' executable for this platform.
* @GuardedBy("this")
*/
private String autotest;
/** 'autospec' executable for this platform.
* @GuardedBy("this")
*/
private String autospec;
RubyPlatform(String id, String interpreterPath, Info info) {
this.id = id;
this.interpreter = interpreterPath;
this.info = info;
this.validator = new RubyPlatformValidator(this);
this.pcs = new PropertyChangeSupport(this);
}
/**
* Tries to find a {@link RubyPlatform platform} for a given project. Might
* return <tt>null</tt>.
*/
@CheckForNull
public static RubyPlatform platformFor(final Project project) {
RubyPlatformProvider rpp = project.getLookup().lookup(RubyPlatformProvider.class);
RubyPlatform result = rpp == null ? null : rpp.getPlatform();
if (result == null && LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine("Could not resolve a platform for " + project + ". " +
"Platform provider: " + rpp);
}
return result;
}
/**
* Tries to find a {@link GemManager gem manager} for a given project, or
* strictly speaking, for its {@link RubyPlatform platform}. Might return
* <tt>null</tt>.
*/
@CheckForNull
public static GemManager gemManagerFor(final Project project) {
RubyPlatform platform = RubyPlatform.platformFor(project);
return platform == null ? null : platform.getGemManager();
}
@CheckForNull
public static String platformDescriptionFor(Project project) {
RubyPlatform platform = platformFor(project);
return platform == null ? null : platform.getInfo().getLongDescription();
}
public Info getInfo() {
return info;
}
/**
* @return whether this platform represents Ruby 1.9 platform.
*/
public boolean is19() {
return getVersion() != null && getVersion().startsWith("1.9");
}
/**
* Checks whether the platform has a valid Rake installed.
*
* @param warn whether to show warning message to the user if ther is no
* valid Rake installed
*/
public boolean hasValidRake(boolean warn) {
boolean valid = isValid(warn) && hasRubyGemsInstalled(warn);
String rakePath = getRake();
valid = (rakePath != null) && new File(rakePath).exists();
possiblyNotifyUser(warn, valid, "rake"); // NOI18N
return valid;
}
/**
* Checks whether the platform has a valid Rails installed.
*
* @param warn whether to show warning message to the user if ther is no
* valid Rails installed
*/
public boolean hasValidRails(boolean warn) {
String railsPath = getRails();
boolean valid = (railsPath != null) && new File(railsPath).exists();
possiblyNotifyUser(warn, valid, "rails"); // NOI18N
return valid;
}
/**
* Checks whether the platform has a valid autotest installed.
*
* @param warn whether to show warning message to the user if ther is no
* valid autotest installed
*/
public boolean hasValidAutoTest(boolean warn) {
boolean validBinary = isValidFile(getAutoTest());
possiblyNotifyUser(warn, validBinary, "autotest"); // NOI18N
return validBinary && hasZenTest();
}
/**
* Checks whether the platform has a valid autotest installed.
*
* @param warn whether to show warning message to the user if ther is no
* valid autotest installed
*/
public boolean hasValidAutoSpec(boolean warn) {
boolean validBinary = isValidFile(getAutoSpec());
possiblyNotifyUser(warn, validBinary, "autospec"); // NOI18N
return validBinary && hasZenTest();
}
private boolean hasZenTest() {
return getGemManager() != null && getGemManager().isGemInstalled("ZenTest"); //NOI18N
}
private boolean isValidFile(String path) {
return path != null && new File(path).exists();
}
/**
* Checks whether project has a valid platform and in turn whether the
* platform has a valid Rake installed.
*
* @param warn whether to show warning message to the user if ther is no
* valid Rake installed
*/
public static boolean hasValidRake(final Project project, final boolean warn) {
RubyPlatform platform = RubyPlatform.platformFor(project);
if (platform == null) {
if (warn) {
showWarning(project);
}
return false;
}
return platform.hasValidRake(warn);
}
public synchronized String getRake() {
if (rake == null) {
rake = findExecutable("rake"); // NOI18N
if (rake != null && !(new File(rake).exists()) && getGemManager().getLatestVersion("rake") != null) { // NOI18N
// On Windows, rake does funny things - you may only get a rake.bat
InstalledFileLocator locator = InstalledFileLocator.getDefault();
File f = locator.locate("modules/org-netbeans-modules-ruby-project.jar", // NOI18N
null, false);
if (f == null) {
throw new RuntimeException("Can't find cluster"); // NOI18N
}
f = new File(f.getParentFile().getParentFile().getAbsolutePath() + File.separator + "rake"); // NOI18N
try {
rake = f.getCanonicalPath();
} catch (IOException ioe) {
Exceptions.printStackTrace(ioe);
}
}
}
return rake;
}
public synchronized String getRails() {
if (rails == null) {
rails = findExecutable("rails"); // NOI18N
}
return rails;
}
public synchronized String getAutoTest() {
if (autotest == null) {
autotest = findExecutable("autotest"); // NOI18N
}
return autotest;
}
public synchronized String getAutoSpec() {
if (autospec == null) {
autospec = findExecutable("autospec"); // NOI18N
}
return autospec;
}
public String getID() {
return id;
}
public String getInterpreter(boolean cannonical) {
String result = interpreter;
if (cannonical) {
try {
result = new File(interpreter).getCanonicalFile().getAbsolutePath();
} catch (IOException e) {
LOGGER.log(Level.WARNING, "Cannot get canonical path", e);
}
}
updateIndexRoots();
return result;
}
public String getInterpreter() {
updateIndexRoots();
return interpreter;
}
public File getInterpreterFile() {
updateIndexRoots();
return new File(interpreter);
}
public File getHome() {
return getHome(true);
}
public synchronized File getHome(boolean canonical) {
if (home == null) {
try {
String rp = getInterpreter(canonical);
if (rp == null) {
return null;
}
File r = new File(rp);
// Handle bogus paths like "/" which cannot possibly point to a valid ruby installation
File p = r.getParentFile();
if (p == null) {
return null;
}
p = p.getParentFile();
if (p == null) {
return null;
}
home = p.getCanonicalFile();
} catch (IOException ioe) {
Exceptions.printStackTrace(ioe);
return null;
}
}
return home;
}
public synchronized String getHomeUrl() {
if (homeUrl == null) {
try {
File r = getHome();
if (r != null) {
homeUrl = r.toURI().toURL().toExternalForm();
}
} catch (MalformedURLException mue) {
Exceptions.printStackTrace(mue);
}
}
return homeUrl;
}
/**
* Return the lib directory for this interprerter. Usually parent of {@link
* #getVersionLibDir()}.
*/
public String getLibDir() {
if (isRubinius()) {
return getRubiniusLibDir();
}
String lib = info.getLibDir();
if (lib == null) {
LOGGER.log(Level.WARNING, "rubylibdir not found for {0}, was: {1}", new String[]{interpreter, lib});
return null;
}
File libDir = new File(lib);
if (!libDir.isDirectory()) {
LOGGER.log(Level.WARNING, "rubylibdir not found for {0}, was: {1}", new String[]{interpreter, lib});
return null;
}
// info.getVersionLibDir() return e.g. .../lib/ruby/1.8
libDir = libDir.getParentFile();
if (libDir == null) {
return null;
}
libDir = libDir.getParentFile();
if (libDir == null) {
return null;
}
return libDir.getAbsolutePath();
}
private String getRubiniusLibDir() {
File lib = new File(getHome(), "lib"); // NOI18N
return lib.isDirectory() ? lib.getAbsolutePath() : null; // NOI18N
}
/** Utility method. See {@link #getLibDir()}. */
public synchronized FileObject getLibDirFO() {
if (libDirFO == null) {
String lib = getLibDir();
if (lib != null) {
libDirFO = FileUtil.toFileObject(new File(lib));
}
}
return libDirFO;
}
/**
* Delegates to {@link Info#getLibDir()}.
*/
public String getVersionLibDir() {
return info.getLibDir();
}
/** Return the site_ruby directory for the current ruby installation. Not cached. */
public String getRubyLibSiteDir() {
String sitedir = null;
File _home = getHome();
assert _home != null : "home not null";
File lib =
new File(_home, "lib" + File.separator + "ruby" + File.separator + "site_ruby"); // NOI18N
if (!lib.exists()) {
return null;
}
File f = new File(lib, DEFAULT_RUBY_RELEASE); // NOI18N
if (f.exists()) {
sitedir = f.getAbsolutePath();
} else {
// Search for a numbered directory
File[] children = lib.listFiles();
for (File c : children) {
if (!c.isDirectory()) {
continue;
}
String name = c.getName();
if (name.matches("\\d+\\.\\d+")) { // NOI18N
sitedir = c.getAbsolutePath();
break;
}
}
if ((sitedir == null) && (children.length > 0)) {
sitedir = children[0].getAbsolutePath();
}
}
return sitedir;
}
/**
* Calls {@link #isValid(boolean)} with <code>false</code>.
*/
public boolean isValid() {
return isValid(false);
}
/**
* Test whether the platform is valid, i.e. has appropriate interpreter,
* <em>lib</em> and <em>bin</em> directories.
*
* @param warn whether to show the dialog to the user if platform is invalid
* @return whether the platform is valid
*/
public boolean isValid(final boolean warn) {
boolean valid = new File(interpreter).isFile() && getLibDir() != null;
if (valid) {
String binDir = getBinDir();
if (binDir != null) {
valid = new File(binDir).isDirectory();
}
}
if (warn && !valid) {
showWarning(this);
}
return valid;
}
private static void showWarning(final RubyPlatform platform) {
String msg = NbBundle.getMessage(RubyPlatform.class, "InvalidRubyPlatform", platform.getLabel());
JButton closeButton = getCloseButton();
Object[] options = new Object[]{closeButton};
showDialog(msg, options);
}
private static void showWarning(final Project project) {
String msg =
NbBundle.getMessage(RubyPlatform.class, "InvalidRubyPlatformForProject",
ProjectUtils.getInformation(project).getDisplayName());
JButton closeButton = getCloseButton();
CustomizerProvider customizer = project.getLookup().lookup(CustomizerProvider.class);
Object[] options;
JButton propertiesButton =
new JButton(NbBundle.getMessage(RubyPlatform.class, "Properties"));
if (customizer != null) {
options = new Object[]{propertiesButton, closeButton};
} else {
options = new Object[]{closeButton};
}
if (showDialog(msg, options) == propertiesButton) {
customizer.showCustomizer();
}
}
private static Object showDialog(String msg, Object[] options) {
DialogDescriptor descriptor =
new DialogDescriptor(msg,
NbBundle.getMessage(RubyPlatform.class, "MissingRuby"), true, options,
options[0],
DialogDescriptor.DEFAULT_ALIGN, new HelpCtx(RubyPlatform.class), null);
descriptor.setMessageType(NotifyDescriptor.Message.ERROR_MESSAGE);
descriptor.setModal(true);
Dialog dlg = null;
try {
dlg = DialogDisplayer.getDefault().createDialog(descriptor);
dlg.setVisible(true);
} finally {
if (dlg != null) {
dlg.dispose();
}
}
return descriptor.getValue();
}
private static JButton getCloseButton() {
JButton closeButton =
new JButton(NbBundle.getMessage(RubyPlatform.class, "CTL_Close"));
closeButton.getAccessibleContext().setAccessibleDescription(
NbBundle.getMessage(RubyPlatform.class, "AD_Close"));
return closeButton;
}
public String getVersion() {
return info.getVersion();
}
public String getLabel() {
return info.getLabel(isDefault());
}
public boolean isDefault() {
RubyPlatform defaultPlatform = RubyPlatformManager.getDefaultPlatform();
return defaultPlatform != null && interpreter.equals(defaultPlatform.getInterpreter());
}
public boolean isJRuby() {
return info.isJRuby();
}
public boolean isRubinius() {
return info.isRubinius();
}
/**
* If the platform is in invalid state, shows general message to the user.
*
* @return whether the platform is valid
*/
public boolean showWarningIfInvalid() {
boolean valid = isValid();
if (!valid) {
Util.notifyLocalized(RubyPlatform.class, "RubyPlatform.InvalidInterpreter", // NOI18N
NotifyDescriptor.WARNING_MESSAGE, getInterpreter());
}
return valid;
}
/**
* See also {@link #hasRubyGemsInstalled}.
*
* @return either an instance of {@link GemManager} or <tt>null</tt>.
*/
@CheckForNull
public synchronized GemManager getGemManager() {
if (gemManager == null && hasRubyGemsInstalled()) {
gemManager = new GemManager(this);
}
return gemManager;
}
public String getBinDir() {
return getBinDir(true);
}
public String getBinDir(boolean canonical) {
String rubybin = null;
String r = getInterpreter(canonical);
if (r != null) {
rubybin = new File(r).getParent();
}
return rubybin;
}
/**
* Try to find a path to the <tt>toFind</tt> executable in the "Ruby
* specific" manner.
*
* @param toFind executable to be find, e.g. rails, rake, rdoc, irb ...
* @return path to the found executable; might be <tt>null</tt> if not
* found.
*/
public String findExecutable(final String toFind) {
return findExecutable(toFind, true);
}
/**
* Try to find a path to the <tt>toFind</tt> executable in the "Ruby
* specific" manner.
*
* @param toFind executable to be find, e.g. rails, rake, rdoc, irb ...
* @return path to the found executable; might be <tt>null</tt> if not
* found.
*/
private String findExecutable(final String toFind, final boolean searchInRubyGems) {
String exec = null;
boolean canonical = true; // default
do {
String binDir = getBinDir();
if (binDir != null) {
LOGGER.log(Level.FINER, "Looking for '{0}' executable; used intepreter: '{1}'", new String[]{toFind, getInterpreter()}); // NOI18N
exec = RubyPlatform.findExecutable(binDir, toFind);
} else {
LOGGER.log(Level.WARNING, "Could not find Ruby interpreter executable when searching for '{0}'", toFind); // NOI18N
}
if (exec == null && searchInRubyGems && hasRubyGemsInstalled()) {
for (File repo : getGemManager().getRepositories()) {
String libGemBinDir = repo.getAbsolutePath() + File.separator + "bin"; // NOI18N
exec = RubyPlatform.findExecutable(libGemBinDir, toFind);
if (exec != null) {
break;
}
}
}
canonical ^= true;
} while (!canonical && exec == null);
// try to find a gem on system path - see issue 116219
if (exec == null) {
exec = Util.findOnPath(toFind);
}
// try *.bat commands on Windows
if (exec == null && !toFind.endsWith(".bat") && Utilities.isWindows()) { // NOI18N
exec = findExecutable(toFind + ".bat", searchInRubyGems); // NOI18N
}
if (exec != null) {
LOGGER.log(Level.FINER, "Found '{0}': '{1}'", new String[]{toFind, exec});
}
return exec;
}
/**
* The same as {@link #findExecutable(String)}, but if fails and withSuffix
* is set to true, it tries to find also executable with the suffix with
* which was compiled the interpreter. E.g. for <em>ruby1.8.6-p111</em>
* tries to find <em>irb1.8.6-p111</em>.
*
* @param toFind see {@link #findExecutable(String)}
* @param withSuffix whether to try also suffix version when non-suffix is not found
* @return see {@link #findExecutable(String)}
*/
private String findExecutable(final String toFind, final boolean searchInRubyGems, final boolean withSuffix) {
String exec = findExecutable(toFind, searchInRubyGems);
if (exec == null && withSuffix && !isJRuby()) { // JRuby is not compiled with custom suffix
String name = new File(getInterpreter(true)).getName();
if (name.startsWith("ruby")) { // NOI18N
String suffix = name.substring(4);
// Try to find with suffix (#120441)
exec = findExecutable(toFind + suffix, searchInRubyGems);
}
}
return exec;
}
private static String findExecutable(final String dir, final String toFind) {
String exec = dir + File.separator + toFind;
if (!new File(exec).isFile()) {
LOGGER.log(Level.FINER, "'{0}' is not a file.", exec); // NOI18N
exec = null;
}
return exec;
}
/**
* Return path to the <em>gem</em> tool if it does exist.
*
* @return path to the <em>gem</em> tool; might be <tt>null</tt> if not
* found.
*/
public synchronized String getGemTool() {
if (gemTool == null) {
gemTool = findExecutable("gem", false, true); // NOI18N
}
return gemTool;
}
public synchronized String getRDoc() {
if (rdoc == null) {
rdoc = findExecutable("rdoc", false, true); // NOI18N
}
return rdoc;
}
public synchronized String getIRB() {
if (irb == null) {
irb = findExecutable(isJRuby() ? "jirb" : "irb", false, true); // NOI18N
}
return irb;
}
public synchronized static FileObject getRubyStubs() {
if (stubsFO == null) {
// Core classes: Stubs generated for the "builtin" Ruby libraries.
File clusterFile = InstalledFileLocator.getDefault().locate(
"modules/org-netbeans-modules-ruby-project.jar", "org.netbeans.modules.ruby.project", false); // NOI18N
if (clusterFile != null) {
File rubyStubs =
new File(clusterFile.getParentFile().getParentFile().getAbsoluteFile(),
// JRUBY_RELEASEDIR + File.separator +
"rubystubs" + File.separator + RUBYSTUBS_VERSION); // NOI18N
assert rubyStubs.exists() && rubyStubs.isDirectory();
stubsFO = FileUtil.toFileObject(rubyStubs);
} else {
// Language registry polls us for some reason (see #153595 stacktrace)
RubyPlatform platform = RubyPlatformManager.getDefaultPlatform();
if (platform != null) { // there does not need to be default platform
String interpreter = platform.getInterpreter();
if (interpreter != null) {
FileObject fo = FileUtil.toFileObject(new File(interpreter));
if (fo != null) {
stubsFO = fo.getParent().getParent().getParent().getFileObject("rubystubs/" + RUBYSTUBS_VERSION); // NOI18N
}
}
}
}
}
return stubsFO;
}
/**
* @return whether everything needed for fast debugging is installed
*/
public boolean hasFastDebuggerInstalled() {
// no usable version of Fast Debugger for Rubinius is available yet
return getGemManager() != null && !isRubinius() && getFastDebuggerProblemsInHTML() == null;
}
/**
* @return null if everything is OK or errors in String
*/
public String getFastDebuggerProblemsInHTML() {
assert getGemManager() != null : "has gemManager when asking whether Fast Debugger is installed";
getGemManager().resetLocal();
StringBuilder errors = new StringBuilder();
// Special case for ruby-debug-ide19, likely temporary until
// ruby-debug-ide support both, 1.8 and 1.9.
// If ruby-debug-ide19 is available, use it.
if (is19() && RUBY_DEBUG_IDE_NAME.equals("ruby-debug-ide") && // NOI18N
checkGem(RUBY_DEBUG_IDE_NAME + "19", getRequiredRDebugIDEVersionPattern())) { // NOI18N
return null;
}
checkGemAndReport(RUBY_DEBUG_IDE_NAME, getRequiredRDebugIDEVersionPattern(), errors);
return errors.length() == 0 ? null : errors.toString();
}
/**
* Platform requires version of <i>ruby-debug-ide</i> which matches pattern
* returned by this function.
*/
private Pattern getRequiredRDebugIDEVersionPattern() {
return Pattern.compile("0\\.4\\..*"); // NOI18N
}
private boolean checkGem(final String gemName, final Pattern gemVersion) {
VersionPredicate predicate = new VersionPredicate() {
public boolean isRight(final String version) {
return gemVersion.matcher(version).matches();
}
};
return getGemManager().isGemInstalledForPlatform(gemName, predicate);
}
private void checkGemAndReport(final String gemName, final Pattern gemVersion, final StringBuilder errors) {
if (!checkGem(gemName, gemVersion)) {
errors.append(NbBundle.getMessage(RubyPlatform.class, "RubyPlatform.GemInVersionMissing", gemName, gemVersion.toString()));
errors.append("<br>"); // NOI18N
}
}
public void reportRubyGemsProblem() {
validator.reportRubyGemsProblem();
}
/** Returns false if check fails. True in success case. */
public boolean checkAndReportRubyGemsProblems() {
return validator.checkAndReportRubyGemsProblems();
}
/**
* Return <tt>null</tt> if there are no problems running gem. Otherwise
* return an error message which describes the problem.
*/
public String getRubyGemsProblems() {
return validator.getRubyGemsProblems();
}
/**
* Returns latest available, but valid version of rdebug-ide gem for this
* platform. So if e.g. 0.1.10, 0.2.0 and 0.3.0 versions are available, but
* this platform can work only with 0.1.10 and 0.2.0, version 0.2.0 is
* returned.
*
* @return latest available valid version: <tt>null</tt> if none suitable
* version is found
*/
public String getLatestAvailableValidRDebugIDEVersions() {
// Special case for ruby-debug-ide19, likely temporary until
// ruby-debug-ide support both, 1.8 and 1.9.
// If ruby-debug-ide19 is available, use it.
if (is19() && RUBY_DEBUG_IDE_NAME.equals("ruby-debug-ide")) { // NOI18N
String version = getLatestAvailableValidRDebugIDEVersions(RUBY_DEBUG_IDE_NAME + "19"); // NOI18N
if (version != null) {
return version;
}
}
return getLatestAvailableValidRDebugIDEVersions(RUBY_DEBUG_IDE_NAME);
}
private String getLatestAvailableValidRDebugIDEVersions(final String gemName) {
List<GemInfo> versions = getGemManager().getVersions(gemName);
for (GemInfo getInfo : versions) {
String version = getInfo.getVersion();
if (getRequiredRDebugIDEVersionPattern().matcher(version).matches()) {
return version;
}
}
return null;
}
/**
* Tries to install fast Ruby debugger for the platform. That is an
* appropriate version of <em>ruby-debug-ide</em> gem.
*
* @return <tt>true</tt> whether the installation succeed; <tt>false</tt> otherwise
*/
public boolean installFastDebugger() {
assert getGemManager() != null : "has gemManager when trying to install fast debugger";
Runnable installer = new Runnable() {
public void run() {
// TODO: ideally this would be e.g. '< 0.3' but then running external
// process has problems with the '<'. See issue 142240.
getGemManager().installGem(RUBY_DEBUG_IDE_NAME, false, false, "0.4.9"); // NOI18N
}
};
if (!EventQueue.isDispatchThread()) {
try {
EventQueue.invokeAndWait(installer);
} catch (InterruptedException ex) {
Exceptions.printStackTrace(ex);
} catch (InvocationTargetException ex) {
Exceptions.printStackTrace(ex);
}
} else {
installer.run();
}
return hasFastDebuggerInstalled();
}
/*
* Return non-null iff the given file is a "system" file - e.g. it's part of
* the Ruby 1.8 libraries, or the Java support libraries, or the user's
* rubygems, or something under $GEM_HOME...
*
* @return The actual root the in the system the file is under.
*/
public FileObject getSystemRoot(FileObject file) {
// See if the file is under the Ruby libraries
FileObject rubyLibFo = isRubinius() ? null : getLibDirFO();
FileObject rubyStubs = getRubyStubs();
FileObject gemHome = getGemHome();
while (file != null) {
if (file.equals(rubyLibFo) || file.equals(rubyStubs) || isGemRoot(file) || file.equals(gemHome)) {
return file;
}
file = file.getParent();
}
return null;
}
private FileObject getGemHome() {
return getGemManager() != null ? getGemManager().getGemHomeFO() : null;
}
/**
* @return true if the given file represents the lib dir of a gem, e.g.
* <code>$GEM_HOME/mygem-0.1/lib</code>, false otherwise. This is useful
* for {@link #getSystemRoot(libDirFO)} to be able to return an indexed root
* (the lib dirs of gems are indexed roots). See also IZ 167814.
*/
private boolean isGemRoot(FileObject file) {
for (int i = 0; i < 3 && file != null; i++) {
file = file.getParent();
}
return file != null && file.isFolder() && file.equals(getGemHome());
}
private void updateIndexRoots() {
// XXX - Parsing API
// if (!indexInitialized) {
// indexInitialized = true;
// // HACK, fix soon
// // Let RepositoryUpdater and friends know where they can root preindexing
// // This should be done in a cleaner way.
// //org.netbeans.modules.gsfret.source.usages.Index.setPreindexRootUrl(getHomeUrl());
//
// org.netbeans.modules.gsfret.source.usages.Index.addPreindexRoot(FileUtil.toFileObject(getHome(true)));
//
// if (hasRubyGemsInstalled()) {
// FileObject gemFo = getGemManager().getGemHomeFO();
// org.netbeans.modules.gsfret.source.usages.Index.addPreindexRoot(gemFo);
// }
//
// }
}
/**
* The gems installed have changed, or the installed ruby has changed etc. --
* force a recomputation of the installed classpath roots.
*/
public void recomputeRoots() {
updateIndexRoots();
// Ensure that source cache is wiped and classpaths recomputed for existing files
// XXX - Parsing API
// Source.clearSourceCache();
pcs.firePropertyChange("roots", null, null); // NOI18N
// // Force ClassIndex registration
// // Dummy class path provider just to trigger recomputation
// ClassPathProviderImpl cpProvider = new ClassPathProviderImpl(null, null, null, null);
// ClassPath[] cps = cpProvider.getProjectClassPaths(ClassPath.BOOT);
// GlobalPathRegistry.getDefault().register(ClassPath.BOOT, cps);
// GlobalPathRegistry.getDefault().unregister(ClassPath.BOOT, cps);
// Possibly clean up index from old ruby root as well?
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
pcs.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
pcs.removePropertyChangeListener(listener);
}
public void setGemHome(File gemHome) {
assert hasRubyGemsInstalled() : "has RubyGems installed";
info.setGemHome(gemHome.getAbsolutePath());
try {
RubyPlatformManager.storePlatform(this);
} catch (IOException ioe) {
LOGGER.log(Level.SEVERE, ioe.getLocalizedMessage(), ioe);
}
getGemManager().reset();
}
/**
* Calls {@link #hasRubyGemsInstalled(boolean)} with <tt>false</tt> for warn
* parameter.
*/
public boolean hasRubyGemsInstalled() {
return hasRubyGemsInstalled(false);
}
/**
* Check whether RubyGems are correctly installed for this platform.
*
* @param warn whether to show warning if RubyGems are not installed or
* installation is broken
* @return whether the RubyGems are correctly installed for this platform
*/
public boolean hasRubyGemsInstalled(boolean warn) {
return validator.hasRubyGemsInstalled(warn);
}
/**
* Notifies the registered listeners that are changes in this platform's gems,
* i.e. a gem was removed or a new gem was installed.
*/
public void fireGemsChanged() {
if (pcs != null) {
pcs.firePropertyChange("gems", null, null); //NOI18N
}
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final RubyPlatform other = (RubyPlatform) obj;
if ((this.interpreter == null) ? (other.interpreter != null) : !this.interpreter.equals(other.interpreter)) {
return false;
}
return true;
}
@Override
public int hashCode() {
int hash = 3;
hash = 97 * hash + (this.interpreter != null ? this.interpreter.hashCode() : 0);
return hash;
}
public int compareTo(final RubyPlatform other) {
return getInterpreter().compareTo(other.getInterpreter());
}
public @Override String toString() {
return "RubyPlatform[id:" + getID() + ", label:" + getLabel() + ", " + getInterpreter() + ", info: " + info + "]"; // NOI18N
}
private void possiblyNotifyUser(boolean warn, boolean valid, String cmd) {
if (warn && !valid) {
String msg = NbBundle.getMessage(RubyPlatform.class, "RubyPlatform.NotInstalledCmd", cmd, getLabel());
NotifyDescriptor nd =
new NotifyDescriptor.Message(msg, NotifyDescriptor.Message.ERROR_MESSAGE);
DialogDisplayer.getDefault().notify(nd);
}
}
public static class Info {
static final String RUBY_KIND = "ruby_kind"; // NOI18N
static final String RUBY_VERSION = "ruby_version"; // NOI18N
static final String JRUBY_VERSION = "jruby_version"; // NOI18N
static final String RUBY_PATCHLEVEL = "ruby_patchlevel"; // NOI18N
static final String RUBY_RELEASE_DATE = "ruby_release_date"; // NOI18N
static final String RUBY_EXECUTABLE = "ruby_executable"; // NOI18N
static final String RUBY_PLATFORM = "ruby_platform"; // NOI18N
static final String RUBY_LIB_DIR = "ruby_lib_dir"; // NOI18N
static final String GEM_HOME = "gem_home"; // NOI18N
static final String GEM_PATH = "gem_path"; // NOI18N
static final String GEM_VERSION = "gem_version"; // NOI18N
private final String kind;
private final String version;
private String jversion;
private String patchlevel;
private String releaseDate;
private String platform;
private String gemHome;
private String gemPath;
private String gemVersion;
private String libDir;
Info(final Properties props) {
this.kind = props.getProperty(RUBY_KIND);
this.version = props.getProperty(RUBY_VERSION);
this.jversion = props.getProperty(JRUBY_VERSION);
this.patchlevel = props.getProperty(RUBY_PATCHLEVEL);
this.releaseDate = props.getProperty(RUBY_RELEASE_DATE);
this.platform = props.getProperty(RUBY_PLATFORM);
this.libDir = props.getProperty(RUBY_LIB_DIR);
setGemHome(props.getProperty(GEM_HOME));
this.gemPath = props.getProperty(GEM_PATH);
this.gemVersion = props.getProperty(GEM_VERSION);
}
Info(String kind, String version) {
this.kind = kind;
this.version = version;
}
static Info forDefaultPlatform() {
// NbBundle.getMessage(RubyPlatformManager.class, "CTL_BundledJRubyLabel")
Info info = new Info("JRuby", "1.8.7"); // NOI18N
info.jversion = "1.6.6"; // NOI18N // FIXME: Kill this embedding
info.patchlevel = "357"; // NOI18N
// XXX this is dynamically generated during JRuby build, should be
// fixed by not hardcoding the default platform info, but rather
// computing as for other platforms
info.releaseDate = "2012-01-10"; // NOI18N
info.platform = "java"; // NOI18N
File jrubyHome = InstalledFileLocator.getDefault().locate("jruby", "org.jruby.distro", false); // NOI18N
// XXX handle valid case when it is not available, see #124534
assert (jrubyHome != null && jrubyHome.isDirectory()) : "Default platform available";
FileObject libDirFO = FileUtil.toFileObject(jrubyHome).getFileObject("/lib/ruby"); // NOI18N
info.libDir = FileUtil.toFile(libDirFO.getFileObject("/1.8")).getAbsolutePath(); // NOI18N
info.gemHome = FileUtil.toFile(libDirFO.getFileObject("/gems/1.8")).getAbsolutePath(); // NOI18N
info.gemPath = info.gemHome;
info.gemVersion = "1.8.15"; // NOI18N
return info;
}
public String getLabel(final boolean isDefault) {
String ver = isJRuby() ? jversion
: version + (patchlevel != null ? "-p" + patchlevel : ""); // NOI18N
return (isDefault ? NbBundle.getMessage(RubyPlatform.class, "RubyPlatformManager.CTL_BundledJRubyLabel") : kind)
+ ' ' + ver;
}
public String getLongDescription() {
StringBuilder sb = new StringBuilder(kind + ' ' + version + ' ' + '(' + releaseDate);
if (patchlevel != null) {
sb.append(" patchlevel ").append(patchlevel); // NOI18N
}
sb.append(") [").append(platform).append(']'); // NOI18N
return sb.toString();
}
public boolean isJRuby() {
return "JRuby".equals(kind); // NOI18N
}
public boolean isRubinius() {
return "Rubinius".equals(kind); // NOI18N
}
public final void setGemHome(String gemHome) {
this.gemHome = gemHome == null ? null : new File(gemHome).getAbsolutePath();
}
public String getGemHome() {
return gemHome;
}
public void setGemPath(String gemPath) {
this.gemPath = gemPath;
}
public String getGemPath() {
return gemPath;
}
public String getGemVersion() {
return gemVersion;
}
public void setGemVersion(String gemVersion) {
this.gemVersion = gemVersion;
}
public String getKind() {
return kind;
}
public String getPatchlevel() {
return patchlevel;
}
public String getPlatform() {
return platform;
}
public String getReleaseDate() {
return releaseDate;
}
/**
* Returns JRuby version as specified by <code>JRUBY_VERSION</code>
* JRuby constant. Supported by JRuby only.
*
* @return JRuby version
*/
public String getJVersion() {
return jversion;
}
/**
* Returns Ruby version as specified by <code>RUBY_VERSION</code>
* constant. Supported by all interpreters.
*
* @return Ruby version
*/
public String getVersion() {
return version;
}
/**
* Get version specific for the platform. E.g. in the case of JRuby it
* returns e.g. 1.6.6 instead of 1.8.6.
*
* @return platform specific version
*/
public String getPlatformVersion() {
return isJRuby() ? getJVersion() : getVersion();
}
/** Returns content of <code>RbConfig::CONFIG['rubylibdir']</code>. */
public String getLibDir() {
return libDir;
}
public @Override String toString() {
return "RubyPlatform$Info[GEM_HOME:" + getGemHome() + ", GEM_PATH: " + getGemPath() // NOI18N
+ ", gemVersion: " + getGemVersion() + ", lib: " + getLibDir() + "]"; // NOI18N
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Info other = (Info) obj;
if (this.kind != other.kind && (this.kind == null || !this.kind.equals(other.kind))) {
return false;
}
if (this.version != other.version && (this.version == null || !this.version.equals(other.version))) {
return false;
}
if (this.patchlevel != other.patchlevel && (this.patchlevel == null || !this.patchlevel.equals(other.patchlevel))) {
return false;
}
if (this.platform != other.platform && (this.platform == null || !this.platform.equals(other.platform))) {
return false;
}
return true;
}
@Override
public int hashCode() {
int hash = 7;
hash = 83 * hash + (this.kind != null ? this.kind.hashCode() : 0);
hash = 83 * hash + (this.version != null ? this.version.hashCode() : 0);
hash = 83 * hash + (this.patchlevel != null ? this.patchlevel.hashCode() : 0);
hash = 83 * hash + (this.platform != null ? this.platform.hashCode() : 0);
return hash;
}
}
}