From ca34c65b09f9e1a87991ff48f151ada915a35f7f Mon Sep 17 00:00:00 2001 From: nik Date: Thu, 11 May 2017 14:58:42 +0300 Subject: [PATCH] project model: store configurations of facets imported from external sources in a separate component This is needed to store such configurations in a separate file (IDEA-171877) while saving configurations of regular manually created facets in *.iml file. --- .../model/serialization/facet/FacetState.java | 10 ++ .../FacetFromExternalSourcesStorage.java | 70 ++++++++++ .../com/intellij/facet/FacetManagerImpl.java | 126 ++++++++++++++---- .../invalid/InvalidFacetConfiguration.java | 2 + .../src/META-INF/LangExtensions.xml | 1 + 5 files changed, 184 insertions(+), 25 deletions(-) create mode 100644 platform/lang-impl/src/com/intellij/facet/FacetFromExternalSourcesStorage.java diff --git a/jps/model-serialization/src/org/jetbrains/jps/model/serialization/facet/FacetState.java b/jps/model-serialization/src/org/jetbrains/jps/model/serialization/facet/FacetState.java index 46f699b7aa62..69d0f7aa50a7 100644 --- a/jps/model-serialization/src/org/jetbrains/jps/model/serialization/facet/FacetState.java +++ b/jps/model-serialization/src/org/jetbrains/jps/model/serialization/facet/FacetState.java @@ -32,6 +32,7 @@ import java.util.ArrayList; public class FacetState { private String myFacetType; private String myName; + private String myExternalSystemId; private Element myConfiguration; private List mySubFacets = new ArrayList<>(); @@ -50,6 +51,11 @@ public class FacetState { return myConfiguration; } + @Attribute("external-system-id") + public String getExternalSystemId() { + return myExternalSystemId; + } + @Property(surroundWithTag = false) @AbstractCollection(surroundWithTag = false) public List getSubFacets() { @@ -71,4 +77,8 @@ public class FacetState { public void setFacetType(final String type) { myFacetType = type; } + + public void setExternalSystemId(String externalSystemId) { + myExternalSystemId = externalSystemId; + } } diff --git a/platform/lang-impl/src/com/intellij/facet/FacetFromExternalSourcesStorage.java b/platform/lang-impl/src/com/intellij/facet/FacetFromExternalSourcesStorage.java new file mode 100644 index 000000000000..042651dfdd90 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/facet/FacetFromExternalSourcesStorage.java @@ -0,0 +1,70 @@ +/* + * Copyright 2000-2017 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.openapi.components.PersistentStateComponent; +import com.intellij.openapi.components.State; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleServiceManager; +import com.intellij.openapi.roots.ProjectModelElement; +import com.intellij.openapi.roots.ProjectModelExternalSource; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.xmlb.XmlSerializerUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.jps.model.serialization.facet.FacetManagerState; + +/** + * @author nik + */ +@State(name = "FacetsFromExternalSources") +public class FacetFromExternalSourcesStorage implements PersistentStateComponent, ProjectModelElement { + private FacetManagerState myState = new FacetManagerState(); + private Module myModule; + + public static FacetFromExternalSourcesStorage getInstance(Module module) { + return ModuleServiceManager.getService(module, FacetFromExternalSourcesStorage.class); + } + + public FacetFromExternalSourcesStorage(@NotNull Module module) { + myModule = module; + } + + @Override + @NotNull + public FacetManagerState getState() { + myState = ((FacetManagerImpl)FacetManager.getInstance(myModule)).saveState(FacetManagerImpl.getImportedFacetPredicate()); + return myState; + } + + @NotNull + FacetManagerState getLoadedState() { + return myState; + } + + @Nullable + @Override + public ProjectModelExternalSource getExternalSource() { + //If different facets came from different external sources it actually doesn't matter which source is returned from this method, + // it's enough to return any non-null value to serialize this component into a separate file. + return ContainerUtil.getFirstItem(((FacetManagerImpl)FacetManager.getInstance(myModule)).getExternalSources()); + } + + @Override + public void loadState(FacetManagerState state) { + XmlSerializerUtil.copyBean(state, myState); + } +} diff --git a/platform/lang-impl/src/com/intellij/facet/FacetManagerImpl.java b/platform/lang-impl/src/com/intellij/facet/FacetManagerImpl.java index 93306a1b2cb3..84484cc296da 100644 --- a/platform/lang-impl/src/com/intellij/facet/FacetManagerImpl.java +++ b/platform/lang-impl/src/com/intellij/facet/FacetManagerImpl.java @@ -16,6 +16,7 @@ package com.intellij.facet; +import com.google.common.base.Predicates; import com.intellij.facet.impl.FacetLoadingErrorDescription; import com.intellij.facet.impl.FacetModelBase; import com.intellij.facet.impl.FacetModelImpl; @@ -33,10 +34,13 @@ 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.roots.ExternalProjectSystemRegistry; import com.intellij.openapi.roots.ProjectModelExternalSource; import com.intellij.openapi.updateSettings.impl.pluginsAdvertisement.UnknownFeaturesCollector; import com.intellij.openapi.util.*; +import com.intellij.openapi.util.registry.Registry; import com.intellij.openapi.util.text.StringUtil; +import com.intellij.util.containers.ContainerUtil; import com.intellij.util.messages.MessageBus; import org.jdom.Element; import org.jetbrains.annotations.NonNls; @@ -46,6 +50,7 @@ import org.jetbrains.jps.model.serialization.facet.FacetManagerState; import org.jetbrains.jps.model.serialization.facet.FacetState; import java.util.*; +import java.util.function.Predicate; /** * @author nik @@ -239,39 +244,75 @@ public class FacetManagerImpl extends FacetManager implements ModuleComponent, P } } - private void addFacet(final FacetType 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())) { + private , C extends FacetConfiguration> void addFacet(final FacetType type, final FacetState state, final Facet underlyingFacet, + final ModifiableFacetModel model) throws InvalidDataException { + Collection facetsOfThisType = underlyingFacet == null ? model.getFacetsByType(type.getId()) + : model.getFacetsByType(underlyingFacet, type.getId()); + if (type.isOnlyOneFacetAllowed() && !facetsOfThisType.isEmpty() && facetsOfThisType.stream().anyMatch(f -> !f.getName().equals(state.getName()))) { 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); + + F facet = null; + if (!facetsOfThisType.isEmpty() && Registry.is("store.imported.project.elements.separately")) { + facet = facetsOfThisType.stream().filter(f -> f.getName().equals(state.getName())).findFirst().orElse(null); + if (facet != null) { + Element newConfiguration = state.getConfiguration(); + //There may be two states of the same facet if configuration is stored in one file but configuration of its sub-facet is stored in another. + //In that case only one of the states will have the real configuration and we'll merge them here. + if (newConfiguration != null) { + FacetUtil.loadFacetConfiguration(facet.getConfiguration(), newConfiguration); + } + } + } + + if (facet == null) { + final C configuration = type.createDefaultConfiguration(); + final Element config = state.getConfiguration(); + FacetUtil.loadFacetConfiguration(configuration, config); + String name = state.getName(); + facet = createFacet(type, name, configuration, underlyingFacet); + if (facet instanceof JDOMExternalizable) { + //todo[nik] remove + ((JDOMExternalizable)facet).readExternal(config); + } + String externalSystemId = state.getExternalSystemId(); + if (externalSystemId != null) { + facet.setExternalSource(ExternalProjectSystemRegistry.getInstance().getSourceById(externalSystemId)); + } + model.addFacet(facet); } - model.addFacet(facet); addFacets(state.getSubFacets(), facet, model); } @Override public void loadState(final FacetManagerState state) { ModifiableFacetModel model = new FacetModelImpl(this); + FacetManagerState importedFacetsState = FacetFromExternalSourcesStorage.getInstance(myModule).getLoadedState(); - addFacets(state.getFacets(), null, model); + addFacets(ContainerUtil.concat(state.getFacets(), importedFacetsState.getFacets()), null, model); commit(model, false); } @Override + @NotNull public FacetManagerState getState() { + return saveState(getImportedFacetPredicate().negate()); + } + + @NotNull + static Predicate getImportedFacetPredicate() { + if (Registry.is("store.imported.project.elements.separately")) { + //we can store imported facets in a separate component only if that component will be stored separately, otherwise we will get modified *.iml files + return facet -> facet.getExternalSource() != null; + } + return Predicates.alwaysFalse(); + } + + @NotNull + FacetManagerState saveState(Predicate filter) { FacetManagerState managerState = new FacetManagerState(); final Facet[] facets = getSortedFacets(); @@ -280,17 +321,11 @@ public class FacetManagerImpl extends FacetManager implements ModuleComponent, P states.put(null, managerState.getFacets()); for (Facet facet : facets) { + if (!filter.test(facet)) continue; final Facet underlyingFacet = facet.getUnderlyingFacet(); - final List 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()); + FacetState facetState = createFacetState(facet); + if (!(facet instanceof InvalidFacet)) { final Element config; try { FacetConfiguration configuration = facet.getConfiguration(); @@ -306,12 +341,44 @@ public class FacetManagerImpl extends FacetManager implements ModuleComponent, P facetState.setConfiguration(config); } - parent.add(facetState); + getOrCreateTargetFacetList(underlyingFacet, states).add(facetState); states.put(facet, facetState.getSubFacets()); } return managerState; } + /** + * Configuration of some facet may be stored in one file, but configuration of its underlying facet may be stored in another file. For such + * sub-facets we create parent elements which don't store configuration but only name and type. + */ + private static List getOrCreateTargetFacetList(Facet underlyingFacet, Map> states) { + List facetStateList = states.get(underlyingFacet); + if (facetStateList == null) { + FacetState state = createFacetState(underlyingFacet); + getOrCreateTargetFacetList(underlyingFacet.getUnderlyingFacet(), states).add(state); + facetStateList = state.getSubFacets(); + states.put(underlyingFacet, facetStateList); + } + return facetStateList; + } + + private static FacetState createFacetState(Facet facet) { + if (facet instanceof InvalidFacet) { + return ((InvalidFacet)facet).getConfiguration().getFacetState(); + } + else { + FacetState facetState = new FacetState(); + ProjectModelExternalSource externalSource = facet.getExternalSource(); + if (externalSource != null && Registry.is("store.imported.project.elements.separately")) { + //set this attribute only if such facets will be stored separately, otherwise we will get modified *.iml files + facetState.setExternalSystemId(externalSource.getId()); + } + facetState.setFacetType(facet.getType().getStringId()); + facetState.setName(facet.getName()); + return facetState; + } + } + public void commit(final ModifiableFacetModel model) { ApplicationManager.getApplication().assertWriteAccessAllowed(); @@ -423,6 +490,10 @@ public class FacetManagerImpl extends FacetManager implements ModuleComponent, P facet.setExternalSource(externalSource); } + Set getExternalSources() { + return myModel.myExternalSources; + } + @Override public void moduleAdded() { if (myModuleAdded) return; @@ -442,6 +513,7 @@ public class FacetManagerImpl extends FacetManager implements ModuleComponent, P private static class FacetManagerModel extends FacetModelBase { private Facet[] myAllFacets = Facet.EMPTY_ARRAY; + private Set myExternalSources = new LinkedHashSet<>(); @Override @NotNull @@ -450,6 +522,10 @@ public class FacetManagerImpl extends FacetManager implements ModuleComponent, P } public void setAllFacets(final Facet[] allFacets) { + myExternalSources.clear(); + for (Facet facet : allFacets) { + ContainerUtil.addIfNotNull(myExternalSources, facet.getExternalSource()); + } myAllFacets = allFacets; facetsChanged(); } diff --git a/platform/lang-impl/src/com/intellij/facet/impl/invalid/InvalidFacetConfiguration.java b/platform/lang-impl/src/com/intellij/facet/impl/invalid/InvalidFacetConfiguration.java index 3f89a93735c5..a88e760f3718 100644 --- a/platform/lang-impl/src/com/intellij/facet/impl/invalid/InvalidFacetConfiguration.java +++ b/platform/lang-impl/src/com/intellij/facet/impl/invalid/InvalidFacetConfiguration.java @@ -16,6 +16,7 @@ package com.intellij.facet.impl.invalid; import com.intellij.facet.FacetConfiguration; +import org.jetbrains.annotations.NotNull; import org.jetbrains.jps.model.serialization.facet.FacetState; import com.intellij.facet.ui.FacetEditorContext; import com.intellij.facet.ui.FacetEditorTab; @@ -36,6 +37,7 @@ public class InvalidFacetConfiguration implements FacetConfiguration { myErrorMessage = errorMessage; } + @NotNull public FacetState getFacetState() { return myFacetState; } diff --git a/platform/platform-resources/src/META-INF/LangExtensions.xml b/platform/platform-resources/src/META-INF/LangExtensions.xml index 73c200780741..da94778e79a9 100644 --- a/platform/platform-resources/src/META-INF/LangExtensions.xml +++ b/platform/platform-resources/src/META-INF/LangExtensions.xml @@ -254,6 +254,7 @@ +