From 0d1adb345b73ed0bd18ff7ef02c7879eb37c2967 Mon Sep 17 00:00:00 2001 From: Rustam Vishnyakov Date: Fri, 2 Oct 2015 15:13:00 +0300 Subject: [PATCH] Scheme exporter API and IDEA-90075 (Export Codestyle as XML File) --- .../codeStyle/CodeStyleSchemeExporterUI.java | 119 ++++++++++++++++++ .../ManageCodeStyleSchemesDialog.form | 2 +- .../ManageCodeStyleSchemesDialog.java | 26 +++- .../CodeStyleSchemeCopyExporter.java | 82 ++++++++++++ .../openapi/options/SchemeExporter.java | 41 ++++++ .../options/SchemeExporterException.java | 25 ++++ .../openapi/options/SchemeExporterEP.java | 90 +++++++++++++ .../src/messages/ApplicationBundle.properties | 5 + .../src/META-INF/LangExtensions.xml | 5 + .../src/META-INF/PlatformExtensionPoints.xml | 5 + 10 files changed, 395 insertions(+), 5 deletions(-) create mode 100644 platform/lang-impl/src/com/intellij/application/options/codeStyle/CodeStyleSchemeExporterUI.java create mode 100644 platform/lang-impl/src/com/intellij/psi/impl/source/codeStyle/CodeStyleSchemeCopyExporter.java create mode 100644 platform/platform-api/src/com/intellij/openapi/options/SchemeExporter.java create mode 100644 platform/platform-api/src/com/intellij/openapi/options/SchemeExporterException.java create mode 100644 platform/platform-impl/src/com/intellij/openapi/options/SchemeExporterEP.java diff --git a/platform/lang-impl/src/com/intellij/application/options/codeStyle/CodeStyleSchemeExporterUI.java b/platform/lang-impl/src/com/intellij/application/options/codeStyle/CodeStyleSchemeExporterUI.java new file mode 100644 index 000000000000..927485a3ad50 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/application/options/codeStyle/CodeStyleSchemeExporterUI.java @@ -0,0 +1,119 @@ +/* + * Copyright 2000-2015 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.application.options.codeStyle; + +import com.intellij.openapi.application.ApplicationBundle; +import com.intellij.openapi.fileChooser.FileChooserFactory; +import com.intellij.openapi.fileChooser.FileSaverDescriptor; +import com.intellij.openapi.fileChooser.FileSaverDialog; +import com.intellij.openapi.options.SchemeExporter; +import com.intellij.openapi.options.SchemeExporterEP; +import com.intellij.openapi.options.SchemeExporterException; +import com.intellij.openapi.ui.MessageType; +import com.intellij.openapi.ui.popup.JBPopupFactory; +import com.intellij.openapi.ui.popup.ListPopup; +import com.intellij.openapi.ui.popup.PopupStep; +import com.intellij.openapi.ui.popup.util.BaseListPopupStep; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.vfs.VirtualFileWrapper; +import com.intellij.psi.codeStyle.CodeStyleScheme; +import com.intellij.util.ArrayUtil; +import org.jetbrains.annotations.NotNull; + +import java.awt.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + + +/** + * @author Rustam Vishnyakov + */ +class CodeStyleSchemeExporterUI { + @NotNull private final Component myParentComponent; + @NotNull private final CodeStyleScheme myScheme; + @NotNull private final StatusCallback myStatusCallback; + + CodeStyleSchemeExporterUI(@NotNull Component parentComponent, @NotNull CodeStyleScheme scheme, @NotNull StatusCallback statusCallback) { + myParentComponent = parentComponent; + myScheme = scheme; + myStatusCallback = statusCallback; + } + + void export() { + ListPopup popup = JBPopupFactory.getInstance().createListPopup( + new BaseListPopupStep(ApplicationBundle.message("code.style.scheme.exporter.ui.export.as.title"), enumExporters()) { + @Override + public PopupStep onChosen(final String selectedValue, boolean finalChoice) { + return doFinalStep(new Runnable() { + @Override + public void run() { + exportSchemeUsing(selectedValue); + } + }); + } + }); + popup.showInCenterOf(myParentComponent); + } + + private static String[] enumExporters() { + List names = new ArrayList(); + Collection> extensions = SchemeExporterEP.getExtensions(CodeStyleScheme.class); + for (SchemeExporterEP extension : extensions) { + names.add(extension.name); + } + return ArrayUtil.toStringArray(names); + } + + private void exportSchemeUsing(@NotNull String exporterName) { + SchemeExporter exporter = SchemeExporterEP.getExporter(exporterName, CodeStyleScheme.class); + if (exporter != null) { + String ext = exporter.getExtension(); + FileSaverDialog saver = + FileChooserFactory.getInstance() + .createSaveFileDialog(new FileSaverDescriptor( + ApplicationBundle.message("code.style.scheme.exporter.ui.file.chooser.title"), + ApplicationBundle.message("code.style.scheme.exporter.ui.file.chooser.message"), + ext), myParentComponent); + VirtualFileWrapper target = saver.save(null, getFileNameSuggestion() + "." + ext); + if (target != null) { + VirtualFile targetFile = target.getVirtualFile(true); + if (targetFile != null) { + try { + exporter.exportScheme(myScheme, targetFile); + myStatusCallback + .showMessage(ApplicationBundle.message("code.style.scheme.exporter.ui.code.style.exported.message", myScheme.getName(), + targetFile.getPresentableUrl()), MessageType.INFO); + } + catch (SchemeExporterException e) { + myStatusCallback.showMessage(e.getMessage(), MessageType.ERROR); + } + } + else { + myStatusCallback.showMessage(ApplicationBundle.message("code.style.scheme.exporter.ui.cannot.write.message"), MessageType.ERROR); + } + } + } + } + + private String getFileNameSuggestion() { + return myScheme.getName(); + } + + interface StatusCallback { + void showMessage(@NotNull String message, @NotNull MessageType messageType); + } +} diff --git a/platform/lang-impl/src/com/intellij/application/options/codeStyle/ManageCodeStyleSchemesDialog.form b/platform/lang-impl/src/com/intellij/application/options/codeStyle/ManageCodeStyleSchemesDialog.form index 01eb0fb7de87..8c11896369d9 100644 --- a/platform/lang-impl/src/com/intellij/application/options/codeStyle/ManageCodeStyleSchemesDialog.form +++ b/platform/lang-impl/src/com/intellij/application/options/codeStyle/ManageCodeStyleSchemesDialog.form @@ -75,7 +75,7 @@ - + diff --git a/platform/lang-impl/src/com/intellij/application/options/codeStyle/ManageCodeStyleSchemesDialog.java b/platform/lang-impl/src/com/intellij/application/options/codeStyle/ManageCodeStyleSchemesDialog.java index 40acf0f0b279..495a80772124 100644 --- a/platform/lang-impl/src/com/intellij/application/options/codeStyle/ManageCodeStyleSchemesDialog.java +++ b/platform/lang-impl/src/com/intellij/application/options/codeStyle/ManageCodeStyleSchemesDialog.java @@ -22,10 +22,7 @@ import com.intellij.openapi.application.ApplicationBundle; import com.intellij.openapi.fileChooser.FileChooserDescriptor; import com.intellij.openapi.fileChooser.FileChooserDialog; import com.intellij.openapi.fileChooser.FileChooserFactory; -import com.intellij.openapi.options.SchemeFactory; -import com.intellij.openapi.options.SchemeImportException; -import com.intellij.openapi.options.SchemeImporter; -import com.intellij.openapi.options.SchemeImporterEP; +import com.intellij.openapi.options.*; import com.intellij.openapi.project.ProjectManager; import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.ui.MessageType; @@ -128,6 +125,19 @@ public class ManageCodeStyleSchemesDialog extends DialogWrapper { }); } + if (SchemeExporterEP.getExtensions(CodeStyleScheme.class).isEmpty()) { + myExportButton.setVisible(false); + } + else { + myExportButton.setVisible(true); + myExportButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + exportSelectedScheme(); + } + }); + } + init(); } @@ -453,4 +463,12 @@ public class ManageCodeStyleSchemesDialog extends DialogWrapper { } } + private void exportSelectedScheme() { + new CodeStyleSchemeExporterUI(myExportButton, getSelectedScheme(), new CodeStyleSchemeExporterUI.StatusCallback() { + @Override + public void showMessage(@NotNull String message, @NotNull MessageType messageType) { + showStatus(myExportButton, message, messageType); + } + }).export(); + } } diff --git a/platform/lang-impl/src/com/intellij/psi/impl/source/codeStyle/CodeStyleSchemeCopyExporter.java b/platform/lang-impl/src/com/intellij/psi/impl/source/codeStyle/CodeStyleSchemeCopyExporter.java new file mode 100644 index 000000000000..4714a0e541eb --- /dev/null +++ b/platform/lang-impl/src/com/intellij/psi/impl/source/codeStyle/CodeStyleSchemeCopyExporter.java @@ -0,0 +1,82 @@ +/* + * Copyright 2000-2015 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.psi.impl.source.codeStyle; + +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.options.SchemeExporter; +import com.intellij.openapi.options.SchemeExporterException; +import com.intellij.openapi.util.ThrowableComputable; +import com.intellij.openapi.util.WriteExternalException; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.codeStyle.CodeStyleScheme; +import org.jdom.Element; +import org.jdom.output.Format; +import org.jdom.output.XMLOutputter; +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; + +/** + * Exports (copies) a code style scheme to an external file as is. + * + * @author Rustam Vishnyakov + */ +public class CodeStyleSchemeCopyExporter implements SchemeExporter { + @Override + public void exportScheme(@NotNull final CodeStyleScheme scheme, @NotNull final VirtualFile targetFile) throws SchemeExporterException { + assert scheme instanceof CodeStyleSchemeImpl; + try { + final Element root = schemeToDom((CodeStyleSchemeImpl)scheme); + ApplicationManager.getApplication().runWriteAction(new ThrowableComputable() { + @Override + public Boolean compute() throws IOException { + writeToFile(targetFile, root); + return Boolean.TRUE; + } + }); + } + catch (Exception e) { + throw new SchemeExporterException("Export failed: " + e.getMessage()); + } + } + + @Override + public String getExtension() { + return "xml"; + } + + private static Element schemeToDom(@NotNull CodeStyleSchemeImpl scheme) throws WriteExternalException { + Element newElement = new Element("code_scheme"); + newElement.setAttribute("name", scheme.getName()); + scheme.writeExternal(newElement); + return newElement; + } + + private void writeToFile(@NotNull VirtualFile file, @NotNull Element element) throws IOException { + OutputStream targetStream = file.getOutputStream(this); + try { + OutputStreamWriter writer = new OutputStreamWriter(targetStream); + Format format = Format.getPrettyFormat(); + format.setLineSeparator("\n"); + new XMLOutputter(format).output(element, writer); + } + finally { + targetStream.close(); + } + } +} diff --git a/platform/platform-api/src/com/intellij/openapi/options/SchemeExporter.java b/platform/platform-api/src/com/intellij/openapi/options/SchemeExporter.java new file mode 100644 index 000000000000..c1139d2aad70 --- /dev/null +++ b/platform/platform-api/src/com/intellij/openapi/options/SchemeExporter.java @@ -0,0 +1,41 @@ +/* + * Copyright 2000-2015 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.options; + +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.annotations.NotNull; + +/** + * Base interface to export a scheme. + * + * @author Rustam Vishnyakov + */ +public interface SchemeExporter { + /** + * Writes a scheme to existing targetFile. An implementor may use targetFile.getOutputStream() method to + * obtain a stream to write to. + * + * @param scheme The scheme to export. + * @param targetFile The target virtual file. + * @throws SchemeExporterException + */ + void exportScheme(@NotNull T scheme, @NotNull VirtualFile targetFile) throws SchemeExporterException; + + /** + * @return Target file extension without a dot, for example "xml". + */ + String getExtension(); +} diff --git a/platform/platform-api/src/com/intellij/openapi/options/SchemeExporterException.java b/platform/platform-api/src/com/intellij/openapi/options/SchemeExporterException.java new file mode 100644 index 000000000000..55a12ee0a03f --- /dev/null +++ b/platform/platform-api/src/com/intellij/openapi/options/SchemeExporterException.java @@ -0,0 +1,25 @@ +/* + * Copyright 2000-2015 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.options; + +/** + * @author Rustam Vishnyakov + */ +public class SchemeExporterException extends Exception { + public SchemeExporterException(String message) { + super(message); + } +} diff --git a/platform/platform-impl/src/com/intellij/openapi/options/SchemeExporterEP.java b/platform/platform-impl/src/com/intellij/openapi/options/SchemeExporterEP.java new file mode 100644 index 000000000000..b41738d8d7ea --- /dev/null +++ b/platform/platform-impl/src/com/intellij/openapi/options/SchemeExporterEP.java @@ -0,0 +1,90 @@ +/* + * Copyright 2000-2015 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.options; + +import com.intellij.openapi.extensions.AbstractExtensionPointBean; +import com.intellij.openapi.extensions.ExtensionPointName; +import com.intellij.openapi.util.LazyInstance; +import com.intellij.util.xmlb.annotations.Attribute; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * Extension point for schemes exporters. + * + * @author Rustam Vishnyakov + */ +public class SchemeExporterEP extends AbstractExtensionPointBean { + public static final ExtensionPointName EP_NAME = ExtensionPointName.create("com.intellij.schemeExporter"); + + @Attribute("name") + public String name; + + @Attribute("schemeClass") + public String schemeClass; + + @Attribute("implementationClass") + public String implementationClass; + + private final LazyInstance> myExporterInstance = new LazyInstance>() { + @Override + protected Class> getInstanceClass() throws ClassNotFoundException { + return findClass(implementationClass); + } + }; + + public SchemeExporter getInstance() { + return myExporterInstance.getValue(); + } + + /** + * Finds extensions supporting the given schemeClass + * @param schemeClass The class of the scheme to search extensions for. + * @return A collection of exporters capable of exporting schemes of the given class. An empty collection is returned if there are + * no matching exporters. + */ + @NotNull + public static Collection> getExtensions(Class schemeClass) { + List> exporters = new ArrayList>(); + for (SchemeExporterEP exporterEP : EP_NAME.getExtensions()) { + if (schemeClass.getName().equals(exporterEP.schemeClass)) { + //noinspection unchecked + exporters.add((SchemeExporterEP)exporterEP); + } + } + return exporters; + } + + /** + * Find an exporter for the given name and scheme class. It is allowed for exporters to have the same name but different scheme classes. + * @param name The exporter name as defined in plug-in configuration. + * @param schemeClass The scheme class the exporter has to support. + * @return The found exporter or null if there are no exporters for the given name and scheme class. + */ + @Nullable + public static SchemeExporter getExporter(@NotNull String name, Class schemeClass) { + for (SchemeExporterEP exporterEP : getExtensions(schemeClass)) { + if (name.equals(exporterEP.name)) { + return exporterEP.getInstance(); + } + } + return null; + } +} diff --git a/platform/platform-resources-en/src/messages/ApplicationBundle.properties b/platform/platform-resources-en/src/messages/ApplicationBundle.properties index 663253c49fdc..f2d869de7904 100644 --- a/platform/platform-resources-en/src/messages/ApplicationBundle.properties +++ b/platform/platform-resources-en/src/messages/ApplicationBundle.properties @@ -506,6 +506,11 @@ message.code.style.scheme.already.exists=Scheme {0} already exists. Overwrite se code.style.scheme.import.unnamed=Unnamed message.code.style.scheme.import.success={0} settings were imported to {1} scheme.{2} message.code.style.scheme.import.failure={0} import failed with error message: {1} +code.style.scheme.exporter.ui.export.as.title=Export As +code.style.scheme.exporter.ui.file.chooser.title=Target File +code.style.scheme.exporter.ui.file.chooser.message=Export to +code.style.scheme.exporter.ui.code.style.exported.message={0} code style exported to {1} +code.style.scheme.exporter.ui.cannot.write.message=Cannot write to selected file/location. label.name=Name: error.scheme.must.have.a.name=Scheme must have a name diff --git a/platform/platform-resources/src/META-INF/LangExtensions.xml b/platform/platform-resources/src/META-INF/LangExtensions.xml index f0adcc31831e..7dc527d1a97d 100644 --- a/platform/platform-resources/src/META-INF/LangExtensions.xml +++ b/platform/platform-resources/src/META-INF/LangExtensions.xml @@ -928,6 +928,11 @@ + + diff --git a/platform/platform-resources/src/META-INF/PlatformExtensionPoints.xml b/platform/platform-resources/src/META-INF/PlatformExtensionPoints.xml index 70609a231b33..ebb00b13df86 100644 --- a/platform/platform-resources/src/META-INF/PlatformExtensionPoints.xml +++ b/platform/platform-resources/src/META-INF/PlatformExtensionPoints.xml @@ -219,6 +219,11 @@ + + + + +