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