001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.modules.junit;
043:
044: import java.awt.EventQueue;
045: import java.util.ArrayList;
046: import java.util.Collection;
047: import java.util.List;
048: import java.util.Map;
049: import javax.swing.Action;
050: import org.netbeans.api.java.classpath.ClassPath;
051: import org.netbeans.api.project.FileOwnerQuery;
052: import org.netbeans.api.project.Project;
053: import org.netbeans.api.project.SourceGroup;
054: import org.netbeans.modules.junit.plugin.JUnitPlugin;
055: import org.netbeans.modules.junit.plugin.JUnitPlugin.CreateTestParam;
056: import org.netbeans.spi.java.classpath.support.ClassPathSupport;
057: import org.openide.DialogDisplayer;
058: import org.openide.NotifyDescriptor;
059: import org.openide.filesystems.FileObject;
060: import org.openide.loaders.DataObject;
061: import org.openide.loaders.DataObjectNotFoundException;
062: import org.openide.nodes.Node;
063: import org.openide.util.HelpCtx;
064: import org.openide.util.NbBundle;
065: import org.openide.cookies.EditorCookie;
066: import org.openide.loaders.DataFolder;
067: import org.openide.util.RequestProcessor;
068:
069: /** Action sensitive to some cookie that does something useful.
070: *
071: * @author vstejskal, David Konecny
072: * @author Marian Petras
073: * @author Ondrej Rypacek
074: */
075: public final class CreateTestAction extends TestAction {
076:
077: public CreateTestAction() {
078: putValue("noIconInMenu", Boolean.TRUE); //NOI18N
079: }
080:
081: /* public members */
082: public String getName() {
083: return NbBundle.getMessage(CreateTestAction.class,
084: "LBL_Action_CreateTest"); //NOI18N
085: }
086:
087: public HelpCtx getHelpCtx() {
088: return new HelpCtx(CreateTestAction.class);
089: }
090:
091: @Override
092: protected void initialize() {
093: super .initialize();
094: putProperty(Action.SHORT_DESCRIPTION, NbBundle.getMessage(
095: CreateTestAction.class, "HINT_Action_CreateTest")); //NOI18N
096: }
097:
098: @Override
099: protected String iconResource() {
100: return "org/netbeans/modules/junit/resources/" //NOI18N
101: + "CreateTestActionIcon.gif"; //NOI18N
102: }
103:
104: @Override
105: protected boolean enable(Node[] nodes) {
106: if (nodes.length == 0) {
107: return false;
108: }
109:
110: /*
111: * In most cases, there is just one node selected - that is why
112: * this case is handled in a special, more effective way
113: * (no collections and iterators created).
114: */
115: if (nodes.length == 1) {
116: final Node node = nodes[0];
117: DataObject dataObj;
118: FileObject fileObj;
119: Project project;
120: if (((dataObj = node.getCookie(DataObject.class)) != null)
121: && ((fileObj = dataObj.getPrimaryFile()) != null)
122: && fileObj.isValid()
123: && ((project = FileOwnerQuery.getOwner(fileObj)) != null)
124: && (getSourceGroup(fileObj, project) != null)
125: && (TestUtil.isJavaFile(fileObj) || (node
126: .getCookie(DataFolder.class) != null))) {
127:
128: JUnitPlugin plugin = TestUtil
129: .getPluginForProject(project);
130: return JUnitPluginTrampoline.DEFAULT.canCreateTests(
131: plugin, fileObj);
132: } else {
133: return false;
134: }
135: }
136:
137: final Collection<FileObject> fileObjs = new ArrayList<FileObject>(
138: nodes.length);
139: Project theProject = null;
140: boolean result = false;
141: for (Node node : nodes) {
142: DataObject dataObj = node.getCookie(DataObject.class);
143: if (dataObj != null) {
144: FileObject fileObj = dataObj.getPrimaryFile();
145: if ((fileObj == null) || !fileObj.isValid()) {
146: continue;
147: }
148:
149: fileObjs.add(fileObj);
150:
151: Project prj = FileOwnerQuery.getOwner(fileObj);
152: if (prj != null) {
153: if (theProject == null) {
154: theProject = prj;
155: }
156: if (prj != theProject) {
157: return false; /* files from different projects */
158: }
159:
160: if ((getSourceGroup(fileObj, prj) != null)
161: && (TestUtil.isJavaFile(fileObj) || (node
162: .getCookie(DataFolder.class) != null))) {
163: result = true;
164: }
165: }
166: }
167: }
168:
169: if (theProject != null) {
170: JUnitPlugin plugin = TestUtil
171: .getPluginForProject(theProject);
172: result &= JUnitPluginTrampoline.DEFAULT.canCreateTests(
173: plugin, fileObjs.toArray(new FileObject[fileObjs
174: .size()]));
175: }
176:
177: return result;
178: }
179:
180: /**
181: * Checks that the selection of nodes the dialog is invoked on is valid.
182: * @return String message describing the problem found or null, if the
183: * selection is ok
184: */
185: private static String checkNodesValidity(Node[] nodes) {
186: FileObject[] files = getFiles(nodes);
187:
188: Project project = getProject(files);
189: if (project == null) {
190: return NbBundle.getMessage(CreateTestAction.class,
191: "MSG_multiproject_selection"); //NOI18N
192: }
193:
194: if (!checkPackages(files)) {
195: return NbBundle.getMessage(CreateTestAction.class,
196: "MSG_invalid_packages"); //NOI18N
197: }
198:
199: return null;
200: }
201:
202: /**
203: * Check that all the files (folders or java files) have correct java
204: * package names.
205: * @return true if all are fine
206: */
207: private static boolean checkPackages(FileObject[] files) {
208: if (files.length == 0) {
209: return true;
210: } else {
211: Project project = FileOwnerQuery.getOwner(files[0]);
212: for (int i = 0; i < files.length; i++) {
213: String packageName = getPackage(project, files[i]);
214: if ((packageName == null)
215: || !TestUtil.isValidPackageName(packageName)) {
216: return false;
217: }
218: }
219: return true;
220: }
221: }
222:
223: /**
224: * Get the package name of <code>file</code>.
225: *
226: * @param project owner of the file (for performance reasons)
227: * @param file the FileObject whose packagename to get
228: * @return package name of the file or null if it cannot be retrieved
229: */
230: private static String getPackage(Project project, FileObject file) {
231: SourceGroup srcGrp = TestUtil.findSourceGroupOwner(project,
232: file);
233: if (srcGrp != null) {
234: ClassPath cp = ClassPathSupport
235: .createClassPath(new FileObject[] { srcGrp
236: .getRootFolder() });
237: return cp.getResourceName(file, '.', false);
238: } else {
239: return null;
240: }
241: }
242:
243: private static FileObject[] getFiles(Node[] nodes) {
244: FileObject[] ret = new FileObject[nodes.length];
245: for (int i = 0; i < nodes.length; i++) {
246: ret[i] = TestUtil.getFileObjectFromNode(nodes[i]);
247: }
248: return ret;
249: }
250:
251: /**
252: * Get the single project for <code>nodes</code> if there is such.
253: * If the nodes belong to different projects or some of the nodes doesn't
254: * have a project, return null.
255: */
256: private static Project getProject(FileObject[] files) {
257: Project project = null;
258: for (int i = 0; i < files.length; i++) {
259: Project nodeProject = FileOwnerQuery.getOwner(files[i]);
260: if (project == null) {
261: project = nodeProject;
262: } else if (project != nodeProject) {
263: return null;
264: }
265: }
266: return project;
267: }
268:
269: @Override
270: protected void performAction(Node[] nodes) {
271: String problem;
272: if ((problem = checkNodesValidity(nodes)) != null) {
273: // TODO report problem
274: NotifyDescriptor msg = new NotifyDescriptor.Message(
275: problem, NotifyDescriptor.WARNING_MESSAGE);
276: DialogDisplayer.getDefault().notify(msg);
277: return;
278: }
279:
280: final FileObject[] filesToTest = getFileObjectsFromNodes(nodes);
281: if (filesToTest == null) {
282: return; //XXX: display some message
283: }
284:
285: /* Determine the plugin to be used: */
286: final JUnitPlugin plugin = TestUtil
287: .getPluginForProject(FileOwnerQuery
288: .getOwner(filesToTest[0]));
289:
290: if (!JUnitPluginTrampoline.DEFAULT.createTestActionCalled(
291: plugin, filesToTest)) {
292: return;
293: }
294:
295: // show configuration dialog
296: // when dialog is canceled, escape the action
297: JUnitCfgOfCreate cfg = new JUnitCfgOfCreate(nodes);
298: if (!cfg.configure()) {
299: return;
300: }
301:
302: /* Store the configuration data: */
303: final boolean singleClass = cfg.isSingleClass();
304: final Map<CreateTestParam, Object> params = TestUtil
305: .getSettingsMap(!singleClass);
306: if (singleClass) {
307: params.put(CreateTestParam.CLASS_NAME, cfg
308: .getTestClassName());
309: }
310: final FileObject targetFolder = cfg.getTargetFolder();
311: cfg = null;
312:
313: RequestProcessor.getDefault().post(new Runnable() {
314: public void run() {
315: /* Now create the tests: */
316: final FileObject[] testFileObjects = JUnitPluginTrampoline.DEFAULT
317: .createTests(plugin, filesToTest, targetFolder,
318: params);
319:
320: /* Open the created/updated test class if appropriate: */
321: if (testFileObjects.length == 1) {
322: try {
323: DataObject dobj = DataObject
324: .find(testFileObjects[0]);
325: final EditorCookie ec = dobj
326: .getCookie(EditorCookie.class);
327: if (ec != null) {
328: EventQueue.invokeLater(new Runnable() {
329: public void run() {
330: ec.open();
331: }
332: });
333: }
334: } catch (DataObjectNotFoundException ex) {
335: ex.printStackTrace();
336: }
337: }
338: }
339: });
340: }
341:
342: /**
343: * Extracts {@code FileObject}s from the given nodes.
344: * Nodes that have (direct or indirect) parent nodes among the given
345: * nodes are ignored.
346: *
347: * @return a non-empty array of {@code FileObject}s
348: * represented by the given nodes;
349: * or {@code null} if no {@code FileObject} was found;
350: */
351: private static FileObject[] getFileObjectsFromNodes(
352: final Node[] nodes) {
353: FileObject[] fileObjects = new FileObject[nodes.length];
354: List<FileObject> fileObjectsList = null;
355:
356: for (int i = 0; i < nodes.length; i++) {
357: final Node node = nodes[i];
358: final FileObject fo;
359: if (!hasParentAmongNodes(nodes, i)
360: && ((fo = getTestFileObject(node)) != null)) {
361: if (fileObjects != null) {
362: fileObjects[i] = fo;
363: } else {
364: if (fileObjectsList == null) {
365: fileObjectsList = new ArrayList<FileObject>(
366: nodes.length - i);
367: }
368: fileObjectsList.add(fo);
369: }
370: } else {
371: fileObjects = null; //signs that some FOs were skipped
372: }
373: }
374: if (fileObjects == null) {
375: if (fileObjectsList != null) {
376: fileObjects = fileObjectsList
377: .toArray(new FileObject[fileObjectsList.size()]);
378: fileObjectsList = null;
379: }
380: }
381:
382: return fileObjects;
383: }
384:
385: /**
386: * Grabs and checks a <code>FileObject</code> from the given node.
387: * If either the file could not be grabbed or the file does not pertain
388: * to any project, a message is displayed.
389: *
390: * @param node node to get a <code>FileObject</code> from.
391: * @return the grabbed <code>FileObject</code>,
392: * or <code>null</code> in case of failure
393: */
394: private static FileObject getTestFileObject(final Node node) {
395: final FileObject fo = TestUtil.getFileObjectFromNode(node);
396: if (fo == null) {
397: TestUtil.notifyUser(NbBundle
398: .getMessage(CreateTestAction.class,
399: "MSG_file_from_node_failed")); //NOI18N
400: return null;
401: }
402: ClassPath cp = ClassPath.getClassPath(fo, ClassPath.SOURCE);
403: if (cp == null) {
404: TestUtil.notifyUser(NbBundle.getMessage(
405: CreateTestAction.class, "MSG_no_project", //NOI18N
406: fo));
407: return null;
408: }
409: return fo;
410: }
411:
412: private static boolean hasParentAmongNodes(final Node[] nodes,
413: final int idx) {
414: Node node;
415:
416: node = nodes[idx].getParentNode();
417: while (null != node) {
418: for (int i = 0; i < nodes.length; i++) {
419: if (i == idx) {
420: continue;
421: }
422: if (node == nodes[i]) {
423: return true;
424: }
425: }
426: node = node.getParentNode();
427: }
428: return false;
429: }
430:
431: }
|