| /* |
| * Copyright 2000-2014 JetBrains s.r.o. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.intellij.openapi.roots.ui.configuration; |
| |
| import com.intellij.facet.impl.ProjectFacetsConfigurator; |
| import com.intellij.openapi.Disposable; |
| import com.intellij.openapi.actionSystem.DataProvider; |
| import com.intellij.openapi.actionSystem.LangDataKeys; |
| import com.intellij.openapi.extensions.ExtensionPointName; |
| import com.intellij.openapi.extensions.Extensions; |
| import com.intellij.openapi.module.Module; |
| import com.intellij.openapi.module.ModuleConfigurationEditor; |
| import com.intellij.openapi.module.impl.ModuleConfigurationStateImpl; |
| import com.intellij.openapi.options.Configurable; |
| import com.intellij.openapi.options.ConfigurationException; |
| import com.intellij.openapi.options.ModuleConfigurableEP; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.roots.ModifiableRootModel; |
| import com.intellij.openapi.roots.ModuleRootManager; |
| import com.intellij.openapi.roots.ModuleRootModel; |
| import com.intellij.openapi.roots.OrderEntry; |
| import com.intellij.openapi.roots.impl.ModuleRootManagerImpl; |
| import com.intellij.openapi.roots.impl.libraries.LibraryEx; |
| import com.intellij.openapi.roots.impl.libraries.LibraryTableBase; |
| import com.intellij.openapi.roots.libraries.Library; |
| import com.intellij.openapi.roots.libraries.LibraryTable; |
| import com.intellij.openapi.util.registry.Registry; |
| import com.intellij.ui.navigation.History; |
| import com.intellij.ui.navigation.Place; |
| import com.intellij.util.EventDispatcher; |
| import com.intellij.util.containers.ContainerUtil; |
| import org.jetbrains.annotations.NonNls; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import javax.swing.*; |
| import java.awt.*; |
| import java.lang.reflect.InvocationHandler; |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Method; |
| import java.lang.reflect.Proxy; |
| import java.util.*; |
| import java.util.List; |
| |
| /** |
| * @author Eugene Zhuravlev |
| * Date: Oct 4, 2003 |
| * Time: 6:29:56 PM |
| */ |
| @SuppressWarnings({"AssignmentToStaticFieldFromInstanceMethod"}) |
| public abstract class ModuleEditor implements Place.Navigator, Disposable { |
| private static final ExtensionPointName<ModuleConfigurableEP> MODULE_CONFIGURABLES = ExtensionPointName.create("com.intellij.moduleConfigurable"); |
| public static final String SELECTED_EDITOR_NAME = "selectedEditor"; |
| |
| private final Project myProject; |
| private JPanel myGenericSettingsPanel; |
| private ModifiableRootModel myModifiableRootModel; // important: in order to correctly update OrderEntries UI use corresponding proxy for the model |
| |
| private final ModulesProvider myModulesProvider; |
| private String myName; |
| private final Module myModule; |
| |
| protected final List<ModuleConfigurationEditor> myEditors = new ArrayList<ModuleConfigurationEditor>(); |
| private ModifiableRootModel myModifiableRootModelProxy; |
| |
| private final EventDispatcher<ChangeListener> myEventDispatcher = EventDispatcher.create(ChangeListener.class); |
| @NonNls private static final String METHOD_COMMIT = "commit"; |
| |
| protected History myHistory; |
| |
| public ModuleEditor(Project project, ModulesProvider modulesProvider, |
| @NotNull Module module) { |
| myProject = project; |
| myModulesProvider = modulesProvider; |
| myModule = module; |
| myName = module.getName(); |
| } |
| |
| public void init(History history) { |
| myHistory = history; |
| |
| for (ModuleConfigurationEditor each : myEditors) { |
| if (each instanceof ModuleElementsEditor) { |
| ((ModuleElementsEditor)each).setHistory(myHistory); |
| } |
| } |
| |
| restoreSelectedEditor(); |
| } |
| |
| public abstract ProjectFacetsConfigurator getFacetsConfigurator(); |
| |
| protected abstract JComponent createCenterPanel(); |
| |
| @Nullable |
| public abstract ModuleConfigurationEditor getSelectedEditor(); |
| |
| public abstract void selectEditor(String displayName); |
| |
| protected abstract void restoreSelectedEditor(); |
| |
| @Nullable |
| public abstract ModuleConfigurationEditor getEditor(@NotNull String displayName); |
| |
| protected abstract void disposeCenterPanel(); |
| |
| public interface ChangeListener extends EventListener { |
| void moduleStateChanged(ModifiableRootModel moduleRootModel); |
| } |
| |
| public void addChangeListener(ChangeListener listener) { |
| myEventDispatcher.addListener(listener); |
| } |
| |
| public void removeChangeListener(ChangeListener listener) { |
| myEventDispatcher.removeListener(listener); |
| } |
| |
| @Nullable |
| public Module getModule() { |
| final Module[] all = myModulesProvider.getModules(); |
| for (Module each : all) { |
| if (each == myModule) return myModule; |
| } |
| |
| return myModulesProvider.getModule(myName); |
| } |
| |
| public ModifiableRootModel getModifiableRootModel() { |
| if (myModifiableRootModel == null) { |
| final Module module = getModule(); |
| if (module != null) { |
| myModifiableRootModel = ((ModuleRootManagerImpl)ModuleRootManager.getInstance(module)).getModifiableModel(new UIRootConfigurationAccessor(myProject)); |
| } |
| } |
| return myModifiableRootModel; |
| } |
| |
| public OrderEntry[] getOrderEntries() { |
| if (myModifiableRootModel == null) { // do not clone all model if not necessary |
| return ModuleRootManager.getInstance(getModule()).getOrderEntries(); |
| } |
| else { |
| return myModifiableRootModel.getOrderEntries(); |
| } |
| } |
| |
| public ModifiableRootModel getModifiableRootModelProxy() { |
| if (myModifiableRootModelProxy == null) { |
| final ModifiableRootModel rootModel = getModifiableRootModel(); |
| if (rootModel != null) { |
| myModifiableRootModelProxy = (ModifiableRootModel)Proxy.newProxyInstance( |
| getClass().getClassLoader(), new Class[]{ModifiableRootModel.class}, new ModifiableRootModelInvocationHandler(rootModel) |
| ); |
| } |
| } |
| return myModifiableRootModelProxy; |
| } |
| |
| public ModuleRootModel getRootModel() { |
| if (myModifiableRootModel != null) { |
| return getModifiableRootModelProxy(); |
| } |
| return ModuleRootManager.getInstance(myModule); |
| } |
| |
| public boolean isModified() { |
| for (ModuleConfigurationEditor moduleElementsEditor : myEditors) { |
| if (moduleElementsEditor.isModified()) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private void createEditors(Module module) { |
| ModuleConfigurationEditorProvider[] providers = collectProviders(module); |
| ModuleConfigurationState state = createModuleConfigurationState(); |
| for (ModuleConfigurationEditorProvider provider : providers) { |
| ModuleConfigurationEditor[] editors = provider.createEditors(state); |
| if (editors.length > 0 && provider instanceof ModuleConfigurationEditorProviderEx && |
| ((ModuleConfigurationEditorProviderEx)provider).isCompleteEditorSet()) { |
| myEditors.clear(); |
| ContainerUtil.addAll(myEditors, editors); |
| break; |
| } |
| else { |
| ContainerUtil.addAll(myEditors, editors); |
| } |
| } |
| |
| for (final Configurable moduleConfigurable : myModule.getComponents(Configurable.class)) { |
| myEditors.add(new ModuleConfigurableWrapper(moduleConfigurable)); |
| } |
| for(ModuleConfigurableEP extension : myModule.getExtensions(MODULE_CONFIGURABLES)) { |
| myEditors.add(new ModuleConfigurableWrapper(extension.createConfigurable())); |
| } |
| } |
| |
| private static ModuleConfigurationEditorProvider[] collectProviders(final Module module) { |
| List<ModuleConfigurationEditorProvider> result = new ArrayList<ModuleConfigurationEditorProvider>(); |
| ContainerUtil.addAll(result, module.getComponents(ModuleConfigurationEditorProvider.class)); |
| ContainerUtil.addAll(result, Extensions.getExtensions(ModuleConfigurationEditorProvider.EP_NAME, module)); |
| return result.toArray(new ModuleConfigurationEditorProvider[result.size()]); |
| } |
| |
| public ModuleConfigurationState createModuleConfigurationState() { |
| return new ModuleConfigurationStateImpl(myProject, myModulesProvider) { |
| @Override |
| public ModifiableRootModel getRootModel() { |
| return getModifiableRootModelProxy(); |
| } |
| |
| @Override |
| public FacetsProvider getFacetsProvider() { |
| return getFacetsConfigurator(); |
| } |
| }; |
| } |
| |
| private JPanel createPanel() { |
| getModifiableRootModel(); //initialize model if needed |
| getModifiableRootModelProxy(); |
| |
| myGenericSettingsPanel = new ModuleEditorPanel(); |
| |
| createEditors(getModule()); |
| |
| if (!Registry.is("ide.new.project.settings")) { |
| JPanel northPanel = new JPanel(new GridBagLayout()); |
| myGenericSettingsPanel.add(northPanel, BorderLayout.NORTH); |
| } |
| |
| final JComponent component = createCenterPanel(); |
| myGenericSettingsPanel.add(component, BorderLayout.CENTER); |
| return myGenericSettingsPanel; |
| } |
| |
| public JPanel getPanel() { |
| if (myGenericSettingsPanel == null) { |
| myGenericSettingsPanel = createPanel(); |
| } |
| |
| return myGenericSettingsPanel; |
| } |
| |
| public void moduleCountChanged() { |
| updateOrderEntriesInEditors(); |
| } |
| |
| private void updateOrderEntriesInEditors() { |
| if (getModule() != null) { //module with attached module libraries was deleted |
| getPanel(); //init editor if needed |
| for (final ModuleConfigurationEditor myEditor : myEditors) { |
| myEditor.moduleStateChanged(); |
| } |
| myEventDispatcher.getMulticaster().moduleStateChanged(getModifiableRootModelProxy()); |
| } |
| } |
| |
| public void updateCompilerOutputPathChanged(String baseUrl, String moduleName){ |
| if (myGenericSettingsPanel == null) return; //wasn't initialized yet |
| for (final ModuleConfigurationEditor myEditor : myEditors) { |
| if (myEditor instanceof ModuleElementsEditor) { |
| ((ModuleElementsEditor)myEditor).moduleCompileOutputChanged(baseUrl, moduleName); |
| } |
| } |
| } |
| |
| @Override |
| public void dispose() { |
| try { |
| for (final ModuleConfigurationEditor myEditor : myEditors) { |
| myEditor.disposeUIResources(); |
| } |
| |
| myEditors.clear(); |
| |
| disposeCenterPanel(); |
| |
| if (myModifiableRootModel != null) { |
| myModifiableRootModel.dispose(); |
| } |
| |
| myGenericSettingsPanel = null; |
| } |
| finally { |
| myModifiableRootModel = null; |
| myModifiableRootModelProxy = null; |
| } |
| } |
| |
| public ModifiableRootModel apply() throws ConfigurationException { |
| try { |
| for (ModuleConfigurationEditor editor : myEditors) { |
| editor.saveData(); |
| editor.apply(); |
| } |
| |
| return myModifiableRootModel; |
| } |
| finally { |
| myModifiableRootModel = null; |
| myModifiableRootModelProxy = null; |
| } |
| } |
| |
| public void canApply() throws ConfigurationException { |
| for (ModuleConfigurationEditor editor : myEditors) { |
| if (editor instanceof ModuleElementsEditor) { |
| ((ModuleElementsEditor)editor).canApply(); |
| } |
| } |
| } |
| |
| public String getName() { |
| return myName; |
| } |
| |
| private class ModifiableRootModelInvocationHandler implements InvocationHandler { |
| private final ModifiableRootModel myDelegateModel; |
| @NonNls private final Set<String> myCheckedNames = new HashSet<String>( |
| Arrays.asList("addOrderEntry", "addLibraryEntry", "addInvalidLibrary", "addModuleOrderEntry", "addInvalidModuleEntry", |
| "removeOrderEntry", "setSdk", "inheritSdk", "inheritCompilerOutputPath", "setExcludeOutput", "replaceEntryOfType", "rearrangeOrderEntries")); |
| |
| ModifiableRootModelInvocationHandler(ModifiableRootModel model) { |
| myDelegateModel = model; |
| } |
| |
| @Override |
| public Object invoke(Object object, Method method, Object[] params) throws Throwable { |
| final boolean needUpdate = myCheckedNames.contains(method.getName()); |
| try { |
| final Object result = method.invoke(myDelegateModel, unwrapParams(params)); |
| if (result instanceof LibraryTable) { |
| return Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{LibraryTable.class}, |
| new LibraryTableInvocationHandler((LibraryTable)result)); |
| } |
| return result; |
| } |
| catch (InvocationTargetException e) { |
| throw e.getCause(); |
| } |
| finally { |
| if (needUpdate) { |
| updateOrderEntriesInEditors(); |
| } |
| } |
| } |
| } |
| |
| private class LibraryTableInvocationHandler implements InvocationHandler, ProxyDelegateAccessor { |
| private final LibraryTable myDelegateTable; |
| @NonNls private final Set<String> myCheckedNames = new HashSet<String>(Arrays.asList("removeLibrary" /*,"createLibrary"*/)); |
| |
| LibraryTableInvocationHandler(LibraryTable table) { |
| myDelegateTable = table; |
| } |
| |
| @Override |
| public Object invoke(Object object, Method method, Object[] params) throws Throwable { |
| final boolean needUpdate = myCheckedNames.contains(method.getName()); |
| try { |
| final Object result = method.invoke(myDelegateTable, unwrapParams(params)); |
| if (result instanceof Library) { |
| return Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{result instanceof LibraryEx ? LibraryEx.class : Library.class}, |
| new LibraryInvocationHandler((Library)result)); |
| } |
| else if (result instanceof LibraryTable.ModifiableModel) { |
| return Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{LibraryTableBase.ModifiableModelEx.class}, |
| new LibraryTableModelInvocationHandler((LibraryTable.ModifiableModel)result)); |
| } |
| if (result instanceof Library[]) { |
| Library[] libraries = (Library[])result; |
| for (int idx = 0; idx < libraries.length; idx++) { |
| Library library = libraries[idx]; |
| libraries[idx] = |
| (Library)Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{library instanceof LibraryEx ? LibraryEx.class : Library.class}, |
| new LibraryInvocationHandler(library)); |
| } |
| } |
| return result; |
| } |
| catch (InvocationTargetException e) { |
| throw e.getCause(); |
| } |
| finally { |
| if (needUpdate) { |
| updateOrderEntriesInEditors(); |
| } |
| } |
| } |
| |
| @Override |
| public Object getDelegate() { |
| return myDelegateTable; |
| } |
| } |
| |
| private class LibraryInvocationHandler implements InvocationHandler, ProxyDelegateAccessor { |
| private final Library myDelegateLibrary; |
| |
| LibraryInvocationHandler(Library delegateLibrary) { |
| myDelegateLibrary = delegateLibrary; |
| } |
| |
| @Override |
| public Object invoke(Object object, Method method, Object[] params) throws Throwable { |
| try { |
| final Object result = method.invoke(myDelegateLibrary, unwrapParams(params)); |
| if (result instanceof LibraryEx.ModifiableModelEx) { |
| return Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{LibraryEx.ModifiableModelEx.class}, |
| new LibraryModifiableModelInvocationHandler((LibraryEx.ModifiableModelEx)result)); |
| } |
| return result; |
| } |
| catch (InvocationTargetException e) { |
| throw e.getCause(); |
| } |
| } |
| |
| @Override |
| public Object getDelegate() { |
| return myDelegateLibrary; |
| } |
| } |
| |
| private class LibraryModifiableModelInvocationHandler implements InvocationHandler, ProxyDelegateAccessor { |
| private final Library.ModifiableModel myDelegateModel; |
| |
| LibraryModifiableModelInvocationHandler(Library.ModifiableModel delegateModel) { |
| myDelegateModel = delegateModel; |
| } |
| |
| @Override |
| public Object invoke(Object object, Method method, Object[] params) throws Throwable { |
| final boolean needUpdate = METHOD_COMMIT.equals(method.getName()); |
| try { |
| return method.invoke(myDelegateModel, unwrapParams(params)); |
| } |
| catch (InvocationTargetException e) { |
| throw e.getCause(); |
| } |
| finally { |
| if (needUpdate) { |
| updateOrderEntriesInEditors(); |
| } |
| } |
| } |
| |
| @Override |
| public Object getDelegate() { |
| return myDelegateModel; |
| } |
| } |
| |
| private class LibraryTableModelInvocationHandler implements InvocationHandler, ProxyDelegateAccessor { |
| private final LibraryTable.ModifiableModel myDelegateModel; |
| |
| LibraryTableModelInvocationHandler(LibraryTable.ModifiableModel delegateModel) { |
| myDelegateModel = delegateModel; |
| } |
| |
| @Override |
| public Object invoke(Object object, Method method, Object[] params) throws Throwable { |
| final boolean needUpdate = METHOD_COMMIT.equals(method.getName()); |
| try { |
| Object result = method.invoke(myDelegateModel, unwrapParams(params)); |
| if (result instanceof Library[]) { |
| Library[] libraries = (Library[])result; |
| for (int idx = 0; idx < libraries.length; idx++) { |
| Library library = libraries[idx]; |
| libraries[idx] = |
| (Library)Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{LibraryEx.class}, |
| new LibraryInvocationHandler(library)); |
| } |
| } |
| if (result instanceof Library) { |
| result = |
| Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{LibraryEx.class}, |
| new LibraryInvocationHandler((Library)result)); |
| } |
| return result; |
| } |
| catch (InvocationTargetException e) { |
| throw e.getCause(); |
| } |
| finally { |
| if (needUpdate) { |
| updateOrderEntriesInEditors(); |
| } |
| } |
| } |
| |
| @Override |
| public Object getDelegate() { |
| return myDelegateModel; |
| } |
| } |
| |
| public interface ProxyDelegateAccessor { |
| Object getDelegate(); |
| } |
| |
| private static Object[] unwrapParams(Object[] params) { |
| if (params == null || params.length == 0) { |
| return params; |
| } |
| final Object[] unwrappedParams = new Object[params.length]; |
| for (int idx = 0; idx < params.length; idx++) { |
| Object param = params[idx]; |
| if (param != null && Proxy.isProxyClass(param.getClass())) { |
| final InvocationHandler invocationHandler = Proxy.getInvocationHandler(param); |
| if (invocationHandler instanceof ProxyDelegateAccessor) { |
| param = ((ProxyDelegateAccessor)invocationHandler).getDelegate(); |
| } |
| } |
| unwrappedParams[idx] = param; |
| } |
| return unwrappedParams; |
| } |
| |
| @Nullable |
| public String getHelpTopic() { |
| if (myEditors.isEmpty()) { |
| return null; |
| } |
| final ModuleConfigurationEditor selectedEditor = getSelectedEditor(); |
| return selectedEditor != null ? selectedEditor.getHelpTopic() : null; |
| } |
| |
| public void setModuleName(final String name) { |
| myName = name; |
| } |
| |
| private class ModuleEditorPanel extends JPanel implements DataProvider{ |
| public ModuleEditorPanel() { |
| super(new BorderLayout()); |
| } |
| |
| @Override |
| public Object getData(String dataId) { |
| if (LangDataKeys.MODULE_CONTEXT.is(dataId)) { |
| return getModule(); |
| } |
| return null; |
| } |
| |
| } |
| |
| @Override |
| public void setHistory(final History history) { |
| } |
| } |