external sytem: do not run import after new project attachment for other linked projects
[idea/community.git] / platform / external-system-api / src / com / intellij / openapi / externalSystem / settings / AbstractExternalSystemSettings.java
1 /*
2  * Copyright 2000-2013 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 package com.intellij.openapi.externalSystem.settings;
17
18 import com.intellij.openapi.Disposable;
19 import com.intellij.openapi.project.Project;
20 import com.intellij.openapi.util.Disposer;
21 import com.intellij.util.containers.ContainerUtilRt;
22 import com.intellij.util.messages.Topic;
23 import org.jetbrains.annotations.NotNull;
24 import org.jetbrains.annotations.Nullable;
25
26 import java.util.*;
27
28 /**
29  * Common base class for external system settings. Defines a minimal api which is necessary for the common external system
30  * support codebase.
31  * <p/>
32  * <b>Note:</b> non-abstract sub-classes of this class are expected to be marked by {@link State} annotation configured as necessary.
33  *  
34  * @author Denis Zhdanov
35  * @since 4/3/13 4:04 PM
36  */
37 public abstract class AbstractExternalSystemSettings<
38   SS extends AbstractExternalSystemSettings<SS, PS, L>,
39   PS extends ExternalProjectSettings,
40   L extends ExternalSystemSettingsListener<PS>>
41   implements Disposable
42 {
43
44   @NotNull private final Topic<L> myChangesTopic;
45   
46   private Project  myProject;
47
48   @NotNull private final Map<String/* project path */, PS> myLinkedProjectsSettings = ContainerUtilRt.newHashMap();
49   
50   @NotNull private final Map<String/* project path */, PS> myLinkedProjectsSettingsView
51     = Collections.unmodifiableMap(myLinkedProjectsSettings);
52
53   protected AbstractExternalSystemSettings(@NotNull Topic<L> topic, @NotNull Project project) {
54     myChangesTopic = topic;
55     myProject = project;
56     Disposer.register(project, this);
57   }
58
59   @Override
60   public void dispose() {
61     myProject = null;
62   }
63
64   @NotNull
65   public Project getProject() {
66     return myProject;
67   }
68
69   /**
70    * Every time particular external system setting is changed corresponding message is sent via ide
71    * <a href="http://confluence.jetbrains.com/display/IDEADEV/IntelliJ+IDEA+Messaging+infrastructure">messaging sub-system</a>.
72    * The problem is that every external system implementation defines it's own topic/listener pair. Listener interface is derived
73    * from the common {@link ExternalSystemSettingsListener} interface and is specific to external sub-system implementation.
74    * However, it's possible that a client wants to perform particular actions based only on {@link ExternalSystemSettingsListener}
75    * facilities. There is no way for such external system-agnostic client to create external system-specific listener
76    * implementation then.
77    * <p/>
78    * That's why this method allows to wrap given 'generic listener' into external system-specific one.
79    *
80    * @param listener  target generic listener to wrap to external system-specific implementation
81    */
82   public abstract void subscribe(@NotNull ExternalSystemSettingsListener<PS> listener);
83
84   public void copyFrom(@NotNull SS settings) {
85     for (PS projectSettings : settings.getLinkedProjectsSettings()) {
86       myLinkedProjectsSettings.put(projectSettings.getExternalProjectPath(), projectSettings);
87     }
88     copyExtraSettingsFrom(settings);
89   }
90   
91   protected abstract void copyExtraSettingsFrom(@NotNull SS settings);
92   
93   @SuppressWarnings("unchecked")
94   @NotNull
95   public Collection<PS> getLinkedProjectsSettings() {
96     return myLinkedProjectsSettingsView.values();
97   }
98
99   @Nullable
100   public PS getLinkedProjectSettings(@NotNull String linkedProjectPath) {
101     PS ps = myLinkedProjectsSettings.get(linkedProjectPath);
102     if(ps == null) {
103       for (PS ps1 : myLinkedProjectsSettings.values()) {
104         for (String modulePath : ps1.getModules()) {
105           if(linkedProjectPath.equals(modulePath)) return ps1;
106         }
107       }
108     }
109     return ps;
110   }
111
112   public void linkProject(@NotNull PS settings) throws IllegalArgumentException {
113     PS existing = getLinkedProjectSettings(settings.getExternalProjectPath());
114     if (existing != null) {
115       throw new IllegalArgumentException(String.format(
116         "Can't link external project '%s'. Reason: it's already registered at the current ide project",
117         settings.getExternalProjectPath()
118       ));
119     }
120     myLinkedProjectsSettings.put(settings.getExternalProjectPath(), settings);
121     getPublisher().onProjectsLinked(Collections.singleton(settings));
122   }
123   
124   /**
125    * Un-links given external project from the current ide project.
126    * 
127    * @param linkedProjectPath  path of external project to be unlinked
128    * @return                   <code>true</code> if there was an external project with the given config path linked to the current
129    *                           ide project;
130    *                           <code>false</code> otherwise
131    */
132   public boolean unlinkExternalProject(@NotNull String linkedProjectPath) {
133     PS removed = myLinkedProjectsSettings.remove(linkedProjectPath);
134     if (removed == null) {
135       return false;
136     }
137     
138     getPublisher().onProjectsUnlinked(Collections.singleton(linkedProjectPath));
139     return true;
140   }
141
142   public void setLinkedProjectsSettings(@NotNull Collection<PS> settings) {
143     List<PS> added = ContainerUtilRt.newArrayList();
144     Map<String, PS> removed = ContainerUtilRt.newHashMap(myLinkedProjectsSettings);
145     myLinkedProjectsSettings.clear();
146     for (PS current : settings) {
147       myLinkedProjectsSettings.put(current.getExternalProjectPath(), current);
148     }
149     
150     for (PS current : settings) {
151       PS old = removed.remove(current.getExternalProjectPath());
152       if (old == null) {
153         added.add(current);
154       }
155       else {
156         if (current.isUseAutoImport() != old.isUseAutoImport()) {
157           getPublisher().onUseAutoImportChange(current.isUseAutoImport(), current.getExternalProjectPath());
158         }
159         checkSettings(old, current);
160       }
161     }
162     if (!added.isEmpty()) {
163       getPublisher().onProjectsLinked(added);
164     }
165     if (!removed.isEmpty()) {
166       getPublisher().onProjectsUnlinked(removed.keySet());
167     }
168   }
169
170   /**
171    * Is assumed to check if given old settings external system-specific state differs from the given new one
172    * and {@link #getPublisher() notify} listeners in case of the positive answer.
173    * 
174    * @param old      old settings state
175    * @param current  current settings state
176    */
177   protected abstract void checkSettings(@NotNull PS old, @NotNull PS current);
178
179   @NotNull
180   public Topic<L> getChangesTopic() {
181     return myChangesTopic;
182   }
183
184   @NotNull
185   public L getPublisher() {
186     return myProject.getMessageBus().syncPublisher(myChangesTopic);
187   }
188
189   protected void fillState(@NotNull State<PS> state) {
190     state.setLinkedExternalProjectsSettings(ContainerUtilRt.newTreeSet(myLinkedProjectsSettings.values()));
191   }
192
193   @SuppressWarnings("unchecked")
194   protected void loadState(@NotNull State<PS> state) {
195     Set<PS> settings = state.getLinkedExternalProjectsSettings();
196     if (settings != null) {
197       myLinkedProjectsSettings.clear();
198       for (PS projectSettings : settings) {
199         myLinkedProjectsSettings.put(projectSettings.getExternalProjectPath(), projectSettings);
200       }
201     }
202   }
203
204   public interface State<S> {
205     
206     Set<S> getLinkedExternalProjectsSettings();
207
208     void setLinkedExternalProjectsSettings(Set<S> settings);
209   }
210 }