blob: 11a0c2b9ccd6f992131df47fbce608bc93f3f804 [file] [log] [blame]
/*
* 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);
}
}
}
}