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-2006 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.refactoring.javascript.ui;
043:
044: import java.io.IOException;
045: import java.lang.ref.WeakReference;
046: import java.util.ArrayList;
047: import java.util.Collection;
048: import java.util.Dictionary;
049: import java.util.List;
050: import javax.swing.JOptionPane;
051: import javax.swing.text.JTextComponent;
052: import org.netbeans.api.fileinfo.NonRecursiveFolder;
053: import org.netbeans.modules.gsf.api.CancellableTask;
054: import org.netbeans.editor.BaseDocument;
055: import org.netbeans.napi.gsfret.source.CompilationController;
056: import org.netbeans.napi.gsfret.source.CompilationInfo;
057: import org.netbeans.napi.gsfret.source.Phase;
058: import org.netbeans.napi.gsfret.source.Source;
059: import org.netbeans.modules.refactoring.javascript.RetoucheUtils;
060: import org.netbeans.modules.refactoring.javascript.JsElementCtx;
061: import org.netbeans.modules.refactoring.spi.ui.UI;
062: import org.netbeans.modules.refactoring.spi.ui.ActionsImplementationProvider;
063: import org.netbeans.modules.refactoring.spi.ui.RefactoringUI;
064: import org.netbeans.modules.javascript.editing.AstUtilities;
065: import org.netbeans.modules.javascript.editing.Element;
066: import org.netbeans.modules.javascript.editing.JsAnalyzer.AnalysisResult;
067: import org.netbeans.modules.javascript.editing.AstElement;
068: import org.netbeans.modules.javascript.editing.JsParseResult;
069: import org.netbeans.modules.javascript.editing.JsUtils;
070: import org.netbeans.modules.javascript.editing.lexer.LexUtilities;
071: import org.openide.ErrorManager;
072: import org.openide.cookies.EditorCookie;
073: import org.openide.filesystems.FileObject;
074: import org.openide.loaders.DataObject;
075: import org.openide.nodes.Node;
076: import org.openide.util.Lookup;
077: import org.openide.util.NbBundle;
078: import org.openide.windows.TopComponent;
079:
080: /**
081: *
082: * @author Jan Becicka
083: */
084: public class RefactoringActionsProvider extends
085: ActionsImplementationProvider {
086: private static boolean isFindUsages;
087:
088: /** Creates a new instance of RefactoringActionsProvider */
089: public RefactoringActionsProvider() {
090: }
091:
092: @Override
093: public void doRename(final Lookup lookup) {
094: Runnable task;
095: EditorCookie ec = lookup.lookup(EditorCookie.class);
096: final Dictionary dictionary = lookup.lookup(Dictionary.class);
097: if (isFromEditor(ec)) {
098: task = new TextComponentTask(ec) {
099: @Override
100: protected RefactoringUI createRefactoringUI(
101: JsElementCtx selectedElement, int startOffset,
102: int endOffset, final CompilationInfo info) {
103: // If you're trying to rename a constructor, rename the enclosing class instead
104: return new RenameRefactoringUI(selectedElement,
105: info);
106: }
107: };
108: } else {
109: task = new NodeToFileObjectTask(lookup
110: .lookupAll(Node.class)) {
111: @Override
112: protected RefactoringUI createRefactoringUI(
113: FileObject[] selectedElements,
114: Collection<JsElementCtx> handles) {
115: String newName = getName(dictionary);
116: if (newName != null) {
117: if (pkg[0] != null)
118: return new RenameRefactoringUI(pkg[0],
119: newName);
120: else
121: return new RenameRefactoringUI(
122: selectedElements[0],
123: newName,
124: handles == null
125: || handles.isEmpty() ? null
126: : handles.iterator().next(),
127: cinfo == null ? null : cinfo.get());
128: } else if (pkg[0] != null)
129: return new RenameRefactoringUI(pkg[0]);
130: else
131: return new RenameRefactoringUI(
132: selectedElements[0], handles == null
133: || handles.isEmpty() ? null
134: : handles.iterator().next(),
135: cinfo == null ? null : cinfo.get());
136: }
137: };
138: }
139: task.run();
140: }
141:
142: /**
143: * returns true if exactly one refactorable file is selected
144: */
145: @Override
146: public boolean canRename(Lookup lookup) {
147: Collection<? extends Node> nodes = lookup.lookupAll(Node.class);
148: if (nodes.size() != 1) {
149: return false;
150: }
151: Node n = nodes.iterator().next();
152: DataObject dob = n.getCookie(DataObject.class);
153: if (dob == null) {
154: return false;
155: }
156: FileObject fo = dob.getPrimaryFile();
157:
158: if (isOutsideJs(lookup, fo)) {
159: return false;
160: }
161:
162: if (RetoucheUtils.isRefactorable(fo)) { //NOI18N
163: return true;
164: }
165:
166: return false;
167: }
168:
169: private boolean isOutsideJs(Lookup lookup, FileObject fo) {
170: if (!JsUtils.isJsFile(fo)) {
171: // We're attempting to refactor in an embedded scenario...
172: // Make sure it's actually in a JavaScript section.
173: EditorCookie ec = lookup.lookup(EditorCookie.class);
174: if (isFromEditor(ec)) {
175: JTextComponent textC = ec.getOpenedPanes()[0];
176: int caret = textC.getCaretPosition();
177: if (LexUtilities.getToken((BaseDocument) textC
178: .getDocument(), caret) == null) {
179: // Not in JavaScript code!
180: return true;
181: }
182:
183: }
184: }
185:
186: return false;
187: }
188:
189: @Override
190: public boolean canCopy(Lookup lookup) {
191: return false;
192: }
193:
194: @Override
195: public boolean canFindUsages(Lookup lookup) {
196: Collection<? extends Node> nodes = lookup.lookupAll(Node.class);
197: if (nodes.size() != 1) {
198: return false;
199: }
200: Node n = nodes.iterator().next();
201: DataObject dob = n.getCookie(DataObject.class);
202: if (dob == null) {
203: return false;
204: }
205:
206: FileObject fo = dob.getPrimaryFile();
207:
208: if (RetoucheUtils.isJsFile(fo) && isOutsideJs(lookup, fo)) {
209: return false;
210: }
211:
212: if ((dob != null) && RetoucheUtils.isJsFile(fo)) { //NOI18N
213: return true;
214: }
215: return false;
216: }
217:
218: @Override
219: public void doFindUsages(Lookup lookup) {
220: Runnable task;
221: EditorCookie ec = lookup.lookup(EditorCookie.class);
222: if (isFromEditor(ec)) {
223: task = new TextComponentTask(ec) {
224: @Override
225: protected RefactoringUI createRefactoringUI(
226: JsElementCtx selectedElement, int startOffset,
227: int endOffset, CompilationInfo info) {
228: return new WhereUsedQueryUI(selectedElement, info);
229: }
230: };
231: } else {
232: task = new NodeToElementTask(lookup.lookupAll(Node.class)) {
233: protected RefactoringUI createRefactoringUI(
234: JsElementCtx selectedElement,
235: CompilationInfo info) {
236: return new WhereUsedQueryUI(selectedElement, info);
237: }
238: };
239: }
240: try {
241: isFindUsages = true;
242: task.run();
243: } finally {
244: isFindUsages = false;
245: }
246: }
247:
248: @Override
249: public boolean canDelete(Lookup lookup) {
250: return false;
251: }
252:
253: static String getName(Dictionary dict) {
254: if (dict == null)
255: return null;
256: return (String) dict.get("name"); //NOI18N
257: }
258:
259: /**
260: * returns true if there is at least one java file in the selection
261: * and all java files are refactorable
262: */
263: @Override
264: public boolean canMove(Lookup lookup) {
265: return false;
266: }
267:
268: @Override
269: public void doMove(final Lookup lookup) {
270: }
271:
272: public static abstract class TextComponentTask implements Runnable,
273: CancellableTask<CompilationController> {
274: private JTextComponent textC;
275: private int caret;
276: private int start;
277: private int end;
278: private RefactoringUI ui;
279:
280: public TextComponentTask(EditorCookie ec) {
281: this .textC = ec.getOpenedPanes()[0];
282: this .caret = textC.getCaretPosition();
283: this .start = textC.getSelectionStart();
284: this .end = textC.getSelectionEnd();
285: assert caret != -1;
286: assert start != -1;
287: assert end != -1;
288: }
289:
290: public void cancel() {
291: }
292:
293: public void run(CompilationController cc) throws Exception {
294: cc.toPhase(Phase.RESOLVED);
295: org.mozilla.javascript.Node root = AstUtilities.getRoot(cc);
296: if (root == null) {
297: // TODO How do I add some kind of error message?
298: System.out
299: .println("FAILURE - can't refactor uncompileable sources");
300: return;
301: }
302:
303: JsElementCtx ctx = new JsElementCtx(cc, caret);
304: if (ctx.getSimpleName() == null) {
305: return;
306: }
307: ui = createRefactoringUI(ctx, start, end, cc);
308: }
309:
310: public final void run() {
311: try {
312: Source source = RetoucheUtils.getSource(textC
313: .getDocument());
314: source.runUserActionTask(this , false);
315: } catch (IOException ioe) {
316: ErrorManager.getDefault().notify(ioe);
317: return;
318: }
319: TopComponent activetc = TopComponent.getRegistry()
320: .getActivated();
321:
322: if (ui != null) {
323: UI.openRefactoringUI(ui, activetc);
324: } else {
325: String key = "ERR_CannotRenameLoc"; // NOI18N
326: if (isFindUsages) {
327: key = "ERR_CannotFindUsages"; // NOI18N
328: }
329: JOptionPane.showMessageDialog(null, NbBundle
330: .getMessage(RefactoringActionsProvider.class,
331: key));
332: }
333: }
334:
335: protected abstract RefactoringUI createRefactoringUI(
336: JsElementCtx selectedElement, int startOffset,
337: int endOffset, CompilationInfo info);
338: }
339:
340: public static abstract class NodeToElementTask implements Runnable,
341: CancellableTask<CompilationController> {
342: private Node node;
343: private RefactoringUI ui;
344:
345: public NodeToElementTask(Collection<? extends Node> nodes) {
346: assert nodes.size() == 1;
347: this .node = nodes.iterator().next();
348: }
349:
350: public void cancel() {
351: }
352:
353: public void run(CompilationController info) throws Exception {
354: info.toPhase(Phase.ELEMENTS_RESOLVED);
355: org.mozilla.javascript.Node root = AstUtilities
356: .getRoot(info);
357: if (root != null) {
358: Element element = AstElement.getElement(info, root);
359: JsElementCtx fileCtx = new JsElementCtx(root, root,
360: element, info.getFileObject(), info);
361: ui = createRefactoringUI(fileCtx, info);
362: }
363: }
364:
365: public final void run() {
366: DataObject o = node.getCookie(DataObject.class);
367: Source source = RetoucheUtils.getSource(o.getPrimaryFile());
368: assert source != null;
369: try {
370: source.runUserActionTask(this , false);
371: } catch (IllegalArgumentException ex) {
372: ex.printStackTrace();
373: } catch (IOException ex) {
374: ex.printStackTrace();
375: }
376: UI.openRefactoringUI(ui);
377: }
378:
379: protected abstract RefactoringUI createRefactoringUI(
380: JsElementCtx selectedElement, CompilationInfo info);
381: }
382:
383: public static abstract class NodeToFileObjectTask implements
384: Runnable, CancellableTask<CompilationController> {
385: private Collection<? extends Node> nodes;
386: private RefactoringUI ui;
387: public NonRecursiveFolder pkg[];
388: public WeakReference<CompilationInfo> cinfo;
389: Collection<JsElementCtx> handles = new ArrayList<JsElementCtx>();
390:
391: public NodeToFileObjectTask(Collection<? extends Node> nodes) {
392: this .nodes = nodes;
393: }
394:
395: public void cancel() {
396: }
397:
398: public void run(CompilationController info) throws Exception {
399: info.toPhase(Phase.ELEMENTS_RESOLVED);
400: org.mozilla.javascript.Node root = AstUtilities
401: .getRoot(info);
402: if (root != null) {
403: JsParseResult rpr = AstUtilities.getParseResult(info);
404: if (rpr != null) {
405: AnalysisResult ar = rpr.getStructure();
406: List<? extends AstElement> els = ar.getElements();
407: if (els.size() > 0) {
408: // TODO - try to find the outermost or most "relevant" module/class in the file?
409: // In Java, we look for a class with the name corresponding to the file.
410: // It's not as simple in Ruby.
411: AstElement element = els.get(0);
412: org.mozilla.javascript.Node node = element
413: .getNode();
414: JsElementCtx representedObject = new JsElementCtx(
415: root, node, element, info
416: .getFileObject(), info);
417: handles.add(representedObject);
418: }
419: }
420: }
421: cinfo = new WeakReference<CompilationInfo>(info);
422: }
423:
424: public void run() {
425: FileObject[] fobs = new FileObject[nodes.size()];
426: pkg = new NonRecursiveFolder[fobs.length];
427: int i = 0;
428: for (Node node : nodes) {
429: DataObject dob = node.getCookie(DataObject.class);
430: if (dob != null) {
431: fobs[i] = dob.getPrimaryFile();
432: Source source = RetoucheUtils.getSource(fobs[i]);
433: if (source == null) {
434: continue;
435: }
436: assert source != null;
437: try {
438: source.runUserActionTask(this , false);
439: } catch (IllegalArgumentException ex) {
440: ex.printStackTrace();
441: } catch (IOException ex) {
442: ex.printStackTrace();
443: }
444:
445: pkg[i++] = node.getLookup().lookup(
446: NonRecursiveFolder.class);
447: }
448: }
449: UI.openRefactoringUI(createRefactoringUI(fobs, handles));
450: }
451:
452: protected abstract RefactoringUI createRefactoringUI(
453: FileObject[] selectedElement,
454: Collection<JsElementCtx> handles);
455: }
456:
457: static boolean isFromEditor(EditorCookie ec) {
458: if (ec != null && ec.getOpenedPanes() != null) {
459: // This doesn't seem to work well - a lot of the time, I'm right clicking
460: // on the editor and it still has another activated view (this is on the mac)
461: // and as a result does file-oriented refactoring rather than the specific
462: // editor node...
463: // TopComponent activetc = TopComponent.getRegistry().getActivated();
464: // if (activetc instanceof CloneableEditorSupport.Pane) {
465: //
466: return true;
467: // }
468: }
469:
470: return false;
471: }
472: }
|