From 3f3e6baeaf7b943c935ce5eb81078a9d594fc66f Mon Sep 17 00:00:00 2001 From: peter Date: Fri, 12 Jul 2013 12:45:23 +0200 Subject: [PATCH] IDEA-110149 Merge Generate toString, JarFinder plugins into IDEA core --- build/scripts/layouts.gant | 2 +- .../InternetAttachSourceProvider.java | 273 ++++++++++++++++ java/java-impl/java-impl.iml | 1 + .../com/intellij/jarFinder/FindJarFix.java | 301 ++++++++++++++++++ .../jarFinder/FindJarQuickFixProvider.java | 22 ++ .../intellij/jarFinder/JavaFindJarFix.java | 78 +++++ .../jarFinder/MavenCentralSourceSearcher.java | 70 ++++ .../jarFinder/SonatypeSourceSearcher.java | 87 +++++ .../intellij/jarFinder/SourceSearcher.java | 77 +++++ .../generate-tostring/src/META-INF/plugin.xml | 33 -- .../jetbrains/generate/tostring/package.html | 5 - plugins/groovy/src/META-INF/plugin.xml | 1 + .../groovy/jarFinder/GroovyFindJarFix.java | 59 ++++ .../GroovyFindJarQuickFixProvider.java | 24 ++ resources/resources.iml | 1 + resources/src/META-INF/IdeaPlugin.xml | 13 + resources/src/idea/JavaActions.xml | 1 + resources/src/idea/RichPlatformPlugin.xml | 2 + 18 files changed, 1011 insertions(+), 39 deletions(-) create mode 100644 java/idea-ui/src/com/intellij/jarFinder/InternetAttachSourceProvider.java create mode 100644 java/java-impl/src/com/intellij/jarFinder/FindJarFix.java create mode 100644 java/java-impl/src/com/intellij/jarFinder/FindJarQuickFixProvider.java create mode 100644 java/java-impl/src/com/intellij/jarFinder/JavaFindJarFix.java create mode 100644 java/java-impl/src/com/intellij/jarFinder/MavenCentralSourceSearcher.java create mode 100644 java/java-impl/src/com/intellij/jarFinder/SonatypeSourceSearcher.java create mode 100644 java/java-impl/src/com/intellij/jarFinder/SourceSearcher.java delete mode 100644 plugins/generate-tostring/src/META-INF/plugin.xml create mode 100644 plugins/groovy/src/org/jetbrains/plugins/groovy/jarFinder/GroovyFindJarFix.java create mode 100644 plugins/groovy/src/org/jetbrains/plugins/groovy/jarFinder/GroovyFindJarQuickFixProvider.java diff --git a/build/scripts/layouts.gant b/build/scripts/layouts.gant index 07a80f02e9e3..bc6304d39f4e 100644 --- a/build/scripts/layouts.gant +++ b/build/scripts/layouts.gant @@ -83,6 +83,7 @@ def layoutFull(String home, String targetDirectory, String patchedDescriptorDir "InspectionGadgetsAnalysis", "InspectionGadgetsPlugin", "IntentionPowerPackPlugin", + "generate-tostring", ].flatten() ant.patternset(id: "resources.included") { @@ -251,7 +252,6 @@ public def layoutCommunityPlugins(String home) { module("ant-jps-plugin") } } - layoutPlugin("ToString", "generate-tostring", "toString") layoutPlugin("uiDesigner", "ui-designer", "uiDesigner") { dir("jps") { jar("ui-designer-jps-plugin.jar") { diff --git a/java/idea-ui/src/com/intellij/jarFinder/InternetAttachSourceProvider.java b/java/idea-ui/src/com/intellij/jarFinder/InternetAttachSourceProvider.java new file mode 100644 index 000000000000..b6dc68b5bc8c --- /dev/null +++ b/java/idea-ui/src/com/intellij/jarFinder/InternetAttachSourceProvider.java @@ -0,0 +1,273 @@ +package com.intellij.jarFinder; + +import com.intellij.codeInsight.AttachSourcesProvider; +import com.intellij.notification.Notification; +import com.intellij.notification.NotificationType; +import com.intellij.openapi.application.AccessToken; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.WriteAction; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.Task; +import com.intellij.openapi.roots.LibraryOrderEntry; +import com.intellij.openapi.roots.OrderRootType; +import com.intellij.openapi.roots.libraries.Library; +import com.intellij.openapi.roots.ui.configuration.PathUIUtils; +import com.intellij.openapi.util.ActionCallback; +import com.intellij.openapi.vfs.JarFileSystem; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiFile; +import com.intellij.util.SystemProperties; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.net.HttpConfigurable; +import com.intellij.util.net.NetUtils; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.*; +import java.net.HttpURLConnection; +import java.util.*; +import java.util.regex.Pattern; + +/** + * @author Sergey Evdokimov + */ +public class InternetAttachSourceProvider implements AttachSourcesProvider { + + private static final Logger LOG = Logger.getInstance("#com.intellij.jarFinder.SonatypeAttachSourceProvider"); + + private static final Pattern ARTIFACT_IDENTIFIER = Pattern.compile("[A-Za-z0-9\\.\\-_]+"); + + @Nullable + protected static VirtualFile getJarByPsiFile(PsiFile psiFile) { + VirtualFile virtualFile = psiFile.getVirtualFile(); + if (virtualFile == null) return null; + + VirtualFile jar = JarFileSystem.getInstance().getVirtualFileForJar(psiFile.getVirtualFile()); + + if (jar == null || !jar.getName().endsWith(".jar")) return null; + + return jar; + } + + @NotNull + @Override + public Collection getActions(List orderEntries, final PsiFile psiFile) { + VirtualFile jar = getJarByPsiFile(psiFile); + if (jar == null) return Collections.emptyList(); + + final String jarName = jar.getNameWithoutExtension(); + int index = jarName.lastIndexOf('-'); + if (index == -1) return Collections.emptyList(); + + final String version = jarName.substring(index + 1); + final String artifactId = jarName.substring(0, index); + + if (!ARTIFACT_IDENTIFIER.matcher(version).matches() || !ARTIFACT_IDENTIFIER.matcher(artifactId).matches()) { + return Collections.emptyList(); + } + + final Set libraries = new HashSet(); + for (LibraryOrderEntry orderEntry : orderEntries) { + ContainerUtil.addIfNotNull(libraries, orderEntry.getLibrary()); + } + + if (libraries.isEmpty()) return Collections.emptyList(); + + final String sourceFileName = jarName + "-sources.jar"; + + for (Library library : libraries) { + for (VirtualFile file : library.getFiles(OrderRootType.SOURCES)) { + if (file.getPath().contains(sourceFileName)) { + if (isRootInExistingFile(file)) { + return Collections.emptyList(); // Sources already attached, but source-jar doesn't contain current class. + } + } + } + } + + final File libSourceDir = getLibrarySourceDir(); + + final File sourceFile = new File(libSourceDir, sourceFileName); + + if (sourceFile.exists()) { + return Collections.singleton(new LightAttachSourcesAction() { + @Override + public String getName() { + return "Attach downloaded source"; + } + + @Override + public String getBusyText() { + return getName(); + } + + @Override + public ActionCallback perform(List orderEntriesContainingFile) { + attachSourceJar(sourceFile, libraries); + return new ActionCallback.Done(); + } + }); + } + + return Collections.singleton(new LightAttachSourcesAction() { + @Override + public String getName() { + return "Search in internet..."; + } + + @Override + public String getBusyText() { + return "Searching..."; + } + + @Override + public ActionCallback perform(List orderEntriesContainingFile) { + final Task task = new Task.Modal(psiFile.getProject(), "Searching source...", true) { + + // Don't move initialization of searchers to static context of top level class, to avoid unnecessary initialization of searcher's classes + private SourceSearcher[] mySearchers = new SourceSearcher[]{new MavenCentralSourceSearcher(), new SonatypeSourceSearcher()}; + + @Override + public void run(@NotNull final ProgressIndicator indicator) { + String artifactUrl = null; + + for (SourceSearcher searcher : mySearchers) { + try { + artifactUrl = searcher.findSourceJar(indicator, artifactId, version); + } + catch (SourceSearchException e) { + showMessage("Downloading failed", e.getMessage(), NotificationType.ERROR); + continue; + } + + if (artifactUrl != null) break; + } + + if (artifactUrl == null) { + showMessage("Source not found", "Sources for: " + jarName + ".jar not found", NotificationType.WARNING); + return; + } + + libSourceDir.mkdirs(); + + if (!libSourceDir.exists()) { + showMessage("Downloading failed", "Failed to create directory to store sources: " + libSourceDir, NotificationType.ERROR); + return; + } + + try { + HttpURLConnection urlConnection = HttpConfigurable.getInstance().openHttpConnection(artifactUrl); + + int contentLength = urlConnection.getContentLength(); + + File tmpDownload = File.createTempFile("download", ".tmp", libSourceDir); + OutputStream out = new BufferedOutputStream(new FileOutputStream(tmpDownload)); + + try { + InputStream in = urlConnection.getInputStream(); + indicator.setText("Downloading sources..."); + indicator.setIndeterminate(false); + try { + NetUtils.copyStreamContent(indicator, in, out, contentLength); + } + finally { + in.close(); + } + } + finally { + out.close(); + } + + if (!sourceFile.exists()) { + if (!tmpDownload.renameTo(sourceFile)) { + LOG.warn("Failed to rename file " + tmpDownload + " to " + sourceFileName); + } + } + } + catch (IOException e) { + showMessage("Downloading failed", "Connection problem. See log for more details.", NotificationType.ERROR); + } + } + + @Override + public void onSuccess() { + attachSourceJar(sourceFile, libraries); + } + + private void showMessage(final String title, final String message, final NotificationType notificationType) { + ApplicationManager.getApplication().invokeLater(new Runnable() { + @Override + public void run() { + new Notification("Source searcher", + title, + message, + notificationType) + .notify(getProject()); + } + }); + } + }; + + task.queue(); + + return new ActionCallback.Done(); + } + }); + } + + private static boolean isRootInExistingFile(VirtualFile root) { + if (root.getFileSystem() instanceof JarFileSystem) { + VirtualFile jar = JarFileSystem.getInstance().getVirtualFileForJar(root); + if (jar == null) return false; + + jar.refresh(false, false); + + return root.isValid(); + } + + return true; + } + + public static void attachSourceJar(@NotNull File sourceJar, @NotNull Collection libraries) { + AccessToken accessToken = WriteAction.start(); + + try { + VirtualFile srcFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(sourceJar); + if (srcFile == null) return; + + VirtualFile jarRoot = JarFileSystem.getInstance().getJarRootForLocalFile(srcFile); + if (jarRoot == null) return; + + VirtualFile[] roots = PathUIUtils.scanAndSelectDetectedJavaSourceRoots(null, new VirtualFile[]{jarRoot}); + if (roots.length == 0) { + roots = new VirtualFile[]{jarRoot}; + } + + for (Library library : libraries) { + Library.ModifiableModel model = library.getModifiableModel(); + List alreadyExistingFiles = Arrays.asList(model.getFiles(OrderRootType.SOURCES)); + + for (VirtualFile root : roots) { + if (!alreadyExistingFiles.contains(root)) { + model.addRoot(root, OrderRootType.SOURCES); + } + } + model.commit(); + } + } + finally { + accessToken.finish(); + } + } + + public static File getLibrarySourceDir() { + String path = System.getProperty("idea.library.source.dir"); + if (path != null) { + return new File(path); + } + + return new File(SystemProperties.getUserHome(), ".ideaLibSources"); + } +} diff --git a/java/java-impl/java-impl.iml b/java/java-impl/java-impl.iml index 38af588e1a42..bf44242b28b0 100644 --- a/java/java-impl/java-impl.iml +++ b/java/java-impl/java-impl.iml @@ -36,6 +36,7 @@ + diff --git a/java/java-impl/src/com/intellij/jarFinder/FindJarFix.java b/java/java-impl/src/com/intellij/jarFinder/FindJarFix.java new file mode 100644 index 000000000000..8fdf05c0a620 --- /dev/null +++ b/java/java-impl/src/com/intellij/jarFinder/FindJarFix.java @@ -0,0 +1,301 @@ +package com.intellij.jarFinder; + +import com.intellij.codeInsight.daemon.impl.quickfix.OrderEntryFix; +import com.intellij.codeInsight.hint.HintManager; +import com.intellij.codeInsight.intention.IntentionAction; +import com.intellij.ide.util.PropertiesComponent; +import com.intellij.openapi.application.AccessToken; +import com.intellij.openapi.application.WriteAction; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.fileChooser.FileChooser; +import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleUtil; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.progress.Task; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.popup.JBPopupFactory; +import com.intellij.openapi.util.Iconable; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.CommonClassNames; +import com.intellij.psi.JavaPsiFacade; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.ui.components.JBList; +import com.intellij.util.IncorrectOperationException; +import com.intellij.util.NotNullFunction; +import com.intellij.util.PlatformIcons; +import com.intellij.util.download.DownloadableFileDescription; +import com.intellij.util.download.DownloadableFileService; +import org.apache.xerces.parsers.DOMParser; +import org.jetbrains.annotations.NotNull; +import org.w3c.dom.Document; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +import javax.swing.*; +import java.io.File; +import java.io.IOException; +import java.util.*; + +/** + * @author Konstantin Bulenkov + */ +public abstract class FindJarFix implements IntentionAction, Iconable { + private static final String CLASS_ROOT_URL = "http://findjar.com/class/"; + private static final String CLASS_PAGE_EXT = ".html"; + private static final String SERVICE_URL = "http://findjar.com"; + private static final String LINK_TAG_NAME = "a"; + private static final String LINK_ATTR_NAME = "href"; + + protected final T myRef; + protected final Module myModule; + protected JComponent myEditorComponent; + + public FindJarFix(T ref) { + myRef = ref; + myModule = ModuleUtil.findModuleForPsiElement(ref); + } + + @NotNull + @Override + public String getText() { + return "Find jar on web"; + } + + @NotNull + @Override + public String getFamilyName() { + return "Family name"; + } + + @Override + public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) { + return myRef.isValid() + && JavaPsiFacade.getInstance(project).findClass(CommonClassNames.JAVA_LANG_OBJECT, file.getResolveScope()) != null + && myModule != null + && isFqnsOk(project, getPossibleFqns(myRef)); + } + + private static boolean isFqnsOk(Project project, List fqns) { + if (fqns.isEmpty()) return false; + final JavaPsiFacade facade = JavaPsiFacade.getInstance(project); + final GlobalSearchScope scope = GlobalSearchScope.allScope(project); + for (String fqn : fqns) { + if (facade.findClass(fqn, scope) != null) return false; + } + return true; + } + + @Override + public void invoke(@NotNull Project project, final Editor editor, final PsiFile file) throws IncorrectOperationException { + final List fqns = getPossibleFqns(myRef); + myEditorComponent = editor.getComponent(); + if (fqns.size() > 1) { + final JBList listOfFqns = new JBList(fqns); + JBPopupFactory.getInstance() + .createListPopupBuilder(listOfFqns) + .setTitle("Select Qualified Name") + .setItemChoosenCallback(new Runnable() { + @Override + public void run() { + final Object value = listOfFqns.getSelectedValue(); + if (value instanceof String) { + findJarsForFqn(((String)value), editor); + } + } + }).createPopup().showInBestPositionFor(editor); + } + else if (fqns.size() == 1) { + findJarsForFqn(fqns.get(0), editor); + } + } + + private void findJarsForFqn(final String fqn, final Editor editor) { + final Map libs = new HashMap(); + + final Runnable runnable = new Runnable() { + public void run() { + try { + final DOMParser parser = new DOMParser(); + parser.parse(CLASS_ROOT_URL + fqn.replace('.', '/') + CLASS_PAGE_EXT); + final Document doc = parser.getDocument(); + if (doc != null) { + final NodeList links = doc.getElementsByTagName(LINK_TAG_NAME); + for (int i = 0; i < links.getLength(); i++) { + final Node link = links.item(i); + final String libName = link.getTextContent(); + final NamedNodeMap attributes = link.getAttributes(); + if (attributes != null) { + final Node href = attributes.getNamedItem(LINK_ATTR_NAME); + if (href != null) { + final String pathToJar = href.getTextContent(); + if (pathToJar != null && (pathToJar.startsWith("/jar/") || pathToJar.startsWith("/class/../"))) { + libs.put(libName, SERVICE_URL + pathToJar); + } + } + } + } + } + } + catch (IOException ignore) {// + } + catch (SAXException e) {// + } + } + }; + + final Task.Modal task = new Task.Modal(editor.getProject(), "Looking for libraries", true) { + @Override + public void run(@NotNull ProgressIndicator indicator) { + indicator.setIndeterminate(true); + runnable.run(); + } + + @Override + public void onSuccess() { + super.onSuccess(); + if (libs.isEmpty()) { + HintManager.getInstance().showInformationHint(editor, "No libraries found for '" + fqn + "'"); + } else { + final ArrayList variants = new ArrayList(libs.keySet()); + Collections.sort(variants, new Comparator() { + @Override + public int compare(String o1, String o2) { + return o1.compareTo(o2); + } + }); + final JBList libNames = new JBList(variants); + libNames.installCellRenderer(new NotNullFunction() { + @NotNull + @Override + public JComponent fun(Object o) { + return new JLabel(o.toString(), PlatformIcons.JAR_ICON, SwingConstants.LEFT); + } + }); + if (libs.size() == 1) { + final String jarName = libs.keySet().iterator().next(); + final String url = libs.get(jarName); + initiateDownload(url, jarName); + } else { + JBPopupFactory.getInstance() + .createListPopupBuilder(libNames) + .setTitle("Select a jar file") + .setItemChoosenCallback(new Runnable() { + @Override + public void run() { + final Object value = libNames.getSelectedValue(); + if (value instanceof String) { + final String jarName = (String)value; + final String url = libs.get(jarName); + if (url != null) { + initiateDownload(url, jarName); + } + } + } + }) + .createPopup().showInBestPositionFor(editor); + } + } + } + }; + + ProgressManager.getInstance().run(task); + } + + private void initiateDownload(String url, String jarName) { + DOMParser parser = new DOMParser(); + try { + parser.parse(url); + final Document doc = parser.getDocument(); + if (doc != null) { + final NodeList links = doc.getElementsByTagName(LINK_TAG_NAME); + if (links != null) { + for (int i = 0; i < links.getLength(); i++) { + final Node item = links.item(i); + if (item != null) { + final NamedNodeMap attributes = item.getAttributes(); + if (attributes != null) { + final Node link = attributes.getNamedItem(LINK_ATTR_NAME); + if (link != null) { + final String jarUrl = link.getTextContent(); + if (jarUrl != null && jarUrl.endsWith(jarName)) { + downloadJar(jarUrl, jarName); + } + } + } + } + } + } + } + } + catch (SAXException e) {// + } + catch (IOException e) {// + } + } + + private void downloadJar(String jarUrl, String jarName) { + final Project project = myModule.getProject(); + final String dirPath = PropertiesComponent.getInstance(project).getValue("findjar.last.used.dir"); + VirtualFile toSelect = dirPath == null ? null : LocalFileSystem.getInstance().findFileByIoFile(new File(dirPath)); + final VirtualFile file = FileChooser.chooseFile(FileChooserDescriptorFactory.createSingleFolderDescriptor(), project, toSelect); + if (file != null) { + PropertiesComponent.getInstance(project).setValue("findjar.last.used.dir", file.getPath()); + final DownloadableFileService downloader = DownloadableFileService.getInstance(); + final DownloadableFileDescription description = downloader.createFileDescription(jarUrl, jarName); + final VirtualFile[] jars = downloader.createDownloader(Arrays.asList(description), project, myEditorComponent, jarName) + .toDirectory(file.getPath()).download(); + if (jars != null && jars.length == 1) { + AccessToken token = WriteAction.start(); + try { + OrderEntryFix.addJarToRoots(jars[0].getPresentableUrl(), myModule, myRef); + } + finally { + token.finish(); + } + } + } + } + + protected abstract Collection getFqns(@NotNull T ref); + + protected List getPossibleFqns(T ref) { + Collection fqns = getFqns(ref); + + List res = new ArrayList(fqns.size()); + + for (String fqn : fqns) { + if (fqn.startsWith("java.") || fqn.startsWith("javax.swing.")) { + continue; + } + final int index = fqn.lastIndexOf('.'); + if (index == -1) { + continue; + } + final String className = fqn.substring(index + 1); + if (className.length() == 0 || Character.isLowerCase(className.charAt(0))) { + continue; + } + + res.add(fqn); + } + + return res; + } + + @Override + public boolean startInWriteAction() { + return false; + } + + @Override + public Icon getIcon(int flags) { + return PlatformIcons.WEB_ICON; + } +} diff --git a/java/java-impl/src/com/intellij/jarFinder/FindJarQuickFixProvider.java b/java/java-impl/src/com/intellij/jarFinder/FindJarQuickFixProvider.java new file mode 100644 index 000000000000..9cf66d3991fe --- /dev/null +++ b/java/java-impl/src/com/intellij/jarFinder/FindJarQuickFixProvider.java @@ -0,0 +1,22 @@ +package com.intellij.jarFinder; + +import com.intellij.codeInsight.daemon.QuickFixActionRegistrar; +import com.intellij.codeInsight.quickfix.UnresolvedReferenceQuickFixProvider; +import com.intellij.psi.PsiJavaCodeReferenceElement; +import org.jetbrains.annotations.NotNull; + +/** + * @author Konstantin Bulenkov + */ +public class FindJarQuickFixProvider extends UnresolvedReferenceQuickFixProvider { + @Override + public void registerFixes(PsiJavaCodeReferenceElement ref, QuickFixActionRegistrar registrar) { + registrar.register(new JavaFindJarFix(ref)); + } + + @NotNull + @Override + public Class getReferenceClass() { + return PsiJavaCodeReferenceElement.class; + } +} diff --git a/java/java-impl/src/com/intellij/jarFinder/JavaFindJarFix.java b/java/java-impl/src/com/intellij/jarFinder/JavaFindJarFix.java new file mode 100644 index 000000000000..98e5a6709a5c --- /dev/null +++ b/java/java-impl/src/com/intellij/jarFinder/JavaFindJarFix.java @@ -0,0 +1,78 @@ +package com.intellij.jarFinder; + +import com.intellij.psi.*; +import com.intellij.psi.impl.source.PsiImportStaticStatementImpl; +import com.intellij.psi.util.PsiTreeUtil; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +/** + * @author Sergey Evdokimov + */ +public class JavaFindJarFix extends FindJarFix { + public JavaFindJarFix(PsiQualifiedReferenceElement ref) { + super(ref); + } + + @Override + protected Collection getFqns(@NotNull PsiQualifiedReferenceElement ref) { + final PsiImportStatementBase importStatement = PsiTreeUtil.getParentOfType(ref.getElement(), PsiImportStatementBase.class); + + //from static imports + if (importStatement != null) { + if (importStatement instanceof PsiImportStatement) { + final String importFQN = ((PsiImportStatement)importStatement).getQualifiedName(); + if (importFQN != null && !importFQN.endsWith("*")) { + return Collections.singleton(importFQN); + } + } + else if (importStatement instanceof PsiImportStaticStatementImpl) { + final PsiJavaCodeReferenceElement classRef = ((PsiImportStaticStatementImpl)importStatement).getClassReference(); + if (classRef != null) { + final String importFQN = classRef.getQualifiedName(); + if (importFQN != null) { + return Collections.singleton(importFQN); + } + } + } + return Collections.emptyList(); + } + + final PsiElement qualifier = ref.getQualifier(); + if (qualifier instanceof PsiQualifiedReference) { + //PsiQualifiedReference r = (PsiQualifiedReference)qualifier; + //TODO[kb] get fqn from expressions like org.unresolvedPackage.MyClass.staticMethodCall(...); + return Collections.emptyList(); + } + final String className = ref.getReferenceName(); + PsiFile file = ref.getContainingFile().getOriginalFile(); + if (className != null && file instanceof PsiJavaFile) { + final PsiImportList importList = ((PsiJavaFile)file).getImportList(); + if (importList != null) { + final PsiImportStatementBase statement = importList.findSingleImportStatement(className); + if (statement instanceof PsiImportStatement) { + final String importFQN = ((PsiImportStatement)statement).getQualifiedName(); + if (importFQN != null) { + return Collections.singleton(importFQN); + } + } + else { + List res = new ArrayList(); + // iterate through * + for (PsiImportStatementBase imp : importList.getAllImportStatements()) { + if (imp.isOnDemand() && imp instanceof PsiImportStatement) { + res.add(((PsiImportStatement)imp).getQualifiedName() + "." + className); + } + } + + return res; + } + } + } + return Collections.emptyList(); + } +} diff --git a/java/java-impl/src/com/intellij/jarFinder/MavenCentralSourceSearcher.java b/java/java-impl/src/com/intellij/jarFinder/MavenCentralSourceSearcher.java new file mode 100644 index 000000000000..deaed59885ec --- /dev/null +++ b/java/java-impl/src/com/intellij/jarFinder/MavenCentralSourceSearcher.java @@ -0,0 +1,70 @@ +package com.intellij.jarFinder; + +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.progress.ProgressIndicator; +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.JDOMException; +import org.jdom.xpath.XPath; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; +import java.util.List; + +/** + * @author Sergey Evdokimov + */ +public class MavenCentralSourceSearcher extends SourceSearcher { + + private static final Logger LOG = Logger.getInstance(MavenCentralSourceSearcher.class); + + @Nullable + @Override + protected String findSourceJar(@NotNull ProgressIndicator indicator, + @NotNull String artifactId, + @NotNull String version) throws SourceSearchException { + try { + indicator.setIndeterminate(true); + indicator.setText("Connecting to http://search.maven.org"); + + indicator.checkCanceled(); + + String url = "http://search.maven.org/solrsearch/select?rows=3&wt=xml&q=a:%22" + artifactId + "%22%20AND%20v:%22" + version + "%22%20AND%20l:%22sources%22"; + Document document = readDocumentCancelable(indicator, url); + + indicator.checkCanceled(); + + List artifactList = (List)XPath.newInstance("/response/result/doc/str[@name='g']").selectNodes(document); + if (artifactList.isEmpty()) { + return null; + } + + Element element; + + if (artifactList.size() == 1) { + element = artifactList.get(0); + } + else { + // TODO handle + return null; + } + + String groupId = element.getValue(); + + String downloadUrl = "http://search.maven.org/remotecontent?filepath=" + groupId.replace('.', '/') + '/' + artifactId + '/' + version + '/' + artifactId + '-' + version + "-sources.jar"; + + return downloadUrl; + } + catch (JDOMException e) { + LOG.warn(e); + throw new SourceSearchException("Failed to parse response from server. See log for more details."); + } + catch (IOException e) { + indicator.checkCanceled(); // Cause of IOException may be canceling of operation. + + LOG.warn(e); + throw new SourceSearchException("Connection problem. See log for more details."); + } + } +} diff --git a/java/java-impl/src/com/intellij/jarFinder/SonatypeSourceSearcher.java b/java/java-impl/src/com/intellij/jarFinder/SonatypeSourceSearcher.java new file mode 100644 index 000000000000..2a942cca8946 --- /dev/null +++ b/java/java-impl/src/com/intellij/jarFinder/SonatypeSourceSearcher.java @@ -0,0 +1,87 @@ +package com.intellij.jarFinder; + +import com.intellij.notification.NotificationType; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.util.Pair; +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.JDOMException; +import org.jdom.xpath.XPath; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; +import java.util.List; + +/** + * @author Sergey Evdokimov + */ +public class SonatypeSourceSearcher extends SourceSearcher { + + private static final Logger LOG = Logger.getInstance(SonatypeSourceSearcher.class); + + @Nullable + @Override + public String findSourceJar(@NotNull final ProgressIndicator indicator, @NotNull String artifactId, @NotNull String version) + throws SourceSearchException { + try { + indicator.setIndeterminate(true); + indicator.setText("Connecting to https://oss.sonatype.org"); + + indicator.checkCanceled(); + + String url = "https://oss.sonatype.org/service/local/lucene/search?collapseresults=true&c=sources&a=" + artifactId + "&v=" + version; + Document document = readDocumentCancelable(indicator, url); + + indicator.checkCanceled(); + + List artifactList = (List)XPath.newInstance("/searchNGResponse/data/artifact").selectNodes(document); + if (artifactList.isEmpty()) { + return null; + } + + Element element; + + if (artifactList.size() == 1) { + element = artifactList.get(0); + } + else { + // TODO handle + return null; + } + + List artifactHintList = + (List)XPath.newInstance("artifactHits/artifactHit/artifactLinks/artifactLink/classifier[text()='sources']/../../..") + .selectNodes(element); + if (artifactHintList.isEmpty()) { + return null; + } + + String groupId = element.getChildTextTrim("groupId"); + String repositoryId = artifactHintList.get(0).getChildTextTrim("repositoryId"); + + String downloadUrl = "https://oss.sonatype.org/service/local/artifact/maven/redirect?r=" + + repositoryId + + "&g=" + + groupId + + "&a=" + + artifactId + + "&v=" + + version + + "&e=jar&c=sources"; + + return downloadUrl; + } + catch (JDOMException e) { + LOG.warn(e); + throw new SourceSearchException("Failed to parse response from server. See log for more details."); + } + catch (IOException e) { + indicator.checkCanceled(); // Cause of IOException may be canceling of operation. + + LOG.warn(e); + throw new SourceSearchException("Connection problem. See log for more details."); + } + } +} diff --git a/java/java-impl/src/com/intellij/jarFinder/SourceSearcher.java b/java/java-impl/src/com/intellij/jarFinder/SourceSearcher.java new file mode 100644 index 000000000000..b1d635af8525 --- /dev/null +++ b/java/java-impl/src/com/intellij/jarFinder/SourceSearcher.java @@ -0,0 +1,77 @@ +package com.intellij.jarFinder; + +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.util.net.HttpConfigurable; +import org.jdom.Document; +import org.jdom.JDOMException; +import org.jdom.input.SAXBuilder; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; + +/** + * @author Sergey Evdokimov + */ +public abstract class SourceSearcher { + + /** + * @param indicator + * @param artifactId + * @param version + * @return groupId of found artifact and url. + */ + @Nullable + protected abstract String findSourceJar(@NotNull final ProgressIndicator indicator, @NotNull String artifactId, @NotNull String version) throws SourceSearchException; + + protected static Document readDocumentCancelable(final ProgressIndicator indicator, String url) throws JDOMException, IOException { + final HttpURLConnection urlConnection = HttpConfigurable.getInstance().openHttpConnection(url); + + Thread t = new Thread(new Runnable() { + @Override + public void run() { + try { + //noinspection InfiniteLoopStatement + while (true) { + if (indicator.isCanceled()) { + urlConnection.disconnect(); + } + + //noinspection BusyWait + Thread.sleep(100); + } + } + catch (InterruptedException ignored) { + + } + } + }); + + t.start(); + + try { + urlConnection.setRequestProperty("accept", "application/xml"); + + InputStream inputStream = urlConnection.getInputStream(); + try { + return new SAXBuilder().build(inputStream); + } + finally { + inputStream.close(); + } + } + finally { + t.interrupt(); + } + } +} + +class SourceSearchException extends Exception { + + SourceSearchException(String message) { + super(message); + } + +} \ No newline at end of file diff --git a/plugins/generate-tostring/src/META-INF/plugin.xml b/plugins/generate-tostring/src/META-INF/plugin.xml deleted file mode 100644 index 7889a0ff3b36..000000000000 --- a/plugins/generate-tostring/src/META-INF/plugin.xml +++ /dev/null @@ -1,33 +0,0 @@ - - GenerateToString - Adds a new action 'toString()' in the generate menu (alt + ins). The action generates a toString() - method that dumps the classes fields. Java body code is generated using Velocity Macro and you can change this - to fit your needs. Full documentation included (Click hyperlink from Settings). - - 5.0 - Claus Ibsen - - - - - - - - - - - - - - - - - diff --git a/plugins/generate-tostring/src/org/jetbrains/generate/tostring/package.html b/plugins/generate-tostring/src/org/jetbrains/generate/tostring/package.html index 53296bfaf325..59030937a65d 100644 --- a/plugins/generate-tostring/src/org/jetbrains/generate/tostring/package.html +++ b/plugins/generate-tostring/src/org/jetbrains/generate/tostring/package.html @@ -2,11 +2,6 @@

Generate toString() plugin

-

Plugin home page

- The plugin is hosted at Google code where there is an official issue tracker and wiki pages with more information. -
- Plugin home page: generate-tostring -

Introduction

GenerateToString is a action plugin for IDEA that is used to create or update java classes toString() method. The reason is valuebeans usually needs to dump their field values for debug purpose, and it's tedious to write diff --git a/plugins/groovy/src/META-INF/plugin.xml b/plugins/groovy/src/META-INF/plugin.xml index 155320cf9eac..72d1233a2d96 100644 --- a/plugins/groovy/src/META-INF/plugin.xml +++ b/plugins/groovy/src/META-INF/plugin.xml @@ -1415,6 +1415,7 @@ + diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/jarFinder/GroovyFindJarFix.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/jarFinder/GroovyFindJarFix.java new file mode 100644 index 000000000000..4f4c6262d425 --- /dev/null +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/jarFinder/GroovyFindJarFix.java @@ -0,0 +1,59 @@ +package org.jetbrains.plugins.groovy.jarFinder; + +import com.intellij.jarFinder.FindJarFix; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiQualifiedReference; +import com.intellij.psi.util.PsiTreeUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.plugins.groovy.lang.psi.GrReferenceElement; +import org.jetbrains.plugins.groovy.lang.psi.GroovyFile; +import org.jetbrains.plugins.groovy.lang.psi.api.toplevel.imports.GrImportStatement; +import org.jetbrains.plugins.groovy.lang.psi.api.types.GrCodeReferenceElement; + +import java.util.Collection; +import java.util.Collections; + +/** + * @author Sergey Evdokimov + */ +public class GroovyFindJarFix extends FindJarFix { + public GroovyFindJarFix(GrReferenceElement ref) { + super(ref); + } + + @Override + protected Collection getFqns(@NotNull GrReferenceElement ref) { + GrImportStatement importStatement = PsiTreeUtil.getParentOfType(ref.getElement(), GrImportStatement.class); + + //from static imports + if (importStatement != null) { + GrCodeReferenceElement reference = importStatement.getImportReference(); + if (reference != null) { + return Collections.singleton(reference.getText()); + } + + return Collections.emptyList(); + } + + if (ref.getQualifier() != null) return Collections.emptyList(); + + final String className = ref.getReferenceName(); + if (className == null) return Collections.emptyList(); + + PsiFile file = ref.getContainingFile().getOriginalFile(); + if (!(file instanceof GroovyFile)) return Collections.emptyList(); + + GrImportStatement[] importList = ((GroovyFile)file).getImportStatements(); + + for (GrImportStatement imp : importList) { + if (className.equals(imp.getImportedName())) { + GrCodeReferenceElement importReference = imp.getImportReference(); + if (importReference == null) return Collections.emptyList(); + return Collections.singleton(importReference.getText()); + } + } + + return Collections.emptyList(); + } +} diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/jarFinder/GroovyFindJarQuickFixProvider.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/jarFinder/GroovyFindJarQuickFixProvider.java new file mode 100644 index 000000000000..15021e83cdb6 --- /dev/null +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/jarFinder/GroovyFindJarQuickFixProvider.java @@ -0,0 +1,24 @@ +package org.jetbrains.plugins.groovy.jarFinder; + +import com.intellij.codeInsight.daemon.QuickFixActionRegistrar; +import com.intellij.codeInsight.quickfix.UnresolvedReferenceQuickFixProvider; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.plugins.groovy.lang.psi.GrReferenceElement; +import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression; +import org.jetbrains.plugins.groovy.lang.psi.api.types.GrCodeReferenceElement; + +/** + * @author Sergey Evdokimov + */ +public class GroovyFindJarQuickFixProvider extends UnresolvedReferenceQuickFixProvider { + @Override + public void registerFixes(GrReferenceElement ref, QuickFixActionRegistrar registrar) { + registrar.register(new GroovyFindJarFix(ref)); + } + + @NotNull + @Override + public Class getReferenceClass() { + return GrReferenceElement.class; + } +} diff --git a/resources/resources.iml b/resources/resources.iml index 35f699e9f562..0e8c4a289246 100644 --- a/resources/resources.iml +++ b/resources/resources.iml @@ -15,6 +15,7 @@ +
diff --git a/resources/src/META-INF/IdeaPlugin.xml b/resources/src/META-INF/IdeaPlugin.xml index b7c3fc7b2afe..30980be671d7 100644 --- a/resources/src/META-INF/IdeaPlugin.xml +++ b/resources/src/META-INF/IdeaPlugin.xml @@ -1444,6 +1444,19 @@ + + + + + + + diff --git a/resources/src/idea/JavaActions.xml b/resources/src/idea/JavaActions.xml index 3d63ba8b2508..69fa91defe5f 100644 --- a/resources/src/idea/JavaActions.xml +++ b/resources/src/idea/JavaActions.xml @@ -11,6 +11,7 @@ + diff --git a/resources/src/idea/RichPlatformPlugin.xml b/resources/src/idea/RichPlatformPlugin.xml index dcb2acda4d18..302b7793998e 100644 --- a/resources/src/idea/RichPlatformPlugin.xml +++ b/resources/src/idea/RichPlatformPlugin.xml @@ -283,6 +283,8 @@ + +