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