blob: 93315c57cc49ae75795a6e81703642eaa06c0e83 [file] [log] [blame]
/*
* Copyright 2000-2009 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.facet;
import com.intellij.facet.impl.*;
import com.intellij.facet.impl.invalid.InvalidFacet;
import com.intellij.facet.impl.invalid.InvalidFacetConfiguration;
import com.intellij.facet.impl.invalid.InvalidFacetManager;
import com.intellij.facet.impl.invalid.InvalidFacetType;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.PersistentStateComponent;
import com.intellij.openapi.components.State;
import com.intellij.openapi.components.Storage;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleComponent;
import com.intellij.openapi.module.ModuleType;
import com.intellij.openapi.module.ProjectLoadingErrorsNotifier;
import com.intellij.openapi.project.ProjectBundle;
import com.intellij.openapi.updateSettings.impl.pluginsAdvertisement.UnknownFeaturesCollector;
import com.intellij.openapi.util.*;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.messages.MessageBus;
import org.jdom.Element;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jps.model.serialization.facet.FacetManagerState;
import org.jetbrains.jps.model.serialization.facet.FacetState;
import java.util.*;
/**
* @author nik
*/
@State(
name = FacetManagerImpl.COMPONENT_NAME,
storages = {
@Storage(
file = "$MODULE_FILE$"
)
}
)
public class FacetManagerImpl extends FacetManager implements ModuleComponent, PersistentStateComponent<FacetManagerState> {
private static final Logger LOG = Logger.getInstance("#com.intellij.facet.FacetManagerImpl");
@NonNls public static final String COMPONENT_NAME = "FacetManager";
private final Module myModule;
private final FacetTypeRegistry myFacetTypeRegistry;
private final FacetManagerModel myModel = new FacetManagerModel();
private boolean myInsideCommit = false;
private final MessageBus myMessageBus;
private boolean myModuleAdded;
public FacetManagerImpl(final Module module, MessageBus messageBus, final FacetTypeRegistry facetTypeRegistry) {
myModule = module;
myMessageBus = messageBus;
myFacetTypeRegistry = facetTypeRegistry;
}
@Override
@NotNull
public ModifiableFacetModel createModifiableModel() {
FacetModelImpl model = new FacetModelImpl(this);
model.addFacetsFromManager();
return model;
}
@Override
@NotNull
public Facet[] getAllFacets() {
return myModel.getAllFacets();
}
@Override
@Nullable
public <F extends Facet> F getFacetByType(FacetTypeId<F> typeId) {
return myModel.getFacetByType(typeId);
}
@Override
@Nullable
public <F extends Facet> F findFacet(final FacetTypeId<F> type, final String name) {
return myModel.findFacet(type, name);
}
@Override
@Nullable
public <F extends Facet> F getFacetByType(@NotNull final Facet underlyingFacet, final FacetTypeId<F> typeId) {
return myModel.getFacetByType(underlyingFacet, typeId);
}
@Override
@NotNull
public <F extends Facet> Collection<F> getFacetsByType(@NotNull final Facet underlyingFacet, final FacetTypeId<F> typeId) {
return myModel.getFacetsByType(underlyingFacet, typeId);
}
@Override
@NotNull
public <F extends Facet> Collection<F> getFacetsByType(FacetTypeId<F> typeId) {
return myModel.getFacetsByType(typeId);
}
@Override
@NotNull
public Facet[] getSortedFacets() {
return myModel.getSortedFacets();
}
@Override
@NotNull
public String getFacetName(@NotNull Facet facet) {
return myModel.getFacetName(facet);
}
@Override
@NotNull
public <F extends Facet, C extends FacetConfiguration> F createFacet(@NotNull final FacetType<F, C> type, @NotNull final String name, @NotNull final C configuration,
@Nullable final Facet underlying) {
final F facet = type.createFacet(myModule, name, configuration, underlying);
assertTrue(facet.getModule() == myModule, facet, "module");
assertTrue(facet.getConfiguration() == configuration, facet, "configuration");
assertTrue(Comparing.equal(facet.getName(), name), facet, "name");
assertTrue(facet.getUnderlyingFacet() == underlying, facet, "underlyingFacet");
return facet;
}
@Override
@NotNull
public <F extends Facet, C extends FacetConfiguration> F createFacet(@NotNull final FacetType<F, C> type, @NotNull final String name, @Nullable final Facet underlying) {
C configuration = ProjectFacetManager.getInstance(myModule.getProject()).createDefaultConfiguration(type);
return createFacet(type, name, configuration, underlying);
}
@Override
@NotNull
public <F extends Facet, C extends FacetConfiguration> F addFacet(@NotNull final FacetType<F, C> type, @NotNull final String name, @Nullable final Facet underlying) {
final ModifiableFacetModel model = createModifiableModel();
final F facet = createFacet(type, name, underlying);
model.addFacet(facet);
model.commit();
return facet;
}
private static void assertTrue(final boolean value, final Facet facet, final String parameter) {
if (!value) {
LOG.error("Facet type " + facet.getType().getClass().getName() + " violates the contract of FacetType.createFacet method about '" +
parameter + "' parameter");
}
}
private void addFacets(final List<FacetState> facetStates, final Facet underlyingFacet, ModifiableFacetModel model) {
for (FacetState child : facetStates) {
final String typeId = child.getFacetType();
if (typeId == null) {
addInvalidFacet(child, model, underlyingFacet, ProjectBundle.message("error.message.facet.type.isn.t.specified"));
continue;
}
final FacetType<?,?> type = myFacetTypeRegistry.findFacetType(typeId);
if (type == null) {
addInvalidFacet(child, model, underlyingFacet, ProjectBundle.message("error.message.unknown.facet.type.0", typeId), typeId);
continue;
}
ModuleType moduleType = ModuleType.get(myModule);
if (!type.isSuitableModuleType(moduleType)) {
addInvalidFacet(child, model, underlyingFacet, ProjectBundle.message("error.message.0.facets.are.not.allowed.in.1",
type.getPresentableName(), moduleType.getName()));
continue;
}
FacetType<?,?> expectedUnderlyingType = null;
FacetTypeId<?> underlyingTypeId = type.getUnderlyingFacetType();
if (underlyingTypeId != null) {
expectedUnderlyingType = myFacetTypeRegistry.findFacetType(underlyingTypeId);
}
FacetType actualUnderlyingType = underlyingFacet != null ? underlyingFacet.getType() : null;
if (expectedUnderlyingType != null) {
if (!expectedUnderlyingType.equals(actualUnderlyingType)) {
addInvalidFacet(child, model, underlyingFacet, ProjectBundle.message("error.message.0.facet.must.be.placed.under.1.facet",
type.getPresentableName(),
expectedUnderlyingType.getPresentableName()));
continue;
}
}
else if (actualUnderlyingType != null) {
addInvalidFacet(child, model, underlyingFacet, ProjectBundle.message("error.message.0.cannot.be.placed.under.1",
type.getPresentableName(), actualUnderlyingType.getPresentableName()));
continue;
}
try {
addFacet(type, child, underlyingFacet, model);
}
catch (InvalidDataException e) {
LOG.info(e);
addInvalidFacet(child, model, underlyingFacet, ProjectBundle.message("error.message.cannot.load.facet.configuration.0", e.getMessage()));
}
}
}
private void addInvalidFacet(final FacetState state,
ModifiableFacetModel model,
final Facet underlyingFacet,
final String errorMessage) {
addInvalidFacet(state, model, underlyingFacet, errorMessage, null);
}
private void addInvalidFacet(final FacetState state,
ModifiableFacetModel model,
final Facet underlyingFacet,
final String errorMessage,
final String typeId) {
final InvalidFacetManager invalidFacetManager = InvalidFacetManager.getInstance(myModule.getProject());
final InvalidFacetType type = InvalidFacetType.getInstance();
final InvalidFacetConfiguration configuration = new InvalidFacetConfiguration(state, errorMessage);
final InvalidFacet facet = createFacet(type, StringUtil.notNullize(state.getName()), configuration, underlyingFacet);
model.addFacet(facet);
if (!invalidFacetManager.isIgnored(facet)) {
FacetLoadingErrorDescription description = new FacetLoadingErrorDescription(facet);
ProjectLoadingErrorsNotifier.getInstance(myModule.getProject()).registerError(description);
if (typeId != null) {
UnknownFeaturesCollector.getInstance(myModule.getProject()).registerUnknownFeature("com.intellij.facetType", typeId);
}
}
}
private <C extends FacetConfiguration> void addFacet(final FacetType<?, C> type, final FacetState state, final Facet underlyingFacet,
final ModifiableFacetModel model) throws InvalidDataException {
if (type.isOnlyOneFacetAllowed() &&
(underlyingFacet == null && !model.getFacetsByType(type.getId()).isEmpty() ||
underlyingFacet != null && !model.getFacetsByType(underlyingFacet, type.getId()).isEmpty())) {
LOG.info("'" + state.getName() + "' facet removed from module " + myModule.getName() + ", because only one "
+ type.getPresentableName() + " facet allowed");
return;
}
final C configuration = type.createDefaultConfiguration();
final Element config = state.getConfiguration();
FacetUtil.loadFacetConfiguration(configuration, config);
String name = state.getName();
final Facet facet = createFacet(type, name, configuration, underlyingFacet);
if (facet instanceof JDOMExternalizable) {
//todo[nik] remove
((JDOMExternalizable)facet).readExternal(config);
}
model.addFacet(facet);
addFacets(state.getSubFacets(), facet, model);
}
@Override
public void loadState(final FacetManagerState state) {
ModifiableFacetModel model = new FacetModelImpl(this);
addFacets(state.getFacets(), null, model);
commit(model, false);
}
@Override
public FacetManagerState getState() {
FacetManagerState managerState = new FacetManagerState();
final Facet[] facets = getSortedFacets();
Map<Facet, List<FacetState>> states = new HashMap<Facet, List<FacetState>>();
states.put(null, managerState.getFacets());
for (Facet facet : facets) {
final Facet underlyingFacet = facet.getUnderlyingFacet();
final List<FacetState> parent = states.get(underlyingFacet);
FacetState facetState;
if (facet instanceof InvalidFacet) {
facetState = ((InvalidFacet)facet).getConfiguration().getFacetState();
}
else {
facetState = new FacetState();
facetState.setFacetType(facet.getType().getStringId());
facetState.setName(facet.getName());
final Element config;
try {
FacetConfiguration configuration = facet.getConfiguration();
config = FacetUtil.saveFacetConfiguration(configuration);
if (facet instanceof JDOMExternalizable) {
//todo[nik] remove
((JDOMExternalizable)facet).writeExternal(config);
}
}
catch (WriteExternalException e) {
continue;
}
facetState.setConfiguration(config);
}
parent.add(facetState);
states.put(facet, facetState.getSubFacets());
}
return managerState;
}
public void commit(final ModifiableFacetModel model) {
ApplicationManager.getApplication().assertWriteAccessAllowed();
commit(model, true);
}
private void commit(final ModifiableFacetModel model, final boolean fireEvents) {
LOG.assertTrue(!myInsideCommit, "Recursive commit");
Set<Facet> toRemove = new HashSet<Facet>(Arrays.asList(getAllFacets()));
List<Facet> toAdd = new ArrayList<Facet>();
List<FacetRenameInfo> toRename = new ArrayList<FacetRenameInfo>();
final FacetManagerListener publisher = myMessageBus.syncPublisher(FACETS_TOPIC);
try {
myInsideCommit = true;
for (Facet facet : model.getAllFacets()) {
boolean isNew = !toRemove.remove(facet);
if (isNew) {
toAdd.add(facet);
}
}
List<Facet> newFacets = new ArrayList<Facet>();
for (Facet facet : getAllFacets()) {
if (!toRemove.contains(facet)) {
newFacets.add(facet);
}
}
newFacets.addAll(toAdd);
for (Facet facet : newFacets) {
final String newName = model.getNewName(facet);
if (newName != null && !newName.equals(facet.getName())) {
toRename.add(new FacetRenameInfo(facet, facet.getName(), newName));
}
}
if (fireEvents) {
for (Facet facet : toAdd) {
publisher.beforeFacetAdded(facet);
}
for (Facet facet : toRemove) {
publisher.beforeFacetRemoved(facet);
}
for (FacetRenameInfo info : toRename) {
publisher.beforeFacetRenamed(info.myFacet);
}
}
for (FacetRenameInfo info : toRename) {
info.myFacet.setName(info.myNewName);
}
myModel.setAllFacets(newFacets.toArray(new Facet[newFacets.size()]));
}
finally {
myInsideCommit = false;
}
if (myModuleAdded) {
for (Facet facet : toAdd) {
facet.initFacet();
}
}
for (Facet facet : toRemove) {
Disposer.dispose(facet);
}
if (fireEvents) {
for (Facet facet : toAdd) {
publisher.facetAdded(facet);
}
for (Facet facet : toRemove) {
publisher.facetRemoved(facet);
}
for (FacetRenameInfo info : toRename) {
publisher.facetRenamed(info.myFacet, info.myOldName);
}
}
for (Facet facet : toAdd) {
final Module module = facet.getModule();
if (!module.equals(myModule)) {
LOG.error(facet + " is created for module " + module + " but added to module " + myModule);
}
final FacetType<?,?> type = facet.getType();
if (type.isOnlyOneFacetAllowed()) {
if (type.getUnderlyingFacetType() == null) {
final Collection<?> facets = getFacetsByType(type.getId());
if (facets.size() > 1) {
LOG.error("Only one '" + type.getPresentableName() + "' facet per module allowed, but " + facets.size() + " facets found in module '" +
myModule.getName() + "'");
}
}
else {
final Facet underlyingFacet = facet.getUnderlyingFacet();
LOG.assertTrue(underlyingFacet != null, "Underlying facet is not specified for '" + facet.getName() + "'");
final Collection<?> facets = getFacetsByType(underlyingFacet, type.getId());
if (facets.size() > 1) {
LOG.error("Only one '" + type.getPresentableName() + "' facet per parent facet allowed, but " + facets.size() + " sub-facets found in facet " + underlyingFacet.getName());
}
}
}
}
}
@Override
public void projectOpened() {
}
@Override
public void projectClosed() {
}
@Override
public void moduleAdded() {
if (myModuleAdded) return;
for (Facet facet : getAllFacets()) {
facet.initFacet();
}
myModuleAdded = true;
}
@Override
@NonNls
@NotNull
public String getComponentName() {
return COMPONENT_NAME;
}
@Override
public void initComponent() {
}
@Override
public void disposeComponent() {
}
private static class FacetManagerModel extends FacetModelBase {
private Facet[] myAllFacets = Facet.EMPTY_ARRAY;
@Override
@NotNull
public Facet[] getAllFacets() {
return myAllFacets;
}
public void setAllFacets(final Facet[] allFacets) {
myAllFacets = allFacets;
facetsChanged();
}
}
private static class FacetRenameInfo {
private final Facet myFacet;
private final String myOldName;
private final String myNewName;
public FacetRenameInfo(final Facet facet, final String oldName, final String newName) {
myFacet = facet;
myOldName = oldName;
myNewName = newName;
}
}
}