Merge branch 'optionsForAnnotationProcessor' of https://github.com/klausbayrhammer...
[idea/community.git] / plugins / maven / src / main / java / org / jetbrains / idea / maven / project / MavenProject.java
1 /*
2  * Copyright 2000-2013 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package org.jetbrains.idea.maven.project;
17
18 import com.intellij.execution.configurations.ParametersList;
19 import com.intellij.openapi.module.ModuleType;
20 import com.intellij.openapi.module.StdModuleTypes;
21 import com.intellij.openapi.project.Project;
22 import com.intellij.openapi.util.Comparing;
23 import com.intellij.openapi.util.Condition;
24 import com.intellij.openapi.util.Key;
25 import com.intellij.openapi.util.Pair;
26 import com.intellij.openapi.util.io.BufferExposingByteArrayOutputStream;
27 import com.intellij.openapi.util.io.FileUtil;
28 import com.intellij.openapi.util.text.StringUtil;
29 import com.intellij.openapi.vfs.LocalFileSystem;
30 import com.intellij.openapi.vfs.VirtualFile;
31 import com.intellij.util.containers.ContainerUtil;
32 import gnu.trove.THashSet;
33 import org.jdom.Element;
34 import org.jetbrains.annotations.NotNull;
35 import org.jetbrains.annotations.Nullable;
36 import org.jetbrains.idea.maven.importing.MavenExtraArtifactType;
37 import org.jetbrains.idea.maven.importing.MavenImporter;
38 import org.jetbrains.idea.maven.model.*;
39 import org.jetbrains.idea.maven.plugins.api.MavenModelPropertiesPatcher;
40 import org.jetbrains.idea.maven.server.MavenEmbedderWrapper;
41 import org.jetbrains.idea.maven.server.NativeMavenProjectHolder;
42 import org.jetbrains.idea.maven.utils.*;
43
44 import java.io.*;
45 import java.util.*;
46 import java.util.concurrent.ConcurrentHashMap;
47
48 public class MavenProject {
49
50   private static final Key<MavenArtifactIndex> DEPENDENCIES_CACHE_KEY = Key.create("MavenProject.DEPENDENCIES_CACHE_KEY");
51   private static final Key<List<String>> FILTERS_CACHE_KEY = Key.create("MavenProject.FILTERS_CACHE_KEY");
52
53   @NotNull private final VirtualFile myFile;
54   @NotNull private volatile State myState = new State();
55
56   private static Map<String, String> COMPILER_LEVEL_TABLE = ContainerUtil.<String,String>immutableMapBuilder()
57     .put("1.1", "1.1")
58     .put("1.2", "1.2")
59     .put("1.3", "1.3")
60     .put("1.4", "1.4")
61     .put("1.5", "1.5")
62     .put("5", "1.5")
63     .put("1.6", "1.6")
64     .put("6", "1.6")
65     .put("1.7", "1.7")
66     .put("7", "1.7")
67     .put("1.8", "1.8")
68     .put("8", "1.8")
69     .build();
70
71   public enum ProcMode {BOTH, ONLY, NONE}
72
73   @Nullable
74   public static MavenProject read(DataInputStream in) throws IOException {
75     String path = in.readUTF();
76     int length = in.readInt();
77     byte[] bytes = new byte[length];
78     in.readFully(bytes);
79
80     // should read full byte content first!!!
81
82     VirtualFile file = LocalFileSystem.getInstance().findFileByPath(path);
83     if (file == null) return null;
84
85     ByteArrayInputStream bs = new ByteArrayInputStream(bytes);
86     ObjectInputStream os = new ObjectInputStream(bs);
87     try {
88       try {
89         MavenProject result = new MavenProject(file);
90         result.myState = (State)os.readObject();
91         return result;
92       }
93       catch (ClassNotFoundException e) {
94         IOException ioException = new IOException();
95         ioException.initCause(e);
96         throw ioException;
97       }
98     }
99     finally {
100       os.close();
101       bs.close();
102     }
103   }
104
105   public void write(@NotNull DataOutputStream out) throws IOException {
106     out.writeUTF(getPath());
107
108     BufferExposingByteArrayOutputStream bs = new BufferExposingByteArrayOutputStream();
109     ObjectOutputStream os = new ObjectOutputStream(bs);
110     try {
111       os.writeObject(myState);
112
113       out.writeInt(bs.size());
114       out.write(bs.getInternalBuffer(), 0, bs.size());
115     }
116     finally {
117       os.close();
118       bs.close();
119     }
120   }
121
122   public MavenProject(@NotNull VirtualFile file) {
123     myFile = file;
124   }
125
126   @NotNull
127   private MavenProjectChanges set(@NotNull MavenProjectReaderResult readerResult,
128                                   @NotNull MavenGeneralSettings settings,
129                                   boolean updateLastReadStamp,
130                                   boolean resetArtifacts,
131                                   boolean resetProfiles) {
132     State newState = myState.clone();
133
134     if (updateLastReadStamp) newState.myLastReadStamp = myState.myLastReadStamp + 1;
135
136     newState.myReadingProblems = readerResult.readingProblems;
137     newState.myLocalRepository = settings.getEffectiveLocalRepository();
138
139     newState.myActivatedProfilesIds = readerResult.activatedProfiles;
140
141     MavenModel model = readerResult.mavenModel;
142
143     newState.myMavenId = model.getMavenId();
144     if (model.getParent() != null) {
145       newState.myParentId = model.getParent().getMavenId();
146     }
147
148     newState.myPackaging = model.getPackaging();
149     newState.myName = model.getName();
150
151     newState.myFinalName = model.getBuild().getFinalName();
152     newState.myDefaultGoal = model.getBuild().getDefaultGoal();
153
154     newState.myBuildDirectory = model.getBuild().getDirectory();
155     newState.myOutputDirectory = model.getBuild().getOutputDirectory();
156     newState.myTestOutputDirectory = model.getBuild().getTestOutputDirectory();
157
158     doSetFolders(newState, readerResult);
159
160     newState.myFilters = model.getBuild().getFilters();
161     newState.myProperties = model.getProperties();
162
163     doSetResolvedAttributes(newState, readerResult, resetArtifacts);
164
165     MavenModelPropertiesPatcher.patch(newState.myProperties, newState.myPlugins);
166
167     newState.myModulesPathsAndNames = collectModulePathsAndNames(model, getDirectory());
168     Collection<String> newProfiles = collectProfilesIds(model.getProfiles());
169     if (resetProfiles || newState.myProfilesIds == null) {
170       newState.myProfilesIds = newProfiles;
171     }
172     else {
173       Set<String> mergedProfiles = new THashSet<String>(newState.myProfilesIds);
174       mergedProfiles.addAll(newProfiles);
175       newState.myProfilesIds = new ArrayList<String>(mergedProfiles);
176     }
177
178     newState.myModelMap = readerResult.nativeModelMap;
179
180     return setState(newState);
181   }
182
183   private MavenProjectChanges setState(State newState) {
184     MavenProjectChanges changes = myState.getChanges(newState);
185     myState = newState;
186     return changes;
187   }
188
189   private static void doSetResolvedAttributes(State state,
190                                               MavenProjectReaderResult readerResult,
191                                               boolean reset) {
192     MavenModel model = readerResult.mavenModel;
193
194     Set<MavenId> newUnresolvedArtifacts = new THashSet<MavenId>();
195     LinkedHashSet<MavenRemoteRepository> newRepositories = new LinkedHashSet<MavenRemoteRepository>();
196     LinkedHashSet<MavenArtifact> newDependencies = new LinkedHashSet<MavenArtifact>();
197     LinkedHashSet<MavenArtifactNode> newDependencyTree = new LinkedHashSet<MavenArtifactNode>();
198     LinkedHashSet<MavenPlugin> newPlugins = new LinkedHashSet<MavenPlugin>();
199     LinkedHashSet<MavenArtifact> newExtensions = new LinkedHashSet<MavenArtifact>();
200
201     if (!reset) {
202       if (state.myUnresolvedArtifactIds != null) newUnresolvedArtifacts.addAll(state.myUnresolvedArtifactIds);
203       if (state.myRemoteRepositories != null) newRepositories.addAll(state.myRemoteRepositories);
204       if (state.myDependencies != null) newDependencies.addAll(state.myDependencies);
205       if (state.myDependencyTree != null) newDependencyTree.addAll(state.myDependencyTree);
206       if (state.myPlugins != null) newPlugins.addAll(state.myPlugins);
207       if (state.myExtensions != null) newExtensions.addAll(state.myExtensions);
208     }
209
210     newUnresolvedArtifacts.addAll(readerResult.unresolvedArtifactIds);
211     newRepositories.addAll(model.getRemoteRepositories());
212     newDependencyTree.addAll(model.getDependencyTree());
213     newDependencies.addAll(model.getDependencies());
214     newPlugins.addAll(model.getPlugins());
215     newExtensions.addAll(model.getExtensions());
216
217     state.myUnresolvedArtifactIds = newUnresolvedArtifacts;
218     state.myRemoteRepositories = new ArrayList<MavenRemoteRepository>(newRepositories);
219     state.myDependencies = new ArrayList<MavenArtifact>(newDependencies);
220     state.myDependencyTree = new ArrayList<MavenArtifactNode>(newDependencyTree);
221     state.myPlugins = new ArrayList<MavenPlugin>(newPlugins);
222     state.myExtensions = new ArrayList<MavenArtifact>(newExtensions);
223   }
224
225   private MavenProjectChanges setFolders(MavenProjectReaderResult readerResult) {
226     State newState = myState.clone();
227     doSetFolders(newState, readerResult);
228     return setState(newState);
229   }
230
231   private static void doSetFolders(State newState, MavenProjectReaderResult readerResult) {
232     MavenModel model = readerResult.mavenModel;
233     newState.mySources = model.getBuild().getSources();
234     newState.myTestSources = model.getBuild().getTestSources();
235
236     newState.myResources = model.getBuild().getResources();
237     newState.myTestResources = model.getBuild().getTestResources();
238   }
239
240   private static Map<String, String> collectModulePathsAndNames(MavenModel mavenModel, String baseDir) {
241     String basePath = baseDir + "/";
242     Map<String, String> result = new LinkedHashMap<String, String>();
243     for (Map.Entry<String, String> each : collectModulesRelativePathsAndNames(mavenModel).entrySet()) {
244       result.put(new Path(basePath + each.getKey()).getPath(), each.getValue());
245     }
246     return result;
247   }
248
249   private static Map<String, String> collectModulesRelativePathsAndNames(MavenModel mavenModel) {
250     LinkedHashMap<String, String> result = new LinkedHashMap<String, String>();
251     for (String name : mavenModel.getModules()) {
252       name = name.trim();
253
254       if (name.length() == 0) continue;
255
256       String originalName = name;
257       // module name can be relative and contain either / of \\ separators
258
259       name = FileUtil.toSystemIndependentName(name);
260       if (!name.endsWith("/")) name += "/";
261       name += MavenConstants.POM_XML;
262
263       result.put(name, originalName);
264     }
265     return result;
266   }
267
268   private static Collection<String> collectProfilesIds(Collection<MavenProfile> profiles) {
269     if (profiles == null) return Collections.emptyList();
270
271     Set<String> result = new THashSet<String>(profiles.size());
272     for (MavenProfile each : profiles) {
273       result.add(each.getId());
274     }
275     return result;
276   }
277
278   public long getLastReadStamp() {
279     return myState.myLastReadStamp;
280   }
281
282   @NotNull
283   public VirtualFile getFile() {
284     return myFile;
285   }
286
287   @NotNull
288   public String getPath() {
289     return myFile.getPath();
290   }
291
292   @NotNull
293   public String getDirectory() {
294     return myFile.getParent().getPath();
295   }
296
297   @NotNull
298   public VirtualFile getDirectoryFile() {
299     return myFile.getParent();
300   }
301
302   @Nullable
303   public VirtualFile getProfilesXmlFile() {
304     return MavenUtil.findProfilesXmlFile(myFile);
305   }
306
307   @NotNull
308   public File getProfilesXmlIoFile() {
309     return MavenUtil.getProfilesXmlIoFile(myFile);
310   }
311
312   public boolean hasReadingProblems() {
313     return !myState.myReadingProblems.isEmpty();
314   }
315
316   @Nullable
317   public String getName() {
318     return myState.myName;
319   }
320
321   @NotNull
322   public String getDisplayName() {
323     State state = myState;
324     if (StringUtil.isEmptyOrSpaces(state.myName)) return state.myMavenId.getArtifactId();
325     return state.myName;
326   }
327
328   @NotNull
329   public Map<String, String> getModelMap() {
330     return myState.myModelMap;
331   }
332
333   @NotNull
334   public MavenId getMavenId() {
335     return myState.myMavenId;
336   }
337
338   @Nullable
339   public MavenId getParentId() {
340     return myState.myParentId;
341   }
342
343   @NotNull
344   public String getPackaging() {
345     return myState.myPackaging;
346   }
347
348   @NotNull
349   public String getFinalName() {
350     return myState.myFinalName;
351   }
352
353   @Nullable
354   public String getDefaultGoal() {
355     return myState.myDefaultGoal;
356   }
357
358   @NotNull
359   public String getBuildDirectory() {
360     return myState.myBuildDirectory;
361   }
362
363   @NotNull
364   public String getGeneratedSourcesDirectory(boolean testSources) {
365     return getBuildDirectory() + (testSources ? "/generated-test-sources" : "/generated-sources");
366   }
367
368   @NotNull
369   public String getAnnotationProcessorDirectory(boolean testSources) {
370     Element cfg = getPluginGoalConfiguration("org.bsc.maven", "maven-processor-plugin", testSources ? "process-test" : "process");
371     if (cfg != null) {
372       String out = MavenJDOMUtil.findChildValueByPath(cfg, "outputDirectory");
373       if (out == null) {
374         out = MavenJDOMUtil.findChildValueByPath(cfg, "defaultOutputDirectory");
375         if (out == null) {
376           return getBuildDirectory() + "/generated-sources/apt";
377         }
378       }
379
380       if (!new File(out).isAbsolute()) {
381         out = getDirectory() + '/' + out;
382       }
383
384       return out;
385     }
386
387     String def = getGeneratedSourcesDirectory(testSources) + (testSources ? "/test-annotations" : "/annotations");
388     return MavenJDOMUtil.findChildValueByPath(getCompilerConfig(),
389                                               testSources ? "generatedTestSourcesDirectory" : "generatedSourcesDirectory",
390                                               def);
391   }
392
393   @NotNull
394   public ProcMode getProcMode() {
395     Element compilerConfiguration = getPluginExecutionConfiguration("org.apache.maven.plugins", "maven-compiler-plugin", "default-compile");
396     if (compilerConfiguration == null) {
397       compilerConfiguration = getCompilerConfig();
398     }
399
400     if (compilerConfiguration == null) {
401       return ProcMode.BOTH;
402     }
403
404     Element procElement = compilerConfiguration.getChild("proc");
405     if (procElement != null) {
406       String procMode = procElement.getValue();
407       return ("only".equalsIgnoreCase(procMode)) ? ProcMode.ONLY : ("none".equalsIgnoreCase(procMode)) ? ProcMode.NONE : ProcMode.BOTH;
408     }
409
410     String compilerArgument = compilerConfiguration.getChildTextTrim("compilerArgument");
411     if ("-proc:none".equals(compilerArgument)) {
412       return ProcMode.NONE;
413     }
414     if ("-proc:only".equals(compilerArgument)) {
415       return ProcMode.ONLY;
416     }
417
418     //Element compilerArguments = compilerConfiguration.getChild("compilerArguments");
419     //if (compilerArguments != null) {
420     //  for (Element element : (List<Element>)compilerArguments.getChildren()) {
421     //    String argName = element.getName();
422     //    if (argName.startsWith("-")) {
423     //      argName = argName.substring(1);
424     //    }
425     //
426     //    if ("proc:none".equals(argName)) {
427     //      return ProcMode.NONE;
428     //    }
429     //    if ("proc:only".equals(argName)) {
430     //      return ProcMode.ONLY;
431     //    }
432     //  }
433     //}
434
435     return ProcMode.BOTH;
436   }
437
438   //@Nullable
439   //private static Element getAnnotationProcessorsConfiguration(@Nullable Element compilerConfig) {
440   //  return (compilerConfig == null) ? null : compilerConfig.getChild("annotationProcessors");
441   //}
442
443   public Map<String, String> getAnnotationProcessorOptions() {
444     Element compilerConfig = getCompilerConfig();
445     if (compilerConfig != null) {
446       return getAnnotationProcessorOptionsFromCompilerConfig(compilerConfig);
447     }
448     MavenPlugin bscMavenPlugin = findPlugin("org.bsc.maven", "maven-processor-plugin");
449     if (bscMavenPlugin != null) {
450       return getAnnotationProcessorOptionsFromProcessorPlugin(bscMavenPlugin);
451     }
452     return Collections.emptyMap();
453   }
454
455   private Map<String, String> getAnnotationProcessorOptionsFromCompilerConfig(Element compilerConfig) {
456     Map<String, String> res = null;
457
458     String compilerArgument = compilerConfig.getChildText("compilerArgument");
459     if (!StringUtil.isEmptyOrSpaces(compilerArgument)) {
460       ParametersList parametersList = new ParametersList();
461       parametersList.addParametersString(compilerArgument);
462
463       for (String param : parametersList.getParameters()) {
464         if (param.startsWith("-A")) {
465           int idx = param.indexOf('=', 3);
466           if (idx >= 0) {
467             if (res == null) {
468               res = new LinkedHashMap<String, String>();
469             }
470
471             res.put(param.substring(2, idx), param.substring(idx + 1));
472           }
473         }
474       }
475     }
476
477     Element compilerArguments = compilerConfig.getChild("compilerArguments");
478     if (compilerArguments != null) {
479       for (Element e : compilerArguments.getChildren()){
480         String name = e.getName();
481         if (name.startsWith("-")) {
482           name = name.substring(1);
483         }
484
485         if (name.length() > 1 && name.charAt(0) == 'A') {
486           if (res == null) {
487             res = new LinkedHashMap<String, String>();
488           }
489
490           res.put(name.substring(1), e.getTextTrim());
491         }
492       }
493     }
494
495     if (res == null) return Collections.emptyMap();
496
497     return res;
498   }
499
500   private Map<String, String> getAnnotationProcessorOptionsFromProcessorPlugin(MavenPlugin bscMavenPlugin) {
501     Element cfg = bscMavenPlugin.getGoalConfiguration("process");
502     if (cfg == null) {
503       cfg = bscMavenPlugin.getConfigurationElement();
504     }
505     LinkedHashMap<String, String> res = new LinkedHashMap<String, String>();
506     if (cfg != null) {
507       List<Element> options = cfg.getChild("options").getChildren();
508       for (Element option : options) {
509         res.put(option.getName(), option.getText());
510       }
511     }
512     return res;
513   }
514
515   @Nullable
516   public List<String> getDeclaredAnnotationProcessors() {
517     Element compilerConfig = getCompilerConfig();
518     if (compilerConfig != null) {
519       Element processors = compilerConfig.getChild("annotationProcessors");
520       if (processors != null) {
521         List<String> res = new ArrayList<String>();
522
523         for (Element element : processors.getChildren("annotationProcessor")){
524           String processorClassName = element.getTextTrim();
525           if (!processorClassName.isEmpty()) {
526             res.add(processorClassName);
527           }
528         }
529
530         return res;
531       }
532     }
533
534     MavenPlugin bscMavenPlugin = findPlugin("org.bsc.maven", "maven-processor-plugin");
535     if (bscMavenPlugin != null) {
536       Element cfg = bscMavenPlugin.getGoalConfiguration("process");
537       if (cfg == null) {
538         cfg = bscMavenPlugin.getConfigurationElement();
539       }
540
541       if (cfg != null) {
542         Element processors = cfg.getChild("processors");
543         if (processors != null) {
544           List<String> res = new ArrayList<String>();
545
546           for (Element element : processors.getChildren("processor")){
547             String processorClassName = element.getTextTrim();
548             if (!processorClassName.isEmpty()) {
549               res.add(processorClassName);
550             }
551           }
552
553           return res;
554         }
555       }
556     }
557
558     return null;
559   }
560
561   @NotNull
562   public String getOutputDirectory() {
563     return myState.myOutputDirectory;
564   }
565
566   @NotNull
567   public String getTestOutputDirectory() {
568     return myState.myTestOutputDirectory;
569   }
570
571   @NotNull
572   public List<String> getSources() {
573     return myState.mySources;
574   }
575
576   @NotNull
577   public List<String> getTestSources() {
578     return myState.myTestSources;
579   }
580
581   @NotNull
582   public List<MavenResource> getResources() {
583     return myState.myResources;
584   }
585
586   @NotNull
587   public List<MavenResource> getTestResources() {
588     return myState.myTestResources;
589   }
590
591   @NotNull
592   public List<String> getFilters() {
593     return myState.myFilters;
594   }
595
596   public List<String> getFilterPropertiesFiles() {
597     List<String> res = getCachedValue(FILTERS_CACHE_KEY);
598     if (res == null) {
599       Element propCfg = getPluginGoalConfiguration("org.codehaus.mojo", "properties-maven-plugin", "read-project-properties");
600       if (propCfg != null) {
601         Element files = propCfg.getChild("files");
602         if (files != null) {
603           res = new ArrayList<String>();
604
605           for (Element file : files.getChildren("file")) {
606             File f = new File(file.getValue());
607             if (!f.isAbsolute()) {
608               f = new File(getDirectory(), file.getValue());
609             }
610
611             res.add(f.getAbsolutePath());
612           }
613         }
614       }
615
616       if (res == null) {
617         res = getFilters();
618       }
619       else {
620         res.addAll(getFilters());
621       }
622
623       res = putCachedValue(FILTERS_CACHE_KEY, res);
624     }
625
626     return res;
627   }
628
629   @NotNull
630   public MavenProjectChanges read(@NotNull MavenGeneralSettings generalSettings,
631                                   @NotNull MavenExplicitProfiles profiles,
632                                   @NotNull MavenProjectReader reader,
633                                   @NotNull MavenProjectReaderProjectLocator locator) {
634     return set(reader.readProject(generalSettings, myFile, profiles, locator), generalSettings, true, false, true);
635   }
636
637   @NotNull
638   public Pair<MavenProjectChanges, NativeMavenProjectHolder> resolve(@NotNull Project project,
639                                                                      @NotNull MavenGeneralSettings generalSettings,
640                                                                      @NotNull MavenEmbedderWrapper embedder,
641                                                                      @NotNull MavenProjectReader reader,
642                                                                      @NotNull MavenProjectReaderProjectLocator locator,
643                                                                      @NotNull ResolveContext context)
644     throws MavenProcessCanceledException {
645     MavenProjectReaderResult result = reader.resolveProject(generalSettings,
646                                                             embedder,
647                                                             getFile(),
648                                                             getActivatedProfilesIds(),
649                                                             locator);
650     MavenProjectChanges changes = set(result, generalSettings, false, result.readingProblems.isEmpty(), false);
651
652     if (result.nativeMavenProject != null) {
653       for (MavenImporter eachImporter : getSuitableImporters()) {
654         eachImporter.resolve(project, this, result.nativeMavenProject, embedder, context);
655       }
656     }
657     return Pair.create(changes, result.nativeMavenProject);
658   }
659
660   @NotNull
661   public Pair<Boolean, MavenProjectChanges> resolveFolders(@NotNull MavenEmbedderWrapper embedder,
662                                                            @NotNull MavenImportingSettings importingSettings,
663                                                            @NotNull MavenConsole console) throws MavenProcessCanceledException {
664     MavenProjectReaderResult result = MavenProjectReader.generateSources(embedder,
665                                                                          importingSettings,
666                                                                          getFile(),
667                                                                          getActivatedProfilesIds(),
668                                                                          console);
669     if (result == null || !result.readingProblems.isEmpty()) return Pair.create(false, MavenProjectChanges.NONE);
670     MavenProjectChanges changes = setFolders(result);
671     return Pair.create(true, changes);
672   }
673
674   public void resetCache() {
675     // todo a bit hacky
676     synchronized (myState) {
677       myState.resetCache();
678     }
679   }
680
681   public boolean isAggregator() {
682     return "pom".equals(getPackaging()) || !getModulePaths().isEmpty();
683   }
684
685   @NotNull
686   public List<MavenProjectProblem> getProblems() {
687     State state = myState;
688     synchronized (state) {
689       if (state.myProblemsCache == null) {
690         state.myProblemsCache = collectProblems(myFile, state);
691       }
692       return state.myProblemsCache;
693     }
694   }
695
696   private static List<MavenProjectProblem> collectProblems(VirtualFile file, State state) {
697     List<MavenProjectProblem> result = new ArrayList<MavenProjectProblem>();
698
699     validateParent(file, state, result);
700     result.addAll(state.myReadingProblems);
701
702     for (Map.Entry<String, String> each : state.myModulesPathsAndNames.entrySet()) {
703       if (LocalFileSystem.getInstance().findFileByPath(each.getKey()) == null) {
704         result.add(createDependencyProblem(file, ProjectBundle.message("maven.project.problem.moduleNotFound", each.getValue())));
705       }
706     }
707
708     validateDependencies(file, state, result);
709     validateExtensions(file, state, result);
710     validatePlugins(file, state, result);
711
712     return result;
713   }
714
715   private static void validateParent(VirtualFile file, State state, List<MavenProjectProblem> result) {
716     if (!isParentResolved(state)) {
717       result.add(createDependencyProblem(file, ProjectBundle.message("maven.project.problem.parentNotFound", state.myParentId)));
718     }
719   }
720
721   private static void validateDependencies(VirtualFile file, State state, List<MavenProjectProblem> result) {
722     for (MavenArtifact each : getUnresolvedDependencies(state)) {
723       result.add(createDependencyProblem(file, ProjectBundle.message("maven.project.problem.unresolvedDependency",
724                                                                      each.getDisplayStringWithType())));
725     }
726   }
727
728   private static void validateExtensions(VirtualFile file, State state, List<MavenProjectProblem> result) {
729     for (MavenArtifact each : getUnresolvedExtensions(state)) {
730       result.add(createDependencyProblem(file, ProjectBundle.message("maven.project.problem.unresolvedExtension",
731                                                                      each.getDisplayStringSimple())));
732     }
733   }
734
735   private static void validatePlugins(VirtualFile file, State state, List<MavenProjectProblem> result) {
736     for (MavenPlugin each : getUnresolvedPlugins(state)) {
737       result.add(createDependencyProblem(file, ProjectBundle.message("maven.project.problem.unresolvedPlugin", each)));
738     }
739   }
740
741   private static MavenProjectProblem createDependencyProblem(VirtualFile file, String description) {
742     return new MavenProjectProblem(file.getPath(), description, MavenProjectProblem.ProblemType.DEPENDENCY);
743   }
744
745   private static boolean isParentResolved(State state) {
746     return !state.myUnresolvedArtifactIds.contains(state.myParentId);
747   }
748
749   private static List<MavenArtifact> getUnresolvedDependencies(State state) {
750     synchronized (state) {
751       if (state.myUnresolvedDependenciesCache == null) {
752         List<MavenArtifact> result = new ArrayList<MavenArtifact>();
753         for (MavenArtifact each : state.myDependencies) {
754           if (!each.isResolved()) result.add(each);
755         }
756         state.myUnresolvedDependenciesCache = result;
757       }
758       return state.myUnresolvedDependenciesCache;
759     }
760   }
761
762   private static List<MavenArtifact> getUnresolvedExtensions(State state) {
763     synchronized (state) {
764       if (state.myUnresolvedExtensionsCache == null) {
765         List<MavenArtifact> result = new ArrayList<MavenArtifact>();
766         for (MavenArtifact each : state.myExtensions) {
767           // Collect only extensions that were attempted to be resolved.
768           // It is because embedder does not even try to resolve extensions that
769           // are not necessary.
770           if (state.myUnresolvedArtifactIds.contains(each.getMavenId())
771               && !pomFileExists(state.myLocalRepository, each)) {
772             result.add(each);
773           }
774         }
775         state.myUnresolvedExtensionsCache = result;
776       }
777       return state.myUnresolvedExtensionsCache;
778     }
779   }
780
781   private static boolean pomFileExists(File localRepository, MavenArtifact artifact) {
782     return MavenArtifactUtil.hasArtifactFile(localRepository, artifact.getMavenId(), "pom");
783   }
784
785   private static List<MavenPlugin> getUnresolvedPlugins(State state) {
786     synchronized (state) {
787       if (state.myUnresolvedPluginsCache == null) {
788         List<MavenPlugin> result = new ArrayList<MavenPlugin>();
789         for (MavenPlugin each : getDeclaredPlugins(state)) {
790           if (!MavenArtifactUtil.hasArtifactFile(state.myLocalRepository, each.getMavenId())) {
791             result.add(each);
792           }
793         }
794         state.myUnresolvedPluginsCache = result;
795       }
796       return state.myUnresolvedPluginsCache;
797     }
798   }
799
800   @NotNull
801   public List<VirtualFile> getExistingModuleFiles() {
802     LocalFileSystem fs = LocalFileSystem.getInstance();
803
804     List<VirtualFile> result = new ArrayList<VirtualFile>();
805     Set<String> pathsInStack = getModulePaths();
806     for (String each : pathsInStack) {
807       VirtualFile f = fs.findFileByPath(each);
808       if (f != null) result.add(f);
809     }
810     return result;
811   }
812
813   @NotNull
814   public Set<String> getModulePaths() {
815     return getModulesPathsAndNames().keySet();
816   }
817
818   @NotNull
819   public Map<String, String> getModulesPathsAndNames() {
820     return myState.myModulesPathsAndNames;
821   }
822
823   @NotNull
824   public Collection<String> getProfilesIds() {
825     return myState.myProfilesIds;
826   }
827
828   @NotNull
829   public MavenExplicitProfiles getActivatedProfilesIds() {
830     return myState.myActivatedProfilesIds;
831   }
832
833   @NotNull
834   public List<MavenArtifact> getDependencies() {
835     return myState.myDependencies;
836   }
837
838   @NotNull
839   public List<MavenArtifactNode> getDependencyTree() {
840     return myState.myDependencyTree;
841   }
842
843   @NotNull
844   public Set<String> getSupportedPackagings() {
845     Set<String> result = ContainerUtil.newHashSet(MavenConstants.TYPE_POM,
846                                                   MavenConstants.TYPE_JAR,
847                                                   "ejb", "ejb-client", "war", "ear", "bundle", "maven-plugin");
848     for (MavenImporter each : getSuitableImporters()) {
849       each.getSupportedPackagings(result);
850     }
851     return result;
852   }
853
854   public Set<String> getDependencyTypesFromImporters(@NotNull SupportedRequestType type) {
855     THashSet<String> res = new THashSet<String>();
856
857     for (MavenImporter each : getSuitableImporters()) {
858       each.getSupportedDependencyTypes(res, type);
859     }
860
861     return res;
862   }
863
864   @NotNull
865   public Set<String> getSupportedDependencyScopes() {
866     Set<String> result = new THashSet<String>(Arrays.asList(MavenConstants.SCOPE_COMPILE,
867                                                             MavenConstants.SCOPE_PROVIDED,
868                                                             MavenConstants.SCOPE_RUNTIME,
869                                                             MavenConstants.SCOPE_TEST,
870                                                             MavenConstants.SCOPE_SYSTEM));
871     for (MavenImporter each : getSuitableImporters()) {
872       each.getSupportedDependencyScopes(result);
873     }
874     return result;
875   }
876
877   public void addDependency(@NotNull MavenArtifact dependency) {
878     State state = myState;
879     List<MavenArtifact> dependenciesCopy = new ArrayList<MavenArtifact>(state.myDependencies);
880     dependenciesCopy.add(dependency);
881     state.myDependencies = dependenciesCopy;
882
883     state.myCache.clear();
884   }
885
886   @NotNull
887   public List<MavenArtifact> findDependencies(@NotNull MavenProject depProject) {
888     return findDependencies(depProject.getMavenId());
889   }
890
891   public List<MavenArtifact> findDependencies(@NotNull MavenId id) {
892     return getDependencyArtifactIndex().findArtifacts(id);
893   }
894
895   @NotNull
896   public List<MavenArtifact> findDependencies(@Nullable String groupId, @Nullable String artifactId) {
897     return getDependencyArtifactIndex().findArtifacts(groupId, artifactId);
898   }
899
900   public boolean hasUnresolvedArtifacts() {
901     State state = myState;
902     return !isParentResolved(state)
903            || !getUnresolvedDependencies(state).isEmpty()
904            || !getUnresolvedExtensions(state).isEmpty();
905   }
906
907   public boolean hasUnresolvedPlugins() {
908     return !getUnresolvedPlugins(myState).isEmpty();
909   }
910
911   @NotNull
912   public List<MavenPlugin> getPlugins() {
913     return myState.myPlugins;
914   }
915
916   @NotNull
917   public List<MavenPlugin> getDeclaredPlugins() {
918     return getDeclaredPlugins(myState);
919   }
920
921   private static List<MavenPlugin> getDeclaredPlugins(State state) {
922     return ContainerUtil.findAll(state.myPlugins, new Condition<MavenPlugin>() {
923       public boolean value(MavenPlugin mavenPlugin) {
924         return !mavenPlugin.isDefault();
925       }
926     });
927   }
928
929   @Nullable
930   public Element getPluginConfiguration(@Nullable String groupId, @Nullable String artifactId) {
931     return getPluginGoalConfiguration(groupId, artifactId, null);
932   }
933
934   @Nullable
935   public Element getPluginGoalConfiguration(@Nullable String groupId, @Nullable String artifactId, @Nullable String goal) {
936     MavenPlugin plugin = findPlugin(groupId, artifactId);
937     if (plugin == null) return null;
938
939     if (goal == null) {
940       return plugin.getConfigurationElement();
941     }
942
943     return plugin.getGoalConfiguration(goal);
944   }
945
946   public Element getPluginExecutionConfiguration(@Nullable String groupId, @Nullable String artifactId, @NotNull String executionId) {
947     MavenPlugin plugin = findPlugin(groupId, artifactId);
948     if (plugin == null) return null;
949
950     return plugin.getExecutionConfiguration(executionId);
951   }
952
953   @Nullable
954   public MavenPlugin findPlugin(@Nullable String groupId, @Nullable String artifactId) {
955     return findPlugin(groupId, artifactId, false);
956   }
957
958   @Nullable
959   public MavenPlugin findPlugin(@Nullable String groupId, @Nullable String artifactId, final boolean explicitlyDeclaredOnly) {
960     final List<MavenPlugin> plugins = explicitlyDeclaredOnly ? getDeclaredPlugins() : getPlugins();
961     for (MavenPlugin each : plugins) {
962       if (each.getMavenId().equals(groupId, artifactId)) return each;
963     }
964     return null;
965   }
966
967   @Nullable
968   public String getEncoding() {
969     String encoding = myState.myProperties.getProperty("project.build.sourceEncoding");
970     if (encoding != null) return encoding;
971
972     Element pluginConfiguration = getPluginConfiguration("org.apache.maven.plugins", "maven-resources-plugin");
973     if (pluginConfiguration != null) {
974       return pluginConfiguration.getChildTextTrim("encoding");
975     }
976
977     return null;
978   }
979
980   @Nullable
981   public String getSourceLevel() {
982     return getCompilerLevel("source");
983   }
984
985   @Nullable
986   public String getTargetLevel() {
987     return getCompilerLevel("target");
988   }
989
990   @Nullable
991   private String getCompilerLevel(String level) {
992     String result = MavenJDOMUtil.findChildValueByPath(getCompilerConfig(), level);
993
994     if (result == null) {
995       result = myState.myProperties.getProperty("maven.compiler." + level);
996     }
997
998     return normalizeCompilerLevel(result);
999   }
1000
1001   @Nullable
1002   private Element getCompilerConfig() {
1003     return getPluginConfiguration("org.apache.maven.plugins", "maven-compiler-plugin");
1004   }
1005
1006   @Nullable
1007   public static String normalizeCompilerLevel(@Nullable String level) {
1008     return COMPILER_LEVEL_TABLE.get(level);
1009   }
1010
1011   @NotNull
1012   public Properties getProperties() {
1013     return myState.myProperties;
1014   }
1015
1016   @NotNull
1017   public File getLocalRepository() {
1018     return myState.myLocalRepository;
1019   }
1020
1021   @NotNull
1022   public List<MavenRemoteRepository> getRemoteRepositories() {
1023     return myState.myRemoteRepositories;
1024   }
1025
1026   @NotNull
1027   public List<MavenImporter> getSuitableImporters() {
1028     return MavenImporter.getSuitableImporters(this);
1029   }
1030
1031   @NotNull
1032   public ModuleType getModuleType() {
1033     final List<MavenImporter> importers = getSuitableImporters();
1034     // getSuitableImporters() guarantees that all returned importers require the same module type
1035     return importers.size() > 0 ? importers.get(0).getModuleType() : StdModuleTypes.JAVA;
1036   }
1037
1038   @NotNull
1039   public Pair<String, String> getClassifierAndExtension(@NotNull MavenArtifact artifact, @NotNull MavenExtraArtifactType type) {
1040     for (MavenImporter each : getSuitableImporters()) {
1041       Pair<String, String> result = each.getExtraArtifactClassifierAndExtension(artifact, type);
1042       if (result != null) return result;
1043     }
1044     return Pair.create(type.getDefaultClassifier(), type.getDefaultExtension());
1045   }
1046
1047   public MavenArtifactIndex getDependencyArtifactIndex() {
1048     MavenArtifactIndex res = getCachedValue(DEPENDENCIES_CACHE_KEY);
1049     if (res == null) {
1050       res = MavenArtifactIndex.build(getDependencies());
1051       res = putCachedValue(DEPENDENCIES_CACHE_KEY, res);
1052     }
1053
1054     return res;
1055   }
1056
1057   @Nullable
1058   public <V> V getCachedValue(Key<V> key) {
1059     //noinspection unchecked
1060     return (V)myState.myCache.get(key);
1061   }
1062
1063   @NotNull
1064   public <V> V putCachedValue(Key<V> key, @NotNull V value) {
1065     ConcurrentHashMap<Key, Object> map = myState.myCache;
1066     Object oldValue = map.putIfAbsent(key, value);
1067     if (oldValue != null) {
1068       return (V)oldValue;
1069     }
1070
1071     return value;
1072   }
1073
1074   @Override
1075   public String toString() {
1076     return getMavenId().toString();
1077   }
1078
1079   private static class State implements Cloneable, Serializable {
1080     long myLastReadStamp = 0;
1081
1082     MavenId myMavenId;
1083     MavenId myParentId;
1084     String myPackaging;
1085     String myName;
1086
1087     String myFinalName;
1088     String myDefaultGoal;
1089
1090     String myBuildDirectory;
1091     String myOutputDirectory;
1092     String myTestOutputDirectory;
1093
1094     List<String> mySources;
1095     List<String> myTestSources;
1096     List<MavenResource> myResources;
1097     List<MavenResource> myTestResources;
1098
1099     List<String> myFilters;
1100     Properties myProperties;
1101     List<MavenPlugin> myPlugins;
1102     List<MavenArtifact> myExtensions;
1103
1104     List<MavenArtifact> myDependencies;
1105     List<MavenArtifactNode> myDependencyTree;
1106     List<MavenRemoteRepository> myRemoteRepositories;
1107
1108     Map<String, String> myModulesPathsAndNames;
1109
1110     Map<String, String> myModelMap;
1111
1112     Collection<String> myProfilesIds;
1113     MavenExplicitProfiles myActivatedProfilesIds;
1114
1115     Collection<MavenProjectProblem> myReadingProblems;
1116     Set<MavenId> myUnresolvedArtifactIds;
1117     File myLocalRepository;
1118
1119     volatile List<MavenProjectProblem> myProblemsCache;
1120     volatile List<MavenArtifact> myUnresolvedDependenciesCache;
1121     volatile List<MavenPlugin> myUnresolvedPluginsCache;
1122     volatile List<MavenArtifact> myUnresolvedExtensionsCache;
1123
1124     transient ConcurrentHashMap<Key, Object> myCache = new ConcurrentHashMap<Key, Object>();
1125
1126     @Override
1127     public State clone() {
1128       try {
1129         State result = (State)super.clone();
1130         myCache = new ConcurrentHashMap<Key, Object>();
1131         result.resetCache();
1132         return result;
1133       }
1134       catch (CloneNotSupportedException e) {
1135         throw new RuntimeException(e);
1136       }
1137     }
1138
1139     private void resetCache() {
1140       myProblemsCache = null;
1141       myUnresolvedDependenciesCache = null;
1142       myUnresolvedPluginsCache = null;
1143       myUnresolvedExtensionsCache = null;
1144
1145       myCache.clear();
1146     }
1147
1148     public MavenProjectChanges getChanges(State other) {
1149       if (myLastReadStamp == 0) return MavenProjectChanges.ALL;
1150
1151       MavenProjectChanges result = new MavenProjectChanges();
1152
1153       result.packaging = !Comparing.equal(myPackaging, other.myPackaging);
1154
1155       result.output = !Comparing.equal(myFinalName, other.myFinalName)
1156                         || !Comparing.equal(myBuildDirectory, other.myBuildDirectory)
1157                         || !Comparing.equal(myOutputDirectory, other.myOutputDirectory)
1158                         || !Comparing.equal(myTestOutputDirectory, other.myTestOutputDirectory);
1159
1160       result.sources = !Comparing.equal(mySources, other.mySources)
1161                         || !Comparing.equal(myTestSources, other.myTestSources)
1162                         || !Comparing.equal(myResources, other.myResources)
1163                         || !Comparing.equal(myTestResources, other.myTestResources);
1164
1165       boolean repositoryChanged = !Comparing.equal(myLocalRepository, other.myLocalRepository);
1166
1167       result.dependencies = repositoryChanged || !Comparing.equal(myDependencies, other.myDependencies);
1168
1169       result.plugins = repositoryChanged || !Comparing.equal(myPlugins, other.myPlugins);
1170
1171       return result;
1172     }
1173
1174     private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
1175       in.defaultReadObject();
1176       myCache = new ConcurrentHashMap<Key, Object>();
1177     }
1178   }
1179 }