blob: 3547d3847c2547b0304f43b547a853380e1cb8ef [file] [log] [blame]
/*
* Copyright 2000-2014 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.packaging.impl.artifacts;
import com.intellij.compiler.server.BuildManager;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.Result;
import com.intellij.openapi.application.WriteAction;
import com.intellij.openapi.components.*;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.module.ProjectLoadingErrorsNotifier;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ex.ProjectRootManagerEx;
import com.intellij.openapi.updateSettings.impl.pluginsAdvertisement.UnknownFeaturesCollector;
import com.intellij.openapi.util.ModificationTracker;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.SimpleModificationTracker;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFileManager;
import com.intellij.packaging.artifacts.*;
import com.intellij.packaging.elements.*;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.xmlb.SkipDefaultValuesSerializationFilters;
import com.intellij.util.xmlb.XmlSerializer;
import gnu.trove.THashSet;
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.artifact.ArtifactManagerState;
import org.jetbrains.jps.model.serialization.artifact.ArtifactPropertiesState;
import org.jetbrains.jps.model.serialization.artifact.ArtifactState;
import java.util.*;
/**
* @author nik
*/
@State(
name = ArtifactManagerImpl.COMPONENT_NAME,
storages = {
@Storage(file = StoragePathMacros.PROJECT_FILE),
@Storage(file = StoragePathMacros.PROJECT_CONFIG_DIR + "/artifacts/", scheme = StorageScheme.DIRECTORY_BASED,
stateSplitter = ArtifactManagerStateSplitter.class)
}
)
public class ArtifactManagerImpl extends ArtifactManager implements ProjectComponent, PersistentStateComponent<ArtifactManagerState> {
private static final Logger LOG = Logger.getInstance("#com.intellij.packaging.impl.artifacts.ArtifactManagerImpl");
@NonNls public static final String COMPONENT_NAME = "ArtifactManager";
@NonNls public static final String PACKAGING_ELEMENT_NAME = "element";
@NonNls public static final String TYPE_ID_ATTRIBUTE = "id";
private final ArtifactManagerModel myModel;
private final Project myProject;
private final DefaultPackagingElementResolvingContext myResolvingContext;
private boolean myInsideCommit = false;
private boolean myLoaded;
private final SimpleModificationTracker myModificationTracker = new SimpleModificationTracker();
private final Map<String, LocalFileSystem.WatchRequest> myWatchedOutputs = new HashMap<String, LocalFileSystem.WatchRequest>();
public ArtifactManagerImpl(Project project) {
myProject = project;
myModel = new ArtifactManagerModel();
myResolvingContext = new DefaultPackagingElementResolvingContext(myProject);
((ArtifactPointerManagerImpl)ArtifactPointerManager.getInstance(project)).setArtifactManager(this);
}
@NotNull
public Artifact[] getArtifacts() {
return myModel.getArtifacts();
}
public Artifact findArtifact(@NotNull String name) {
return myModel.findArtifact(name);
}
@NotNull
public Artifact getArtifactByOriginal(@NotNull Artifact artifact) {
return myModel.getArtifactByOriginal(artifact);
}
@NotNull
public Artifact getOriginalArtifact(@NotNull Artifact artifact) {
return myModel.getOriginalArtifact(artifact);
}
@NotNull
public Collection<? extends Artifact> getArtifactsByType(@NotNull ArtifactType type) {
return myModel.getArtifactsByType(type);
}
@Override
public List<? extends Artifact> getAllArtifactsIncludingInvalid() {
return myModel.getAllArtifactsIncludingInvalid();
}
public ArtifactManagerState getState() {
final ArtifactManagerState state = new ArtifactManagerState();
for (Artifact artifact : getAllArtifactsIncludingInvalid()) {
final ArtifactState artifactState;
if (artifact instanceof InvalidArtifact) {
artifactState = ((InvalidArtifact)artifact).getState();
}
else {
artifactState = new ArtifactState();
artifactState.setBuildOnMake(artifact.isBuildOnMake());
artifactState.setName(artifact.getName());
artifactState.setOutputPath(artifact.getOutputPath());
artifactState.setRootElement(serializePackagingElement(artifact.getRootElement()));
artifactState.setArtifactType(artifact.getArtifactType().getId());
for (ArtifactPropertiesProvider provider : artifact.getPropertiesProviders()) {
final ArtifactPropertiesState propertiesState = serializeProperties(provider, artifact.getProperties(provider));
if (propertiesState != null) {
artifactState.getPropertiesList().add(propertiesState);
}
}
Collections.sort(artifactState.getPropertiesList(), new Comparator<ArtifactPropertiesState>() {
public int compare(ArtifactPropertiesState o1, ArtifactPropertiesState o2) {
return o1.getId().compareTo(o2.getId());
}
});
}
state.getArtifacts().add(artifactState);
}
return state;
}
@Nullable
private static <S> ArtifactPropertiesState serializeProperties(ArtifactPropertiesProvider provider, ArtifactProperties<S> properties) {
final ArtifactPropertiesState state = new ArtifactPropertiesState();
state.setId(provider.getId());
final Element options = new Element("options");
XmlSerializer.serializeInto(properties.getState(), options, new SkipDefaultValuesSerializationFilters());
if (options.getContent().isEmpty() && options.getAttributes().isEmpty()) return null;
state.setOptions(options);
return state;
}
private static Element serializePackagingElement(PackagingElement<?> packagingElement) {
Element element = new Element(PACKAGING_ELEMENT_NAME);
element.setAttribute(TYPE_ID_ATTRIBUTE, packagingElement.getType().getId());
final Object bean = packagingElement.getState();
if (bean != null) {
XmlSerializer.serializeInto(bean, element, new SkipDefaultValuesSerializationFilters());
}
if (packagingElement instanceof CompositePackagingElement) {
for (PackagingElement<?> child : ((CompositePackagingElement<?>)packagingElement).getChildren()) {
element.addContent(serializePackagingElement(child));
}
}
return element;
}
private <T> PackagingElement<T> deserializeElement(Element element) throws UnknownPackagingElementTypeException {
final String id = element.getAttributeValue(TYPE_ID_ATTRIBUTE);
PackagingElementType<?> type = PackagingElementFactory.getInstance().findElementType(id);
if (type == null) {
throw new UnknownPackagingElementTypeException(id);
}
PackagingElement<T> packagingElement = (PackagingElement<T>)type.createEmpty(myProject);
T state = packagingElement.getState();
if (state != null) {
XmlSerializer.deserializeInto(state, element);
packagingElement.loadState(state);
}
final List children = element.getChildren(PACKAGING_ELEMENT_NAME);
//noinspection unchecked
for (Element child : (List<? extends Element>)children) {
((CompositePackagingElement<?>)packagingElement).addOrFindChild(deserializeElement(child));
}
return packagingElement;
}
public void loadState(ArtifactManagerState managerState) {
final List<ArtifactImpl> artifacts = new ArrayList<ArtifactImpl>();
for (ArtifactState state : managerState.getArtifacts()) {
artifacts.add(loadArtifact(state));
}
if (myLoaded) {
final ArtifactModelImpl model = new ArtifactModelImpl(this, artifacts);
doCommit(model);
}
else {
myModel.setArtifactsList(artifacts);
myLoaded = true;
}
}
private ArtifactImpl loadArtifact(ArtifactState state) {
ArtifactType type = ArtifactType.findById(state.getArtifactType());
if (type == null) {
return createInvalidArtifact(state, "Unknown artifact type: " + state.getArtifactType());
}
final Element element = state.getRootElement();
final String artifactName = state.getName();
final CompositePackagingElement<?> rootElement;
if (element != null) {
try {
rootElement = (CompositePackagingElement<?>)deserializeElement(element);
}
catch (UnknownPackagingElementTypeException e) {
return createInvalidArtifact(state, "Unknown element: " + e.getTypeId());
}
}
else {
rootElement = type.createRootElement(artifactName);
}
final ArtifactImpl artifact = new ArtifactImpl(artifactName, type, state.isBuildOnMake(), rootElement, state.getOutputPath());
final List<ArtifactPropertiesState> propertiesList = state.getPropertiesList();
for (ArtifactPropertiesState propertiesState : propertiesList) {
final ArtifactPropertiesProvider provider = ArtifactPropertiesProvider.findById(propertiesState.getId());
if (provider != null) {
deserializeProperties(artifact.getProperties(provider), propertiesState);
}
else {
return createInvalidArtifact(state, "Unknown artifact properties: " + propertiesState.getId());
}
}
return artifact;
}
private InvalidArtifact createInvalidArtifact(ArtifactState state, String errorMessage) {
final InvalidArtifact artifact = new InvalidArtifact(state, errorMessage);
ProjectLoadingErrorsNotifier.getInstance(myProject).registerError(new ArtifactLoadingErrorDescription(myProject, artifact));
UnknownFeaturesCollector.getInstance(myProject).registerUnknownFeature("com.intellij.packaging.artifacts.ArtifactType", state.getArtifactType());
return artifact;
}
private static <S> void deserializeProperties(ArtifactProperties<S> artifactProperties, ArtifactPropertiesState propertiesState) {
final Element options = propertiesState.getOptions();
if (artifactProperties == null || options == null) {
return;
}
final S state = artifactProperties.getState();
if (state != null) {
XmlSerializer.deserializeInto(state, options);
artifactProperties.loadState(state);
}
}
public void disposeComponent() {
LocalFileSystem.getInstance().removeWatchedRoots(myWatchedOutputs.values());
}
@NotNull
public String getComponentName() {
return COMPONENT_NAME;
}
public void initComponent() {
VirtualFileManager.getInstance().addVirtualFileListener(new ArtifactVirtualFileListener(myProject, this), myProject);
updateWatchedRoots();
}
private void updateWatchedRoots() {
Set<String> pathsToRemove = new HashSet<String>(myWatchedOutputs.keySet());
Set<String> toAdd = new HashSet<String>();
for (Artifact artifact : getArtifacts()) {
final String path = artifact.getOutputPath();
if (path != null && path.length() > 0) {
pathsToRemove.remove(path);
if (!myWatchedOutputs.containsKey(path)) {
toAdd.add(path);
}
}
}
List<LocalFileSystem.WatchRequest> requestsToRemove = new ArrayList<LocalFileSystem.WatchRequest>();
for (String path : pathsToRemove) {
final LocalFileSystem.WatchRequest request = myWatchedOutputs.remove(path);
ContainerUtil.addIfNotNull(request, requestsToRemove);
}
Set<LocalFileSystem.WatchRequest> newRequests = LocalFileSystem.getInstance().replaceWatchedRoots(requestsToRemove, toAdd, null);
for (LocalFileSystem.WatchRequest request : newRequests) {
myWatchedOutputs.put(request.getRootPath(), request);
}
}
public void projectOpened() {
}
public void projectClosed() {
}
@Override
public Artifact[] getSortedArtifacts() {
return myModel.getSortedArtifacts();
}
@Override
public ModifiableArtifactModel createModifiableModel() {
return new ArtifactModelImpl(this, getArtifactsList());
}
@Override
public PackagingElementResolvingContext getResolvingContext() {
return myResolvingContext;
}
public List<ArtifactImpl> getArtifactsList() {
return myModel.myArtifactsList;
}
public void commit(ArtifactModelImpl artifactModel) {
ApplicationManager.getApplication().assertWriteAccessAllowed();
doCommit(artifactModel);
}
private void doCommit(ArtifactModelImpl artifactModel) {
boolean hasChanges;
LOG.assertTrue(!myInsideCommit, "Recursive commit");
myInsideCommit = true;
try {
final List<ArtifactImpl> allArtifacts = artifactModel.getOriginalArtifacts();
final Set<ArtifactImpl> removed = new THashSet<ArtifactImpl>(myModel.myArtifactsList);
final List<ArtifactImpl> added = new ArrayList<ArtifactImpl>();
final List<Pair<ArtifactImpl, String>> changed = new ArrayList<Pair<ArtifactImpl, String>>();
for (ArtifactImpl artifact : allArtifacts) {
final boolean isAdded = !removed.remove(artifact);
final ArtifactImpl modifiableCopy = artifactModel.getModifiableCopy(artifact);
if (isAdded) {
added.add(artifact);
}
else if (modifiableCopy != null && !modifiableCopy.equals(artifact)) {
final String oldName = artifact.getName();
artifact.copyFrom(modifiableCopy);
changed.add(Pair.create(artifact, oldName));
}
}
myModel.setArtifactsList(allArtifacts);
myModificationTracker.incModificationCount();
final ArtifactListener publisher = myProject.getMessageBus().syncPublisher(TOPIC);
hasChanges = !removed.isEmpty() || !added.isEmpty() || !changed.isEmpty();
ProjectRootManagerEx.getInstanceEx(myProject).mergeRootsChangesDuring(new Runnable() {
public void run() {
for (ArtifactImpl artifact : removed) {
publisher.artifactRemoved(artifact);
}
//it's important to send 'removed' events before 'added'. Otherwise when artifacts are reloaded from xml artifact pointers will be damaged
for (ArtifactImpl artifact : added) {
publisher.artifactAdded(artifact);
}
for (Pair<ArtifactImpl, String> pair : changed) {
publisher.artifactChanged(pair.getFirst(), pair.getSecond());
}
}
});
}
finally {
myInsideCommit = false;
}
updateWatchedRoots();
if (hasChanges) {
BuildManager.getInstance().clearState(myProject);
}
}
public Project getProject() {
return myProject;
}
@NotNull
public Artifact addArtifact(@NotNull final String name, @NotNull final ArtifactType type, final CompositePackagingElement<?> root) {
return new WriteAction<Artifact>() {
protected void run(@NotNull final Result<Artifact> result) {
final ModifiableArtifactModel model = createModifiableModel();
final ModifiableArtifact artifact = model.addArtifact(name, type);
if (root != null) {
artifact.setRootElement(root);
}
model.commit();
result.setResult(artifact);
}
}.execute().getResultObject();
}
@Override
public void addElementsToDirectory(@NotNull Artifact artifact, @NotNull String relativePath, @NotNull PackagingElement<?> element) {
addElementsToDirectory(artifact, relativePath, Collections.singletonList(element));
}
@Override
public void addElementsToDirectory(@NotNull Artifact artifact, @NotNull String relativePath,
@NotNull Collection<? extends PackagingElement<?>> elements) {
final ModifiableArtifactModel model = createModifiableModel();
final CompositePackagingElement<?> root = model.getOrCreateModifiableArtifact(artifact).getRootElement();
PackagingElementFactory.getInstance().getOrCreateDirectory(root, relativePath).addOrFindChildren(elements);
new WriteAction() {
protected void run(@NotNull final Result result) {
model.commit();
}
}.execute();
}
@Override
public ModificationTracker getModificationTracker() {
return myModificationTracker;
}
private static class ArtifactManagerModel extends ArtifactModelBase {
private List<ArtifactImpl> myArtifactsList = new ArrayList<ArtifactImpl>();
private Artifact[] mySortedArtifacts;
public void setArtifactsList(List<ArtifactImpl> artifactsList) {
myArtifactsList = artifactsList;
artifactsChanged();
}
@Override
protected void artifactsChanged() {
super.artifactsChanged();
mySortedArtifacts = null;
}
protected List<? extends Artifact> getArtifactsList() {
return myArtifactsList;
}
public Artifact[] getSortedArtifacts() {
if (mySortedArtifacts == null) {
mySortedArtifacts = getArtifacts().clone();
Arrays.sort(mySortedArtifacts, ARTIFACT_COMPARATOR);
}
return mySortedArtifacts;
}
}
}