/* * 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]" * * Contributor(s): * * The Original Software is NetBeans. The Initial Developer of the Original * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 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.modules.ruby.railsprojects; import java.awt.Dialog; import java.awt.event.ActionEvent; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.HashMap; import java.util.Map; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.swing.JButton; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.text.Document; import javax.swing.text.JTextComponent; import org.netbeans.api.extexecution.ExecutionDescriptor; import org.netbeans.api.project.FileOwnerQuery; import org.netbeans.api.project.Project; import org.netbeans.api.project.ProjectUtils; import org.netbeans.api.extexecution.print.ConvertedLine; import org.netbeans.modules.ruby.rhtml.lexer.api.RhtmlTokenId; import org.netbeans.api.ruby.platform.RubyInstallation; import org.netbeans.api.ruby.platform.RubyPlatform; import org.netbeans.editor.BaseAction; import org.netbeans.api.extexecution.ExecutionService; import org.netbeans.api.extexecution.input.InputProcessor; import org.netbeans.api.extexecution.input.InputProcessors; import org.netbeans.api.extexecution.input.LineProcessors; import org.netbeans.api.extexecution.print.LineConvertor; import org.netbeans.api.extexecution.print.LineConvertors; import org.netbeans.api.extexecution.print.LineConvertors.FileLocator; import org.netbeans.modules.ruby.railsprojects.ui.customizer.RailsProjectProperties; import org.netbeans.modules.ruby.platform.execution.DirectoryFileLocator; import org.netbeans.modules.ruby.platform.execution.ExecutionUtils.FileLocation; import org.netbeans.modules.ruby.platform.execution.RubyExecutionDescriptor; import org.netbeans.modules.ruby.platform.execution.OutputProcessor; import org.netbeans.modules.ruby.platform.execution.RubyLineConvertorFactory; import org.netbeans.modules.ruby.platform.execution.RubyProcessCreator; import org.netbeans.modules.ruby.railsprojects.Generator.Script; import org.openide.DialogDescriptor; import org.openide.DialogDisplayer; import org.openide.ErrorManager; import org.openide.filesystems.FileObject; import org.openide.filesystems.FileSystem; import org.openide.filesystems.FileUtil; import org.openide.loaders.DataObject; import org.openide.nodes.Node; import org.openide.util.Exceptions; import org.openide.util.HelpCtx; import org.openide.util.Lookup; import org.openide.util.NbBundle; import org.openide.util.Utilities; import org.openide.util.actions.NodeAction; import org.openide.util.actions.SystemAction; public final class GenerateAction extends NodeAction { public static final String EDITOR_ACTION_NAME = "rails-generator"; private static final Logger LOGGER = Logger.getLogger(GenerateAction.class.getName()); private boolean forcing; private boolean preview; /** Editor action which lets you open the dialog as an editor action */ public static class EditorAction extends BaseAction { public EditorAction() { super(EDITOR_ACTION_NAME, 0); } @Override public void actionPerformed(ActionEvent evt, final JTextComponent target) { SystemAction.get(GenerateAction.class).actionPerformed(evt, target); } @Override public Class getShortDescriptionBundleClass() { return GenerateAction.class; } } @Override protected void performAction(Node[] activatedNodes) { Lookup lookup = activatedNodes[0].getLookup(); RailsProject project = lookup.lookup(RailsProject.class); if (project == null) { DataObject dataObject = lookup.lookup(DataObject.class); if (dataObject == null) { return; } Project p = FileOwnerQuery.getOwner(dataObject.getPrimaryFile()); if (p instanceof RailsProject) { project = (RailsProject)p; } } if (project == null) { return; } // if (!RubyInstallation.getInstance().isValidRuby(true)) { // return; // } // #141908 -- check whether rails is installed in vendor/ FileObject railsInstall = project.getProjectDirectory().getFileObject("vendor/rails/railties"); // NOI18N RubyPlatform platform = RubyPlatform.platformFor(project); if (railsInstall == null && !platform.hasValidRails(true)) { LOGGER.warning("No valid Rails installation found, platform is:" + RubyPlatform.platformFor(project)); return; } Generator generator = activatedNodes[0].getLookup().lookup(Generator.class); if (generator == null) { generator = Generator.CONTROLLER; } generate(project, generator, null, null, false, false); } public void generate(Project project, String generatorName, String name, String params) { assert generatorName.equals("controller") : "Only the controller generator is supported"; // NOI18N Generator generator = Generator.CONTROLLER; if (project != null) { generate((RailsProject)project, generator, name, params, true, true); } else { assert false; } } public void generate(final RailsProject project, Generator generator, String initialName, String initialParams, boolean initialEnabled, boolean noOverwrite) { boolean cancelled; final JButton okButton = new JButton(NbBundle.getMessage(GenerateAction.class, "Ok")); okButton.getAccessibleContext() .setAccessibleDescription(NbBundle.getMessage(GenerateAction.class, "AD_Ok")); final GeneratorPanel panel = new GeneratorPanel(project, generator); if (noOverwrite) { panel.setForcing(false); } else { panel.setForcing(forcing); } panel.setPretend(preview); if (initialName != null) { panel.setInitialState(initialName, initialParams); } Object[] options = new Object[] { okButton, DialogDescriptor.CANCEL_OPTION }; panel.setChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent e) { okButton.setEnabled(panel.isDataValid()); } }); okButton.setEnabled(initialEnabled); String projectName = ProjectUtils.getInformation(project).getDisplayName(); DialogDescriptor desc = new DialogDescriptor(panel, NbBundle.getMessage(GenerateAction.class, "GeneratorTitle", projectName), true, options, options[0], DialogDescriptor.DEFAULT_ALIGN, null, null); desc.setMessageType(DialogDescriptor.PLAIN_MESSAGE); Dialog dlg = DialogDisplayer.getDefault().createDialog(desc); dlg.getAccessibleContext().setAccessibleDescription(NbBundle.getMessage(GenerateAction.class, "AD_GeneratorDialog")); dlg.setVisible(true); if (desc.getValue() != options[0]) { cancelled = true; } else { cancelled = false; } dlg.dispose(); if (!noOverwrite) { forcing = panel.isForce(); // Persist state for next invocation (this session only) } preview = panel.isPretend(); if (!cancelled) { final String type = panel.getType(); if (type.length() > 0) { // TODO: Toggle OK state based on valid entry final FileObject dir = project.getProjectDirectory(); final File pwd = FileUtil.toFile(project.getProjectDirectory()); final Script generatorScript = panel.getScript(); final String scriptToRun = "script" + File.separator + generatorScript.script; // NOI18N List<String> argvList = new ArrayList<String>(); argvList.addAll(generatorScript.args); argvList.add(type); String[] names = Utilities.parseParameters(panel.getGeneratedName()); if (names != null) { for (String name : names) { argvList.add(name); } } String[] firstParameterList = panel.getFirstParameterList(); if (firstParameterList != null && firstParameterList.length > 0 && firstParameterList[0].length() > 0) { for (String parameter : firstParameterList) { argvList.add(parameter); } String[] remainingParameters = panel.getSecondParameterList(); if (remainingParameters != null && remainingParameters.length >0 && remainingParameters[0].length() > 0) { for (String parameter : remainingParameters) { argvList.add(parameter); } } } if (panel.isForce()) { argvList.add("--force"); // NOI18N } else { argvList.add("--skip"); // NOI18N } if (panel.isPretend()) { argvList.add("--pretend"); // NOI18N } final String[] argv = argvList.toArray(new String[argvList.size()]); try { final String charsetName = project.evaluator().getProperty(RailsProjectProperties.SOURCE_ENCODING); project.getProjectDirectory().getFileSystem().runAtomicAction(new FileSystem.AtomicAction() { public void run() throws IOException { FileLocator locator = new DirectoryFileLocator(dir); StatefulConvertor convertor = new StatefulConvertor(locator, RailsProjectGenerator.RAILS_GENERATOR_PATTERN, RubyLineConvertorFactory.EXT_RE, 2, -1); String displayName = NbBundle.getMessage(GenerateAction.class, "RailsGenerator"); Map<String, String> env = new HashMap<String, String>(); String railsEnv = project.evaluator().getProperty(RailsProjectProperties.RAILS_ENV); if (railsEnv != null) { env.put("RAILS_ENV", railsEnv); } RubyExecutionDescriptor descriptor = new RubyExecutionDescriptor(RubyPlatform.platformFor(project), displayName, pwd, scriptToRun) .additionalArgs(argv) .fileLocator(locator) .addStandardRecognizers() .addOutConvertor(convertor) .addErrConvertor(convertor); descriptor.addAdditionalEnv(env); descriptor.setOutProcessorFactory(new ExecutionDescriptor.InputProcessorFactory() { @Override public InputProcessor newInputProcessor(InputProcessor defaultProcessor) { return InputProcessors.ansiStripping(defaultProcessor); } }); RubyProcessCreator rpc = new RubyProcessCreator(descriptor, charsetName); Future<Integer> execution = ExecutionService.newService(rpc, descriptor.toExecutionDescriptor(), displayName).run(); try { execution.get(); } catch (CancellationException ex) { // do nothing, the user cancelled the generator process } catch (InterruptedException ex) { Exceptions.printStackTrace(ex); } catch (ExecutionException ex) { Exceptions.printStackTrace(ex); } project.getProjectDirectory().getFileSystem().refresh(true); List<FileLocation> locations = convertor.getLocations(); List<FileObject> rubyFiles = new ArrayList<FileObject>(); List<FileObject> rhtmlFiles = new ArrayList<FileObject>(); // Process in reverse order such that first files in the list are added last // (so they will be on top) for (int i = locations.size() - 1; i >= 0; i--) { FileLocation loc = locations.get(i); String file = loc.file; if (file != null) { FileObject fo = locator.find(file); if (fo != null) { String mimeType = fo.getMIMEType(); if (mimeType.equals(RubyInstallation.RUBY_MIME_TYPE)) { rubyFiles.add(fo); } else if (mimeType.equals(RhtmlTokenId.MIME_TYPE)) { rhtmlFiles.add(fo); } } } } if (rhtmlFiles.size() <= 4) { for (FileObject fo : rhtmlFiles) { OutputProcessor.open(fo, 1); } } if (rubyFiles.size() <= 4) { for (FileObject fo : rubyFiles) { OutputProcessor.open(fo, 1); } } } }); project.getProjectDirectory().getFileSystem().refresh(true); } catch (IOException ioe) { ErrorManager.getDefault().notify(ioe); } } } } @Override public String getName() { return NbBundle.getMessage(GenerateAction.class, EDITOR_ACTION_NAME); } @Override protected boolean enable(Node[] activatedNodes) { if ((activatedNodes == null) || (activatedNodes.length != 1)) { return false; } Lookup lookup = activatedNodes[0].getLookup(); RailsProject project = lookup.lookup(RailsProject.class); if (project != null) { return true; } DataObject dataObject = lookup.lookup(DataObject.class); return dataObject != null; } @Override protected void initialize() { super.initialize(); // see org.openide.util.actions.SystemAction.iconResource() javadoc for more details putValue("noIconInMenu", Boolean.TRUE); // NOI18N } @Override public HelpCtx getHelpCtx() { return HelpCtx.DEFAULT_HELP; } @Override protected boolean asynchronous() { return true; } private static class StatefulConvertor implements LineConvertor { private final FileLocator locator; private final Pattern linePattern; private final Pattern filePattern; private final int fileGroup; private final int lineGroup; private final List<FileLocation> locations = new ArrayList<FileLocation>(); public StatefulConvertor(FileLocator locator, Pattern linePattern, Pattern filePattern, int fileGroup, int lineGroup) { this.locator = locator; this.linePattern = linePattern; this.fileGroup = fileGroup; this.lineGroup = lineGroup; this.filePattern = filePattern; } public synchronized List<ConvertedLine> convert(final String line) { // Don't try to match lines that are too long - the java.util.regex library // throws stack exceptions (101234) if (line.length() > 400) { return null; } Matcher match = linePattern.matcher(line); if (match.matches()) { String file = null; int lineno = -1; if (fileGroup >= 0) { file = match.group(fileGroup); // Make some adjustments - easier to do here than in the regular expression // (See 109721 and 109724 for example) if (file.startsWith("\"")) { // NOI18N file = file.substring(1); } if (file.startsWith("./")) { // NOI18N file = file.substring(2); } if (filePattern != null && !filePattern.matcher(file).matches()) { return null; } } if (lineGroup >= 0) { String linenoStr = match.group(lineGroup); try { lineno = Integer.parseInt(linenoStr); } catch (NumberFormatException nfe) { LOGGER.log(Level.INFO, null, nfe); lineno = 0; } } if (!line.trim().startsWith("skip")) { // NOI18N locations.add(new FileLocation(file, lineno)); } return Collections.<ConvertedLine>singletonList( ConvertedLine.forText(line, new FindFileListener(file, lineno, locator))); } return null; } synchronized List<FileLocation> getLocations() { return locations; } } public void actionPerformed(ActionEvent evt, JTextComponent target) { DataObject dobj = (DataObject)target.getDocument().getProperty(Document.StreamDescriptionProperty); if (dobj != null) { Node n = dobj.getNodeDelegate(); if (n != null) { Node[] nodes = new Node[] { n }; if (enable(nodes)) { performAction(nodes); } } } } public boolean appliesTo(String mimeType) { return RubyInstallation.RHTML_MIME_TYPE.equals(mimeType) || RubyInstallation.RUBY_MIME_TYPE.equals(mimeType); } }