d0659cab55d3c626e8fa1ec8a6d0275b44c1fcb0
[idea/community.git] / platform / lang-impl / src / com / intellij / facet / FacetManagerImpl.java
1 /*
2  * Copyright 2000-2016 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
17 package com.intellij.facet;
18
19 import com.intellij.facet.impl.FacetLoadingErrorDescription;
20 import com.intellij.facet.impl.FacetModelBase;
21 import com.intellij.facet.impl.FacetModelImpl;
22 import com.intellij.facet.impl.FacetUtil;
23 import com.intellij.facet.impl.invalid.InvalidFacet;
24 import com.intellij.facet.impl.invalid.InvalidFacetConfiguration;
25 import com.intellij.facet.impl.invalid.InvalidFacetManager;
26 import com.intellij.facet.impl.invalid.InvalidFacetType;
27 import com.intellij.openapi.application.ApplicationManager;
28 import com.intellij.openapi.components.PersistentStateComponent;
29 import com.intellij.openapi.components.State;
30 import com.intellij.openapi.components.Storage;
31 import com.intellij.openapi.components.StoragePathMacros;
32 import com.intellij.openapi.diagnostic.Logger;
33 import com.intellij.openapi.module.Module;
34 import com.intellij.openapi.module.ModuleComponent;
35 import com.intellij.openapi.module.ModuleType;
36 import com.intellij.openapi.module.ProjectLoadingErrorsNotifier;
37 import com.intellij.openapi.project.ProjectBundle;
38 import com.intellij.openapi.updateSettings.impl.pluginsAdvertisement.UnknownFeaturesCollector;
39 import com.intellij.openapi.util.*;
40 import com.intellij.openapi.util.text.StringUtil;
41 import com.intellij.util.messages.MessageBus;
42 import org.jdom.Element;
43 import org.jetbrains.annotations.NonNls;
44 import org.jetbrains.annotations.NotNull;
45 import org.jetbrains.annotations.Nullable;
46 import org.jetbrains.jps.model.serialization.facet.FacetManagerState;
47 import org.jetbrains.jps.model.serialization.facet.FacetState;
48
49 import java.util.*;
50
51 /**
52  * @author nik
53  */
54 @State(
55   name = FacetManagerImpl.COMPONENT_NAME,
56   storages = @Storage(StoragePathMacros.MODULE_FILE)
57 )
58 public class FacetManagerImpl extends FacetManager implements ModuleComponent, PersistentStateComponent<FacetManagerState> {
59   private static final Logger LOG = Logger.getInstance("#com.intellij.facet.FacetManagerImpl");
60   @NonNls public static final String COMPONENT_NAME = "FacetManager";
61
62   private final Module myModule;
63   private final FacetManagerModel myModel = new FacetManagerModel();
64   private boolean myInsideCommit = false;
65   private final MessageBus myMessageBus;
66   private boolean myModuleAdded;
67
68   public FacetManagerImpl(final Module module, MessageBus messageBus) {
69     myModule = module;
70     myMessageBus = messageBus;
71   }
72
73   @Override
74   @NotNull
75   public ModifiableFacetModel createModifiableModel() {
76     FacetModelImpl model = new FacetModelImpl(this);
77     model.addFacetsFromManager();
78     return model;
79   }
80
81   @Override
82   @NotNull
83   public Facet[] getAllFacets() {
84     return myModel.getAllFacets();
85   }
86
87   @Override
88   @Nullable
89   public <F extends Facet> F getFacetByType(FacetTypeId<F> typeId) {
90     return myModel.getFacetByType(typeId);
91   }
92
93   @Override
94   @Nullable
95   public <F extends Facet> F findFacet(final FacetTypeId<F> type, final String name) {
96     return myModel.findFacet(type, name);
97   }
98
99   @Override
100   @Nullable
101   public <F extends Facet> F getFacetByType(@NotNull final Facet underlyingFacet, final FacetTypeId<F> typeId) {
102     return myModel.getFacetByType(underlyingFacet, typeId);
103   }
104
105   @Override
106   @NotNull
107   public <F extends Facet> Collection<F> getFacetsByType(@NotNull final Facet underlyingFacet, final FacetTypeId<F> typeId) {
108     return myModel.getFacetsByType(underlyingFacet, typeId);
109   }
110
111
112   @Override
113   @NotNull
114   public <F extends Facet> Collection<F> getFacetsByType(FacetTypeId<F> typeId) {
115     return myModel.getFacetsByType(typeId);
116   }
117
118
119   @Override
120   @NotNull
121   public Facet[] getSortedFacets() {
122     return myModel.getSortedFacets();
123   }
124
125   @Override
126   @NotNull
127   public String getFacetName(@NotNull Facet facet) {
128     return myModel.getFacetName(facet);
129   }
130
131   @Override
132   @NotNull
133   public <F extends Facet, C extends FacetConfiguration> F createFacet(@NotNull final FacetType<F, C> type, @NotNull final String name, @NotNull final C configuration,
134                                                                           @Nullable final Facet underlying) {
135     final F facet = type.createFacet(myModule, name, configuration, underlying);
136     assertTrue(facet.getModule() == myModule, facet, "module");
137     assertTrue(facet.getConfiguration() == configuration, facet, "configuration");
138     assertTrue(Comparing.equal(facet.getName(), name), facet, "name");
139     assertTrue(facet.getUnderlyingFacet() == underlying, facet, "underlyingFacet");
140     return facet;
141   }
142
143   @Override
144   @NotNull
145   public <F extends Facet, C extends FacetConfiguration> F createFacet(@NotNull final FacetType<F, C> type, @NotNull final String name, @Nullable final Facet underlying) {
146     C configuration = ProjectFacetManager.getInstance(myModule.getProject()).createDefaultConfiguration(type);
147     return createFacet(type, name, configuration, underlying);
148   }
149
150   @Override
151   @NotNull
152   public <F extends Facet, C extends FacetConfiguration> F addFacet(@NotNull final FacetType<F, C> type, @NotNull final String name, @Nullable final Facet underlying) {
153     final ModifiableFacetModel model = createModifiableModel();
154     final F facet = createFacet(type, name, underlying);
155     model.addFacet(facet);
156     model.commit();
157     return facet;
158   }
159
160   private static void assertTrue(final boolean value, final Facet facet, final String parameter) {
161     if (!value) {
162       LOG.error("Facet type " + facet.getType().getClass().getName() + " violates the contract of FacetType.createFacet method about '" +
163                 parameter + "' parameter");
164     }
165   }
166
167   private void addFacets(final List<FacetState> facetStates, final Facet underlyingFacet, ModifiableFacetModel model) {
168
169     FacetTypeRegistry registry = FacetTypeRegistry.getInstance();
170     for (FacetState child : facetStates) {
171       final String typeId = child.getFacetType();
172       if (typeId == null) {
173         addInvalidFacet(child, model, underlyingFacet, ProjectBundle.message("error.message.facet.type.isn.t.specified"));
174         continue;
175       }
176
177       final FacetType<?,?> type = registry.findFacetType(typeId);
178       if (type == null) {
179         addInvalidFacet(child, model, underlyingFacet, ProjectBundle.message("error.message.unknown.facet.type.0", typeId), typeId);
180         continue;
181       }
182
183       ModuleType moduleType = ModuleType.get(myModule);
184       if (!type.isSuitableModuleType(moduleType)) {
185         addInvalidFacet(child, model, underlyingFacet, ProjectBundle.message("error.message.0.facets.are.not.allowed.in.1",
186                                                                       type.getPresentableName(), moduleType.getName()));
187         continue;
188       }
189
190       FacetType<?,?> expectedUnderlyingType = null;
191       FacetTypeId<?> underlyingTypeId = type.getUnderlyingFacetType();
192       if (underlyingTypeId != null) {
193         expectedUnderlyingType = registry.findFacetType(underlyingTypeId);
194       }
195       FacetType actualUnderlyingType = underlyingFacet != null ? underlyingFacet.getType() : null;
196       if (expectedUnderlyingType != null) {
197         if (!expectedUnderlyingType.equals(actualUnderlyingType)) {
198           addInvalidFacet(child, model, underlyingFacet, ProjectBundle.message("error.message.0.facet.must.be.placed.under.1.facet",
199                                                                         type.getPresentableName(),
200                                                                         expectedUnderlyingType.getPresentableName()));
201           continue;
202         }
203       }
204       else if (actualUnderlyingType != null) {
205         addInvalidFacet(child, model, underlyingFacet, ProjectBundle.message("error.message.0.cannot.be.placed.under.1",
206                                                                       type.getPresentableName(), actualUnderlyingType.getPresentableName()));
207         continue;
208       }
209
210       try {
211         addFacet(type, child, underlyingFacet, model);
212       }
213       catch (InvalidDataException e) {
214         LOG.info(e);
215         addInvalidFacet(child, model, underlyingFacet, ProjectBundle.message("error.message.cannot.load.facet.configuration.0", e.getMessage()));
216       }
217     }
218   }
219
220   private void addInvalidFacet(final FacetState state,
221                                ModifiableFacetModel model,
222                                final Facet underlyingFacet,
223                                final String errorMessage) {
224     addInvalidFacet(state, model, underlyingFacet, errorMessage, null);
225   }
226
227   private void addInvalidFacet(final FacetState state,
228                                ModifiableFacetModel model,
229                                final Facet underlyingFacet,
230                                final String errorMessage,
231                                final String typeId) {
232     final InvalidFacetManager invalidFacetManager = InvalidFacetManager.getInstance(myModule.getProject());
233     final InvalidFacetType type = InvalidFacetType.getInstance();
234     final InvalidFacetConfiguration configuration = new InvalidFacetConfiguration(state, errorMessage);
235     final InvalidFacet facet = createFacet(type, StringUtil.notNullize(state.getName()), configuration, underlyingFacet);
236     model.addFacet(facet);
237     if (!invalidFacetManager.isIgnored(facet)) {
238       FacetLoadingErrorDescription description = new FacetLoadingErrorDescription(facet);
239       ProjectLoadingErrorsNotifier.getInstance(myModule.getProject()).registerError(description);
240       if (typeId != null) {
241         UnknownFeaturesCollector.getInstance(myModule.getProject()).registerUnknownFeature("com.intellij.facetType", typeId, "Facet");
242       }
243     }
244   }
245
246   private <C extends FacetConfiguration> void addFacet(final FacetType<?, C> type, final FacetState state, final Facet underlyingFacet,
247                                                        final ModifiableFacetModel model) throws InvalidDataException {
248     if (type.isOnlyOneFacetAllowed() &&
249         (underlyingFacet == null && !model.getFacetsByType(type.getId()).isEmpty() ||
250          underlyingFacet != null && !model.getFacetsByType(underlyingFacet, type.getId()).isEmpty())) {
251       LOG.info("'" + state.getName() + "' facet removed from module " + myModule.getName() + ", because only one "
252                + type.getPresentableName() + " facet allowed");
253       return;
254     }
255     final C configuration = type.createDefaultConfiguration();
256     final Element config = state.getConfiguration();
257     FacetUtil.loadFacetConfiguration(configuration, config);
258     String name = state.getName();
259     final Facet facet = createFacet(type, name, configuration, underlyingFacet);
260     if (facet instanceof JDOMExternalizable) {
261       //todo[nik] remove
262       ((JDOMExternalizable)facet).readExternal(config);
263     }
264     model.addFacet(facet);
265     addFacets(state.getSubFacets(), facet, model);
266   }
267
268   @Override
269   public void loadState(final FacetManagerState state) {
270     ModifiableFacetModel model = new FacetModelImpl(this);
271
272     addFacets(state.getFacets(), null, model);
273
274     commit(model, false);
275   }
276
277   @Override
278   public FacetManagerState getState() {
279     FacetManagerState managerState = new FacetManagerState();
280
281     final Facet[] facets = getSortedFacets();
282
283     Map<Facet, List<FacetState>> states = new HashMap<Facet, List<FacetState>>();
284     states.put(null, managerState.getFacets());
285
286     for (Facet facet : facets) {
287       final Facet underlyingFacet = facet.getUnderlyingFacet();
288       final List<FacetState> parent = states.get(underlyingFacet);
289
290       FacetState facetState;
291       if (facet instanceof InvalidFacet) {
292         facetState = ((InvalidFacet)facet).getConfiguration().getFacetState();
293       }
294       else {
295         facetState = new FacetState();
296         facetState.setFacetType(facet.getType().getStringId());
297         facetState.setName(facet.getName());
298         final Element config;
299         try {
300           FacetConfiguration configuration = facet.getConfiguration();
301           config = FacetUtil.saveFacetConfiguration(configuration);
302           if (facet instanceof JDOMExternalizable) {
303             //todo[nik] remove
304             ((JDOMExternalizable)facet).writeExternal(config);
305           }
306         }
307         catch (WriteExternalException e) {
308           continue;
309         }
310         facetState.setConfiguration(config);
311       }
312
313       parent.add(facetState);
314       states.put(facet, facetState.getSubFacets());
315     }
316     return managerState;
317   }
318
319
320   public void commit(final ModifiableFacetModel model) {
321     ApplicationManager.getApplication().assertWriteAccessAllowed();
322     commit(model, true);
323   }
324
325   private void commit(final ModifiableFacetModel model, final boolean fireEvents) {
326     LOG.assertTrue(!myInsideCommit, "Recursive commit");
327
328     Set<Facet> toRemove = new HashSet<Facet>(Arrays.asList(getAllFacets()));
329     List<Facet> toAdd = new ArrayList<Facet>();
330     List<FacetRenameInfo> toRename = new ArrayList<FacetRenameInfo>();
331
332     final FacetManagerListener publisher = myMessageBus.syncPublisher(FACETS_TOPIC);
333
334     try {
335       myInsideCommit = true;
336
337       for (Facet facet : model.getAllFacets()) {
338         boolean isNew = !toRemove.remove(facet);
339         if (isNew) {
340           toAdd.add(facet);
341         }
342       }
343
344       List<Facet> newFacets = new ArrayList<Facet>();
345       for (Facet facet : getAllFacets()) {
346         if (!toRemove.contains(facet)) {
347           newFacets.add(facet);
348         }
349       }
350       newFacets.addAll(toAdd);
351
352       for (Facet facet : newFacets) {
353         final String newName = model.getNewName(facet);
354         if (newName != null && !newName.equals(facet.getName())) {
355           toRename.add(new FacetRenameInfo(facet, facet.getName(), newName));
356         }
357       }
358
359       if (fireEvents) {
360         for (Facet facet : toAdd) {
361           publisher.beforeFacetAdded(facet);
362         }
363         for (Facet facet : toRemove) {
364           publisher.beforeFacetRemoved(facet);
365         }
366         for (FacetRenameInfo info : toRename) {
367           publisher.beforeFacetRenamed(info.myFacet);
368         }
369       }
370
371       for (FacetRenameInfo info : toRename) {
372         info.myFacet.setName(info.myNewName);
373       }
374       myModel.setAllFacets(newFacets.toArray(new Facet[newFacets.size()]));
375     }
376     finally {
377       myInsideCommit = false;
378     }
379
380     if (myModuleAdded) {
381       for (Facet facet : toAdd) {
382         facet.initFacet();
383       }
384     }
385     for (Facet facet : toRemove) {
386       Disposer.dispose(facet);
387     }
388
389     if (fireEvents) {
390       for (Facet facet : toAdd) {
391         publisher.facetAdded(facet);
392       }
393       for (Facet facet : toRemove) {
394         publisher.facetRemoved(facet);
395       }
396       for (FacetRenameInfo info : toRename) {
397         publisher.facetRenamed(info.myFacet, info.myOldName);
398       }
399     }
400     for (Facet facet : toAdd) {
401       final Module module = facet.getModule();
402       if (!module.equals(myModule)) {
403         LOG.error(facet + " is created for module " + module + " but added to module " + myModule);
404       }
405       final FacetType<?,?> type = facet.getType();
406       if (type.isOnlyOneFacetAllowed()) {
407         if (type.getUnderlyingFacetType() == null) {
408           final Collection<?> facets = getFacetsByType(type.getId());
409           if (facets.size() > 1) {
410             LOG.error("Only one '" + type.getPresentableName() + "' facet per module allowed, but " + facets.size() + " facets found in module '" +
411                       myModule.getName() + "'");
412           }
413         }
414         else {
415           final Facet underlyingFacet = facet.getUnderlyingFacet();
416           LOG.assertTrue(underlyingFacet != null, "Underlying facet is not specified for '" + facet.getName() + "'");
417           final Collection<?> facets = getFacetsByType(underlyingFacet, type.getId());
418           if (facets.size() > 1) {
419             LOG.error("Only one '" + type.getPresentableName() + "' facet per parent facet allowed, but " + facets.size() + " sub-facets found in facet " + underlyingFacet.getName());
420           }
421         }
422       }
423     }
424   }
425
426
427   @Override
428   public void projectOpened() {
429   }
430
431   @Override
432   public void projectClosed() {
433   }
434
435   @Override
436   public void moduleAdded() {
437     if (myModuleAdded) return;
438
439     for (Facet facet : getAllFacets()) {
440       facet.initFacet();
441     }
442     myModuleAdded = true;
443   }
444
445   @Override
446   @NonNls
447   @NotNull
448   public String getComponentName() {
449     return COMPONENT_NAME;
450   }
451
452   @Override
453   public void initComponent() {
454   }
455
456   @Override
457   public void disposeComponent() {
458   }
459
460   private static class FacetManagerModel extends FacetModelBase {
461     private Facet[] myAllFacets = Facet.EMPTY_ARRAY;
462
463     @Override
464     @NotNull
465     public Facet[] getAllFacets() {
466       return myAllFacets;
467     }
468
469     public void setAllFacets(final Facet[] allFacets) {
470       myAllFacets = allFacets;
471       facetsChanged();
472     }
473   }
474
475   private static class FacetRenameInfo {
476     private final Facet myFacet;
477     private final String myOldName;
478     private final String myNewName;
479
480     public FacetRenameInfo(final Facet facet, final String oldName, final String newName) {
481       myFacet = facet;
482       myOldName = oldName;
483       myNewName = newName;
484     }
485   }
486 }