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.Tree;
045: import com.sun.source.util.TreePath;
046: import java.awt.EventQueue;
047: import java.io.IOException;
048: import java.util.logging.Level;
049: import java.util.logging.Logger;
050: import javax.lang.model.element.Element;
051: import org.netbeans.api.java.classpath.ClassPath;
052: import org.netbeans.api.java.project.JavaProjectConstants;
053: import org.netbeans.api.java.queries.UnitTestForSourceQuery;
054: import org.netbeans.api.java.source.CancellableTask;
055: import org.netbeans.api.java.source.CompilationController;
056: import org.netbeans.api.java.source.JavaSource.Phase;
057: import org.netbeans.api.project.FileOwnerQuery;
058: import org.netbeans.api.project.Project;
059: import org.netbeans.api.project.ProjectUtils;
060: import org.netbeans.api.project.SourceGroup;
061: import org.netbeans.spi.gototest.TestLocator;
062: import org.netbeans.modules.junit.plugin.JUnitPlugin;
063: import org.netbeans.modules.junit.plugin.JUnitPlugin.Location;
064: import org.netbeans.spi.java.classpath.support.ClassPathSupport;
065: import org.openide.filesystems.FileObject;
066: import org.openide.util.NbBundle;
067: import org.openide.util.RequestProcessor;
068:
069: /**
070: * Jumps to the opposite class or method.
071: * If the cursor is currently in a source method, this action will jump to the
072: * corresponding test method and vice versa. If the cursor is currently in a
073: * source class but not in any method, this action will switch to the beginning
074: * of the corresponding class.
075: *
076: * @see OpenTestAction
077: * @author Marian Petras
078: */
079: @SuppressWarnings("serial")
080: public final class GoToOppositeAction implements TestLocator {
081:
082: public GoToOppositeAction() {
083: }
084:
085: public boolean asynchronous() {
086: return true;
087: }
088:
089: public LocationResult findOpposite(FileObject fileObj,
090: int caretOffset) {
091: throw new UnsupportedOperationException(
092: "JUnit's GoToOppositeAction is asynchronous");
093: }
094:
095: public void findOpposite(FileObject fileObj, int caretOffset,
096: LocationListener callback) {
097: boolean isJavaFile = false;
098: ClassPath srcCP;
099: FileObject fileObjRoot;
100: Project project;
101: boolean sourceToTest = true;
102:
103: if ((fileObj == null)
104: || !fileObj.isFolder()
105: && !(isJavaFile = TestUtil.isJavaFile(fileObj))
106: || ((srcCP = ClassPath.getClassPath(fileObj,
107: ClassPath.SOURCE)) == null)
108: || ((fileObjRoot = srcCP.findOwnerRoot(fileObj)) == null)
109: || ((project = FileOwnerQuery.getOwner(fileObjRoot)) == null)
110: || (UnitTestForSourceQuery.findUnitTests(fileObjRoot).length == 0)
111: && !(sourceToTest = false) //side effect - assignment
112: && (!isJavaFile || (UnitTestForSourceQuery
113: .findSources(fileObjRoot).length == 0))) {
114: callback.foundLocation(fileObj, new LocationResult(null));
115: return;
116: }
117:
118: JUnitPlugin plugin = TestUtil.getPluginForProject(project);
119: assert plugin != null;
120:
121: SourceGroup[] srcGroups;
122: FileObject[] srcRoots;
123: srcGroups = ProjectUtils.getSources(project).getSourceGroups(
124: JavaProjectConstants.SOURCES_TYPE_JAVA);
125: srcRoots = new FileObject[srcGroups.length];
126: for (int i = 0; i < srcGroups.length; i++) {
127: srcRoots[i] = srcGroups[i].getRootFolder();
128: }
129: ClassPath srcClassPath = ClassPathSupport
130: .createClassPath(srcRoots);
131:
132: /*
133: ClasspathInfo cpInfo = ClasspathInfo.create(
134: ClassPath.getClassPath(fileObj, ClassPath.BOOT),
135: ClassPath.getClassPath(fileObj, ClassPath.COMPILE),
136: srcClassPath);
137: int caretPos = editorPane.getCaretPosition();
138: boolean fromSourceToTest = sourceToTest;
139:
140: JavaSource javaSource = JavaSource.create(
141: cpInfo,
142: Collections.<FileObject>singleton(fileObj));
143:
144: ElementFinder elementFinder = new ElementFinder(caretPos);
145: try {
146: javaSource.runUserActionTask(elementFinder, true);
147: } catch (IOException ex) {
148: Logger.getLogger("global").log(Level.SEVERE, null, ex); //NOI18N
149: }
150: Element element = elementFinder.getElement();
151: */
152: RequestProcessor.getDefault().post(
153: new ActionImpl(plugin, callback,
154: new Location(fileObj/*, element*/),
155: sourceToTest, srcClassPath));
156: }
157:
158: /**
159: * Determines an element at the current cursor position.
160: */
161: private class ElementFinder implements
162: CancellableTask<CompilationController> {
163:
164: /** */
165: private final int caretPosition;
166: /** */
167: private volatile boolean cancelled;
168: /** */
169: private Element element = null;
170:
171: /**
172: */
173: private ElementFinder(int caretPosition) {
174: this .caretPosition = caretPosition;
175: }
176:
177: /**
178: */
179: public void run(CompilationController controller)
180: throws IOException {
181: controller.toPhase(Phase.RESOLVED); //cursor position needed
182: if (cancelled) {
183: return;
184: }
185:
186: TreePath treePath = controller.getTreeUtilities().pathFor(
187: caretPosition);
188: if (treePath != null) {
189: if (cancelled) {
190: return;
191: }
192:
193: TreePath parent = treePath.getParentPath();
194: while (parent != null) {
195: Tree.Kind parentKind = parent.getLeaf().getKind();
196: if ((parentKind == Tree.Kind.CLASS)
197: || (parentKind == Tree.Kind.COMPILATION_UNIT)) {
198: break;
199: }
200: treePath = parent;
201: parent = treePath.getParentPath();
202: }
203:
204: }
205:
206: if (treePath != null) {
207: if (cancelled) {
208: return;
209: }
210:
211: try {
212: element = controller.getTrees()
213: .getElement(treePath);
214: } catch (IllegalArgumentException ex) {
215: Logger.getLogger("global").log(Level.WARNING, null,
216: ex);
217: }
218: }
219: }
220:
221: /**
222: */
223: public void cancel() {
224: cancelled = true;
225: }
226:
227: /**
228: */
229: Element getElement() {
230: return element;
231: }
232:
233: }
234:
235: /**
236: *
237: */
238: private class ActionImpl implements Runnable {
239:
240: private final JUnitPlugin plugin;
241: private final Location currLocation;
242: private final boolean sourceToTest;
243: private final ClassPath srcClassPath;
244: private final LocationListener callback;
245:
246: private Location oppoLocation;
247:
248: ActionImpl(JUnitPlugin plugin, LocationListener callback,
249: Location currLocation, boolean sourceToTest,
250: ClassPath srcClassPath) {
251: this .plugin = plugin;
252: this .currLocation = currLocation;
253: this .sourceToTest = sourceToTest;
254: this .srcClassPath = srcClassPath;
255: this .callback = callback;
256: }
257:
258: public void run() {
259: if (!EventQueue.isDispatchThread()) {
260: findOppositeLocation();
261: if ((oppoLocation != null) || sourceToTest) {
262: EventQueue.invokeLater(this );
263: }
264: } else {
265: if (oppoLocation != null) {
266: goToOppositeLocation();
267: } else if (sourceToTest) {
268: displayNoOppositeLocationFound();
269: }
270: }
271: }
272:
273: /**
274: */
275: private void findOppositeLocation() {
276: oppoLocation = sourceToTest ? JUnitPluginTrampoline.DEFAULT
277: .getTestLocation(plugin, currLocation)
278: : JUnitPluginTrampoline.DEFAULT.getTestedLocation(
279: plugin, currLocation);
280: }
281:
282: /**
283: */
284: private void goToOppositeLocation() {
285: assert oppoLocation != null;
286: assert oppoLocation.getFileObject() != null;
287:
288: final FileObject oppoFile = oppoLocation.getFileObject();
289: // final ElementHandle<Element> elementHandle
290: // = oppoLocation.getElementHandle();
291: // if (elementHandle != null) {
292: // OpenTestAction.openFileAtElement(oppoFile, elementHandle);
293: // } else {
294: // OpenTestAction.openFile(oppoFile);
295: callback.foundLocation(currLocation.getFileObject(),
296: new LocationResult(oppoFile, -1));
297: // }
298: }
299:
300: /**
301: */
302: private void displayNoOppositeLocationFound() {
303: String sourceClsName;
304: FileObject fileObj = currLocation.getFileObject();
305: sourceClsName = srcClassPath.getResourceName(fileObj, '.',
306: false);
307: String msgKey = !fileObj.isFolder() ? "MSG_test_class_not_found" //NOI18N
308: : (sourceClsName.length() != 0) ? "MSG_testsuite_class_not_found" //NOI18N
309: : "MSG_testsuite_class_not_found_def_pkg";//NOI18N
310: callback.foundLocation(currLocation.getFileObject(),
311: new LocationResult(NbBundle.getMessage(getClass(),
312: msgKey, sourceClsName)));
313: }
314: }
315:
316: /**
317: * Checks whether this action should be enabled for "Go To Test"
318: * or for "Go To Tested Class" or whether it should be disabled.
319: *
320: * @return {@code Boolean.TRUE} if this action should be enabled for
321: * "Go To Test",<br />
322: * {@code Boolean.FALSE} if this action should be enabled for
323: * "Go To Tested Class",<br />
324: * {@code null} if this action should be disabled
325: */
326: private Boolean checkDirection(FileObject fileObj) {
327: ClassPath srcCP;
328: FileObject fileObjRoot;
329:
330: boolean isJavaFile = false;
331: boolean sourceToTest = true;
332: boolean enabled = (fileObj != null)
333: && (fileObj.isFolder() || (isJavaFile = TestUtil
334: .isJavaFile(fileObj)))
335: && ((srcCP = ClassPath.getClassPath(fileObj,
336: ClassPath.SOURCE)) != null)
337: && ((fileObjRoot = srcCP.findOwnerRoot(fileObj)) != null)
338: && ((UnitTestForSourceQuery.findUnitTests(fileObjRoot).length != 0)
339: || (sourceToTest = false) //side effect - assignment
340: || isJavaFile
341: && (UnitTestForSourceQuery
342: .findSources(fileObjRoot).length != 0));
343:
344: return enabled ? Boolean.valueOf(sourceToTest) : null;
345: }
346:
347: public boolean appliesTo(FileObject fo) {
348: return TestUtil.isJavaFile(fo);
349: }
350:
351: public FileType getFileType(FileObject fo) {
352: Boolean b = checkDirection(fo);
353:
354: if (b == null) {
355: return FileType.NEITHER;
356: } else if (b.booleanValue()) {
357: return FileType.TESTED;
358: } else {
359: return FileType.TEST;
360: }
361: }
362: }
|