| /* |
| * Copyright 2000-2014 JetBrains s.r.o. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.jetbrains.plugins.groovy.grape; |
| |
| import com.intellij.codeInsight.intention.IntentionAction; |
| import com.intellij.execution.CantRunException; |
| import com.intellij.execution.ExecutionException; |
| import com.intellij.execution.configurations.GeneralCommandLine; |
| import com.intellij.execution.configurations.JavaParameters; |
| import com.intellij.execution.process.DefaultJavaProcessHandler; |
| import com.intellij.execution.process.ProcessOutputTypes; |
| import com.intellij.notification.NotificationDisplayType; |
| import com.intellij.notification.NotificationGroup; |
| import com.intellij.notification.NotificationType; |
| import com.intellij.openapi.application.Result; |
| import com.intellij.openapi.application.WriteAction; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.editor.Editor; |
| import com.intellij.openapi.module.Module; |
| import com.intellij.openapi.module.ModuleUtilCore; |
| 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.projectRoots.JavaSdkType; |
| import com.intellij.openapi.projectRoots.JdkUtil; |
| import com.intellij.openapi.projectRoots.Sdk; |
| import com.intellij.openapi.projectRoots.SdkTypeId; |
| import com.intellij.openapi.roots.ModifiableRootModel; |
| import com.intellij.openapi.roots.ModuleRootManager; |
| import com.intellij.openapi.roots.OrderRootType; |
| import com.intellij.openapi.roots.libraries.Library; |
| import com.intellij.openapi.roots.libraries.LibraryTable; |
| import com.intellij.openapi.ui.Messages; |
| import com.intellij.openapi.util.Key; |
| import com.intellij.openapi.util.Ref; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.openapi.vfs.JarFileSystem; |
| import com.intellij.openapi.vfs.LocalFileSystem; |
| import com.intellij.openapi.vfs.VirtualFile; |
| import com.intellij.psi.*; |
| import com.intellij.psi.search.LocalSearchScope; |
| import com.intellij.psi.search.searches.ReferencesSearch; |
| import com.intellij.psi.util.CachedValueProvider; |
| import com.intellij.psi.util.CachedValuesManager; |
| import com.intellij.psi.util.PsiTreeUtil; |
| import com.intellij.util.*; |
| import com.intellij.util.containers.ContainerUtil; |
| import gnu.trove.THashSet; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| import org.jetbrains.plugins.groovy.lang.psi.GrReferenceElement; |
| import org.jetbrains.plugins.groovy.lang.psi.GroovyFile; |
| import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.annotation.GrAnnotation; |
| import org.jetbrains.plugins.groovy.lang.psi.api.types.GrCodeReferenceElement; |
| import org.jetbrains.plugins.groovy.runner.DefaultGroovyScriptRunner; |
| import org.jetbrains.plugins.groovy.runner.GroovyScriptRunConfiguration; |
| |
| import java.io.File; |
| import java.net.MalformedURLException; |
| import java.net.URISyntaxException; |
| import java.net.URL; |
| import java.util.*; |
| |
| /** |
| * @author peter |
| */ |
| public class GrabDependencies implements IntentionAction { |
| private static final Logger LOG = Logger.getInstance("#org.jetbrains.plugins.groovy.grape.GrabDependencies"); |
| |
| private static final NotificationGroup NOTIFICATION_GROUP = new NotificationGroup("Grape", NotificationDisplayType.BALLOON, true); |
| public static final String GRAPE_RUNNER = "org.jetbrains.plugins.groovy.grape.GrapeRunner"; |
| |
| @Override |
| @NotNull |
| public String getText() { |
| return "Grab the artifacts"; |
| } |
| |
| @Override |
| @NotNull |
| public String getFamilyName() { |
| return "Grab"; |
| } |
| |
| @Override |
| public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) { |
| if (!isCorrectModule(file)) return false; |
| |
| int offset = editor.getCaretModel().getOffset(); |
| final GrAnnotation anno = PsiTreeUtil.findElementOfClassAtOffset(file, offset, GrAnnotation.class, false); |
| if (anno != null && isGrabAnnotation(anno)) { |
| return true; |
| } |
| |
| PsiElement at = file.findElementAt(offset); |
| if (at != null && isUnresolvedRefName(at) && findGrab(file) != null) { |
| return true; |
| } |
| |
| return false; |
| } |
| |
| private static PsiAnnotation findGrab(final PsiFile file) { |
| if (!(file instanceof GroovyFile)) return null; |
| |
| return CachedValuesManager.getCachedValue(file, new CachedValueProvider<PsiAnnotation>() { |
| @Nullable |
| @Override |
| public Result<PsiAnnotation> compute() { |
| PsiClass grab = JavaPsiFacade.getInstance(file.getProject()).findClass(GrabAnnos.GRAB_ANNO, file.getResolveScope()); |
| final Ref<PsiAnnotation> result = Ref.create(); |
| if (grab != null) { |
| ReferencesSearch.search(grab, new LocalSearchScope(file)).forEach(new Processor<PsiReference>() { |
| @Override |
| public boolean process(PsiReference reference) { |
| if (reference instanceof GrCodeReferenceElement) { |
| PsiElement parent = ((GrCodeReferenceElement)reference).getParent(); |
| if (parent instanceof PsiAnnotation) { |
| result.set((PsiAnnotation)parent); |
| return false; |
| } |
| } |
| return true; |
| } |
| }); |
| } |
| return Result.create(result.get(), file); |
| } |
| }); |
| } |
| |
| private static boolean isUnresolvedRefName(@NotNull PsiElement at) { |
| PsiElement parent = at.getParent(); |
| return parent instanceof GrReferenceElement && ((GrReferenceElement)parent).getReferenceNameElement() == at && ((GrReferenceElement)parent).resolve() == null; |
| } |
| |
| private static boolean isGrabAnnotation(@NotNull GrAnnotation anno) { |
| final String qname = anno.getQualifiedName(); |
| return qname != null && (qname.startsWith(GrabAnnos.GRAB_ANNO) || GrabAnnos.GRAPES_ANNO.equals(qname)); |
| } |
| |
| private static boolean isCorrectModule(PsiFile file) { |
| final Module module = ModuleUtilCore.findModuleForPsiElement(file); |
| if (module == null) { |
| return false; |
| } |
| |
| final Sdk sdk = ModuleRootManager.getInstance(module).getSdk(); |
| if (sdk == null) { |
| return false; |
| } |
| |
| return file.getOriginalFile().getVirtualFile() != null && sdk.getSdkType() instanceof JavaSdkType; |
| } |
| |
| @Override |
| public void invoke(@NotNull final Project project, Editor editor, PsiFile file) throws IncorrectOperationException { |
| final Module module = ModuleUtilCore.findModuleForPsiElement(file); |
| assert module != null; |
| |
| final VirtualFile vfile = file.getOriginalFile().getVirtualFile(); |
| assert vfile != null; |
| |
| if (JavaPsiFacade.getInstance(project).findClass("org.apache.ivy.core.report.ResolveReport", file.getResolveScope()) == null) { |
| Messages.showErrorDialog("Sorry, but IDEA cannot @Grab the dependencies without Ivy. Please add Ivy to your module dependencies and re-run the action.", |
| "Ivy Missing"); |
| return; |
| } |
| |
| Map<String, String> queries = prepareQueries(file); |
| |
| final Sdk sdk = ModuleRootManager.getInstance(module).getSdk(); |
| assert sdk != null; |
| SdkTypeId sdkType = sdk.getSdkType(); |
| assert sdkType instanceof JavaSdkType; |
| final String exePath = ((JavaSdkType)sdkType).getVMExecutablePath(sdk); |
| |
| final Map<String, GeneralCommandLine> lines = new HashMap<String, GeneralCommandLine>(); |
| for (String grabText : queries.keySet()) { |
| final JavaParameters javaParameters = GroovyScriptRunConfiguration.createJavaParametersWithSdk(module); |
| //debug |
| //javaParameters.getVMParametersList().add("-Xdebug"); javaParameters.getVMParametersList().add("-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5239"); |
| |
| try { |
| DefaultGroovyScriptRunner.configureGenericGroovyRunner(javaParameters, module, GRAPE_RUNNER, false, true); |
| } |
| catch (CantRunException e) { |
| NOTIFICATION_GROUP.createNotification("Can't run @Grab: " + ExceptionUtil.getMessage(e), ExceptionUtil.getThrowableText(e), NotificationType.ERROR, null).notify(project); |
| return; |
| } |
| javaParameters.getClassPath().add(PathUtil.getJarPathForClass(GrapeRunner.class)); |
| |
| javaParameters.getProgramParametersList().add(queries.get(grabText)); |
| |
| lines.put(grabText, JdkUtil.setupJVMCommandLine(exePath, javaParameters, true)); |
| } |
| |
| ProgressManager.getInstance().run(new Task.Backgroundable(project, "Processing @Grab annotations") { |
| |
| @Override |
| public void run(@NotNull ProgressIndicator indicator) { |
| int jarCount = 0; |
| String messages = ""; |
| |
| for (Map.Entry<String, GeneralCommandLine> entry : lines.entrySet()) { |
| String grabText = entry.getKey(); |
| indicator.setText2(grabText); |
| try { |
| final GrapeProcessHandler handler = new GrapeProcessHandler(entry.getValue(), module); |
| handler.startNotify(); |
| handler.waitFor(); |
| jarCount += handler.jarCount; |
| messages += "<b>" + grabText + "</b>: " + handler.messages + "<p>"; |
| } |
| catch (ExecutionException e) { |
| LOG.error(e); |
| } |
| } |
| |
| final String finalMessages = messages; |
| final String title = jarCount + " Grape dependency jar" + (jarCount == 1 ? "" : "s") + " added"; |
| NOTIFICATION_GROUP.createNotification(title, finalMessages, NotificationType.INFORMATION, null).notify(project); |
| } |
| }); |
| } |
| |
| static Map<String, String> prepareQueries(PsiFile file) { |
| final Set<GrAnnotation> grabs = new LinkedHashSet<GrAnnotation>(); |
| final Set<GrAnnotation> excludes = new THashSet<GrAnnotation>(); |
| final Set<GrAnnotation> resolvers = new THashSet<GrAnnotation>(); |
| file.acceptChildren(new PsiRecursiveElementWalkingVisitor() { |
| @Override |
| public void visitElement(PsiElement element) { |
| if (element instanceof GrAnnotation) { |
| GrAnnotation anno = (GrAnnotation)element; |
| String qname = anno.getQualifiedName(); |
| if (GrabAnnos.GRAB_ANNO.equals(qname)) grabs.add(anno); |
| else if (GrabAnnos.GRAB_EXCLUDE_ANNO.equals(qname)) excludes.add(anno); |
| else if (GrabAnnos.GRAB_RESOLVER_ANNO.equals(qname)) resolvers.add(anno); |
| } |
| super.visitElement(element); |
| } |
| }); |
| |
| Function<GrAnnotation, String> mapper = new Function<GrAnnotation, String>() { |
| @Override |
| public String fun(GrAnnotation grAnnotation) { |
| return grAnnotation.getText(); |
| } |
| }; |
| String common = StringUtil.join(excludes, mapper, " ") + " " + StringUtil.join(resolvers, mapper, " "); |
| LinkedHashMap<String, String> result = new LinkedHashMap<String, String>(); |
| for (GrAnnotation grab : grabs) { |
| String grabText = grab.getText(); |
| result.put(grabText, (grabText + " " + common).trim()); |
| } |
| return result; |
| } |
| |
| @Override |
| public boolean startInWriteAction() { |
| return false; |
| } |
| |
| private static class GrapeProcessHandler extends DefaultJavaProcessHandler { |
| private final StringBuilder myStdOut = new StringBuilder(); |
| private final StringBuilder myStdErr = new StringBuilder(); |
| private final Module myModule; |
| |
| public GrapeProcessHandler(GeneralCommandLine commandLine, Module module) throws ExecutionException { |
| super(commandLine); |
| myModule = module; |
| } |
| |
| @Override |
| public void notifyTextAvailable(String text, Key outputType) { |
| text = StringUtil.convertLineSeparators(text); |
| if (LOG.isDebugEnabled()) { |
| LOG.debug(outputType + text); |
| } |
| if (outputType == ProcessOutputTypes.STDOUT) { |
| myStdOut.append(text); |
| } |
| else if (outputType == ProcessOutputTypes.STDERR) { |
| myStdErr.append(text); |
| } |
| } |
| |
| private void addGrapeDependencies(List<VirtualFile> jars) { |
| final ModifiableRootModel model = ModuleRootManager.getInstance(myModule).getModifiableModel(); |
| final LibraryTable.ModifiableModel tableModel = model.getModuleLibraryTable().getModifiableModel(); |
| for (VirtualFile jar : jars) { |
| final VirtualFile jarRoot = JarFileSystem.getInstance().getJarRootForLocalFile(jar); |
| if (jarRoot != null) { |
| OrderRootType rootType = OrderRootType.CLASSES; |
| String libName = "Grab:" + jar.getName(); |
| for (String classifier : ContainerUtil.ar("sources", "source", "src")) { |
| if (libName.endsWith("-" + classifier + ".jar")) { |
| rootType = OrderRootType.SOURCES; |
| libName = StringUtil.trimEnd(libName, "-" + classifier + ".jar") + ".jar"; |
| } |
| } |
| |
| Library library = tableModel.getLibraryByName(libName); |
| if (library == null) { |
| library = tableModel.createLibrary(libName); |
| } |
| |
| final Library.ModifiableModel libModel = library.getModifiableModel(); |
| for (String url : libModel.getUrls(rootType)) { |
| libModel.removeRoot(url, rootType); |
| } |
| libModel.addRoot(jarRoot, rootType); |
| libModel.commit(); |
| } |
| } |
| tableModel.commit(); |
| model.commit(); |
| } |
| |
| int jarCount; |
| String messages = ""; |
| |
| @Override |
| protected void notifyProcessTerminated(int exitCode) { |
| try { |
| final List<VirtualFile> jars = new ArrayList<VirtualFile>(); |
| for (String line : myStdOut.toString().split("\n")) { |
| if (line.startsWith(GrapeRunner.URL_PREFIX)) { |
| try { |
| final URL url = new URL(line.substring(GrapeRunner.URL_PREFIX.length())); |
| final File libFile = new File(url.toURI()); |
| if (libFile.exists() && libFile.getName().endsWith(".jar")) { |
| ContainerUtil.addIfNotNull(jars, LocalFileSystem.getInstance().refreshAndFindFileByIoFile(libFile)); |
| } |
| } |
| catch (MalformedURLException e) { |
| LOG.error(e); |
| } |
| catch (URISyntaxException e) { |
| LOG.error(e); |
| } |
| } |
| } |
| new WriteAction() { |
| @Override |
| protected void run(Result result) throws Throwable { |
| jarCount = jars.size(); |
| messages = jarCount + " jar"; |
| if (jarCount != 1) { |
| messages += "s"; |
| } |
| if (jarCount == 0) { |
| messages += "<br>" + myStdOut.toString().replaceAll("\n", "<br>") + "<p>" + myStdErr.toString().replaceAll("\n", "<br>"); |
| } |
| if (!jars.isEmpty()) { |
| addGrapeDependencies(jars); |
| } |
| } |
| }.execute(); |
| } |
| finally { |
| super.notifyProcessTerminated(exitCode); |
| } |
| } |
| } |
| } |