IDEA-133754 Maven: resources builder: support outputDirectory parameters of maven...
[idea/community.git] / plugins / maven / src / main / java / org / jetbrains / idea / maven / project / MavenResourceCompilerConfigurationGenerator.java
1 package org.jetbrains.idea.maven.project;
2
3 import com.intellij.compiler.CompilerConfiguration;
4 import com.intellij.compiler.CompilerConfigurationImpl;
5 import com.intellij.compiler.server.BuildManager;
6 import com.intellij.openapi.diagnostic.Logger;
7 import com.intellij.openapi.module.Module;
8 import com.intellij.openapi.module.ModuleManager;
9 import com.intellij.openapi.project.Project;
10 import com.intellij.openapi.projectRoots.Sdk;
11 import com.intellij.openapi.roots.*;
12 import com.intellij.openapi.util.Comparing;
13 import com.intellij.openapi.util.JDOMUtil;
14 import com.intellij.openapi.util.io.FileUtil;
15 import com.intellij.openapi.util.io.StreamUtil;
16 import com.intellij.openapi.util.text.StringUtil;
17 import com.intellij.openapi.vfs.LocalFileSystem;
18 import com.intellij.openapi.vfs.VfsUtil;
19 import com.intellij.openapi.vfs.VirtualFile;
20 import com.intellij.packaging.impl.elements.ManifestFileUtil;
21 import com.intellij.util.containers.ContainerUtil;
22 import com.intellij.util.xmlb.XmlSerializer;
23 import org.jdom.Document;
24 import org.jdom.Element;
25 import org.jetbrains.annotations.NotNull;
26 import org.jetbrains.annotations.Nullable;
27 import org.jetbrains.idea.maven.dom.references.MavenFilteredPropertyPsiReferenceProvider;
28 import org.jetbrains.idea.maven.model.MavenId;
29 import org.jetbrains.idea.maven.model.MavenResource;
30 import org.jetbrains.idea.maven.utils.ManifestBuilder;
31 import org.jetbrains.idea.maven.utils.MavenJDOMUtil;
32 import org.jetbrains.idea.maven.utils.MavenUtil;
33 import org.jetbrains.jps.maven.model.impl.*;
34
35 import java.io.*;
36 import java.util.*;
37 import java.util.jar.Manifest;
38 import java.util.regex.Matcher;
39 import java.util.regex.Pattern;
40
41 /**
42  * @author Sergey Evdokimov
43  */
44 public class MavenResourceCompilerConfigurationGenerator {
45
46   private static Logger LOG = Logger.getInstance(MavenResourceCompilerConfigurationGenerator.class);
47
48   private static final Pattern SIMPLE_NEGATIVE_PATTERN = Pattern.compile("!\\?(\\*\\.\\w+)");
49
50   private final Project myProject;
51
52   private final MavenProjectsManager myMavenProjectsManager;
53
54   private final MavenProjectsTree myProjectsTree;
55
56   public MavenResourceCompilerConfigurationGenerator(Project project, MavenProjectsTree projectsTree) {
57     myProject = project;
58     myMavenProjectsManager = MavenProjectsManager.getInstance(project);
59     myProjectsTree = projectsTree;
60   }
61
62   public void generateBuildConfiguration(boolean force) {
63     if (!myMavenProjectsManager.isMavenizedProject()) {
64       return;
65     }
66
67     final BuildManager buildManager = BuildManager.getInstance();
68     final File projectSystemDir = buildManager.getProjectSystemDirectory(myProject);
69     if (projectSystemDir == null) {
70       return;
71     }
72
73     final File mavenConfigFile = new File(projectSystemDir, MavenProjectConfiguration.CONFIGURATION_FILE_RELATIVE_PATH);
74
75     ProjectRootManager projectRootManager = ProjectRootManager.getInstance(myProject);
76
77     ProjectFileIndex fileIndex = projectRootManager.getFileIndex();
78
79     final int crc = myProjectsTree.getFilterConfigCrc(fileIndex) + (int)projectRootManager.getModificationCount();
80
81     final File crcFile = new File(mavenConfigFile.getParent(), "configuration.crc");
82
83     if (!force) {
84       try {
85         DataInputStream crcInput = new DataInputStream(new FileInputStream(crcFile));
86         try {
87           if (crcInput.readInt() == crc) return; // Project had not change since last config generation.
88         }
89         finally {
90           crcInput.close();
91         }
92       }
93       catch (IOException ignored) {
94         // // Config file is not generated.
95       }
96     }
97
98     MavenProjectConfiguration projectConfig = new MavenProjectConfiguration();
99
100     for (MavenProject mavenProject : myMavenProjectsManager.getProjects()) {
101       VirtualFile pomXml = mavenProject.getFile();
102
103       Module module = fileIndex.getModuleForFile(pomXml);
104       if (module == null) continue;
105
106       if (!Comparing.equal(mavenProject.getDirectoryFile(), fileIndex.getContentRootForFile(pomXml))) continue;
107
108       MavenModuleResourceConfiguration resourceConfig = new MavenModuleResourceConfiguration();
109
110       MavenId projectId = mavenProject.getMavenId();
111       resourceConfig.id = new MavenIdBean(projectId.getGroupId(), projectId.getArtifactId(), projectId.getVersion());
112
113       MavenId parentId = mavenProject.getParentId();
114       if (parentId != null) {
115         resourceConfig.parentId = new MavenIdBean(parentId.getGroupId(), parentId.getArtifactId(), parentId.getVersion());
116       }
117       resourceConfig.directory = FileUtil.toSystemIndependentName(mavenProject.getDirectory());
118       resourceConfig.delimitersPattern = MavenFilteredPropertyPsiReferenceProvider.getDelimitersPattern(mavenProject).pattern();
119       for (Map.Entry<String, String> entry : mavenProject.getModelMap().entrySet()) {
120         String key = entry.getKey();
121         String value = entry.getValue();
122         if (value != null) {
123           resourceConfig.modelMap.put(key, value);
124         }
125       }
126
127       Element pluginConfiguration = mavenProject.getPluginConfiguration("org.apache.maven.plugins", "maven-resources-plugin");
128
129       resourceConfig.outputDirectory = getResourcesPluginGoalOutputDirectory(mavenProject, pluginConfiguration, "resources");
130       resourceConfig.testOutputDirectory = getResourcesPluginGoalOutputDirectory(mavenProject, pluginConfiguration, "testResources");
131
132       addResources(resourceConfig.resources, mavenProject.getResources());
133       addResources(resourceConfig.testResources, mavenProject.getTestResources());
134
135       addWebResources(module, projectConfig, mavenProject);
136       addEjbClientArtifactConfiguration(module, projectConfig, mavenProject);
137
138       resourceConfig.filteringExclusions.addAll(MavenProjectsTree.getFilterExclusions(mavenProject));
139
140       final Properties properties = getFilteringProperties(mavenProject);
141       for (Map.Entry<Object, Object> propEntry : properties.entrySet()) {
142         resourceConfig.properties.put((String)propEntry.getKey(), (String)propEntry.getValue());
143       }
144
145       resourceConfig.escapeString = MavenJDOMUtil.findChildValueByPath(pluginConfiguration, "escapeString", null);
146       String escapeWindowsPaths = MavenJDOMUtil.findChildValueByPath(pluginConfiguration, "escapeWindowsPaths");
147       if (escapeWindowsPaths != null) {
148         resourceConfig.escapeWindowsPaths = Boolean.parseBoolean(escapeWindowsPaths);
149       }
150
151       String overwrite = MavenJDOMUtil.findChildValueByPath(pluginConfiguration, "overwrite");
152       if (overwrite != null) {
153         resourceConfig.overwrite = Boolean.parseBoolean(overwrite);
154       }
155
156       projectConfig.moduleConfigurations.put(module.getName(), resourceConfig);
157
158       generateManifest(mavenProject, module);
159     }
160
161     addNonMavenResources(projectConfig);
162
163     final Document document = new Document(new Element("maven-project-configuration"));
164     XmlSerializer.serializeInto(projectConfig, document.getRootElement());
165     buildManager.runCommand(new Runnable() {
166       @Override
167       public void run() {
168         buildManager.clearState(myProject);
169         FileUtil.createIfDoesntExist(mavenConfigFile);
170         try {
171           JDOMUtil.writeDocument(document, mavenConfigFile, "\n");
172
173           DataOutputStream crcOutput = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(crcFile)));
174           try {
175             crcOutput.writeInt(crc);
176           }
177           finally {
178             crcOutput.close();
179           }
180         }
181         catch (IOException e) {
182           throw new RuntimeException(e);
183         }
184       }
185     });
186   }
187
188   @Nullable
189   private static String getResourcesPluginGoalOutputDirectory(@NotNull MavenProject mavenProject,
190                                                               @Nullable Element pluginConfiguration,
191                                                               @NotNull String goal) {
192     final Element goalConfiguration = mavenProject.getPluginGoalConfiguration("org.apache.maven.plugins", "maven-resources-plugin", goal);
193     String outputDirectory = MavenJDOMUtil.findChildValueByPath(goalConfiguration, "outputDirectory", null);
194     if (outputDirectory == null) {
195       outputDirectory = MavenJDOMUtil.findChildValueByPath(pluginConfiguration, "outputDirectory", null);
196     }
197     return outputDirectory == null || FileUtil.isAbsolute(outputDirectory)
198            ? outputDirectory
199            : mavenProject.getDirectory() + '/' + outputDirectory;
200   }
201
202   private static void generateManifest(@NotNull MavenProject mavenProject, @NotNull Module module) {
203     try {
204       String jdkVersion = null;
205       Sdk sdk = ModuleRootManager.getInstance(module).getSdk();
206       if (sdk != null && (jdkVersion = sdk.getVersionString()) != null) {
207         final int quoteIndex = jdkVersion.indexOf('"');
208         if (quoteIndex != -1) {
209           jdkVersion = jdkVersion.substring(quoteIndex + 1, jdkVersion.length() - 1);
210         }
211       }
212       Manifest manifest = new ManifestBuilder(mavenProject).withJdkVersion(jdkVersion).build();
213       File manifestFile = new File(mavenProject.getBuildDirectory(), ManifestFileUtil.MANIFEST_FILE_NAME);
214       FileUtil.createIfDoesntExist(manifestFile);
215       OutputStream outputStream = new FileOutputStream(manifestFile);
216       try {
217         manifest.write(outputStream);
218       }
219       finally {
220         StreamUtil.closeStream(outputStream);
221       }
222     }
223     catch (ManifestBuilder.ManifestBuilderException e) {
224       LOG.error("Unable to generate artifact manifest", e);
225     }
226     catch (IOException e) {
227       LOG.error("Unable to save generated artifact manifest", e);
228     }
229   }
230
231   private Properties getFilteringProperties(MavenProject mavenProject) {
232     final Properties properties = new Properties();
233
234     for (String each : mavenProject.getFilterPropertiesFiles()) {
235       try {
236         FileInputStream in = new FileInputStream(each);
237         try {
238           properties.load(in);
239         }
240         finally {
241           in.close();
242         }
243       }
244       catch (IOException ignored) {
245       }
246     }
247
248     properties.putAll(mavenProject.getProperties());
249
250     properties.put("settings.localRepository", mavenProject.getLocalRepository().getAbsolutePath());
251
252     String jreDir = MavenUtil.getModuleJreHome(myMavenProjectsManager, mavenProject);
253     if (jreDir != null) {
254       properties.put("java.home", jreDir);
255     }
256
257     String javaVersion = MavenUtil.getModuleJavaVersion(myMavenProjectsManager, mavenProject);
258     if (javaVersion != null) {
259       properties.put("java.version", javaVersion);
260     }
261
262     return properties;
263   }
264
265   private static void addResources(final List<ResourceRootConfiguration> container, @NotNull Collection<MavenResource> resources) {
266
267     for (MavenResource resource : resources) {
268       final String dir = resource.getDirectory();
269       if (dir == null) {
270         continue;
271       }
272
273       final ResourceRootConfiguration props = new ResourceRootConfiguration();
274       props.directory = FileUtil.toSystemIndependentName(dir);
275
276       final String target = resource.getTargetPath();
277       props.targetPath = target != null ? FileUtil.toSystemIndependentName(target) : null;
278
279       props.isFiltered = resource.isFiltered();
280       props.includes.clear();
281       for (String include : resource.getIncludes()) {
282         props.includes.add(include.trim());
283       }
284       props.excludes.clear();
285       for (String exclude : resource.getExcludes()) {
286         props.excludes.add(exclude.trim());
287       }
288       container.add(props);
289     }
290   }
291
292   private static void addWebResources(@NotNull Module module, MavenProjectConfiguration projectCfg, MavenProject mavenProject) {
293     Element warCfg = mavenProject.getPluginConfiguration("org.apache.maven.plugins", "maven-war-plugin");
294     if (warCfg == null) return;
295
296     boolean filterWebXml = Boolean.parseBoolean(warCfg.getChildTextTrim("filteringDeploymentDescriptors"));
297     Element webResources = warCfg.getChild("webResources");
298
299     if (webResources == null && !filterWebXml) return;
300
301     String webArtifactName = MavenUtil.getArtifactName("war", module, true);
302
303     MavenWebArtifactConfiguration artifactResourceCfg = projectCfg.webArtifactConfigs.get(webArtifactName);
304     if (artifactResourceCfg == null) {
305       artifactResourceCfg = new MavenWebArtifactConfiguration();
306       artifactResourceCfg.moduleName = module.getName();
307       projectCfg.webArtifactConfigs.put(webArtifactName, artifactResourceCfg);
308     }
309     else {
310       LOG.error("MavenWebArtifactConfiguration already exists.");
311     }
312
313     if (webResources != null) {
314       for (Element resource : webResources.getChildren("resource")) {
315         ResourceRootConfiguration r = new ResourceRootConfiguration();
316         String directory = resource.getChildTextTrim("directory");
317         if (StringUtil.isEmptyOrSpaces(directory)) continue;
318
319         if (!FileUtil.isAbsolute(directory)) {
320           directory = mavenProject.getDirectory() + '/' + directory;
321         }
322
323         r.directory = directory;
324         r.isFiltered = Boolean.parseBoolean(resource.getChildTextTrim("filtering"));
325
326         r.targetPath = resource.getChildTextTrim("targetPath");
327
328         Element includes = resource.getChild("includes");
329         if (includes != null) {
330           for (Element include : includes.getChildren("include")) {
331             String includeText = include.getTextTrim();
332             if (!includeText.isEmpty()) {
333               r.includes.add(includeText);
334             }
335           }
336         }
337
338         Element excludes = resource.getChild("excludes");
339         if (excludes != null) {
340           for (Element exclude : excludes.getChildren("exclude")) {
341             String excludeText = exclude.getTextTrim();
342             if (!excludeText.isEmpty()) {
343               r.excludes.add(excludeText);
344             }
345           }
346         }
347
348         artifactResourceCfg.webResources.add(r);
349       }
350     }
351
352     if (filterWebXml) {
353       ResourceRootConfiguration r = new ResourceRootConfiguration();
354       r.directory = mavenProject.getDirectory() + "/src/main/webapp";
355       r.includes = Collections.singleton("WEB-INF/web.xml");
356       r.isFiltered = true;
357       r.targetPath = "";
358       artifactResourceCfg.webResources.add(r);
359     }
360   }
361
362   private void addEjbClientArtifactConfiguration(Module module, MavenProjectConfiguration projectCfg, MavenProject mavenProject) {
363     Element pluginCfg = mavenProject.getPluginConfiguration("org.apache.maven.plugins", "maven-ejb-plugin");
364
365     if (pluginCfg == null || !Boolean.parseBoolean(pluginCfg.getChildTextTrim("generateClient"))) {
366       return;
367     }
368
369     String ejbClientArtifactName = MavenUtil.getEjbClientArtifactName(module);
370
371     MavenEjbClientConfiguration ejbClientCfg = new MavenEjbClientConfiguration();
372
373     Element includes = pluginCfg.getChild("clientIncludes");
374     if (includes != null) {
375       for (Element include : includes.getChildren("clientInclude")) {
376         String includeText = include.getTextTrim();
377         if (!includeText.isEmpty()) {
378           ejbClientCfg.includes.add(includeText);
379         }
380       }
381     }
382
383     Element excludes = pluginCfg.getChild("clientExcludes");
384     if (excludes != null) {
385       for (Element exclude : excludes.getChildren("clientExclude")) {
386         String excludeText = exclude.getTextTrim();
387         if (!excludeText.isEmpty()) {
388           ejbClientCfg.excludes.add(excludeText);
389         }
390       }
391     }
392
393     if (!ejbClientCfg.isEmpty()) {
394       projectCfg.ejbClientArtifactConfigs.put(ejbClientArtifactName, ejbClientCfg);
395     }
396   }
397
398   private void addNonMavenResources(MavenProjectConfiguration projectCfg) {
399     Set<VirtualFile> processedRoots = new HashSet<VirtualFile>();
400
401     for (MavenProject project : myMavenProjectsManager.getProjects()) {
402       for (String dir : ContainerUtil.concat(project.getSources(), project.getTestSources())) {
403         VirtualFile file = LocalFileSystem.getInstance().findFileByPath(dir);
404         if (file != null) {
405           processedRoots.add(file);
406         }
407       }
408
409       for (MavenResource resource : ContainerUtil.concat(project.getResources(), project.getTestResources())) {
410         VirtualFile file = LocalFileSystem.getInstance().findFileByPath(resource.getDirectory());
411         if (file != null) {
412           processedRoots.add(file);
413         }
414       }
415     }
416
417     CompilerConfiguration compilerConfiguration = CompilerConfiguration.getInstance(myProject);
418
419     for (Module module : ModuleManager.getInstance(myProject).getModules()) {
420       if (!myMavenProjectsManager.isMavenizedModule(module)) continue;
421
422       for (ContentEntry contentEntry : ModuleRootManager.getInstance(module).getContentEntries()) {
423         for (SourceFolder folder : contentEntry.getSourceFolders()) {
424           VirtualFile file = folder.getFile();
425           if (file == null) continue;
426
427           if (!compilerConfiguration.isExcludedFromCompilation(file) && !isUnderRoots(processedRoots, file)) {
428             MavenModuleResourceConfiguration configuration = projectCfg.moduleConfigurations.get(module.getName());
429             if (configuration == null) continue;
430
431             List<ResourceRootConfiguration> resourcesList = folder.isTestSource() ? configuration.testResources : configuration.resources;
432
433             final ResourceRootConfiguration cfg = new ResourceRootConfiguration();
434             cfg.directory = FileUtil.toSystemIndependentName(FileUtil.toSystemIndependentName(file.getPath()));
435
436             CompilerModuleExtension compilerModuleExtension = CompilerModuleExtension.getInstance(module);
437             if (compilerModuleExtension == null) continue;
438
439
440             String compilerOutputUrl = folder.isTestSource()
441                                        ? compilerModuleExtension.getCompilerOutputUrlForTests()
442                                        : compilerModuleExtension.getCompilerOutputUrl();
443
444             cfg.targetPath = VfsUtil.urlToPath(compilerOutputUrl);
445
446             convertIdeaExcludesToMavenExcludes(cfg, (CompilerConfigurationImpl)compilerConfiguration);
447
448             resourcesList.add(cfg);
449           }
450         }
451       }
452     }
453   }
454
455   private static void convertIdeaExcludesToMavenExcludes(ResourceRootConfiguration cfg, CompilerConfigurationImpl compilerConfiguration) {
456     for (String pattern : compilerConfiguration.getResourceFilePatterns()) {
457       Matcher matcher = SIMPLE_NEGATIVE_PATTERN.matcher(pattern);
458       if (matcher.matches()) {
459         cfg.excludes.add("**/" + matcher.group(1));
460       }
461     }
462   }
463
464   private static boolean isUnderRoots(Set<VirtualFile> roots, VirtualFile file) {
465     for (VirtualFile f = file; f != null; f = f.getParent()) {
466       if (roots.contains(file)) {
467         return true;
468       }
469     }
470
471     return false;
472   }
473
474 }