blob: f240681b82e62ab0436e3e4f0e61e0b1948b6c84 [file] [log] [blame]
/*
* Copyright 2000-2013 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.openapi.externalSystem.settings;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.util.containers.ContainerUtilRt;
import com.intellij.util.messages.Topic;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* Common base class for external system settings. Defines a minimal api which is necessary for the common external system
* support codebase.
* <p/>
* <b>Note:</b> non-abstract sub-classes of this class are expected to be marked by {@link State} annotation configured as necessary.
*
* @author Denis Zhdanov
* @since 4/3/13 4:04 PM
*/
public abstract class AbstractExternalSystemSettings<
SS extends AbstractExternalSystemSettings<SS, PS, L>,
PS extends ExternalProjectSettings,
L extends ExternalSystemSettingsListener<PS>>
implements Disposable
{
@NotNull private final Topic<L> myChangesTopic;
private Project myProject;
@NotNull private final Map<String/* project path */, PS> myLinkedProjectsSettings = ContainerUtilRt.newHashMap();
@NotNull private final Map<String/* project path */, PS> myLinkedProjectsSettingsView
= Collections.unmodifiableMap(myLinkedProjectsSettings);
protected AbstractExternalSystemSettings(@NotNull Topic<L> topic, @NotNull Project project) {
myChangesTopic = topic;
myProject = project;
Disposer.register(project, this);
}
@Override
public void dispose() {
myProject = null;
}
@NotNull
public Project getProject() {
return myProject;
}
/**
* Every time particular external system setting is changed corresponding message is sent via ide
* <a href="http://confluence.jetbrains.com/display/IDEADEV/IntelliJ+IDEA+Messaging+infrastructure">messaging sub-system</a>.
* The problem is that every external system implementation defines it's own topic/listener pair. Listener interface is derived
* from the common {@link ExternalSystemSettingsListener} interface and is specific to external sub-system implementation.
* However, it's possible that a client wants to perform particular actions based only on {@link ExternalSystemSettingsListener}
* facilities. There is no way for such external system-agnostic client to create external system-specific listener
* implementation then.
* <p/>
* That's why this method allows to wrap given 'generic listener' into external system-specific one.
*
* @param listener target generic listener to wrap to external system-specific implementation
*/
public abstract void subscribe(@NotNull ExternalSystemSettingsListener<PS> listener);
public void copyFrom(@NotNull SS settings) {
myLinkedProjectsSettings.clear();
for (PS projectSettings : settings.getLinkedProjectsSettings()) {
myLinkedProjectsSettings.put(projectSettings.getExternalProjectPath(), projectSettings);
}
copyExtraSettingsFrom(settings);
}
protected abstract void copyExtraSettingsFrom(@NotNull SS settings);
@SuppressWarnings("unchecked")
@NotNull
public Collection<PS> getLinkedProjectsSettings() {
return myLinkedProjectsSettingsView.values();
}
@Nullable
public PS getLinkedProjectSettings(@NotNull String linkedProjectPath) {
PS ps = myLinkedProjectsSettings.get(linkedProjectPath);
if(ps == null) {
for (PS ps1 : myLinkedProjectsSettings.values()) {
for (String modulePath : ps1.getModules()) {
if(linkedProjectPath.equals(modulePath)) return ps1;
}
}
}
if (ps == null) {
ps = myLinkedProjectsSettings.get(FileUtil.toSystemIndependentName(linkedProjectPath));
}
return ps;
}
public void linkProject(@NotNull PS settings) throws IllegalArgumentException {
PS existing = getLinkedProjectSettings(settings.getExternalProjectPath());
if (existing != null) {
throw new IllegalArgumentException(String.format(
"Can't link external project '%s'. Reason: it's already registered at the current ide project",
settings.getExternalProjectPath()
));
}
myLinkedProjectsSettings.put(settings.getExternalProjectPath(), settings);
getPublisher().onProjectsLinked(Collections.singleton(settings));
}
/**
* Un-links given external project from the current ide project.
*
* @param linkedProjectPath path of external project to be unlinked
* @return <code>true</code> if there was an external project with the given config path linked to the current
* ide project;
* <code>false</code> otherwise
*/
public boolean unlinkExternalProject(@NotNull String linkedProjectPath) {
PS removed = myLinkedProjectsSettings.remove(linkedProjectPath);
if (removed == null) {
return false;
}
getPublisher().onProjectsUnlinked(Collections.singleton(linkedProjectPath));
return true;
}
public void setLinkedProjectsSettings(@NotNull Collection<PS> settings) {
List<PS> added = ContainerUtilRt.newArrayList();
Map<String, PS> removed = ContainerUtilRt.newHashMap(myLinkedProjectsSettings);
myLinkedProjectsSettings.clear();
for (PS current : settings) {
myLinkedProjectsSettings.put(current.getExternalProjectPath(), current);
}
for (PS current : settings) {
PS old = removed.remove(current.getExternalProjectPath());
if (old == null) {
added.add(current);
}
else {
if (current.isUseAutoImport() != old.isUseAutoImport()) {
getPublisher().onUseAutoImportChange(current.isUseAutoImport(), current.getExternalProjectPath());
}
checkSettings(old, current);
}
}
if (!added.isEmpty()) {
getPublisher().onProjectsLinked(added);
}
if (!removed.isEmpty()) {
getPublisher().onProjectsUnlinked(removed.keySet());
}
}
/**
* Is assumed to check if given old settings external system-specific state differs from the given new one
* and {@link #getPublisher() notify} listeners in case of the positive answer.
*
* @param old old settings state
* @param current current settings state
*/
protected abstract void checkSettings(@NotNull PS old, @NotNull PS current);
@NotNull
public Topic<L> getChangesTopic() {
return myChangesTopic;
}
@NotNull
public L getPublisher() {
return myProject.getMessageBus().syncPublisher(myChangesTopic);
}
protected void fillState(@NotNull State<PS> state) {
state.setLinkedExternalProjectsSettings(ContainerUtilRt.newTreeSet(myLinkedProjectsSettings.values()));
}
@SuppressWarnings("unchecked")
protected void loadState(@NotNull State<PS> state) {
Set<PS> settings = state.getLinkedExternalProjectsSettings();
if (settings != null) {
myLinkedProjectsSettings.clear();
for (PS projectSettings : settings) {
myLinkedProjectsSettings.put(projectSettings.getExternalProjectPath(), projectSettings);
}
}
}
public interface State<S> {
Set<S> getLinkedExternalProjectsSettings();
void setLinkedExternalProjectsSettings(Set<S> settings);
}
}