2 * Copyright 2000-2015 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package com.intellij.openapi.roots.ui.configuration;
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;
52 import java.lang.reflect.InvocationHandler;
53 import java.lang.reflect.InvocationTargetException;
54 import java.lang.reflect.Method;
55 import java.lang.reflect.Proxy;
57 import java.util.List;
60 * @author Eugene Zhuravlev
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";
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
73 private final ModulesProvider myModulesProvider;
74 private String myName;
75 private final Module myModule;
77 protected final List<ModuleConfigurationEditor> myEditors = new ArrayList<ModuleConfigurationEditor>();
78 private ModifiableRootModel myModifiableRootModelProxy;
80 private final EventDispatcher<ChangeListener> myEventDispatcher = EventDispatcher.create(ChangeListener.class);
81 @NonNls private static final String METHOD_COMMIT = "commit";
83 protected History myHistory;
85 public ModuleEditor(Project project, ModulesProvider modulesProvider,
86 @NotNull Module module) {
88 myModulesProvider = modulesProvider;
90 myName = module.getName();
93 public void init(History history) {
96 for (ModuleConfigurationEditor each : myEditors) {
97 if (each instanceof ModuleElementsEditor) {
98 ((ModuleElementsEditor)each).setHistory(myHistory);
102 restoreSelectedEditor();
105 public abstract ProjectFacetsConfigurator getFacetsConfigurator();
107 protected abstract JComponent createCenterPanel();
110 public abstract ModuleConfigurationEditor getSelectedEditor();
112 public abstract void selectEditor(String displayName);
114 protected abstract void restoreSelectedEditor();
117 public abstract ModuleConfigurationEditor getEditor(@NotNull String displayName);
119 protected abstract void disposeCenterPanel();
121 public interface ChangeListener extends EventListener {
122 void moduleStateChanged(ModifiableRootModel moduleRootModel);
125 public void addChangeListener(ChangeListener listener) {
126 myEventDispatcher.addListener(listener);
129 public void removeChangeListener(ChangeListener listener) {
130 myEventDispatcher.removeListener(listener);
134 public Module getModule() {
135 final Module[] all = myModulesProvider.getModules();
136 for (Module each : all) {
137 if (each == myModule) return myModule;
140 return myModulesProvider.getModule(myName);
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));
150 return myModifiableRootModel;
153 public OrderEntry[] getOrderEntries() {
154 if (myModifiableRootModel == null) { // do not clone all model if not necessary
155 return ModuleRootManager.getInstance(getModule()).getOrderEntries();
158 return myModifiableRootModel.getOrderEntries();
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)
171 return myModifiableRootModelProxy;
174 public ModuleRootModel getRootModel() {
175 if (myModifiableRootModel != null) {
176 return getModifiableRootModelProxy();
178 return ModuleRootManager.getInstance(myModule);
181 public boolean isModified() {
182 for (ModuleConfigurationEditor moduleElementsEditor : myEditors) {
183 if (moduleElementsEditor.isModified()) {
190 private void createEditors(@Nullable Module module) {
191 if (module == null) return;
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()) {
199 ContainerUtil.addAll(myEditors, editors);
203 ContainerUtil.addAll(myEditors, editors);
207 for (Configurable moduleConfigurable : ComponentsPackage.getComponents(module, Configurable.class)) {
208 myEditors.add(new ModuleConfigurableWrapper(moduleConfigurable));
210 for(ModuleConfigurableEP extension : module.getExtensions(MODULE_CONFIGURABLES)) {
211 if (extension.canCreateConfigurable()) {
212 myEditors.add(new ModuleConfigurableWrapper(extension.createConfigurable()));
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()]);
224 public ModuleConfigurationState createModuleConfigurationState() {
225 return new ModuleConfigurationStateImpl(myProject, myModulesProvider) {
227 public ModifiableRootModel getRootModel() {
228 return getModifiableRootModelProxy();
232 public FacetsProvider getFacetsProvider() {
233 return getFacetsConfigurator();
238 private JPanel createPanel() {
239 getModifiableRootModel(); //initialize model if needed
240 getModifiableRootModelProxy();
242 myGenericSettingsPanel = new ModuleEditorPanel();
244 createEditors(getModule());
246 if (!Registry.is("ide.new.project.settings")) {
247 JPanel northPanel = new JPanel(new GridBagLayout());
248 myGenericSettingsPanel.add(northPanel, BorderLayout.NORTH);
251 final JComponent component = createCenterPanel();
252 myGenericSettingsPanel.add(component, BorderLayout.CENTER);
253 return myGenericSettingsPanel;
256 public JPanel getPanel() {
257 if (myGenericSettingsPanel == null) {
258 myGenericSettingsPanel = createPanel();
261 return myGenericSettingsPanel;
264 public void moduleCountChanged() {
265 updateOrderEntriesInEditors();
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();
274 myEventDispatcher.getMulticaster().moduleStateChanged(getModifiableRootModelProxy());
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);
288 public void dispose() {
290 for (final ModuleConfigurationEditor myEditor : myEditors) {
291 myEditor.disposeUIResources();
296 disposeCenterPanel();
298 if (myModifiableRootModel != null) {
299 myModifiableRootModel.dispose();
302 myGenericSettingsPanel = null;
305 myModifiableRootModel = null;
306 myModifiableRootModelProxy = null;
310 public ModifiableRootModel apply() throws ConfigurationException {
312 for (ModuleConfigurationEditor editor : myEditors) {
317 return myModifiableRootModel;
320 myModifiableRootModel = null;
321 myModifiableRootModelProxy = null;
325 public void canApply() throws ConfigurationException {
326 for (ModuleConfigurationEditor editor : myEditors) {
327 if (editor instanceof ModuleElementsEditor) {
328 ((ModuleElementsEditor)editor).canApply();
333 public String getName() {
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"));
343 ModifiableRootModelInvocationHandler(ModifiableRootModel model) {
344 myDelegateModel = model;
348 public Object invoke(Object object, Method method, Object[] params) throws Throwable {
349 final boolean needUpdate = myCheckedNames.contains(method.getName());
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));
358 catch (InvocationTargetException e) {
363 updateOrderEntriesInEditors();
369 public Object getDelegate() {
370 return myDelegateModel;
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"*/));
378 LibraryTableInvocationHandler(LibraryTable table) {
379 myDelegateTable = table;
383 public Object invoke(Object object, Method method, Object[] params) throws Throwable {
384 final boolean needUpdate = myCheckedNames.contains(method.getName());
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));
391 else if (result instanceof LibraryTable.ModifiableModel) {
392 return Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{LibraryTableBase.ModifiableModelEx.class},
393 new LibraryTableModelInvocationHandler((LibraryTable.ModifiableModel)result));
395 if (result instanceof Library[]) {
396 Library[] libraries = (Library[])result;
397 for (int idx = 0; idx < libraries.length; idx++) {
398 Library library = libraries[idx];
400 (Library)Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{library instanceof LibraryEx ? LibraryEx.class : Library.class},
401 new LibraryInvocationHandler(library));
406 catch (InvocationTargetException e) {
411 updateOrderEntriesInEditors();
417 public Object getDelegate() {
418 return myDelegateTable;
422 private class LibraryInvocationHandler implements InvocationHandler, ProxyDelegateAccessor {
423 private final Library myDelegateLibrary;
425 LibraryInvocationHandler(Library delegateLibrary) {
426 myDelegateLibrary = delegateLibrary;
430 public Object invoke(Object object, Method method, Object[] params) throws Throwable {
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));
439 catch (InvocationTargetException e) {
445 public Object getDelegate() {
446 return myDelegateLibrary;
450 private class LibraryModifiableModelInvocationHandler implements InvocationHandler, ProxyDelegateAccessor {
451 private final Library.ModifiableModel myDelegateModel;
453 LibraryModifiableModelInvocationHandler(Library.ModifiableModel delegateModel) {
454 myDelegateModel = delegateModel;
458 public Object invoke(Object object, Method method, Object[] params) throws Throwable {
459 final boolean needUpdate = METHOD_COMMIT.equals(method.getName());
461 return method.invoke(myDelegateModel, unwrapParams(params));
463 catch (InvocationTargetException e) {
468 updateOrderEntriesInEditors();
474 public Object getDelegate() {
475 return myDelegateModel;
479 private class LibraryTableModelInvocationHandler implements InvocationHandler, ProxyDelegateAccessor {
480 private final LibraryTable.ModifiableModel myDelegateModel;
482 LibraryTableModelInvocationHandler(LibraryTable.ModifiableModel delegateModel) {
483 myDelegateModel = delegateModel;
487 public Object invoke(Object object, Method method, Object[] params) throws Throwable {
488 final boolean needUpdate = METHOD_COMMIT.equals(method.getName());
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];
496 (Library)Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{LibraryEx.class},
497 new LibraryInvocationHandler(library));
500 if (result instanceof Library) {
502 Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{LibraryEx.class},
503 new LibraryInvocationHandler((Library)result));
507 catch (InvocationTargetException e) {
512 updateOrderEntriesInEditors();
518 public Object getDelegate() {
519 return myDelegateModel;
523 public interface ProxyDelegateAccessor {
524 Object getDelegate();
527 private static Object[] unwrapParams(Object[] params) {
528 if (params == null || params.length == 0) {
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();
540 unwrappedParams[idx] = param;
542 return unwrappedParams;
546 public String getHelpTopic() {
547 if (myEditors.isEmpty()) {
550 final ModuleConfigurationEditor selectedEditor = getSelectedEditor();
551 return selectedEditor != null ? selectedEditor.getHelpTopic() : null;
554 public void setModuleName(final String name) {
558 private class ModuleEditorPanel extends JPanel implements DataProvider{
559 public ModuleEditorPanel() {
560 super(new BorderLayout());
564 public Object getData(String dataId) {
565 if (LangDataKeys.MODULE_CONTEXT.is(dataId)) {
574 public void setHistory(final History history) {