EditorConfig documentation test
[idea/community.git] / java / compiler / impl / src / com / intellij / packaging / impl / elements / ManifestFileUtil.java
1 // Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
2 package com.intellij.packaging.impl.elements;
3
4 import com.intellij.CommonBundle;
5 import com.intellij.ide.util.ClassFilter;
6 import com.intellij.ide.util.TreeClassChooser;
7 import com.intellij.ide.util.TreeClassChooserFactory;
8 import com.intellij.openapi.application.ApplicationManager;
9 import com.intellij.openapi.application.ReadAction;
10 import com.intellij.openapi.application.WriteAction;
11 import com.intellij.openapi.compiler.make.ManifestBuilder;
12 import com.intellij.openapi.deployment.DeploymentUtil;
13 import com.intellij.openapi.diagnostic.Logger;
14 import com.intellij.openapi.fileChooser.FileChooser;
15 import com.intellij.openapi.fileChooser.FileChooserDescriptor;
16 import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory;
17 import com.intellij.openapi.module.Module;
18 import com.intellij.openapi.project.Project;
19 import com.intellij.openapi.roots.OrderEnumerator;
20 import com.intellij.openapi.roots.ProjectRootManager;
21 import com.intellij.openapi.ui.Messages;
22 import com.intellij.openapi.ui.TextFieldWithBrowseButton;
23 import com.intellij.openapi.util.Ref;
24 import com.intellij.openapi.util.io.FileUtil;
25 import com.intellij.openapi.util.text.StringUtil;
26 import com.intellij.openapi.vfs.VfsUtil;
27 import com.intellij.openapi.vfs.VfsUtilCore;
28 import com.intellij.openapi.vfs.VirtualFile;
29 import com.intellij.packaging.artifacts.ArtifactType;
30 import com.intellij.packaging.elements.CompositePackagingElement;
31 import com.intellij.packaging.elements.PackagingElement;
32 import com.intellij.packaging.elements.PackagingElementFactory;
33 import com.intellij.packaging.elements.PackagingElementResolvingContext;
34 import com.intellij.packaging.impl.artifacts.ArtifactUtil;
35 import com.intellij.packaging.impl.artifacts.PackagingElementPath;
36 import com.intellij.packaging.impl.artifacts.PackagingElementProcessor;
37 import com.intellij.packaging.ui.ArtifactEditorContext;
38 import com.intellij.packaging.ui.ManifestFileConfiguration;
39 import com.intellij.psi.JavaPsiFacade;
40 import com.intellij.psi.PsiClass;
41 import com.intellij.psi.search.GlobalSearchScope;
42 import com.intellij.psi.util.PsiMethodUtil;
43 import com.intellij.util.PathUtil;
44 import org.jetbrains.annotations.NotNull;
45 import org.jetbrains.annotations.Nullable;
46
47 import java.awt.event.ActionEvent;
48 import java.awt.event.ActionListener;
49 import java.io.IOException;
50 import java.io.InputStream;
51 import java.io.OutputStream;
52 import java.util.ArrayList;
53 import java.util.List;
54 import java.util.jar.Attributes;
55 import java.util.jar.JarFile;
56 import java.util.jar.Manifest;
57
58 /**
59  * @author nik
60  */
61 public class ManifestFileUtil {
62   private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.roots.ui.configuration.artifacts.ArtifactEditorContextImpl");
63   public static final String MANIFEST_PATH = JarFile.MANIFEST_NAME;
64   public static final String MANIFEST_FILE_NAME = PathUtil.getFileName(MANIFEST_PATH);
65   public static final String MANIFEST_DIR_NAME = PathUtil.getParentPath(MANIFEST_PATH);
66
67   private ManifestFileUtil() {
68   }
69
70   @Nullable
71   public static VirtualFile findManifestFile(@NotNull CompositePackagingElement<?> root, PackagingElementResolvingContext context, ArtifactType artifactType) {
72     return ArtifactUtil.findSourceFileByOutputPath(root, MANIFEST_PATH, context, artifactType);
73   }
74
75   @Nullable
76   public static VirtualFile suggestManifestFileDirectory(@NotNull CompositePackagingElement<?> root, PackagingElementResolvingContext context, ArtifactType artifactType) {
77     final VirtualFile metaInfDir = ArtifactUtil.findSourceFileByOutputPath(root, MANIFEST_DIR_NAME, context, artifactType);
78     if (metaInfDir != null) {
79       return metaInfDir;
80     }
81
82     final Ref<VirtualFile> sourceDir = Ref.create(null);
83     final Ref<VirtualFile> sourceFile = Ref.create(null);
84     ArtifactUtil.processElementsWithSubstitutions(root.getChildren(), context, artifactType, PackagingElementPath.EMPTY, new PackagingElementProcessor<PackagingElement<?>>() {
85       @Override
86       public boolean process(@NotNull PackagingElement<?> element, @NotNull PackagingElementPath path) {
87         if (element instanceof FileCopyPackagingElement) {
88           final VirtualFile file = ((FileCopyPackagingElement)element).findFile();
89           if (file != null) {
90             sourceFile.set(file);
91           }
92         }
93         else if (element instanceof DirectoryCopyPackagingElement) {
94           final VirtualFile file = ((DirectoryCopyPackagingElement)element).findFile();
95           if (file != null) {
96             sourceDir.set(file);
97             return false;
98           }
99         }
100         return true;
101       }
102     });
103
104     if (!sourceDir.isNull()) {
105       return sourceDir.get();
106     }
107
108
109     final Project project = context.getProject();
110     return suggestBaseDir(project, sourceFile.get());
111   }
112
113   @Nullable
114   public static VirtualFile suggestManifestFileDirectory(@NotNull Project project, @Nullable Module module) {
115     OrderEnumerator enumerator = module != null ? OrderEnumerator.orderEntries(module) : OrderEnumerator.orderEntries(project);
116     final VirtualFile[] files = enumerator.withoutDepModules().withoutLibraries().withoutSdk().productionOnly().sources().getRoots();
117     if (files.length > 0) {
118       return files[0];
119     }
120     return suggestBaseDir(project, null);
121   }
122
123
124   @Nullable
125   private static VirtualFile suggestBaseDir(@NotNull Project project, final @Nullable VirtualFile file) {
126     final VirtualFile[] contentRoots = ProjectRootManager.getInstance(project).getContentRoots();
127     if (file == null && contentRoots.length > 0) {
128       return contentRoots[0];
129     }
130
131     if (file != null) {
132       for (VirtualFile contentRoot : contentRoots) {
133         if (VfsUtilCore.isAncestor(contentRoot, file, false)) {
134           return contentRoot;
135         }
136       }
137     }
138
139     return project.getBaseDir();
140   }
141
142   public static Manifest readManifest(@NotNull VirtualFile manifestFile) {
143     try (InputStream inputStream = manifestFile.getInputStream()) {
144       return new Manifest(inputStream);
145     }
146     catch (IOException ignored) {
147       return new Manifest();
148     }
149   }
150
151   public static void updateManifest(@NotNull final VirtualFile file, final @Nullable String mainClass, final @Nullable List<String> classpath, final boolean replaceValues) {
152     final Manifest manifest = readManifest(file);
153     final Attributes mainAttributes = manifest.getMainAttributes();
154
155     if (mainClass != null) {
156       mainAttributes.put(Attributes.Name.MAIN_CLASS, mainClass);
157     }
158     else if (replaceValues) {
159       mainAttributes.remove(Attributes.Name.MAIN_CLASS);
160     }
161
162     if (classpath != null && !classpath.isEmpty()) {
163       List<String> updatedClasspath;
164       if (replaceValues) {
165         updatedClasspath = classpath;
166       }
167       else {
168         updatedClasspath = new ArrayList<>();
169         final String oldClasspath = (String)mainAttributes.get(Attributes.Name.CLASS_PATH);
170         if (!StringUtil.isEmpty(oldClasspath)) {
171           updatedClasspath.addAll(StringUtil.split(oldClasspath, " "));
172         }
173         for (String path : classpath) {
174           if (!updatedClasspath.contains(path)) {
175             updatedClasspath.add(path);
176           }
177         }
178       }
179       mainAttributes.put(Attributes.Name.CLASS_PATH, StringUtil.join(updatedClasspath, " "));
180     }
181     else if (replaceValues) {
182       mainAttributes.remove(Attributes.Name.CLASS_PATH);
183     }
184
185     ManifestBuilder.setVersionAttribute(mainAttributes);
186
187     ApplicationManager.getApplication().runWriteAction(() -> {
188       try (OutputStream outputStream = file.getOutputStream(ManifestFileUtil.class)) {
189         manifest.write(outputStream);
190       }
191       catch (IOException e) {
192         LOG.info(e);
193       }
194     });
195   }
196
197   @NotNull
198   public static ManifestFileConfiguration createManifestFileConfiguration(@NotNull VirtualFile manifestFile) {
199     final String path = manifestFile.getPath();
200     Manifest manifest = readManifest(manifestFile);
201     String mainClass = manifest.getMainAttributes().getValue(Attributes.Name.MAIN_CLASS);
202     final String classpathText = manifest.getMainAttributes().getValue(Attributes.Name.CLASS_PATH);
203     final List<String> classpath = new ArrayList<>();
204     if (classpathText != null) {
205       classpath.addAll(StringUtil.split(classpathText, " "));
206     }
207     return new ManifestFileConfiguration(path, classpath, mainClass, manifestFile.isWritable());
208   }
209
210   public static List<String> getClasspathForElements(List<? extends PackagingElement<?>> elements, PackagingElementResolvingContext context, final ArtifactType artifactType) {
211     final List<String> classpath = new ArrayList<>();
212     final PackagingElementProcessor<PackagingElement<?>> processor = new PackagingElementProcessor<PackagingElement<?>>() {
213       @Override
214       public boolean process(@NotNull PackagingElement<?> element, @NotNull PackagingElementPath path) {
215         if (element instanceof FileCopyPackagingElement) {
216           final String fileName = ((FileCopyPackagingElement)element).getOutputFileName();
217           classpath.add(DeploymentUtil.appendToPath(path.getPathString(), fileName));
218         }
219         else if (element instanceof DirectoryCopyPackagingElement) {
220           classpath.add(path.getPathString());
221         }
222         else if (element instanceof ArchivePackagingElement) {
223           final String archiveName = ((ArchivePackagingElement)element).getName();
224           classpath.add(DeploymentUtil.appendToPath(path.getPathString(), archiveName));
225         }
226         return true;
227       }
228     };
229     for (PackagingElement<?> element : elements) {
230       ArtifactUtil.processPackagingElements(element, null, processor, context, true, artifactType);
231     }
232     return classpath;
233   }
234
235   @Nullable
236   public static VirtualFile showDialogAndCreateManifest(final ArtifactEditorContext context, final CompositePackagingElement<?> element) {
237     FileChooserDescriptor descriptor = createDescriptorForManifestDirectory();
238     final VirtualFile directory = suggestManifestFileDirectory(element, context, context.getArtifactType());
239     final VirtualFile file = FileChooser.chooseFile(descriptor, context.getProject(), directory);
240     if (file == null) {
241       return null;
242     }
243
244     return createManifestFile(file, context.getProject());
245   }
246
247   @Nullable
248   public static VirtualFile createManifestFile(final @NotNull VirtualFile directory, final @NotNull Project project) {
249     ApplicationManager.getApplication().assertIsDispatchThread();
250     try {
251       return WriteAction.compute(() -> {
252         VirtualFile dir = directory;
253         if (!dir.getName().equals(MANIFEST_DIR_NAME)) {
254           dir = VfsUtil.createDirectoryIfMissing(dir, MANIFEST_DIR_NAME);
255         }
256         final VirtualFile f = dir.createChildData(dir, MANIFEST_FILE_NAME);
257         try (OutputStream output = f.getOutputStream(dir)) {
258           final Manifest manifest = new Manifest();
259           ManifestBuilder.setVersionAttribute(manifest.getMainAttributes());
260           manifest.write(output);
261         }
262         return f;
263       });
264     }
265     catch (IOException e) {
266       LOG.info(e);
267       Messages.showErrorDialog(project, e.getMessage(), CommonBundle.getErrorTitle());
268       return null;
269     }
270   }
271
272   public static FileChooserDescriptor createDescriptorForManifestDirectory() {
273     FileChooserDescriptor descriptor = FileChooserDescriptorFactory.createSingleFolderDescriptor();
274     descriptor.setTitle("Select Directory for META-INF/MANIFEST.MF file");
275     return descriptor;
276   }
277
278   public static void addManifestFileToLayout(final @NotNull String path, final @NotNull ArtifactEditorContext context,
279                                              final @NotNull CompositePackagingElement<?> element) {
280     context.editLayout(context.getArtifact(), () -> {
281       final VirtualFile file = findManifestFile(element, context, context.getArtifactType());
282       if (file == null || !FileUtil.pathsEqual(file.getPath(), path)) {
283         PackagingElementFactory.getInstance().addFileCopy(element, MANIFEST_DIR_NAME, path, MANIFEST_FILE_NAME);
284       }
285     });
286   }
287
288   @Nullable
289   public static PsiClass selectMainClass(Project project, final @Nullable String initialClassName) {
290     final TreeClassChooserFactory chooserFactory = TreeClassChooserFactory.getInstance(project);
291     final GlobalSearchScope searchScope = GlobalSearchScope.allScope(project);
292     final PsiClass aClass = initialClassName != null ? JavaPsiFacade.getInstance(project).findClass(initialClassName, searchScope) : null;
293     final TreeClassChooser chooser =
294         chooserFactory.createWithInnerClassesScopeChooser("Select Main Class", searchScope, new MainClassFilter(), aClass);
295     chooser.showDialog();
296     return chooser.getSelected();
297   }
298
299   public static void setupMainClassField(final Project project, final TextFieldWithBrowseButton field) {
300     field.addActionListener(new ActionListener() {
301       @Override
302       public void actionPerformed(ActionEvent e) {
303         final PsiClass selected = selectMainClass(project, field.getText());
304         if (selected != null) {
305           field.setText(selected.getQualifiedName());
306         }
307       }
308     });
309   }
310
311   private static class MainClassFilter implements ClassFilter {
312     @Override
313     public boolean isAccepted(final PsiClass aClass) {
314       return ReadAction
315         .compute(() -> PsiMethodUtil.MAIN_CLASS.value(aClass) && PsiMethodUtil.hasMainMethod(aClass));
316     }
317   }
318 }