| /* |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. |
| * |
| * Copyright 2011 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]" |
| * |
| * 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. |
| * |
| * Contributor(s): |
| * |
| * Portions Copyrighted 2011 Sun Microsystems, Inc. |
| */ |
| package org.netbeans.modules.jackpot30.ide.usages; |
| |
| import com.sun.source.util.TreePath; |
| import java.awt.BorderLayout; |
| import java.awt.CardLayout; |
| import java.awt.Dialog; |
| import java.awt.Insets; |
| import java.awt.event.ActionEvent; |
| import java.awt.event.ActionListener; |
| import java.io.IOException; |
| import java.net.URI; |
| import java.net.URISyntaxException; |
| import java.net.URL; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.EnumSet; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.LinkedHashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.Set; |
| import java.util.concurrent.atomic.AtomicBoolean; |
| import javax.lang.model.element.Element; |
| import javax.lang.model.element.ElementKind; |
| import javax.lang.model.element.ExecutableElement; |
| import javax.lang.model.element.TypeElement; |
| import javax.lang.model.type.DeclaredType; |
| import javax.lang.model.type.TypeKind; |
| import javax.lang.model.type.TypeMirror; |
| import javax.lang.model.util.ElementFilter; |
| import javax.swing.JButton; |
| import javax.swing.JLabel; |
| import javax.swing.JPanel; |
| import javax.swing.SwingUtilities; |
| import javax.swing.border.EmptyBorder; |
| import javax.swing.text.JTextComponent; |
| import org.codeviation.pojson.Pojson; |
| import org.netbeans.api.editor.EditorRegistry; |
| import org.netbeans.api.java.source.CompilationController; |
| import org.netbeans.api.java.source.CompilationInfo; |
| import org.netbeans.api.java.source.ElementHandle; |
| import org.netbeans.api.java.source.JavaSource; |
| import org.netbeans.api.java.source.SourceUtils; |
| import org.netbeans.api.java.source.Task; |
| import org.netbeans.api.java.source.ui.ElementHeaders; |
| import org.netbeans.api.java.source.ui.ScanDialog; |
| import org.netbeans.modules.editor.NbEditorUtilities; |
| import org.netbeans.modules.jackpot30.common.api.JavaUtils; |
| import org.netbeans.modules.jackpot30.remoting.api.RemoteIndex; |
| import org.netbeans.modules.jackpot30.remoting.api.Utilities; |
| import org.netbeans.modules.jackpot30.remoting.api.Utilities.RemoteSourceDescription; |
| import org.netbeans.modules.jackpot30.remoting.api.WebUtilities; |
| import org.openide.DialogDescriptor; |
| import org.openide.DialogDisplayer; |
| import org.openide.NotifyDescriptor; |
| import org.openide.NotifyDescriptor.Message; |
| import org.openide.awt.ActionID; |
| import org.openide.awt.ActionReference; |
| import org.openide.awt.ActionReferences; |
| import org.openide.awt.ActionRegistration; |
| import org.openide.filesystems.FileObject; |
| import org.openide.filesystems.FileUtil; |
| import org.openide.filesystems.URLMapper; |
| import org.openide.nodes.Node; |
| import org.openide.util.Cancellable; |
| import org.openide.util.Exceptions; |
| import org.openide.util.NbBundle.Messages; |
| import org.openide.util.RequestProcessor; |
| |
| @ActionID(category = "Refactoring", |
| id = "org.netbeans.modules.jackpot30.ide.usages.RemoteUsages") |
| @ActionRegistration(displayName = "#CTL_RemoteUsages") |
| @ActionReferences({ |
| @ActionReference(path = "Menu/Edit", position = 2250) |
| }) |
| @Messages("CTL_RemoteUsages=Find Remote Usages...") |
| public final class RemoteUsages implements ActionListener { |
| |
| private final RequestProcessor WORKER = new RequestProcessor(RemoteUsages.class.getName(), 1, false, false); |
| |
| public void actionPerformed(ActionEvent e) { |
| JTextComponent comp = EditorRegistry.lastFocusedComponent(); //XXX |
| |
| if (comp == null) return; |
| |
| final FileObject file = NbEditorUtilities.getFileObject(comp.getDocument()); |
| final int pos = comp.getCaretPosition(); |
| final ElementDescription element = findElement(file, pos); |
| |
| if (element == null) { |
| Message message = new NotifyDescriptor.Message("Cannot find usages of this element", NotifyDescriptor.Message.ERROR_MESSAGE); |
| DialogDisplayer.getDefault().notifyLater(message); |
| return ; |
| } |
| |
| final Set<SearchOptions> options = EnumSet.noneOf(SearchOptions.class); |
| final JButton okButton = new JButton("OK"); |
| JButton cancelButton = new JButton("Cancel"); |
| final ElementHandle[] searchFor = new ElementHandle[1]; |
| JPanel dialogContent = constructDialog(element, options, new SelectionListener() { |
| @Override |
| public void elementSelected(ElementHandle<?> selected) { |
| searchFor[0] = selected; |
| } |
| }, okButton); |
| |
| DialogDescriptor dd = new DialogDescriptor(dialogContent, "Remote Find Usages", true, new Object[] {okButton, cancelButton}, okButton, DialogDescriptor.DEFAULT_ALIGN, null, new ActionListener() { |
| @Override public void actionPerformed(ActionEvent e) { } |
| }); |
| final Dialog d = DialogDisplayer.getDefault().createDialog(dd); |
| |
| final AtomicBoolean cancel = new AtomicBoolean(); |
| |
| okButton.addActionListener(new ActionListener() { |
| @Override public void actionPerformed(ActionEvent e) { |
| okButton.setEnabled(false); |
| WORKER.post(new FindUsagesWorker(searchFor[0], options, d, cancel)); |
| } |
| }); |
| |
| cancelButton.addActionListener(new ActionListener() { |
| @Override public void actionPerformed(ActionEvent e) { |
| cancel.set(true); |
| d.setVisible(false); |
| } |
| }); |
| |
| d.setVisible(true); |
| } |
| |
| private static ElementDescription findElement(final FileObject file, final int pos) { |
| final ElementDescription[] handle = new ElementDescription[1]; |
| |
| if ("text/x-java".equals(FileUtil.getMIMEType(file, "text/x-java"))) { |
| final JavaSource js = JavaSource.forFileObject(file); |
| |
| ScanDialog.runWhenScanFinished(new Runnable() { |
| @Override public void run() { |
| try { |
| js.runUserActionTask(new Task<CompilationController>() { |
| @Override public void run(CompilationController parameter) throws Exception { |
| parameter.toPhase(JavaSource.Phase.RESOLVED); |
| |
| TreePath tp = parameter.getTreeUtilities().pathFor(pos); |
| Element el = parameter.getTrees().getElement(tp); |
| |
| if (el != null && JavaUtils.SUPPORTED_KINDS.contains(el.getKind())) { |
| handle[0] = new ElementDescription(parameter, el); |
| } |
| } |
| }, true); |
| } catch (IOException ex) { |
| Exceptions.printStackTrace(ex); |
| } |
| } |
| |
| }, "Find Remote Usages"); |
| |
| return handle[0]; |
| } else { |
| RemoteSourceDescription rsd = org.netbeans.modules.jackpot30.remoting.api.Utilities.remoteSource(file); |
| |
| if (rsd != null) { |
| try { |
| URI sourceURI = new URI(rsd.idx.remote.toExternalForm() + "/ui/target?path=" + WebUtilities.escapeForQuery(rsd.idx.remoteSegment) + "&relative=" + WebUtilities.escapeForQuery(rsd.relative) + "&position=" + pos); |
| Map<Object, Object> targetData = Pojson.load(HashMap.class, sourceURI.toURL().openStream()); |
| |
| String signature = (String) targetData.get("signature"); |
| |
| if (signature != null) { |
| List<String> baseMethodsSpec = (List<String>) targetData.get("superMethods"); |
| baseMethodsSpec = baseMethodsSpec != null ? baseMethodsSpec : Collections.<String>emptyList(); |
| List<ElementHandle<?>> baseMethods = new ArrayList<ElementHandle<?>>(baseMethodsSpec.size()); |
| for (String spec : baseMethodsSpec) { |
| baseMethods.add(signature2Handle(spec)); |
| } |
| return new ElementDescription(signature2Handle(signature), |
| baseMethods); |
| } |
| } catch (URISyntaxException ex) { |
| Exceptions.printStackTrace(ex); |
| } catch (IOException ex) { |
| Exceptions.printStackTrace(ex); |
| } |
| } |
| |
| return null; |
| } |
| } |
| |
| private static ElementHandle<?> signature2Handle(String signature) { |
| if (signature == null) return null; |
| String[] parts = signature.split(":"); |
| ElementHandle<?> h = Utilities.createElementHandle(ElementKind.valueOf(parts[0]), |
| parts[1], |
| parts.length > 2 ? parts[2] : null, |
| parts.length > 3 ? parts[3] : null); |
| return h; |
| } |
| |
| private JPanel constructDialog(ElementDescription toSearch, Set<SearchOptions> options, SelectionListener sl, JButton ok) { |
| JPanel searchKind; |
| |
| switch (toSearch.element.getKind()) { |
| case METHOD: searchKind = new MethodOptions(toSearch, options, sl); break; |
| case CLASS: |
| case INTERFACE: |
| case ANNOTATION_TYPE: searchKind = new ClassOptions(options); break; |
| default: |
| options.add(RemoteUsages.SearchOptions.USAGES); |
| searchKind = new JPanel(); |
| break; |
| } |
| |
| final JPanel progress = new JPanel(); |
| |
| progress.setLayout(new CardLayout()); |
| progress.add(new JPanel(), "hide"); |
| progress.add(new JLabel("Querying remote server(s), please wait"), "show"); |
| |
| ok.addActionListener(new ActionListener() { |
| @Override public void actionPerformed(ActionEvent e) { |
| ((CardLayout) progress.getLayout()).show(progress, "show"); |
| } |
| }); |
| |
| JPanel result = new JPanel(); |
| |
| result.setLayout(new BorderLayout()); |
| result.setBorder(new EmptyBorder(new Insets(12, 12, 12, 12))); |
| |
| result.add(new JLabel("Usages of: " + toSearch.displayName), BorderLayout.NORTH); |
| result.add(searchKind, BorderLayout.CENTER); |
| result.add(progress, BorderLayout.SOUTH); |
| |
| sl.elementSelected(toSearch.element); |
| |
| return result; |
| } |
| |
| public static final class ElementDescription { |
| public final ElementHandle<?> element; |
| public final String displayName; |
| public final List<ElementHandle<?>> superMethods; |
| |
| public ElementDescription(CompilationInfo info, Element el) { |
| element = ElementHandle.create(el); |
| displayName = displayNameForElement(ElementHandle.create(el)); |
| |
| if (el.getKind() == ElementKind.METHOD) { |
| superMethods = superMethods(info, new HashSet<TypeElement>(), (ExecutableElement) el, (TypeElement) el.getEnclosingElement()); |
| } else { |
| superMethods = null; |
| } |
| } |
| |
| private List<ElementHandle<?>> superMethods(CompilationInfo info, Set<TypeElement> seenTypes, ExecutableElement baseMethod, TypeElement currentType) { |
| if (!seenTypes.add(currentType)) |
| return Collections.emptyList(); |
| |
| List<ElementHandle<?>> result = new ArrayList<ElementHandle<?>>(); |
| |
| for (TypeElement sup : superTypes(info, currentType)) { |
| for (ExecutableElement ee : ElementFilter.methodsIn(sup.getEnclosedElements())) { |
| if (info.getElements().overrides(baseMethod, ee, (TypeElement) baseMethod.getEnclosingElement())) { |
| result.add(ElementHandle.create(ee)); |
| } |
| } |
| |
| result.addAll(superMethods(info, seenTypes, baseMethod, currentType)); |
| } |
| |
| return result; |
| } |
| |
| private List<TypeElement> superTypes(CompilationInfo info, TypeElement type) { |
| List<TypeElement> superTypes = new ArrayList<TypeElement>(); |
| |
| for (TypeMirror sup : info.getTypes().directSupertypes(type.asType())) { |
| if (sup.getKind() == TypeKind.DECLARED) { |
| superTypes.add((TypeElement) ((DeclaredType) sup).asElement()); |
| } |
| } |
| |
| return superTypes; |
| } |
| |
| public ElementDescription(ElementHandle<?> element, List<ElementHandle<?>> superMethods) { |
| this.element = element; |
| displayName = displayNameForElement(element); |
| this.superMethods = superMethods; |
| } |
| |
| private String displayNameForElement(ElementHandle<?> el) throws UnsupportedOperationException { |
| String[] signatures = SourceUtils.getJVMSignature(el); |
| String classSimpleName = signatures[0]; |
| int lastDotDollar = Math.max(classSimpleName.lastIndexOf('.'), classSimpleName.lastIndexOf('$')); |
| if (lastDotDollar > (-1)) classSimpleName = classSimpleName.substring(lastDotDollar + 1); |
| switch (el.getKind()) { |
| case METHOD: |
| return signatures[1] + Utilities.decodeMethodParameterTypes(signatures[2]); |
| case CONSTRUCTOR: |
| return classSimpleName + Utilities.decodeMethodParameterTypes(signatures[2]); |
| case CLASS: |
| case INTERFACE: |
| case ENUM: |
| case ANNOTATION_TYPE: |
| return classSimpleName; |
| case FIELD: |
| case ENUM_CONSTANT: |
| return signatures[1]; |
| default: |
| throw new UnsupportedOperationException(); |
| } |
| } |
| |
| } |
| |
| private static class FindUsagesWorker implements Runnable, Cancellable { |
| |
| private final ElementHandle<?> toSearch; |
| private final Set<SearchOptions> options; |
| private final Dialog d; |
| private final AtomicBoolean cancel; |
| |
| public FindUsagesWorker(ElementHandle<?> toSearch, Set<SearchOptions> options, Dialog d, AtomicBoolean cancel) { |
| this.toSearch = toSearch; |
| this.options = options; |
| this.d = d; |
| this.cancel = cancel; |
| } |
| |
| @Override public void run() { |
| try { |
| final String serialized = JavaUtils.serialize(toSearch); |
| |
| Set<FileObject> resultSet = new HashSet<FileObject>(); |
| List<FileObject> result = new ArrayList<FileObject>(); |
| Map<RemoteIndex, List<String>> unmappable = new HashMap<RemoteIndex, List<String>>(); |
| |
| for (RemoteIndex idx : RemoteIndex.loadIndices()) { |
| URL localFolderURL = idx.getLocalFolder(); |
| FileObject localFolder = localFolderURL != null ? URLMapper.findFileObject(localFolderURL) : null; |
| |
| if (options.contains(SearchOptions.USAGES)) { |
| URI resolved = new URI(idx.remote.toExternalForm() + "/usages/search?path=" + WebUtilities.escapeForQuery(idx.remoteSegment) + "&signatures=" + WebUtilities.escapeForQuery(serialized)); |
| Collection<? extends String> response = WebUtilities.requestStringArrayResponse(resolved, cancel); |
| |
| if (cancel.get()) return; |
| if (response == null) continue; |
| |
| for (String path : response) { |
| if (path.trim().isEmpty()) continue; |
| FileObject file = localFolder != null ? localFolder.getFileObject(path) : null; |
| |
| if (file != null) { |
| if (resultSet.add(file)) { |
| result.add(file); |
| } |
| } else { |
| List<String> um = unmappable.get(idx); |
| |
| if (um == null) { |
| unmappable.put(idx, um = new ArrayList<String>()); |
| } |
| |
| um.add(path); |
| } |
| } |
| } |
| |
| if (options.contains(SearchOptions.SUB)) { |
| URI resolved; |
| if (toSearch.getKind() == ElementKind.METHOD) { |
| resolved = new URI(idx.remote.toExternalForm() + "/implements/search?path=" + WebUtilities.escapeForQuery(idx.remoteSegment) + "&method=" + WebUtilities.escapeForQuery(serialized)); |
| } else { |
| resolved = new URI(idx.remote.toExternalForm() + "/implements/search?path=" + WebUtilities.escapeForQuery(idx.remoteSegment) + "&type=" + WebUtilities.escapeForQuery(toSearch.getBinaryName())); |
| } |
| |
| String response = WebUtilities.requestStringResponse(resolved, cancel); |
| |
| if (cancel.get()) return; |
| if (response == null) continue; |
| |
| //XXX: |
| Map<String, List<Map<String, String>>> formattedResponse = Pojson.load(LinkedHashMap.class, response); |
| |
| for (Entry<String, List<Map<String, String>>> e : formattedResponse.entrySet()) { |
| for (Map<String, String> p : e.getValue()) { |
| String path = p.get("file"); |
| FileObject file = localFolder != null ? localFolder.getFileObject(path) : null; |
| |
| if (file != null) { |
| if (resultSet.add(file)) { |
| result.add(file); |
| } |
| } else { |
| List<String> um = unmappable.get(idx); |
| |
| if (um == null) { |
| unmappable.put(idx, um = new ArrayList<String>()); |
| } |
| |
| um.add(path); |
| } |
| } |
| } |
| } |
| } |
| |
| final Node view = Nodes.constructSemiLogicalView(result, unmappable, toSearch, options); |
| |
| if (!cancel.get()) { |
| SwingUtilities.invokeLater(new Runnable() { |
| @Override public void run() { |
| RemoteUsagesWindowTopComponent.openFor(view); |
| } |
| }); |
| } |
| } catch (URISyntaxException ex) { |
| Exceptions.printStackTrace(ex); |
| } finally { |
| cancel.set(true); |
| SwingUtilities.invokeLater(new Runnable() { |
| @Override public void run() { |
| d.setVisible(false); |
| } |
| }); |
| } |
| } |
| |
| @Override public boolean cancel() { |
| cancel.set(true); |
| return true; |
| } |
| } |
| |
| public enum SearchOptions { |
| USAGES, |
| SUB; |
| } |
| |
| public interface SelectionListener { |
| public void elementSelected(ElementHandle<?> selected); |
| } |
| } |