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