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 com.sun.source.tree.ClassTree;
045: import com.sun.source.tree.CompilationUnitTree;
046: import com.sun.source.tree.MethodTree;
047: import com.sun.source.tree.Tree;
048: import com.sun.source.util.SourcePositions;
049: import com.sun.source.util.TreePathScanner;
050: import com.sun.source.util.Trees;
051: import java.io.IOException;
052: import java.net.URL;
053: import java.util.ArrayList;
054: import java.util.List;
055: import java.util.logging.Level;
056: import java.util.logging.Level;
057: import java.util.logging.Logger;
058: import javax.lang.model.element.Element;
059: import javax.swing.Action;
060: import javax.swing.JEditorPane;
061: import javax.swing.text.StyledDocument;
062: import org.netbeans.api.java.classpath.ClassPath;
063: import org.netbeans.api.java.queries.UnitTestForSourceQuery;
064: import org.netbeans.api.java.source.CancellableTask;
065: import org.netbeans.api.java.source.CompilationController;
066: import org.netbeans.api.java.source.ElementHandle;
067: import org.netbeans.api.java.source.JavaSource;
068: import org.netbeans.api.java.source.JavaSource.Phase;
069: import org.netbeans.spi.java.classpath.PathResourceImplementation;
070: import org.netbeans.spi.java.classpath.support.ClassPathSupport;
071: import org.openide.ErrorManager;
072: import org.openide.cookies.EditorCookie;
073: import org.openide.cookies.LineCookie;
074: import org.openide.cookies.OpenCookie;
075: import org.openide.filesystems.FileObject;
076: import org.openide.loaders.DataObject;
077: import org.openide.loaders.DataObjectNotFoundException;
078: import org.openide.nodes.Node;
079: import org.openide.text.Line;
080: import org.openide.text.NbDocument;
081: import org.openide.util.HelpCtx;
082: import org.openide.util.NbBundle;
083:
084: /** Action sensitive to some DataFolder or SourceCookie cookie
085: * which tries to open JUnit test corresponding to the selected source file.
086: *
087: * @author Nathan W. Phelps, David Konecny
088: * @author Marian Petras
089: * @ version 1.0
090: */
091: @SuppressWarnings("serial")
092: public class OpenTestAction extends TestAction {
093:
094: public OpenTestAction() {
095: putValue("noIconInMenu", Boolean.TRUE);
096: }
097:
098: protected void performAction(Node[] nodes) {
099: FileObject selectedFO;
100:
101: for (int i = 0; i < nodes.length; i++) {
102: // get test class or suite class file, if it was not such one pointed by the node
103: selectedFO = TestUtil.getFileObjectFromNode(nodes[i]);
104: if (selectedFO == null) {
105: TestUtil.notifyUser(NbBundle.getMessage(
106: OpenTestAction.class,
107: "MSG_file_from_node_failed"));
108: continue;
109: }
110: ClassPath cp = ClassPath.getClassPath(selectedFO,
111: ClassPath.SOURCE);
112: if (cp == null) {
113: TestUtil.notifyUser(NbBundle.getMessage(
114: OpenTestAction.class, "MSG_no_project",
115: selectedFO));
116: continue;
117: }
118:
119: FileObject packageRoot = cp.findOwnerRoot(selectedFO);
120: URL[] testRoots = UnitTestForSourceQuery
121: .findUnitTests(packageRoot);
122: FileObject fileToOpen = null;
123: for (int j = 0; j < testRoots.length; j++) {
124: fileToOpen = findUnitTestInTestRoot(cp, selectedFO,
125: testRoots[j]);
126: if (fileToOpen != null)
127: break;
128: }
129:
130: if (fileToOpen != null) {
131: openFile(fileToOpen);
132: } else {
133: String testClsName = getTestName(cp, selectedFO)
134: .replace('/', '.');
135: String pkgName = cp.getResourceName(selectedFO, '.',
136: false);
137: boolean isPackage = selectedFO.isFolder();
138: boolean isDefPkg = isPackage && (pkgName.length() == 0);
139: String msgPattern = !isPackage ? "MSG_test_class_not_found" //NOI18N
140: : isDefPkg ? "MSG_testsuite_class_not_found_def_pkg" //NOI18N
141: : "MSG_testsuite_class_not_found"; //NOI18N
142:
143: String[] params = isDefPkg ? new String[] { testClsName }
144: : new String[] { testClsName, pkgName };
145:
146: TestUtil.notifyUser(NbBundle.getMessage(
147: OpenTestAction.class, msgPattern, params),
148: ErrorManager.INFORMATIONAL);
149: continue;
150: }
151: }
152: }
153:
154: private static FileObject findUnitTestInTestRoot(ClassPath cp,
155: FileObject selectedFO, URL testRoot) {
156: ClassPath testClassPath = null;
157: if (testRoot == null) { //no tests, use sources instead
158: testClassPath = cp;
159: } else {
160: try {
161: List<PathResourceImplementation> cpItems = new ArrayList<PathResourceImplementation>();
162: cpItems.add(ClassPathSupport.createResource(testRoot));
163: testClassPath = ClassPathSupport
164: .createClassPath(cpItems);
165: } catch (IllegalArgumentException ex) {
166: ErrorManager.getDefault().notify(
167: ErrorManager.INFORMATIONAL, ex);
168: testClassPath = cp;
169: }
170: }
171: String testName = getTestName(cp, selectedFO);
172: return testClassPath.findResource(testName + ".java");
173: }
174:
175: private static String getTestName(ClassPath cp,
176: FileObject selectedFO) {
177: String resource = cp.getResourceName(selectedFO, '/', false);
178: String testName = null;
179: if (selectedFO.isFolder()) {
180: //find Suite for package
181: testName = TestUtil.convertPackage2SuiteName(resource);
182: } else {
183: // find Test for class
184: testName = TestUtil.convertClass2TestName(resource);
185: }
186:
187: return testName;
188: }
189:
190: /**
191: * Open given file in editor.
192: * @return true if file was opened or false
193: */
194: public static boolean openFile(FileObject fo) {
195: DataObject dobj;
196: try {
197: dobj = DataObject.find(fo);
198: } catch (DataObjectNotFoundException e) {
199: getLogger().log(Level.WARNING, null, e);
200: return false;
201: }
202: assert dobj != null;
203:
204: EditorCookie editorCookie = dobj.getCookie(EditorCookie.class);
205: if (editorCookie != null) {
206: editorCookie.open();
207: return true;
208: }
209:
210: OpenCookie openCookie = dobj.getCookie(OpenCookie.class);
211: if (openCookie != null) {
212: openCookie.open();
213: return true;
214: }
215:
216: return false;
217: }
218:
219: /**
220: */
221: static boolean openFileAtElement(FileObject fileObject,
222: ElementHandle<Element> element) {
223: final DataObject dataObject;
224: try {
225: dataObject = DataObject.find(fileObject);
226: } catch (DataObjectNotFoundException e) {
227: getLogger().log(Level.INFO, null, e);
228: return false;
229: }
230: assert dataObject != null;
231:
232: final EditorCookie editorCookie = dataObject
233: .getCookie(EditorCookie.class);
234: if (editorCookie != null) {
235:
236: StyledDocument doc;
237: try {
238: doc = editorCookie.openDocument();
239: } catch (IOException ex) {
240: String msg = ex.getLocalizedMessage();
241: if (msg == null) {
242: msg = ex.getMessage();
243: }
244: getLogger().log(Level.SEVERE, msg, ex);
245: return false;
246: }
247:
248: editorCookie.open();
249:
250: LineCookie lineCookie = dataObject
251: .getCookie(LineCookie.class);
252: if ((lineCookie != null) && (element != null)
253: && (doc != null)) {
254: int currentPos = -1;
255: JEditorPane[] editorPanes = editorCookie
256: .getOpenedPanes();
257: if ((editorPanes != null) && (editorPanes.length != 0)) {
258: currentPos = editorPanes[0].getCaretPosition();
259: }
260: int[] elementPositionBounds = null;
261: try {
262: elementPositionBounds = getPositionRange(
263: fileObject, element);
264: } catch (IOException ex) {
265: getLogger().log(Level.WARNING, null, ex);
266: }
267: if ((currentPos == -1)
268: || (elementPositionBounds != null)
269: && ((currentPos < elementPositionBounds[0]) || (currentPos >= elementPositionBounds[1]))) {
270: int startPos = elementPositionBounds[0];
271: int lineNum = NbDocument.findLineNumber(doc,
272: startPos);
273: if (lineNum != -1) {
274: Line line = lineCookie.getLineSet().getCurrent(
275: lineNum);
276: try {
277: int lineOffset = NbDocument.findLineOffset(
278: doc, lineNum);
279: int column = startPos - lineOffset;
280: line.show(Line.SHOW_GOTO, column);
281: } catch (IndexOutOfBoundsException ex) {
282: Logger.getLogger(
283: OpenTestAction.class.getName())
284: .log(Level.INFO, null, ex);
285: line.show(Line.SHOW_GOTO);
286: }
287: }
288: }
289: }
290: return true;
291: }
292:
293: OpenCookie openCookie = dataObject.getCookie(OpenCookie.class);
294: if (openCookie != null) {
295: openCookie.open();
296: return true;
297: }
298:
299: return false;
300: }
301:
302: /**
303: */
304: private static Logger getLogger() {
305: return Logger.getLogger(OpenTestAction.class.getName());
306: }
307:
308: /**
309: */
310: private static int[] getPositionRange(FileObject fileObj,
311: ElementHandle<Element> elemHandle) throws IOException {
312: PositionRangeFinder posFinder = new PositionRangeFinder(
313: fileObj, elemHandle);
314: JavaSource.forFileObject(fileObj).runUserActionTask(posFinder,
315: true);
316: return posFinder.getPositionRange();
317: }
318:
319: /**
320: *
321: */
322: private static class PositionRangeFinder implements
323: CancellableTask<CompilationController> {
324:
325: private final FileObject fileObj;
326: private final ElementHandle<Element> elemHandle;
327: private int[] positionRange = null;
328: private volatile boolean cancelled;
329:
330: /**
331: */
332: private PositionRangeFinder(FileObject fileObj,
333: ElementHandle<Element> elemHandle) {
334: this .fileObj = fileObj;
335: this .elemHandle = elemHandle;
336: }
337:
338: /**
339: */
340: public void run(CompilationController controller)
341: throws IOException {
342: try {
343: controller.toPhase(Phase.RESOLVED); //cursor position needed
344: } catch (IOException ex) {
345: Logger.getLogger("global").log(Level.SEVERE, null, ex); //NOI18N
346: }
347: if (cancelled) {
348: return;
349: }
350:
351: Element element = elemHandle.resolve(controller);
352: if (cancelled || (element == null)) {
353: return;
354: }
355:
356: Trees trees = controller.getTrees();
357: CompilationUnitTree compUnit = controller
358: .getCompilationUnit();
359: DeclarationTreeFinder treeFinder = new DeclarationTreeFinder(
360: element, trees);
361: treeFinder.scan(compUnit, null);
362: Tree tree = treeFinder.getDeclarationTree();
363:
364: if (tree != null) {
365: SourcePositions srcPositions = trees
366: .getSourcePositions();
367: long startPos = srcPositions.getStartPosition(compUnit,
368: tree);
369: long endPos = srcPositions.getEndPosition(compUnit,
370: tree);
371:
372: if ((startPos >= 0)
373: && (startPos <= (long) Integer.MAX_VALUE)
374: && (endPos >= 0)
375: && (endPos <= (long) Integer.MAX_VALUE)) {
376: positionRange = new int[2];
377: positionRange[0] = (int) startPos;
378: positionRange[1] = (int) endPos;
379: }
380: }
381: }
382:
383: /**
384: */
385: public void cancel() {
386: cancelled = true;
387: }
388:
389: /**
390: */
391: int[] getPositionRange() {
392: return positionRange;
393: }
394:
395: }
396:
397: /**
398: *
399: */
400: private static class DeclarationTreeFinder extends
401: TreePathScanner<Void, Void> {
402:
403: private final Element element;
404: private final Trees trees;
405: private Tree declTree;
406:
407: /**
408: */
409: private DeclarationTreeFinder(Element element, Trees trees) {
410: this .element = element;
411: this .trees = trees;
412: }
413:
414: @Override
415: public Void visitClass(ClassTree tree, Void d) {
416: if (declTree == null) {
417: handleDeclaration();
418: super .visitClass(tree, d);
419: }
420: return null;
421: }
422:
423: @Override
424: public Void visitMethod(MethodTree tree, Void d) {
425: if (declTree == null) {
426: handleDeclaration();
427: super .visitMethod(tree, d);
428: }
429: return null;
430: }
431:
432: /**
433: */
434: public void handleDeclaration() {
435: Element found = trees.getElement(getCurrentPath());
436:
437: if (element.equals(found)) {
438: declTree = getCurrentPath().getLeaf();
439: }
440: }
441:
442: /**
443: */
444: Tree getDeclarationTree() {
445: return declTree;
446: }
447:
448: }
449:
450: public String getName() {
451: return NbBundle.getMessage(OpenTestAction.class,
452: "LBL_Action_OpenTest");
453: }
454:
455: protected String iconResource() {
456: return "org/netbeans/modules/junit/resources/OpenTestActionIcon.gif";
457: }
458:
459: public HelpCtx getHelpCtx() {
460: return new HelpCtx(OpenTestAction.class);
461: }
462:
463: /** Perform special enablement check in addition to the normal one.
464: protected boolean enable (Node[] nodes) {
465: if (! super.enable (nodes)) return false;
466: if (...) ...;
467: }
468: */
469:
470: protected void initialize() {
471: super .initialize();
472: putProperty(Action.SHORT_DESCRIPTION, NbBundle.getMessage(
473: OpenTestAction.class, "HINT_Action_OpenTest"));
474: }
475:
476: }
|