0001: /*
0002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
0003: *
0004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
0005: *
0006: * The contents of this file are subject to the terms of either the GNU
0007: * General Public License Version 2 only ("GPL") or the Common
0008: * Development and Distribution License("CDDL") (collectively, the
0009: * "License"). You may not use this file except in compliance with the
0010: * License. You can obtain a copy of the License at
0011: * http://www.netbeans.org/cddl-gplv2.html
0012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
0013: * specific language governing permissions and limitations under the
0014: * License. When distributing the software, include this License Header
0015: * Notice in each file and include the License file at
0016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
0017: * particular file as subject to the "Classpath" exception as provided
0018: * by Sun in the GPL Version 2 section of the License file that
0019: * accompanied this code. If applicable, add the following below the
0020: * License Header, with the fields enclosed by brackets [] replaced by
0021: * your own identifying information:
0022: * "Portions Copyrighted [year] [name of copyright owner]"
0023: *
0024: * Contributor(s):
0025: *
0026: * The Original Software is NetBeans. The Initial Developer of the Original
0027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
0028: * Microsystems, Inc. All Rights Reserved.
0029: *
0030: * If you wish your version of this file to be governed by only the CDDL
0031: * or only the GPL Version 2, indicate your decision by adding
0032: * "[Contributor] elects to include this software in this distribution
0033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
0034: * single choice of license, a recipient has the option to distribute
0035: * your version of this file under either the CDDL, the GPL Version 2 or
0036: * to extend the choice of license to its licensees as provided above.
0037: * However, if you add GPL Version 2 code and therefore, elected the GPL
0038: * Version 2 license, then the option applies only if the new code is
0039: * made subject to such option by the copyright holder.
0040: */
0041:
0042: package org.netbeans.modules.ruby.rubyproject;
0043:
0044: import java.awt.Dialog;
0045: import java.awt.event.MouseEvent;
0046: import java.io.File;
0047: import java.io.IOException;
0048: import java.net.MalformedURLException;
0049: import java.net.URL;
0050: import java.text.MessageFormat;
0051: import java.util.Arrays;
0052: import java.util.Collection;
0053: import java.util.HashSet;
0054: import java.util.LinkedHashSet;
0055: import java.util.Set;
0056: import javax.swing.JButton;
0057: import javax.swing.event.ChangeEvent;
0058: import javax.swing.event.ChangeListener;
0059: import org.netbeans.modules.gsf.api.DeclarationFinder.DeclarationLocation;
0060: import org.netbeans.api.project.ProjectInformation;
0061: import org.netbeans.api.project.ProjectManager;
0062: import org.netbeans.modules.ruby.platform.execution.ExecutionDescriptor;
0063: import org.netbeans.api.ruby.platform.RubyInstallation;
0064: import org.netbeans.api.project.ProjectUtils;
0065: import org.netbeans.api.ruby.platform.RubyPlatform;
0066: import org.netbeans.api.ruby.platform.RubyPlatformManager;
0067: import org.netbeans.modules.ruby.platform.RubyExecution;
0068: import org.netbeans.modules.ruby.platform.execution.OutputRecognizer;
0069: import org.netbeans.modules.ruby.platform.gems.GemManager;
0070: import org.netbeans.modules.ruby.rubyproject.ui.customizer.RubyProjectProperties;
0071: import org.netbeans.modules.ruby.rubyproject.ui.customizer.MainClassChooser;
0072: import org.netbeans.modules.ruby.rubyproject.ui.customizer.MainClassWarning;
0073: import org.netbeans.spi.project.ActionProvider;
0074: import org.netbeans.modules.ruby.spi.project.support.rake.EditableProperties;
0075: import org.netbeans.modules.ruby.spi.project.support.rake.RakeProjectHelper;
0076: import org.netbeans.spi.project.ui.support.DefaultProjectOperations;
0077: import org.openide.DialogDescriptor;
0078: import org.openide.DialogDisplayer;
0079: import org.openide.ErrorManager;
0080: import org.openide.LifecycleManager;
0081: import org.openide.awt.HtmlBrowser;
0082: import org.openide.awt.MouseUtils;
0083: import org.openide.cookies.SaveCookie;
0084: import org.openide.filesystems.FileObject;
0085: import org.openide.filesystems.FileUtil;
0086: import org.openide.loaders.DataObject;
0087: import org.openide.loaders.DataObjectNotFoundException;
0088: import org.openide.util.Lookup;
0089: import org.openide.util.NbBundle;
0090: import org.openide.util.Utilities;
0091:
0092: /**
0093: * Action provider of the Ruby project. This is the place where to do
0094: * strange things to Ruby actions. E.g. compile-single.
0095: */
0096: public class RubyActionProvider implements ActionProvider,
0097: ScriptDescProvider {
0098:
0099: /**
0100: * Standard command for running rdoc on a project.
0101: * @see org.netbeans.spi.project.ActionProvider
0102: */
0103: public static final String COMMAND_RDOC = "rdoc"; // NOI18N
0104:
0105: /**
0106: * Command for running auto test on this project (if installed)
0107: */
0108: public static final String COMMAND_AUTOTEST = "autotest"; // NOI18N
0109:
0110: /**
0111: * Standard command for running the IRB console on a project
0112: */
0113: public static final String COMMAND_IRB_CONSOLE = "irb-console"; // NOI18N
0114:
0115: // Commands available from Ruby project
0116: private static final String[] supportedActions = { COMMAND_BUILD,
0117: COMMAND_CLEAN, COMMAND_REBUILD, COMMAND_AUTOTEST,
0118: COMMAND_RDOC,
0119: COMMAND_IRB_CONSOLE,
0120: // COMMAND_COMPILE_SINGLE,
0121: COMMAND_RUN, COMMAND_RUN_SINGLE, COMMAND_DEBUG,
0122: COMMAND_DEBUG_SINGLE,
0123: // COMMAND_TEST,
0124: COMMAND_TEST_SINGLE, COMMAND_DEBUG_TEST_SINGLE,
0125: COMMAND_DELETE, COMMAND_COPY, COMMAND_MOVE, COMMAND_RENAME, };
0126:
0127: // Project
0128: RubyProject project;
0129:
0130: // Ant project helper of the project
0131: private UpdateHelper updateHelper;
0132:
0133: /**Set of commands which are affected by background scanning*/
0134: final Set<String> bkgScanSensitiveActions;
0135:
0136: public RubyActionProvider(RubyProject project,
0137: UpdateHelper updateHelper) {
0138: this .bkgScanSensitiveActions = new HashSet<String>(Arrays
0139: .asList(new String[] { COMMAND_RUN, COMMAND_RUN_SINGLE,
0140: COMMAND_DEBUG, COMMAND_DEBUG_SINGLE, }));
0141:
0142: this .updateHelper = updateHelper;
0143: this .project = project;
0144: }
0145:
0146: public String[] getSupportedActions() {
0147: return supportedActions;
0148: }
0149:
0150: private void runRubyScript(FileObject fileObject, String target,
0151: String displayName, final Lookup context,
0152: final boolean debug, OutputRecognizer[] extraRecognizers) {
0153: ExecutionDescriptor desc = getScriptDescriptor(null,
0154: fileObject, target, displayName, context, debug,
0155: extraRecognizers);
0156:
0157: RubyExecution service = new RubyExecution(desc, project
0158: .evaluator().getProperty(
0159: RubyProjectProperties.SOURCE_ENCODING));
0160: service.run();
0161: }
0162:
0163: public ExecutionDescriptor getScriptDescriptor(File pwd,
0164: FileObject fileObject, String target, String displayName,
0165: final Lookup context, final boolean debug,
0166: OutputRecognizer[] extraRecognizers) {
0167:
0168: String options = project.evaluator().getProperty(
0169: RubyProjectProperties.RUN_JVM_ARGS);
0170:
0171: if (options != null && options.trim().length() == 0) {
0172: options = null;
0173: }
0174:
0175: // Set the load path from the source and test folders.
0176: // Load paths are additive so users can add their own in the
0177: // options field as well.
0178: FileObject[] srcPath = project.getSourceRoots().getRoots();
0179: FileObject[] testPath = project.getTestSourceRoots().getRoots();
0180: StringBuilder sb = new StringBuilder();
0181: if (srcPath != null && srcPath.length > 0) {
0182: for (FileObject root : srcPath) {
0183: if (sb.length() > 0) {
0184: sb.append(' ');
0185: }
0186: sb.append("-I\""); // NOI18N
0187: sb.append(FileUtil.toFile(root).getAbsoluteFile());
0188: sb.append("\""); // NOI18N
0189: }
0190: }
0191: if (testPath != null && testPath.length > 0) {
0192: for (FileObject root : testPath) {
0193: if (sb.length() > 0) {
0194: sb.append(' ');
0195: }
0196: sb.append("-I\""); // NOI18N
0197: sb.append(FileUtil.toFile(root).getAbsoluteFile());
0198: sb.append("\""); // NOI18N
0199: }
0200: }
0201: String includePath = sb.toString();
0202: if (options != null) {
0203: options = includePath + " " + options; // NOI18N
0204: } else {
0205: options = includePath;
0206: }
0207:
0208: target = locate(target, srcPath, testPath);
0209:
0210: if (pwd == null) {
0211: String runDir = project.evaluator().getProperty(
0212: RubyProjectProperties.RUN_WORK_DIR);
0213: pwd = getSourceFolder();
0214: if (runDir != null && runDir.length() > 0) {
0215: File dir = new File(runDir);
0216: if (!dir.exists()) {
0217: // Is it relative to the project directory?
0218: dir = new File(FileUtil.toFile(project
0219: .getProjectDirectory()), runDir);
0220: if (!dir.exists()) {
0221: // Could it be relative to one of the source folders?
0222: if (srcPath != null && srcPath.length > 0) {
0223: for (FileObject root : srcPath) {
0224: dir = new File(FileUtil.toFile(root),
0225: runDir);
0226: if (dir.exists()) {
0227: break;
0228: }
0229: }
0230: }
0231: }
0232: }
0233: if (dir.exists()) {
0234: pwd = dir;
0235: }
0236: }
0237: }
0238:
0239: String classPath = project.evaluator().getProperty(
0240: RubyProjectProperties.JAVAC_CLASSPATH);
0241:
0242: ExecutionDescriptor desc = new ExecutionDescriptor(RubyPlatform
0243: .platformFor(project), displayName, pwd, target);
0244: desc.debug(debug);
0245: desc.showSuspended(true);
0246: desc.allowInput();
0247: desc.fileObject(fileObject);
0248: desc.initialArgs(options);
0249: desc.classPath(classPath);
0250: desc.additionalArgs(getApplicationArguments());
0251: desc.fileLocator(new RubyFileLocator(context, project));
0252: desc.addStandardRecognizers();
0253: desc.addOutputRecognizer(RubyExecution.RUBY_TEST_OUTPUT);
0254:
0255: if (extraRecognizers != null) {
0256: for (OutputRecognizer recognizer : extraRecognizers) {
0257: desc.addOutputRecognizer(recognizer);
0258: }
0259: }
0260:
0261: return desc;
0262: }
0263:
0264: private String locate(String target, final FileObject[] srcPath,
0265: final FileObject[] testPath) {
0266: // Locate the target and specify it by full path. This is necessary
0267: // because JRuby and Ruby don't locate the script from the load path it
0268: // seems.
0269: if (!new File(target).exists()) {
0270: if (srcPath != null && srcPath.length > 0) {
0271: boolean found = false; // Prefer the first match
0272: for (FileObject root : srcPath) {
0273: FileObject fo = root.getFileObject(target);
0274: if (fo != null) {
0275: target = FileUtil.toFile(fo).getAbsolutePath();
0276: found = true;
0277: break;
0278: }
0279: }
0280: if (!found && testPath != null) {
0281: for (FileObject root : testPath) {
0282: FileObject fo = root.getFileObject(target);
0283: if (fo != null) {
0284: target = FileUtil.toFile(fo)
0285: .getAbsolutePath();
0286: break;
0287: }
0288: }
0289: }
0290: }
0291: }
0292: return target;
0293: }
0294:
0295: private FileObject getCurrentFile(Lookup context) {
0296: FileObject[] fos = findSources(context);
0297: if (fos == null) {
0298: fos = findTestSources(context, false);
0299: }
0300: if (fos == null || fos.length == 0) {
0301: for (DataObject d : context.lookupAll(DataObject.class)) {
0302: FileObject fo = d.getPrimaryFile();
0303: if (fo.getMIMEType().equals(
0304: RubyInstallation.RUBY_MIME_TYPE)) {
0305: return fo;
0306: }
0307: }
0308: return null;
0309: }
0310:
0311: return fos[0];
0312: }
0313:
0314: private void saveFile(FileObject file) {
0315: // Save the file
0316: try {
0317: DataObject dobj = DataObject.find(file);
0318: if (dobj != null) {
0319: SaveCookie saveCookie = dobj
0320: .getCookie(SaveCookie.class);
0321: if (saveCookie != null) {
0322: saveCookie.save();
0323: }
0324: }
0325: } catch (DataObjectNotFoundException donfe) {
0326: ErrorManager.getDefault().notify(donfe);
0327: } catch (IOException ioe) {
0328: ErrorManager.getDefault().notify(ioe);
0329: }
0330: }
0331:
0332: private void openIrbConsole(Lookup context) {
0333: // FIXME: project or platfrom sensitive
0334: RubyPlatform platform = RubyPlatformManager
0335: .getDefaultPlatform();
0336: platform.findExecutable("irb"); // NOI18N
0337:
0338: String displayName = "IRB"; //NbBundle.getMessage(RailsActionProvider.class, "RailsConsole");
0339: File pwd = FileUtil.toFile(project.getProjectDirectory());
0340: String classPath = project.evaluator().getProperty(
0341: RubyProjectProperties.JAVAC_CLASSPATH);
0342:
0343: new RubyExecution(new ExecutionDescriptor(platform,
0344: displayName, pwd).showSuspended(false).showProgress(
0345: false).classPath(classPath).allowInput()
0346: .initialArgs(
0347: "-e \"require 'irb';" /* + " require 'irb/completion';"*/
0348: + " IRB.start\"")
0349: . // NOI18N
0350: //additionalArgs(getApplicationArguments()).
0351: fileLocator(new RubyFileLocator(context, project))
0352: .addStandardRecognizers(), project.evaluator()
0353: .getProperty(RubyProjectProperties.SOURCE_ENCODING))
0354: .run();
0355: }
0356:
0357: public void invokeAction(final String command, final Lookup context)
0358: throws IllegalArgumentException {
0359: // Initialize the configuration: find a way to pass this to the launched child process!
0360: //RubyConfigurationProvider.Config c = context.lookup(RubyConfigurationProvider.Config.class);
0361: //if (c != null) {
0362: // String config;
0363: // if (c.name != null) {
0364: // config = c.name;
0365: // } else {
0366: // // Invalid but overrides any valid setting in config.properties.
0367: // config = "";
0368: // }
0369: // Properties p = new Properties();
0370: // p.setProperty(RubyConfigurationProvider.PROP_CONFIG, config);
0371: // TODO: Somehow pass the properties to the launched process, and have it digest it
0372: //}
0373:
0374: RubyPlatform platform = RubyPlatform.platformFor(project);
0375: assert platform != null : "Action '" + command
0376: + "' should be disabled when platform is invalid";
0377: GemManager gemManager = platform.getGemManager();
0378:
0379: // TODO Check for valid installation of Ruby and Rake
0380: if (COMMAND_RUN.equals(command)
0381: || COMMAND_DEBUG.equals(command)) {
0382: if (!platform.isValidRuby(true)) {
0383: return;
0384: }
0385:
0386: String config = project.evaluator().getProperty(
0387: RubyConfigurationProvider.PROP_CONFIG);
0388: String path;
0389: if (config == null || config.length() == 0) {
0390: path = RakeProjectHelper.PROJECT_PROPERTIES_PATH;
0391: } else {
0392: // Set main class for a particular config only.
0393: path = "nbproject/configs/" + config + ".properties"; // NOI18N
0394: }
0395: EditableProperties ep = updateHelper.getProperties(path);
0396:
0397: // check project's main class
0398: // Check whether main class is defined in this config. Note that we use the evaluator,
0399: // not ep.getProperty(MAIN_CLASS), since it is permissible for the default pseudoconfig
0400: // to define a main class - in this case an active config need not override it.
0401: String mainClass = project.evaluator().getProperty(
0402: RubyProjectProperties.MAIN_CLASS);
0403: MainClassStatus result = isSetMainClass(project
0404: .getSourceRoots().getRoots(), mainClass);
0405: if (context.lookup(RubyConfigurationProvider.Config.class) != null) {
0406: // If a specific config was selected, just skip this check for now.
0407: // XXX would ideally check that that config in fact had a main class.
0408: // But then evaluator.getProperty(MAIN_CLASS) would be inaccurate.
0409: // Solvable but punt on it for now.
0410: result = MainClassStatus.SET_AND_VALID;
0411: }
0412: if (result != MainClassStatus.SET_AND_VALID) {
0413: do {
0414: // show warning, if cancel then return
0415: if (showMainClassWarning(mainClass, ProjectUtils
0416: .getInformation(project).getDisplayName(),
0417: ep, result)) {
0418: return;
0419: }
0420: // No longer use the evaluator: have not called putProperties yet so it would not work.
0421: mainClass = ep
0422: .get(RubyProjectProperties.MAIN_CLASS);
0423: result = isSetMainClass(project.getSourceRoots()
0424: .getRoots(), mainClass);
0425: } while (result != MainClassStatus.SET_AND_VALID);
0426: try {
0427: if (updateHelper.requestSave()) {
0428: updateHelper.putProperties(path, ep);
0429: ProjectManager.getDefault()
0430: .saveProject(project);
0431: } else {
0432: return;
0433: }
0434: } catch (IOException ioe) {
0435: ErrorManager.getDefault().log(
0436: ErrorManager.INFORMATIONAL,
0437: "Error while saving project: " + ioe); // NOI18N
0438: }
0439: }
0440:
0441: // Save all files first
0442: LifecycleManager.getDefault().saveAll();
0443:
0444: String displayName = (mainClass != null) ? NbBundle
0445: .getMessage(RubyActionProvider.class, "Ruby")
0446: : NbBundle.getMessage(RubyActionProvider.class,
0447: "Rake");
0448:
0449: ProjectInformation info = ProjectUtils
0450: .getInformation(project);
0451: if (info != null) {
0452: displayName = info.getDisplayName();
0453: }
0454:
0455: if (mainClass != null) {
0456: // TODO - compute mainclass
0457: FileObject fileObject = null;
0458: runRubyScript(fileObject, mainClass, displayName,
0459: context, COMMAND_DEBUG.equals(command), null);
0460: return;
0461: }
0462:
0463: // Default to running rake
0464: if (!gemManager.isValidRake(true)) {
0465: return;
0466: }
0467:
0468: RubyFileLocator fileLocator = new RubyFileLocator(context,
0469: project);
0470: File pwd = getSourceFolder(); // Or project directory?
0471: String classPath = project.evaluator().getProperty(
0472: RubyProjectProperties.JAVAC_CLASSPATH);
0473: new RubyExecution(new ExecutionDescriptor(platform,
0474: displayName, pwd, gemManager.getRake())
0475: .fileLocator(fileLocator).allowInput().classPath(
0476: classPath).appendJdkToPath(
0477: platform.isJRuby())
0478: .addStandardRecognizers().addOutputRecognizer(
0479: RubyExecution.RUBY_TEST_OUTPUT), project
0480: .evaluator().getProperty(
0481: RubyProjectProperties.SOURCE_ENCODING))
0482: .run();
0483:
0484: return;
0485: } else if (COMMAND_RUN_SINGLE.equals(command)
0486: || COMMAND_DEBUG_SINGLE.equals(command)) {
0487: if (!platform.isValidRuby(true)) {
0488: return;
0489: }
0490:
0491: FileObject file = getCurrentFile(context);
0492:
0493: if (RakeSupport.isRakeFile(file)) {
0494: if (!gemManager.isValidRake(true)) {
0495: return;
0496: }
0497:
0498: // Save all files first - this rake file could be accessing other files
0499: LifecycleManager.getDefault().saveAll();
0500: RakeSupport rake = new RakeSupport(project);
0501: rake.runRake(null, file, file.getName(),
0502: new RubyFileLocator(context, project), true,
0503: COMMAND_DEBUG_SINGLE.equals(command));
0504: return;
0505: }
0506:
0507: RSpecSupport rspec = new RSpecSupport(project);
0508: if (rspec.isRSpecInstalled()
0509: && RSpecSupport.isSpecFile(file)) {
0510: // Save all files first - this rake file could be accessing other files
0511: LifecycleManager.getDefault().saveAll();
0512: rspec.runRSpec(null, file, file.getName(),
0513: new RubyFileLocator(context, project), true,
0514: COMMAND_DEBUG_SINGLE.equals(command));
0515: return;
0516: }
0517:
0518: saveFile(file);
0519:
0520: //String target = FileUtil.getRelativePath(getRoot(project.getSourceRoots().getRoots(),file), file);
0521: runRubyScript(file,
0522: FileUtil.toFile(file).getAbsolutePath(), file
0523: .getNameExt(), context,
0524: COMMAND_DEBUG_SINGLE.equals(command), null);
0525: return;
0526: } else if (COMMAND_REBUILD.equals(command)) {
0527: if (!gemManager.isValidRake(true)) {
0528: return;
0529: }
0530:
0531: // Save all files first
0532: LifecycleManager.getDefault().saveAll();
0533:
0534: String displayName = NbBundle.getMessage(
0535: RubyActionProvider.class, "Rake");
0536: File pwd = getSourceFolder(); // Or project directory
0537:
0538: Runnable finishedAction = new Runnable() {
0539: public void run() {
0540: invokeAction(COMMAND_BUILD, context);
0541: }
0542: };
0543:
0544: String classPath = project.evaluator().getProperty(
0545: RubyProjectProperties.JAVAC_CLASSPATH);
0546: new RubyExecution(new ExecutionDescriptor(platform,
0547: displayName, pwd, gemManager.getRake())
0548: .additionalArgs("clean")
0549: . // NOI18N
0550: postBuild(finishedAction).allowInput().classPath(
0551: classPath).appendJdkToPath(
0552: platform.isJRuby()).fileLocator(
0553: new RubyFileLocator(context, project))
0554: .addStandardRecognizers(), project.evaluator()
0555: .getProperty(RubyProjectProperties.SOURCE_ENCODING))
0556: .run();
0557: return;
0558:
0559: } else if (COMMAND_BUILD.equals(command)) {
0560: if (!gemManager.isValidRake(true)) {
0561: return;
0562: }
0563:
0564: // Save all files first
0565: LifecycleManager.getDefault().saveAll();
0566:
0567: String displayName = NbBundle.getMessage(
0568: RubyActionProvider.class, "Rake");
0569: File pwd = getSourceFolder(); // Or project directory?
0570: String classPath = project.evaluator().getProperty(
0571: RubyProjectProperties.JAVAC_CLASSPATH);
0572: new RubyExecution(new ExecutionDescriptor(platform,
0573: displayName, pwd, gemManager.getRake())
0574: .allowInput().classPath(classPath).appendJdkToPath(
0575: platform.isJRuby()).fileLocator(
0576: new RubyFileLocator(context, project))
0577: .addStandardRecognizers(), project.evaluator()
0578: .getProperty(RubyProjectProperties.SOURCE_ENCODING))
0579: .run();
0580: return;
0581: } else if (COMMAND_CLEAN.equals(command)) {
0582: if (!gemManager.isValidRake(true)) {
0583: return;
0584: }
0585:
0586: //RubyFileLocator fileLocator = new RubyFileLocator(context);
0587: String displayName = NbBundle.getMessage(
0588: RubyActionProvider.class, "Rake");
0589:
0590: File pwd = getSourceFolder(); // Or project directory?
0591: String classPath = project.evaluator().getProperty(
0592: RubyProjectProperties.JAVAC_CLASSPATH);
0593: new RubyExecution(new ExecutionDescriptor(platform,
0594: displayName, pwd, gemManager.getRake())
0595: .additionalArgs("clean")
0596: . // NOI18N
0597: allowInput().classPath(classPath).appendJdkToPath(
0598: platform.isJRuby()).fileLocator(
0599: new RubyFileLocator(context, project))
0600: .addStandardRecognizers(), project.evaluator()
0601: .getProperty(RubyProjectProperties.SOURCE_ENCODING))
0602: .run();
0603: return;
0604: }
0605:
0606: if (COMMAND_RDOC.equals(command)) {
0607: LifecycleManager.getDefault().saveAll();
0608: File pwd = FileUtil.toFile(project.getProjectDirectory());
0609:
0610: Runnable showBrowser = new Runnable() {
0611: public void run() {
0612: // TODO - wait for the file to be created
0613: // Open brower on the doc directory
0614: FileObject doc = project.getProjectDirectory()
0615: .getFileObject("doc"); // NOI18N
0616: if (doc != null) {
0617: FileObject index = doc
0618: .getFileObject("index.html"); // NOI18N
0619: if (index != null) {
0620: try {
0621: URL url = FileUtil.toFile(index)
0622: .toURI().toURL();
0623:
0624: HtmlBrowser.URLDisplayer.getDefault()
0625: .showURL(url);
0626: } catch (MalformedURLException ex) {
0627: ErrorManager.getDefault().notify(ex);
0628: }
0629: }
0630: }
0631: }
0632: };
0633:
0634: RubyFileLocator fileLocator = new RubyFileLocator(context,
0635: project);
0636: String displayName = NbBundle.getMessage(
0637: RubyActionProvider.class, "RubyDocumentation");
0638:
0639: new RubyExecution(
0640: new ExecutionDescriptor(platform, displayName, pwd)
0641: .
0642: //gemManager.getRDoc()).
0643: additionalArgs("-r", "rdoc/rdoc", "-e",
0644: "begin; r = RDoc::RDoc.new; r.document(ARGV); end")
0645: .fileLocator(fileLocator).postBuild(
0646: showBrowser)
0647: .addStandardRecognizers(),
0648: project.evaluator().getProperty(
0649: RubyProjectProperties.SOURCE_ENCODING))
0650: .run();
0651:
0652: return;
0653: }
0654:
0655: if (COMMAND_AUTOTEST.equals(command)) {
0656: if (AutoTestSupport.isInstalled(project)) {
0657: AutoTestSupport support = new AutoTestSupport(context,
0658: project, project.evaluator().getProperty(
0659: RubyProjectProperties.SOURCE_ENCODING));
0660: support.setClassPath(project.evaluator().getProperty(
0661: RubyProjectProperties.JAVAC_CLASSPATH));
0662: support.start();
0663: }
0664:
0665: return;
0666: }
0667:
0668: if (COMMAND_TEST_SINGLE.equals(command)
0669: || COMMAND_DEBUG_TEST_SINGLE.equals(command)) {
0670: if (!platform.isValidRuby(true)) {
0671: return;
0672: }
0673:
0674: // Run test normally - don't pop up browser
0675: FileObject file = getCurrentFile(context);
0676:
0677: if (file == null) {
0678: return;
0679: }
0680:
0681: saveFile(file);
0682:
0683: // If we try to "test" a file that has a corresponding test file,
0684: // run/debug the test file instead
0685: DeclarationLocation location = new GotoTest().findTest(
0686: file, -1);
0687: if (location != DeclarationLocation.NONE) {
0688: file = location.getFileObject();
0689: // Save the test file too
0690: saveFile(file);
0691: }
0692:
0693: boolean isDebug = COMMAND_DEBUG_TEST_SINGLE.equals(command);
0694:
0695: RSpecSupport rspec = new RSpecSupport(project);
0696: if (rspec.isRSpecInstalled()
0697: && RSpecSupport.isSpecFile(file)) {
0698: rspec.runRSpec(null, file, file.getName(),
0699: new RubyFileLocator(context, project), true,
0700: isDebug);
0701: return;
0702: }
0703:
0704: runRubyScript(file,
0705: FileUtil.toFile(file).getAbsolutePath(), file
0706: .getNameExt(), context, isDebug,
0707: new OutputRecognizer[] { new TestNotifier(true,
0708: true) });
0709: }
0710:
0711: if (COMMAND_TEST.equals(command)) {
0712: if (!platform.isValidRuby(true)) {
0713: return;
0714: }
0715:
0716: FileObject[] files = findTestSourcesForSources(context);
0717:
0718: //return;
0719: throw new RuntimeException("Not yet implemented");
0720: }
0721:
0722: if (COMMAND_IRB_CONSOLE.equals(command)) {
0723: openIrbConsole(context);
0724: return;
0725: }
0726:
0727: if (COMMAND_DELETE.equals(command)) {
0728: DefaultProjectOperations
0729: .performDefaultDeleteOperation(project);
0730: return;
0731: }
0732:
0733: if (COMMAND_COPY.equals(command)) {
0734: DefaultProjectOperations
0735: .performDefaultCopyOperation(project);
0736: return;
0737: }
0738:
0739: if (COMMAND_MOVE.equals(command)) {
0740: DefaultProjectOperations
0741: .performDefaultMoveOperation(project);
0742: return;
0743: }
0744:
0745: if (COMMAND_RENAME.equals(command)) {
0746: DefaultProjectOperations.performDefaultRenameOperation(
0747: project, null);
0748: return;
0749: }
0750: }
0751:
0752: // /**
0753: // * @return array of targets or null to stop execution; can return empty array
0754: // */
0755: // private String[] getTargetNames(String command, Lookup context, Properties p) throws IllegalArgumentException {
0756: // String[] targetNames = new String[0];
0757: // if ( command.equals( COMMAND_COMPILE_SINGLE ) ) {
0758: // throw new RuntimeException("Not yet implemented");
0759: // FileObject[] sourceRoots = project.getSourceRoots().getRoots();
0760: // FileObject[] files = findSourcesAndPackages( context, sourceRoots);
0761: // boolean recursive = (context.lookup(NonRecursiveFolder.class) == null);
0762: // if (files != null) {
0763: // p.setProperty("javac.includes", ActionUtils.antIncludesList(files, getRoot(sourceRoots,files[0]), recursive)); // NOI18N
0764: // targetNames = new String[] {"compile-single"}; // NOI18N
0765: // }
0766: // else {
0767: // FileObject[] testRoots = project.getTestSourceRoots().getRoots();
0768: // files = findSourcesAndPackages(context, testRoots);
0769: // p.setProperty("javac.includes", ActionUtils.antIncludesList(files, getRoot(testRoots,files[0]), recursive)); // NOI18N
0770: // targetNames = new String[] {"compile-test-single"}; // NOI18N
0771: // }
0772: // }
0773: // else if ( command.equals( COMMAND_TEST_SINGLE ) ) {
0774: // FileObject[] files = findTestSourcesForSources(context);
0775: // targetNames = setupTestSingle(p, files);
0776: // }
0777: // else if ( command.equals( COMMAND_DEBUG_TEST_SINGLE ) ) {
0778: // FileObject[] files = findTestSourcesForSources(context);
0779: // targetNames = setupDebugTestSingle(p, files);
0780: // } else if (command.equals (COMMAND_RUN_SINGLE) || command.equals (COMMAND_DEBUG_SINGLE)) {
0781: // FileObject[] files = findTestSources(context, false);
0782: // if (files != null) {
0783: // if (command.equals(COMMAND_RUN_SINGLE)) {
0784: // targetNames = setupTestSingle(p, files);
0785: // } else {
0786: // targetNames = setupDebugTestSingle(p, files);
0787: // }
0788: // } else {
0789: // FileObject file = findSources(context)[0];
0790: // String clazz = FileUtil.getRelativePath(getRoot(project.getSourceRoots().getRoots(),file), file);
0791: //// p.setProperty("javac.includes", clazz); // NOI18N
0792: // // Convert foo/FooTest.java -> foo.FooTest
0793: // // XXX What about Ruby?
0794: // if (clazz.endsWith(".java")) { // NOI18N
0795: // clazz = clazz.substring(0, clazz.length() - 5);
0796: // }
0797: // clazz = clazz.replace('/','.');
0798: //
0799: // if (!RubyProjectUtil.hasMainMethod (file)) {
0800: // NotifyDescriptor nd = new NotifyDescriptor.Message(NbBundle.getMessage(RubyActionProvider.class, "LBL_No_Main_Classs_Found", clazz), NotifyDescriptor.INFORMATION_MESSAGE);
0801: // DialogDisplayer.getDefault().notify(nd);
0802: // return null;
0803: // } else {
0804: // if (command.equals (COMMAND_RUN_SINGLE)) {
0805: // p.setProperty("run.class", clazz); // NOI18N
0806: // targetNames = (String[])commands.get(COMMAND_RUN_SINGLE);
0807: // } else {
0808: // p.setProperty("debug.class", clazz); // NOI18N
0809: // targetNames = (String[])commands.get(COMMAND_DEBUG_SINGLE);
0810: // }
0811: // }
0812: // }
0813: // }
0814: // return targetNames;
0815: // }
0816:
0817: public boolean isActionEnabled(String command, Lookup context) {
0818: // FileObject buildXml = findBuildXml();
0819: // if ( buildXml == null || !buildXml.isValid()) {
0820: // return false;
0821: // }
0822: if (command.equals(COMMAND_COMPILE_SINGLE)) {
0823: return findSourcesAndPackages(context, project
0824: .getSourceRoots().getRoots()) != null
0825: || findSourcesAndPackages(context, project
0826: .getTestSourceRoots().getRoots()) != null;
0827: // }
0828: // else if ( command.equals( COMMAND_TEST_SINGLE ) ) {
0829: // return findTestSourcesForSources(context) != null;
0830: // }
0831: // else if ( command.equals( COMMAND_DEBUG_TEST_SINGLE ) ) {
0832: // FileObject[] files = findTestSourcesForSources(context);
0833: // return files != null && files.length == 1;
0834: } else if (command.equals(COMMAND_RUN_SINGLE)
0835: || command.equals(COMMAND_DEBUG_SINGLE)) {
0836: FileObject fos[] = findSources(context);
0837: if (fos != null && fos.length == 1) {
0838: return true;
0839: }
0840: fos = findTestSources(context, false);
0841: return fos != null && fos.length == 1;
0842: } else {
0843: // other actions are global
0844: return true;
0845: }
0846: }
0847:
0848: // Private methods -----------------------------------------------------------------
0849:
0850: /** Find selected sources, the sources has to be under single source root,
0851: * @param context the lookup in which files should be found
0852: * @return The file objects in the sources folder
0853: */
0854: private FileObject[] findSources(Lookup context) {
0855: FileObject[] srcPath = project.getSourceRoots().getRoots();
0856: for (int i = 0; i < srcPath.length; i++) {
0857: FileObject[] files = findSelectedFiles(context, srcPath[i],
0858: RubyInstallation.RUBY_MIME_TYPE, true); // NOI18N
0859: if (files != null) {
0860: return files;
0861: }
0862: }
0863: return null;
0864: }
0865:
0866: private FileObject[] findSourcesAndPackages(Lookup context,
0867: FileObject srcDir) {
0868: if (srcDir != null) {
0869: FileObject[] files = findSelectedFiles(context, srcDir,
0870: null, true); // NOI18N
0871: //Check if files are either packages or Ruby files
0872: if (files != null) {
0873: for (int i = 0; i < files.length; i++) {
0874: if (!files[i].isFolder()
0875: && files[i].getMIMEType().equals(
0876: RubyInstallation.RUBY_MIME_TYPE)) {
0877: return null;
0878: }
0879: }
0880: }
0881: return files;
0882: } else {
0883: return null;
0884: }
0885: }
0886:
0887: private FileObject[] findSourcesAndPackages(Lookup context,
0888: FileObject[] srcRoots) {
0889: for (int i = 0; i < srcRoots.length; i++) {
0890: FileObject[] result = findSourcesAndPackages(context,
0891: srcRoots[i]);
0892: if (result != null) {
0893: return result;
0894: }
0895: }
0896: return null;
0897: }
0898:
0899: /** Find either selected tests or tests which belong to selected source files
0900: */
0901: private FileObject[] findTestSources(Lookup context,
0902: boolean checkInSrcDir) {
0903: //XXX: Ugly, should be rewritten
0904: FileObject[] testSrcPath = project.getTestSourceRoots()
0905: .getRoots();
0906: for (int i = 0; i < testSrcPath.length; i++) {
0907: FileObject[] files = findSelectedFiles(context,
0908: testSrcPath[i], RubyInstallation.RUBY_MIME_TYPE,
0909: true); // NOI18N
0910: if (files != null) {
0911: return files;
0912: }
0913: }
0914: // if (checkInSrcDir && testSrcPath.length>0) {
0915: // FileObject[] files = findSources (context);
0916: // if (files != null) {
0917: // //Try to find the test under the test roots
0918: // FileObject srcRoot = getRoot(project.getSourceRoots().getRoots(),files[0]);
0919: // for (int i=0; i<testSrcPath.length; i++) {
0920: // FileObject[] files2 = ActionUtils.regexpMapFiles(files,srcRoot, SRCDIRJAVA, testSrcPath[i], SUBST, true);
0921: // if (files2 != null) {
0922: // return files2;
0923: // }
0924: // }
0925: // }
0926: // }
0927: return null;
0928: }
0929:
0930: /** Find tests corresponding to selected sources.
0931: */
0932: private FileObject[] findTestSourcesForSources(Lookup context) {
0933: FileObject[] sourceFiles = findSources(context);
0934: if (sourceFiles == null) {
0935: return null;
0936: }
0937: FileObject[] testSrcPath = project.getTestSourceRoots()
0938: .getRoots();
0939: if (testSrcPath.length == 0) {
0940: return null;
0941: }
0942: FileObject[] srcPath = project.getSourceRoots().getRoots();
0943: FileObject srcDir = getRoot(srcPath, sourceFiles[0]);
0944: // for (int i=0; i<testSrcPath.length; i++) {
0945: // FileObject[] files2 = ActionUtils.regexpMapFiles(sourceFiles, srcDir, SRCDIRJAVA, testSrcPath[i], SUBST, true);
0946: // if (files2 != null) {
0947: // return files2;
0948: // }
0949: // }
0950: // return null;
0951: return new FileObject[] { srcDir }; // XXX This is bogus
0952: }
0953:
0954: private FileObject getRoot(FileObject[] roots, FileObject file) {
0955: assert file != null : "File can't be null"; //NOI18N
0956: FileObject srcDir = null;
0957: for (int i = 0; i < roots.length; i++) {
0958: assert roots[i] != null : "Source Path Root can't be null"; //NOI18N
0959: if (FileUtil.isParentOf(roots[i], file)
0960: || roots[i].equals(file)) {
0961: srcDir = roots[i];
0962: break;
0963: }
0964: }
0965: return srcDir;
0966: }
0967:
0968: private static enum MainClassStatus {
0969: SET_AND_VALID, SET_BUT_INVALID, UNSET
0970: }
0971:
0972: /**
0973: * Tests if the main class is set
0974: * @param sourcesRoots source roots
0975: * @param mainClass main class name
0976: * @return status code
0977: */
0978: private MainClassStatus isSetMainClass(FileObject[] sourcesRoots,
0979: String mainClass) {
0980:
0981: // support for unit testing
0982: if (MainClassChooser.unitTestingSupport_hasMainMethodResult != null) {
0983: return MainClassChooser.unitTestingSupport_hasMainMethodResult ? MainClassStatus.SET_AND_VALID
0984: : MainClassStatus.SET_BUT_INVALID;
0985: }
0986:
0987: if (mainClass == null || mainClass.length() == 0) {
0988: return MainClassStatus.UNSET;
0989: }
0990:
0991: //ClassPath classPath = ClassPath.getClassPath (sourcesRoots[0], ClassPath.EXECUTE); //Single compilation unit
0992: if (RubyProjectUtil.isMainClass(mainClass, sourcesRoots)) {
0993: return MainClassStatus.SET_AND_VALID;
0994: }
0995: return MainClassStatus.SET_BUT_INVALID;
0996: }
0997:
0998: /**
0999: * Asks user for name of main class
1000: * @param mainClass current main class
1001: * @param projectName the name of project
1002: * @param ep project.properties to possibly edit
1003: * @param messgeType type of dialog
1004: * @return true if user selected main class
1005: */
1006: private boolean showMainClassWarning(String mainClass,
1007: String projectName, EditableProperties ep,
1008: MainClassStatus messageType) {
1009: boolean canceled;
1010: final JButton okButton = new JButton(NbBundle.getMessage(
1011: RubyActionProvider.class,
1012: "LBL_MainClassWarning_ChooseMainClass_OK")); // NOI18N
1013: okButton.getAccessibleContext().setAccessibleDescription(
1014: NbBundle.getMessage(RubyActionProvider.class,
1015: "AD_MainClassWarning_ChooseMainClass_OK"));
1016:
1017: // main class goes wrong => warning
1018: String message;
1019: switch (messageType) {
1020: case UNSET:
1021: message = MessageFormat.format(NbBundle.getMessage(
1022: RubyActionProvider.class, "LBL_MainClassNotFound"),
1023: new Object[] { projectName });
1024: break;
1025: case SET_BUT_INVALID:
1026: message = MessageFormat.format(NbBundle.getMessage(
1027: RubyActionProvider.class, "LBL_MainClassWrong"),
1028: new Object[] { mainClass, projectName });
1029: break;
1030: default:
1031: throw new IllegalArgumentException();
1032: }
1033: final MainClassWarning panel = new MainClassWarning(message,
1034: project.getSourceRoots().getRoots());
1035: Object[] options = new Object[] { okButton,
1036: DialogDescriptor.CANCEL_OPTION };
1037:
1038: panel.addChangeListener(new ChangeListener() {
1039: public void stateChanged(ChangeEvent e) {
1040: if (e.getSource() instanceof MouseEvent
1041: && MouseUtils.isDoubleClick(((MouseEvent) e
1042: .getSource()))) {
1043: // click button and the finish dialog with selected class
1044: okButton.doClick();
1045: } else {
1046: okButton
1047: .setEnabled(panel.getSelectedMainClass() != null);
1048: }
1049: }
1050: });
1051:
1052: okButton.setEnabled(false);
1053: DialogDescriptor desc = new DialogDescriptor(panel,
1054: NbBundle.getMessage(RubyActionProvider.class,
1055: "CTL_MainClassWarning_Title", ProjectUtils
1056: .getInformation(project)
1057: .getDisplayName()), // NOI18N
1058: true, options, options[0],
1059: DialogDescriptor.BOTTOM_ALIGN, null, null);
1060: desc.setMessageType(DialogDescriptor.INFORMATION_MESSAGE);
1061: Dialog dlg = DialogDisplayer.getDefault().createDialog(desc);
1062: dlg.setVisible(true);
1063: if (desc.getValue() != options[0]) {
1064: canceled = true;
1065: } else {
1066: mainClass = panel.getSelectedMainClass();
1067: canceled = false;
1068: ep.put(RubyProjectProperties.MAIN_CLASS,
1069: mainClass == null ? "" : mainClass);
1070: }
1071: dlg.dispose();
1072:
1073: return canceled;
1074: }
1075:
1076: // From the ant module - ActionUtils.
1077: // However, I've modified it to do its search based on mime type rather than file suffixes
1078: // (since some Ruby files do not use a .rb extension and are discovered based on the initial shebang line)
1079:
1080: public static FileObject[] findSelectedFiles(Lookup context,
1081: FileObject dir, String mimeType, boolean strict) {
1082: if (dir != null && !dir.isFolder()) {
1083: throw new IllegalArgumentException("Not a folder: " + dir); // NOI18N
1084: }
1085: Collection<FileObject> files = new LinkedHashSet<FileObject>(); // #50644: remove dupes
1086: // XXX this should perhaps also check for FileObject's...
1087: for (DataObject d : context.lookupAll(DataObject.class)) {
1088: FileObject f = d.getPrimaryFile();
1089: boolean matches = FileUtil.toFile(f) != null;
1090: if (dir != null) {
1091: matches &= (FileUtil.isParentOf(dir, f) || dir == f);
1092: }
1093: if (mimeType != null) {
1094: matches &= f.getMIMEType().equals(mimeType);
1095: }
1096: // Generally only files from one project will make sense.
1097: // Currently the action UI infrastructure (PlaceHolderAction)
1098: // checks for that itself. Should there be another check here?
1099: if (matches) {
1100: files.add(f);
1101: } else if (strict) {
1102: return null;
1103: }
1104: }
1105: if (files.isEmpty()) {
1106: return null;
1107: }
1108: return files.toArray(new FileObject[files.size()]);
1109: }
1110:
1111: private File getSourceFolder() {
1112: // Default to using the project source directory
1113: FileObject[] srcPath = project.getSourceRoots().getRoots();
1114: if (srcPath != null && srcPath.length > 0) {
1115: return FileUtil.toFile(srcPath[0]);
1116: } else {
1117: return FileUtil.toFile(project.getProjectDirectory());
1118: }
1119: }
1120:
1121: private String[] getApplicationArguments() {
1122: String applicationArgs = project.evaluator().getProperty(
1123: RubyProjectProperties.APPLICATION_ARGS);
1124: return (applicationArgs == null || applicationArgs.trim()
1125: .length() == 0) ? null : Utilities
1126: .parseParameters(applicationArgs);
1127: }
1128:
1129: }
|