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