[i18n] idea-ui
[idea/community.git] / java / compiler / impl / src / com / intellij / packaging / impl / artifacts / ArtifactManagerImpl.java
1 // Copyright 2000-2020 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.artifacts;
3
4 import com.intellij.compiler.server.BuildManager;
5 import com.intellij.configurationStore.XmlSerializer;
6 import com.intellij.openapi.Disposable;
7 import com.intellij.openapi.application.ApplicationManager;
8 import com.intellij.openapi.application.WriteAction;
9 import com.intellij.openapi.compiler.JavaCompilerBundle;
10 import com.intellij.openapi.components.PersistentStateComponent;
11 import com.intellij.openapi.components.State;
12 import com.intellij.openapi.components.Storage;
13 import com.intellij.openapi.diagnostic.Logger;
14 import com.intellij.openapi.module.ProjectLoadingErrorsNotifier;
15 import com.intellij.openapi.project.Project;
16 import com.intellij.openapi.project.ProjectUtilCore;
17 import com.intellij.openapi.roots.ExternalProjectSystemRegistry;
18 import com.intellij.openapi.roots.ProjectModelExternalSource;
19 import com.intellij.openapi.roots.ex.ProjectRootManagerEx;
20 import com.intellij.openapi.updateSettings.impl.pluginsAdvertisement.UnknownFeaturesCollector;
21 import com.intellij.openapi.util.ModificationTracker;
22 import com.intellij.openapi.util.Pair;
23 import com.intellij.openapi.util.SimpleModificationTracker;
24 import com.intellij.openapi.vfs.LocalFileSystem;
25 import com.intellij.openapi.vfs.VirtualFileManager;
26 import com.intellij.packaging.artifacts.*;
27 import com.intellij.packaging.elements.*;
28 import com.intellij.util.containers.ContainerUtil;
29 import gnu.trove.THashSet;
30 import org.jdom.Element;
31 import org.jetbrains.annotations.Nls;
32 import org.jetbrains.annotations.NonNls;
33 import org.jetbrains.annotations.NotNull;
34 import org.jetbrains.annotations.Nullable;
35 import org.jetbrains.jps.model.serialization.artifact.ArtifactManagerState;
36 import org.jetbrains.jps.model.serialization.artifact.ArtifactPropertiesState;
37 import org.jetbrains.jps.model.serialization.artifact.ArtifactState;
38
39 import java.util.*;
40 import java.util.function.Function;
41
42 @State(name = ArtifactManagerImpl.COMPONENT_NAME, storages = @Storage(value = "artifacts", stateSplitter = ArtifactManagerStateSplitter.class))
43 public final class ArtifactManagerImpl extends ArtifactManager implements PersistentStateComponent<ArtifactManagerState>, Disposable {
44   private static final Logger LOG = Logger.getInstance(ArtifactManagerImpl.class);
45   @NonNls public static final String COMPONENT_NAME = "ArtifactManager";
46   @NonNls public static final String PACKAGING_ELEMENT_NAME = "element";
47   @NonNls public static final String TYPE_ID_ATTRIBUTE = "id";
48   public static final String FEATURE_TYPE = "com.intellij.packaging.artifacts.ArtifactType";
49   private final ArtifactManagerModel myModel;
50   private final Project myProject;
51   private final DefaultPackagingElementResolvingContext myResolvingContext;
52   private boolean myInsideCommit = false;
53   private boolean myLoaded;
54   private final SimpleModificationTracker myModificationTracker = new SimpleModificationTracker();
55   private final Map<String, LocalFileSystem.WatchRequest> myWatchedOutputs = new HashMap<>();
56
57   public ArtifactManagerImpl(@NotNull Project project) {
58     myProject = project;
59     myModel = new ArtifactManagerModel();
60     myResolvingContext = new DefaultPackagingElementResolvingContext(myProject);
61     ((ArtifactPointerManagerImpl)ArtifactPointerManager.getInstance(project)).setArtifactManager(this);
62     new DynamicArtifactExtensionsLoader(this).installListeners(this);
63   }
64
65   @Override
66   public Artifact @NotNull [] getArtifacts() {
67     return myModel.getArtifacts();
68   }
69
70   @Override
71   public Artifact findArtifact(@NotNull String name) {
72     return myModel.findArtifact(name);
73   }
74
75   @Override
76   @NotNull
77   public Artifact getArtifactByOriginal(@NotNull Artifact artifact) {
78     return myModel.getArtifactByOriginal(artifact);
79   }
80
81   @Override
82   @NotNull
83   public Artifact getOriginalArtifact(@NotNull Artifact artifact) {
84     return myModel.getOriginalArtifact(artifact);
85   }
86
87   @Override
88   @NotNull
89   public Collection<? extends Artifact> getArtifactsByType(@NotNull ArtifactType type) {
90     return myModel.getArtifactsByType(type);
91   }
92
93   @Override
94   public List<? extends Artifact> getAllArtifactsIncludingInvalid() {
95     return myModel.getAllArtifactsIncludingInvalid();
96   }
97
98   @Override
99   public ArtifactManagerState getState() {
100     final ArtifactManagerState state = new ArtifactManagerState();
101     for (Artifact artifact : getAllArtifactsIncludingInvalid()) {
102       state.getArtifacts().add(saveArtifact(artifact));
103     }
104     return state;
105   }
106
107   ArtifactState saveArtifact(Artifact artifact) {
108     ArtifactState artifactState;
109     if (artifact instanceof InvalidArtifact) {
110       artifactState = ((InvalidArtifact)artifact).getState();
111     }
112     else {
113       artifactState = new ArtifactState();
114       artifactState.setBuildOnMake(artifact.isBuildOnMake());
115       artifactState.setName(artifact.getName());
116       artifactState.setOutputPath(artifact.getOutputPath());
117       artifactState.setRootElement(serializePackagingElement(artifact.getRootElement()));
118       artifactState.setArtifactType(artifact.getArtifactType().getId());
119       ProjectModelExternalSource externalSource = artifact.getExternalSource();
120       if (externalSource != null && ProjectUtilCore.isExternalStorageEnabled(myProject)) {
121         //we can add this attribute only if the artifact configuration will be stored separately, otherwise we will get modified files in .idea/artifacts.
122         artifactState.setExternalSystemId(externalSource.getId());
123       }
124
125       for (ArtifactPropertiesProvider provider : artifact.getPropertiesProviders()) {
126         final ArtifactPropertiesState propertiesState = serializeProperties(provider, artifact.getProperties(provider));
127         if (propertiesState != null) {
128           artifactState.getPropertiesList().add(propertiesState);
129         }
130       }
131       artifactState.getPropertiesList().sort(Comparator.comparing(ArtifactPropertiesState::getId));
132     }
133     return artifactState;
134   }
135
136   public void replaceArtifacts(@NotNull Collection<? extends Artifact> toReplace, Function<Artifact, ArtifactImpl> replacement) {
137     if (toReplace.isEmpty()) return;
138     
139     ArtifactModelImpl model = createModifiableModel();
140     for (Artifact artifact : toReplace) {
141       model.removeArtifact(artifact);
142       model.addArtifact(replacement.apply(artifact));
143     }
144     model.commit();
145   }
146
147   @Nullable
148   private static <S> ArtifactPropertiesState serializeProperties(ArtifactPropertiesProvider provider, ArtifactProperties<S> properties) {
149     if (properties.getState() == null) return null;
150     final Element options = XmlSerializer.serialize(properties.getState());
151     if (options == null) {
152       return null;
153     }
154
155     options.setName("options");
156     final ArtifactPropertiesState state = new ArtifactPropertiesState();
157     state.setId(provider.getId());
158     state.setOptions(options);
159     return state;
160   }
161
162   private static Element serializePackagingElement(PackagingElement<?> packagingElement) {
163     Element element = new Element(PACKAGING_ELEMENT_NAME);
164     element.setAttribute(TYPE_ID_ATTRIBUTE, packagingElement.getType().getId());
165     final Object bean = packagingElement.getState();
166     if (bean != null) {
167       XmlSerializer.serializeObjectInto(bean, element);
168     }
169     if (packagingElement instanceof CompositePackagingElement) {
170       for (PackagingElement<?> child : ((CompositePackagingElement<?>)packagingElement).getChildren()) {
171         element.addContent(serializePackagingElement(child));
172       }
173     }
174     return element;
175   }
176
177   private <T> PackagingElement<T> deserializeElement(Element element) throws UnknownPackagingElementTypeException {
178     final String id = element.getAttributeValue(TYPE_ID_ATTRIBUTE);
179     PackagingElementType<?> type = PackagingElementFactory.getInstance().findElementType(id);
180     if (type == null) {
181       throw new UnknownPackagingElementTypeException(id);
182     }
183
184     PackagingElement<T> packagingElement = (PackagingElement<T>)type.createEmpty(myProject);
185     T state = packagingElement.getState();
186     if (state != null) {
187       XmlSerializer.deserializeInto(element, state);
188       packagingElement.loadState(state);
189     }
190     final List<? extends Element> children = element.getChildren(PACKAGING_ELEMENT_NAME);
191     for (Element child : children) {
192       ((CompositePackagingElement<?>)packagingElement).addOrFindChild(deserializeElement(child));
193     }
194     return packagingElement;
195   }
196
197   @Override
198   public void loadState(@NotNull ArtifactManagerState managerState) {
199     List<ArtifactState> artifactStates = managerState.getArtifacts();
200     final List<ArtifactImpl> artifacts = new ArrayList<>(artifactStates.size());
201     if (!artifactStates.isEmpty()) {
202       ApplicationManager.getApplication().runReadAction(() -> {
203         for (ArtifactState state : artifactStates) {
204           artifacts.add(loadArtifact(state));
205         }
206       });
207     }
208
209     if (myLoaded) {
210       final ArtifactModelImpl model = new ArtifactModelImpl(this, artifacts);
211       doCommit(model);
212     }
213     else {
214       myModel.setArtifactsList(artifacts);
215       myLoaded = true;
216     }
217   }
218
219   ArtifactImpl loadArtifact(ArtifactState state) {
220     ArtifactType type = ArtifactType.findById(state.getArtifactType());
221     ProjectModelExternalSource externalSource = findExternalSource(state.getExternalSystemId());
222     if (type == null) {
223       return createInvalidArtifact(state, externalSource, JavaCompilerBundle.message("unknown.artifact.type.0", state.getArtifactType()));
224     }
225
226     final Element element = state.getRootElement();
227     final String artifactName = state.getName();
228     final CompositePackagingElement<?> rootElement;
229     if (element != null) {
230       try {
231         rootElement = (CompositePackagingElement<?>)deserializeElement(element);
232       }
233       catch (UnknownPackagingElementTypeException e) {
234         return createInvalidArtifact(state, externalSource, JavaCompilerBundle.message("unknown.element.0", e.getTypeId()));
235       }
236     }
237     else {
238       rootElement = type.createRootElement(artifactName);
239     }
240
241     final ArtifactImpl artifact = new ArtifactImpl(artifactName, type, state.isBuildOnMake(), rootElement, state.getOutputPath(),
242                                                    externalSource);
243     final List<ArtifactPropertiesState> propertiesList = state.getPropertiesList();
244     for (ArtifactPropertiesState propertiesState : propertiesList) {
245       final ArtifactPropertiesProvider provider = ArtifactPropertiesProvider.findById(propertiesState.getId());
246       if (provider != null) {
247         deserializeProperties(artifact.getProperties(provider), propertiesState);
248       }
249       else {
250         final String message = JavaCompilerBundle.message("unknown.artifact.properties.0", propertiesState.getId());
251         return createInvalidArtifact(state, externalSource, message);
252       }
253     }
254     return artifact;
255   }
256
257   private InvalidArtifact createInvalidArtifact(ArtifactState state,
258                                                 ProjectModelExternalSource externalSource,
259                                                 @Nls(capitalization = Nls.Capitalization.Sentence) String errorMessage) {
260     final InvalidArtifact artifact = new InvalidArtifact(state, errorMessage, externalSource);
261     ProjectLoadingErrorsNotifier.getInstance(myProject).registerError(new ArtifactLoadingErrorDescription(myProject, artifact));
262     UnknownFeaturesCollector.getInstance(myProject).registerUnknownFeature(FEATURE_TYPE, state.getArtifactType(), "Artifact");
263     return artifact;
264   }
265
266   @Nullable
267   private static ProjectModelExternalSource findExternalSource(@Nullable String externalSourceId) {
268     return externalSourceId != null ? ExternalProjectSystemRegistry.getInstance().getSourceById(externalSourceId) : null;
269   }
270
271   private static <S> void deserializeProperties(ArtifactProperties<S> artifactProperties, ArtifactPropertiesState propertiesState) {
272     final Element options = propertiesState.getOptions();
273     if (artifactProperties == null || options == null) {
274       return;
275     }
276     final S state = artifactProperties.getState();
277     if (state != null) {
278       XmlSerializer.deserializeInto(options, state);
279       artifactProperties.loadState(state);
280     }
281   }
282
283   @Override
284   public void dispose() {
285     LocalFileSystem.getInstance().removeWatchedRoots(myWatchedOutputs.values());
286   }
287
288   @Override
289   public void initializeComponent() {
290     myProject.getMessageBus().connect(this).subscribe(VirtualFileManager.VFS_CHANGES, new ArtifactVirtualFileListener(myProject, this));
291     updateWatchedRoots();
292   }
293
294   private void updateWatchedRoots() {
295     Set<String> pathsToRemove = new HashSet<>(myWatchedOutputs.keySet());
296     Set<String> toAdd = new HashSet<>();
297     for (Artifact artifact : getArtifacts()) {
298       final String path = artifact.getOutputPath();
299       if (path != null && path.length() > 0) {
300         pathsToRemove.remove(path);
301         if (!myWatchedOutputs.containsKey(path)) {
302           toAdd.add(path);
303         }
304       }
305     }
306
307     List<LocalFileSystem.WatchRequest> requestsToRemove = new ArrayList<>();
308     for (String path : pathsToRemove) {
309       final LocalFileSystem.WatchRequest request = myWatchedOutputs.remove(path);
310       ContainerUtil.addIfNotNull(requestsToRemove, request);
311     }
312
313     Set<LocalFileSystem.WatchRequest> newRequests = LocalFileSystem.getInstance().replaceWatchedRoots(requestsToRemove, toAdd, null);
314     for (LocalFileSystem.WatchRequest request : newRequests) {
315       myWatchedOutputs.put(request.getRootPath(), request);
316     }
317   }
318
319   @Override
320   public Artifact[] getSortedArtifacts() {
321     return myModel.getSortedArtifacts();
322   }
323
324   @Override
325   public ArtifactModelImpl createModifiableModel() {
326     return new ArtifactModelImpl(this, getArtifactsList());
327   }
328
329   @Override
330   public PackagingElementResolvingContext getResolvingContext() {
331     return myResolvingContext;
332   }
333
334   public List<? extends ArtifactImpl> getArtifactsList() {
335     return myModel.myArtifactsList;
336   }
337
338   public void commit(ArtifactModelImpl artifactModel) {
339     ApplicationManager.getApplication().assertWriteAccessAllowed();
340
341     doCommit(artifactModel);
342   }
343
344   private void doCommit(ArtifactModelImpl artifactModel) {
345     boolean hasChanges;
346     LOG.assertTrue(!myInsideCommit, "Recursive commit");
347     myInsideCommit = true;
348     try {
349
350       final List<ArtifactImpl> allArtifacts = artifactModel.getOriginalArtifacts();
351
352       final Set<ArtifactImpl> removed = new THashSet<>(myModel.myArtifactsList);
353       final List<ArtifactImpl> added = new ArrayList<>();
354       final List<Pair<ArtifactImpl, String>> changed = new ArrayList<>();
355
356       for (ArtifactImpl artifact : allArtifacts) {
357         final boolean isAdded = !removed.remove(artifact);
358         final ArtifactImpl modifiableCopy = artifactModel.getModifiableCopy(artifact);
359         if (isAdded) {
360           added.add(artifact);
361         }
362         else if (modifiableCopy != null && !modifiableCopy.equals(artifact)) {
363           final String oldName = artifact.getName();
364           artifact.copyFrom(modifiableCopy);
365           changed.add(Pair.create(artifact, oldName));
366         }
367       }
368
369       myModel.setArtifactsList(allArtifacts);
370       myModificationTracker.incModificationCount();
371       final ArtifactListener publisher = myProject.getMessageBus().syncPublisher(TOPIC);
372       hasChanges = !removed.isEmpty() || !added.isEmpty() || !changed.isEmpty();
373       ProjectRootManagerEx.getInstanceEx(myProject).mergeRootsChangesDuring(() -> {
374         for (ArtifactImpl artifact : removed) {
375           publisher.artifactRemoved(artifact);
376         }
377         //it's important to send 'removed' events before 'added'. Otherwise when artifacts are reloaded from xml artifact pointers will be damaged
378         for (ArtifactImpl artifact : added) {
379           publisher.artifactAdded(artifact);
380         }
381         for (Pair<ArtifactImpl, String> pair : changed) {
382           publisher.artifactChanged(pair.getFirst(), pair.getSecond());
383         }
384       });
385     }
386     finally {
387       myInsideCommit = false;
388     }
389     updateWatchedRoots();
390     if (hasChanges) {
391       BuildManager.getInstance().clearState(myProject);
392     }
393   }
394
395   public Project getProject() {
396     return myProject;
397   }
398
399   @Override
400   @NotNull
401   public Artifact addArtifact(@NotNull final String name, @NotNull final ArtifactType type, final CompositePackagingElement<?> root) {
402     return WriteAction.compute(() -> {
403       final ModifiableArtifactModel model = createModifiableModel();
404       final ModifiableArtifact artifact = model.addArtifact(name, type);
405       if (root != null) {
406         artifact.setRootElement(root);
407       }
408       model.commit();
409       return artifact;
410     });
411   }
412
413   @Override
414   public void addElementsToDirectory(@NotNull Artifact artifact, @NotNull String relativePath, @NotNull PackagingElement<?> element) {
415     addElementsToDirectory(artifact, relativePath, Collections.singletonList(element));
416   }
417
418   @Override
419   public void addElementsToDirectory(@NotNull Artifact artifact, @NotNull String relativePath,
420                                      @NotNull Collection<? extends PackagingElement<?>> elements) {
421     final ModifiableArtifactModel model = createModifiableModel();
422     final CompositePackagingElement<?> root = model.getOrCreateModifiableArtifact(artifact).getRootElement();
423     PackagingElementFactory.getInstance().getOrCreateDirectory(root, relativePath).addOrFindChildren(elements);
424     WriteAction.run(() -> model.commit());
425   }
426
427   @Override
428   public ModificationTracker getModificationTracker() {
429     return myModificationTracker;
430   }
431
432   private static class ArtifactManagerModel extends ArtifactModelBase {
433     private List<? extends ArtifactImpl> myArtifactsList = new ArrayList<>();
434     private Artifact[] mySortedArtifacts;
435
436     public void setArtifactsList(List<? extends ArtifactImpl> artifactsList) {
437       myArtifactsList = artifactsList;
438       artifactsChanged();
439     }
440
441     @Override
442     protected void artifactsChanged() {
443       super.artifactsChanged();
444       mySortedArtifacts = null;
445     }
446
447     @Override
448     protected List<? extends Artifact> getArtifactsList() {
449       return myArtifactsList;
450     }
451
452     public Artifact[] getSortedArtifacts() {
453       if (mySortedArtifacts == null) {
454         mySortedArtifacts = getArtifacts().clone();
455         Arrays.sort(mySortedArtifacts, ARTIFACT_COMPARATOR);
456       }
457       return mySortedArtifacts;
458     }
459   }
460
461 }