Scheme exporter API and IDEA-90075 (Export Codestyle as XML File)

This commit is contained in:
Rustam Vishnyakov
2015-10-02 15:26:22 +03:00
parent 71c6c77578
commit 0d1adb345b
10 changed files with 395 additions and 5 deletions
@@ -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<String>(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<String> names = new ArrayList<String>();
Collection<SchemeExporterEP<CodeStyleScheme>> extensions = SchemeExporterEP.getExtensions(CodeStyleScheme.class);
for (SchemeExporterEP<CodeStyleScheme> extension : extensions) {
names.add(extension.name);
}
return ArrayUtil.toStringArray(names);
}
private void exportSchemeUsing(@NotNull String exporterName) {
SchemeExporter<CodeStyleScheme> 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);
}
}
@@ -75,7 +75,7 @@
<grid row="3" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value="Export..."/>
<text value="E&amp;xport..."/>
</properties>
</component>
<component id="816ef" class="javax.swing.JButton" binding="myImportButton" default-binding="true">
@@ -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();
}
}
@@ -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<CodeStyleScheme> {
@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<Boolean, IOException>() {
@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();
}
}
}
@@ -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 <T extends Scheme> {
/**
* Writes a scheme to existing <code>targetFile</code>. An implementor may use <code>targetFile.getOutputStream()</code> 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();
}
@@ -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);
}
}
@@ -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 <S extends Scheme> extends AbstractExtensionPointBean {
public static final ExtensionPointName<SchemeExporterEP> 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<SchemeExporter<S>> myExporterInstance = new LazyInstance<SchemeExporter<S>>() {
@Override
protected Class<SchemeExporter<S>> getInstanceClass() throws ClassNotFoundException {
return findClass(implementationClass);
}
};
public SchemeExporter<S> getInstance() {
return myExporterInstance.getValue();
}
/**
* Finds extensions supporting the given <code>schemeClass</code>
* @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 <S extends Scheme> Collection<SchemeExporterEP<S>> getExtensions(Class<S> schemeClass) {
List<SchemeExporterEP<S>> exporters = new ArrayList<SchemeExporterEP<S>>();
for (SchemeExporterEP<?> exporterEP : EP_NAME.getExtensions()) {
if (schemeClass.getName().equals(exporterEP.schemeClass)) {
//noinspection unchecked
exporters.add((SchemeExporterEP<S>)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 <S extends Scheme> SchemeExporter<S> getExporter(@NotNull String name, Class<S> schemeClass) {
for (SchemeExporterEP<S> exporterEP : getExtensions(schemeClass)) {
if (name.equals(exporterEP.name)) {
return exporterEP.getInstance();
}
}
return null;
}
}
@@ -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
@@ -928,6 +928,11 @@
<smartPointerElementInfoFactory implementation="com.intellij.psi.impl.smartPointers.AnchorElementInfoFactory"/>
<codeInsight.lineMarkerProvider language="" implementationClass="com.intellij.execution.lineMarker.RunLineMarkerProvider"/>
<projectService serviceImplementation="com.intellij.execution.TestStateStorage"/>
<schemeExporter
name="Code Style XML File"
schemeClass="com.intellij.psi.codeStyle.CodeStyleScheme"
implementationClass="com.intellij.psi.impl.source.codeStyle.CodeStyleSchemeCopyExporter"/>
</extensions>
</idea-plugin>
@@ -219,6 +219,11 @@
<with attribute="implementationClass" implements="com.intellij.openapi.options.SchemeImporter"/>
</extensionPoint>
<extensionPoint name="schemeExporter" beanClass="com.intellij.openapi.options.SchemeExporterEP">
<with attribute="schemeClass" implements="com.intellij.openapi.options.Scheme"/>
<with attribute="implementationClass" implements="com.intellij.openapi.options.SchemeExporter"/>
</extensionPoint>
<extensionPoint qualifiedName="org.jetbrains.urlOpener" interface="com.intellij.ide.browsers.UrlOpener"/>
<extensionPoint name="tipAndTrick" beanClass="com.intellij.ide.util.TipAndTrickBean"/>