358967c172ed135c6f20f9b9f94c22e3d17c2274
[idea/community.git] / java / idea-ui / src / com / intellij / openapi / roots / ui / configuration / ModuleEditor.java
1 /*
2  * Copyright 2000-2015 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.roots.ui.configuration;
17
18 import com.intellij.facet.impl.ProjectFacetsConfigurator;
19 import com.intellij.openapi.Disposable;
20 import com.intellij.openapi.actionSystem.DataProvider;
21 import com.intellij.openapi.actionSystem.LangDataKeys;
22 import com.intellij.openapi.components.ComponentsPackage;
23 import com.intellij.openapi.extensions.ExtensionPointName;
24 import com.intellij.openapi.extensions.Extensions;
25 import com.intellij.openapi.module.Module;
26 import com.intellij.openapi.module.ModuleConfigurationEditor;
27 import com.intellij.openapi.module.impl.ModuleConfigurationStateImpl;
28 import com.intellij.openapi.options.Configurable;
29 import com.intellij.openapi.options.ConfigurationException;
30 import com.intellij.openapi.options.ModuleConfigurableEP;
31 import com.intellij.openapi.project.Project;
32 import com.intellij.openapi.roots.ModifiableRootModel;
33 import com.intellij.openapi.roots.ModuleRootManager;
34 import com.intellij.openapi.roots.ModuleRootModel;
35 import com.intellij.openapi.roots.OrderEntry;
36 import com.intellij.openapi.roots.impl.ModuleRootManagerImpl;
37 import com.intellij.openapi.roots.impl.libraries.LibraryEx;
38 import com.intellij.openapi.roots.impl.libraries.LibraryTableBase;
39 import com.intellij.openapi.roots.libraries.Library;
40 import com.intellij.openapi.roots.libraries.LibraryTable;
41 import com.intellij.openapi.util.registry.Registry;
42 import com.intellij.ui.navigation.History;
43 import com.intellij.ui.navigation.Place;
44 import com.intellij.util.EventDispatcher;
45 import com.intellij.util.containers.ContainerUtil;
46 import org.jetbrains.annotations.NonNls;
47 import org.jetbrains.annotations.NotNull;
48 import org.jetbrains.annotations.Nullable;
49
50 import javax.swing.*;
51 import java.awt.*;
52 import java.lang.reflect.InvocationHandler;
53 import java.lang.reflect.InvocationTargetException;
54 import java.lang.reflect.Method;
55 import java.lang.reflect.Proxy;
56 import java.util.*;
57 import java.util.List;
58
59 /**
60  * @author Eugene Zhuravlev
61  *         Date: Oct 4, 2003
62  *         Time: 6:29:56 PM
63  */
64 @SuppressWarnings({"AssignmentToStaticFieldFromInstanceMethod"})
65 public abstract class ModuleEditor implements Place.Navigator, Disposable {
66   private static final ExtensionPointName<ModuleConfigurableEP> MODULE_CONFIGURABLES = ExtensionPointName.create("com.intellij.moduleConfigurable");
67   public static final String SELECTED_EDITOR_NAME = "selectedEditor";
68
69   private final Project myProject;
70   private JPanel myGenericSettingsPanel;
71   private ModifiableRootModel myModifiableRootModel; // important: in order to correctly update OrderEntries UI use corresponding proxy for the model
72
73   private final ModulesProvider myModulesProvider;
74   private String myName;
75   private final Module myModule;
76
77   protected final List<ModuleConfigurationEditor> myEditors = new ArrayList<ModuleConfigurationEditor>();
78   private ModifiableRootModel myModifiableRootModelProxy;
79
80   private final EventDispatcher<ChangeListener> myEventDispatcher = EventDispatcher.create(ChangeListener.class);
81   @NonNls private static final String METHOD_COMMIT = "commit";
82
83   protected History myHistory;
84
85   public ModuleEditor(Project project, ModulesProvider modulesProvider,
86                       @NotNull Module module) {
87     myProject = project;
88     myModulesProvider = modulesProvider;
89     myModule = module;
90     myName = module.getName();
91   }
92
93   public void init(History history) {
94     myHistory = history;
95
96     for (ModuleConfigurationEditor each : myEditors) {
97       if (each instanceof ModuleElementsEditor) {
98         ((ModuleElementsEditor)each).setHistory(myHistory);
99       }
100     }
101
102     restoreSelectedEditor();
103   }
104
105   public abstract ProjectFacetsConfigurator getFacetsConfigurator();
106
107   protected abstract JComponent createCenterPanel();
108
109   @Nullable
110   public abstract ModuleConfigurationEditor getSelectedEditor();
111
112   public abstract void selectEditor(String displayName);
113
114   protected abstract void restoreSelectedEditor();
115
116   @Nullable
117   public abstract ModuleConfigurationEditor getEditor(@NotNull String displayName);
118
119   protected abstract void disposeCenterPanel();
120
121   public interface ChangeListener extends EventListener {
122     void moduleStateChanged(ModifiableRootModel moduleRootModel);
123   }
124
125   public void addChangeListener(ChangeListener listener) {
126     myEventDispatcher.addListener(listener);
127   }
128
129   public void removeChangeListener(ChangeListener listener) {
130     myEventDispatcher.removeListener(listener);
131   }
132
133   @Nullable
134   public Module getModule() {
135     final Module[] all = myModulesProvider.getModules();
136     for (Module each : all) {
137       if (each == myModule) return myModule;
138     }
139
140     return myModulesProvider.getModule(myName);
141   }
142
143   public ModifiableRootModel getModifiableRootModel() {
144     if (myModifiableRootModel == null) {
145       final Module module = getModule();
146       if (module != null) {
147         myModifiableRootModel = ((ModuleRootManagerImpl)ModuleRootManager.getInstance(module)).getModifiableModel(new UIRootConfigurationAccessor(myProject));
148       }
149     }
150     return myModifiableRootModel;
151   }
152
153   public OrderEntry[] getOrderEntries() {
154     if (myModifiableRootModel == null) { // do not clone all model if not necessary
155       return ModuleRootManager.getInstance(getModule()).getOrderEntries();
156     }
157     else {
158       return myModifiableRootModel.getOrderEntries();
159     }
160   }
161
162   public ModifiableRootModel getModifiableRootModelProxy() {
163     if (myModifiableRootModelProxy == null) {
164       final ModifiableRootModel rootModel = getModifiableRootModel();
165       if (rootModel != null) {
166         myModifiableRootModelProxy = (ModifiableRootModel)Proxy.newProxyInstance(
167           getClass().getClassLoader(), new Class[]{ModifiableRootModel.class}, new ModifiableRootModelInvocationHandler(rootModel)
168         );
169       }
170     }
171     return myModifiableRootModelProxy;
172   }
173
174   public ModuleRootModel getRootModel() {
175     if (myModifiableRootModel != null) {
176       return getModifiableRootModelProxy();
177     }
178     return ModuleRootManager.getInstance(myModule);
179   }
180
181   public boolean isModified() {
182     for (ModuleConfigurationEditor moduleElementsEditor : myEditors) {
183       if (moduleElementsEditor.isModified()) {
184         return true;
185       }
186     }
187     return false;
188   }
189
190   private void createEditors(@Nullable Module module) {
191     if (module == null) return;
192
193     ModuleConfigurationState state = createModuleConfigurationState();
194     for (ModuleConfigurationEditorProvider provider : collectProviders(module)) {
195       ModuleConfigurationEditor[] editors = provider.createEditors(state);
196       if (editors.length > 0 && provider instanceof ModuleConfigurationEditorProviderEx &&
197           ((ModuleConfigurationEditorProviderEx)provider).isCompleteEditorSet()) {
198         myEditors.clear();
199         ContainerUtil.addAll(myEditors, editors);
200         break;
201       }
202       else {
203         ContainerUtil.addAll(myEditors, editors);
204       }
205     }
206
207     for (Configurable moduleConfigurable : ComponentsPackage.getComponents(module, Configurable.class)) {
208       myEditors.add(new ModuleConfigurableWrapper(moduleConfigurable));
209     }
210     for(ModuleConfigurableEP extension : module.getExtensions(MODULE_CONFIGURABLES)) {
211       if (extension.canCreateConfigurable()) {
212         myEditors.add(new ModuleConfigurableWrapper(extension.createConfigurable()));
213       }
214     }
215   }
216
217   private static ModuleConfigurationEditorProvider[] collectProviders(@NotNull Module module) {
218     List<ModuleConfigurationEditorProvider> result = new ArrayList<ModuleConfigurationEditorProvider>();
219     result.addAll(ComponentsPackage.getComponents(module, ModuleConfigurationEditorProvider.class));
220     ContainerUtil.addAll(result, Extensions.getExtensions(ModuleConfigurationEditorProvider.EP_NAME, module));
221     return result.toArray(new ModuleConfigurationEditorProvider[result.size()]);
222   }
223
224   public ModuleConfigurationState createModuleConfigurationState() {
225     return new ModuleConfigurationStateImpl(myProject, myModulesProvider) {
226       @Override
227       public ModifiableRootModel getRootModel() {
228         return getModifiableRootModelProxy();
229       }
230
231       @Override
232       public FacetsProvider getFacetsProvider() {
233         return getFacetsConfigurator();
234       }
235     };
236   }
237
238   private JPanel createPanel() {
239     getModifiableRootModel(); //initialize model if needed
240     getModifiableRootModelProxy();
241
242     myGenericSettingsPanel = new ModuleEditorPanel();
243
244     createEditors(getModule());
245
246     if (!Registry.is("ide.new.project.settings")) {
247       JPanel northPanel = new JPanel(new GridBagLayout());
248       myGenericSettingsPanel.add(northPanel, BorderLayout.NORTH);
249     }
250
251     final JComponent component = createCenterPanel();
252     myGenericSettingsPanel.add(component, BorderLayout.CENTER);
253     return myGenericSettingsPanel;
254   }
255
256   public JPanel getPanel() {
257     if (myGenericSettingsPanel == null) {
258       myGenericSettingsPanel = createPanel();
259     }
260
261     return myGenericSettingsPanel;
262   }
263
264   public void moduleCountChanged() {
265     updateOrderEntriesInEditors();
266   }
267
268   private void updateOrderEntriesInEditors() {
269     if (getModule() != null) { //module with attached module libraries was deleted
270       getPanel();  //init editor if needed
271       for (final ModuleConfigurationEditor myEditor : myEditors) {
272         myEditor.moduleStateChanged();
273       }
274       myEventDispatcher.getMulticaster().moduleStateChanged(getModifiableRootModelProxy());
275     }
276   }
277
278   public void updateCompilerOutputPathChanged(String baseUrl, String moduleName){
279     if (myGenericSettingsPanel == null) return; //wasn't initialized yet
280     for (final ModuleConfigurationEditor myEditor : myEditors) {
281       if (myEditor instanceof ModuleElementsEditor) {
282         ((ModuleElementsEditor)myEditor).moduleCompileOutputChanged(baseUrl, moduleName);
283       }
284     }
285   }
286
287   @Override
288   public void dispose() {
289     try {
290       for (final ModuleConfigurationEditor myEditor : myEditors) {
291         myEditor.disposeUIResources();
292       }
293
294       myEditors.clear();
295
296       disposeCenterPanel();
297
298       if (myModifiableRootModel != null) {
299         myModifiableRootModel.dispose();
300       }
301
302       myGenericSettingsPanel = null;
303     }
304     finally {
305       myModifiableRootModel = null;
306       myModifiableRootModelProxy = null;
307     }
308   }
309
310   public ModifiableRootModel apply() throws ConfigurationException {
311     try {
312       for (ModuleConfigurationEditor editor : myEditors) {
313         editor.saveData();
314         editor.apply();
315       }
316
317       return myModifiableRootModel;
318     }
319     finally {
320       myModifiableRootModel = null;
321       myModifiableRootModelProxy = null;
322     }
323   }
324
325   public void canApply() throws ConfigurationException {
326     for (ModuleConfigurationEditor editor : myEditors) {
327       if (editor instanceof ModuleElementsEditor) {
328         ((ModuleElementsEditor)editor).canApply();
329       }
330     }
331   }
332
333   public String getName() {
334     return myName;
335   }
336
337   private class ModifiableRootModelInvocationHandler implements InvocationHandler, ProxyDelegateAccessor {
338     private final ModifiableRootModel myDelegateModel;
339     @NonNls private final Set<String> myCheckedNames = new HashSet<String>(
340       Arrays.asList("addOrderEntry", "addLibraryEntry", "addInvalidLibrary", "addModuleOrderEntry", "addInvalidModuleEntry",
341                     "removeOrderEntry", "setSdk", "inheritSdk", "inheritCompilerOutputPath", "setExcludeOutput", "replaceEntryOfType", "rearrangeOrderEntries"));
342
343     ModifiableRootModelInvocationHandler(ModifiableRootModel model) {
344       myDelegateModel = model;
345     }
346
347     @Override
348     public Object invoke(Object object, Method method, Object[] params) throws Throwable {
349       final boolean needUpdate = myCheckedNames.contains(method.getName());
350       try {
351         final Object result = method.invoke(myDelegateModel, unwrapParams(params));
352         if (result instanceof LibraryTable) {
353           return Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{LibraryTable.class},
354                                         new LibraryTableInvocationHandler((LibraryTable)result));
355         }
356         return result;
357       }
358       catch (InvocationTargetException e) {
359         throw e.getCause();
360       }
361       finally {
362         if (needUpdate) {
363           updateOrderEntriesInEditors();
364         }
365       }
366     }
367
368     @Override
369     public Object getDelegate() {
370       return myDelegateModel;
371     }
372   }
373
374   private class LibraryTableInvocationHandler implements InvocationHandler, ProxyDelegateAccessor {
375     private final LibraryTable myDelegateTable;
376     @NonNls private final Set<String> myCheckedNames = new HashSet<String>(Arrays.asList("removeLibrary" /*,"createLibrary"*/));
377
378     LibraryTableInvocationHandler(LibraryTable table) {
379       myDelegateTable = table;
380     }
381
382     @Override
383     public Object invoke(Object object, Method method, Object[] params) throws Throwable {
384       final boolean needUpdate = myCheckedNames.contains(method.getName());
385       try {
386         final Object result = method.invoke(myDelegateTable, unwrapParams(params));
387         if (result instanceof Library) {
388           return Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{result instanceof LibraryEx ? LibraryEx.class : Library.class},
389                                         new LibraryInvocationHandler((Library)result));
390         }
391         else if (result instanceof LibraryTable.ModifiableModel) {
392           return Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{LibraryTableBase.ModifiableModelEx.class},
393                                         new LibraryTableModelInvocationHandler((LibraryTable.ModifiableModel)result));
394         }
395         if (result instanceof Library[]) {
396           Library[] libraries = (Library[])result;
397           for (int idx = 0; idx < libraries.length; idx++) {
398             Library library = libraries[idx];
399             libraries[idx] =
400             (Library)Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{library instanceof LibraryEx ? LibraryEx.class : Library.class},
401                                             new LibraryInvocationHandler(library));
402           }
403         }
404         return result;
405       }
406       catch (InvocationTargetException e) {
407         throw e.getCause();
408       }
409       finally {
410         if (needUpdate) {
411           updateOrderEntriesInEditors();
412         }
413       }
414     }
415
416     @Override
417     public Object getDelegate() {
418       return myDelegateTable;
419     }
420   }
421
422   private class LibraryInvocationHandler implements InvocationHandler, ProxyDelegateAccessor {
423     private final Library myDelegateLibrary;
424
425     LibraryInvocationHandler(Library delegateLibrary) {
426       myDelegateLibrary = delegateLibrary;
427     }
428
429     @Override
430     public Object invoke(Object object, Method method, Object[] params) throws Throwable {
431       try {
432         final Object result = method.invoke(myDelegateLibrary, unwrapParams(params));
433         if (result instanceof LibraryEx.ModifiableModelEx) {
434           return Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{LibraryEx.ModifiableModelEx.class},
435                                         new LibraryModifiableModelInvocationHandler((LibraryEx.ModifiableModelEx)result));
436         }
437         return result;
438       }
439       catch (InvocationTargetException e) {
440         throw e.getCause();
441       }
442     }
443
444     @Override
445     public Object getDelegate() {
446       return myDelegateLibrary;
447     }
448   }
449
450   private class LibraryModifiableModelInvocationHandler implements InvocationHandler, ProxyDelegateAccessor {
451     private final Library.ModifiableModel myDelegateModel;
452
453     LibraryModifiableModelInvocationHandler(Library.ModifiableModel delegateModel) {
454       myDelegateModel = delegateModel;
455     }
456
457     @Override
458     public Object invoke(Object object, Method method, Object[] params) throws Throwable {
459       final boolean needUpdate = METHOD_COMMIT.equals(method.getName());
460       try {
461         return method.invoke(myDelegateModel, unwrapParams(params));
462       }
463       catch (InvocationTargetException e) {
464         throw e.getCause();
465       }
466       finally {
467         if (needUpdate) {
468           updateOrderEntriesInEditors();
469         }
470       }
471     }
472
473     @Override
474     public Object getDelegate() {
475       return myDelegateModel;
476     }
477   }
478
479   private class LibraryTableModelInvocationHandler implements InvocationHandler, ProxyDelegateAccessor {
480     private final LibraryTable.ModifiableModel myDelegateModel;
481
482     LibraryTableModelInvocationHandler(LibraryTable.ModifiableModel delegateModel) {
483       myDelegateModel = delegateModel;
484     }
485
486     @Override
487     public Object invoke(Object object, Method method, Object[] params) throws Throwable {
488       final boolean needUpdate = METHOD_COMMIT.equals(method.getName());
489       try {
490         Object result = method.invoke(myDelegateModel, unwrapParams(params));
491         if (result instanceof Library[]) {
492           Library[] libraries = (Library[])result;
493           for (int idx = 0; idx < libraries.length; idx++) {
494             Library library = libraries[idx];
495             libraries[idx] =
496             (Library)Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{LibraryEx.class},
497                                             new LibraryInvocationHandler(library));
498           }
499         }
500         if (result instanceof Library) {
501           result =
502           Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{LibraryEx.class},
503                                  new LibraryInvocationHandler((Library)result));
504         }
505         return result;
506       }
507       catch (InvocationTargetException e) {
508         throw e.getCause();
509       }
510       finally {
511         if (needUpdate) {
512           updateOrderEntriesInEditors();
513         }
514       }
515     }
516
517     @Override
518     public Object getDelegate() {
519       return myDelegateModel;
520     }
521   }
522
523   public interface ProxyDelegateAccessor {
524     Object getDelegate();
525   }
526
527   private static Object[] unwrapParams(Object[] params) {
528     if (params == null || params.length == 0) {
529       return params;
530     }
531     final Object[] unwrappedParams = new Object[params.length];
532     for (int idx = 0; idx < params.length; idx++) {
533       Object param = params[idx];
534       if (param != null && Proxy.isProxyClass(param.getClass())) {
535         final InvocationHandler invocationHandler = Proxy.getInvocationHandler(param);
536         if (invocationHandler instanceof ProxyDelegateAccessor) {
537           param = ((ProxyDelegateAccessor)invocationHandler).getDelegate();
538         }
539       }
540       unwrappedParams[idx] = param;
541     }
542     return unwrappedParams;
543   }
544
545   @Nullable
546   public String getHelpTopic() {
547     if (myEditors.isEmpty()) {
548       return null;
549     }
550     final ModuleConfigurationEditor selectedEditor = getSelectedEditor();
551     return selectedEditor != null ? selectedEditor.getHelpTopic() : null;
552   }
553
554   public void setModuleName(final String name) {
555     myName = name;
556   }
557
558   private class ModuleEditorPanel extends JPanel implements DataProvider{
559     public ModuleEditorPanel() {
560       super(new BorderLayout());
561     }
562
563     @Override
564     public Object getData(String dataId) {
565       if (LangDataKeys.MODULE_CONTEXT.is(dataId)) {
566         return getModule();
567       }
568       return null;
569     }
570
571   }
572
573   @Override
574   public void setHistory(final History history) {
575   }
576 }