blob: d032b835b34a6e2918057a00d37225b27c8a79ec [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2017 IBM Corporation and others.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
* xored software, Inc. - initial API and Implementation (Yuri Strot)
*******************************************************************************/
package org.eclipse.dltk.formatter.profile;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.dltk.internal.ui.DLTKUIException;
import org.eclipse.dltk.ui.DLTKUIPlugin;
import org.eclipse.dltk.ui.formatter.IProfile;
import org.eclipse.dltk.ui.formatter.IProfileStore;
import org.eclipse.dltk.ui.formatter.IProfileVersioner;
import org.eclipse.osgi.util.NLS;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
/**
* Can load/store profiles from/to profilesKey
*/
public class ProfileStore implements IProfileStore {
/** The default encoding to use */
public static final String ENCODING = "UTF-8"; //$NON-NLS-1$
/**
* A SAX event handler to parse the xml format for profiles.
*/
private final class ProfileDefaultHandler extends DefaultHandler {
private List<IProfile> fProfiles;
private int fVersion;
private String fName;
private Map<String, String> fSettings;
private String fFormatter;
@Override
public void startElement(String uri, String localName, String qName,
Attributes attributes) throws SAXException {
if (qName.equals(XML_NODE_SETTING)) {
final String key = attributes.getValue(XML_ATTRIBUTE_ID);
final String value = attributes.getValue(XML_ATTRIBUTE_VALUE);
fSettings.put(key, value);
} else if (qName.equals(XML_NODE_PROFILE)) {
fName = attributes.getValue(XML_ATTRIBUTE_NAME);
fFormatter = attributes
.getValue(XML_ATTRIBUTE_PROFILE_FORMATTER);
try {
fVersion = Integer.parseInt(
attributes.getValue(XML_ATTRIBUTE_VERSION));
} catch (NumberFormatException ex) {
throw new SAXException(ex);
}
fSettings = new HashMap<>(200);
} else if (qName.equals(XML_NODE_ROOT)) {
fProfiles = new ArrayList<>();
}
}
@Override
public void endElement(String uri, String localName, String qName) {
if (qName.equals(XML_NODE_PROFILE)) {
for (Map.Entry<String, String> entry : defaults.entrySet()) {
if (!fSettings.containsKey(entry.getKey())) {
fSettings.put(entry.getKey(), entry.getValue());
}
}
fProfiles.add(new CustomProfile(fName, fSettings, fFormatter,
fVersion));
fName = null;
fSettings = null;
fFormatter = null;
fVersion = 1;
}
}
public List<IProfile> getProfiles() {
return fProfiles;
}
}
/**
* Identifiers for the XML file.
*/
private final static String XML_NODE_ROOT = "profiles"; //$NON-NLS-1$
private final static String XML_NODE_PROFILE = "profile"; //$NON-NLS-1$
private final static String XML_NODE_SETTING = "setting"; //$NON-NLS-1$
private final static String XML_ATTRIBUTE_VERSION = "version"; //$NON-NLS-1$
private final static String XML_ATTRIBUTE_ID = "id"; //$NON-NLS-1$
private final static String XML_ATTRIBUTE_NAME = "name"; //$NON-NLS-1$
private final static String XML_ATTRIBUTE_PROFILE_FORMATTER = "formatter"; //$NON-NLS-1$
private final static String XML_ATTRIBUTE_VALUE = "value"; //$NON-NLS-1$
private final Map<String, String> defaults;
public ProfileStore(IProfileVersioner versioner,
Map<String, String> defaults) {
this.versioner = versioner;
this.defaults = defaults;
}
/**
* @return the profile versioner
*/
public IProfileVersioner getVersioner() {
return versioner;
}
public String writeProfiles(Collection<IProfile> profiles)
throws CoreException {
ByteArrayOutputStream stream = new ByteArrayOutputStream(2000);
try {
writeProfilesToStream(profiles, stream, ENCODING, versioner);
String val;
try {
val = stream.toString(ENCODING);
} catch (UnsupportedEncodingException e) {
val = stream.toString();
}
return val;
} finally {
try {
stream.close();
} catch (IOException e) { /* ignore */
}
}
}
public List<IProfile> readProfilesFromString(String profiles)
throws CoreException {
if (profiles != null && profiles.length() > 0) {
byte[] bytes = profiles.getBytes(StandardCharsets.UTF_8);
try (InputStream is = new ByteArrayInputStream(bytes)) {
List<IProfile> res = readProfilesFromStream(is);
if (res != null) {
for (int i = 0; i < res.size(); i++) {
versioner.update(res.get(i));
}
}
return res;
} catch (IOException e) {
}
}
return null;
}
@Override
public List<IProfile> readProfilesFromStream(InputStream is)
throws CoreException {
return readProfilesFromSource(new InputSource(is));
}
/**
* Read the available profiles from the internal XML file and return them as
* collection or <code>null</code> if the file is not a profile file.
*
* @param file
* The file to read from
* @return returns a list of <code>CustomProfile</code> or <code>null</code>
* @throws CoreException
*/
@Override
public List<IProfile> readProfilesFromFile(File file) throws CoreException {
try (final FileInputStream reader = new FileInputStream(file)) {
return readProfilesFromStream(reader);
} catch (IOException e) {
throw createException(e,
ProfilesMessages.ProfileStore_readingProblems);
}
}
/**
* Load profiles from a XML stream and add them to a map or
* <code>null</code> if the source is not a profile store.
*
* @param inputSource
* The input stream
* @return returns a list of <code>CustomProfile</code> or <code>null</code>
* @throws CoreException
*/
private List<IProfile> readProfilesFromSource(InputSource inputSource)
throws CoreException {
final ProfileDefaultHandler handler = new ProfileDefaultHandler();
try {
final SAXParserFactory factory = SAXParserFactory.newInstance();
final SAXParser parser = factory.newSAXParser();
parser.parse(inputSource, handler);
} catch (SAXException e) {
throw createException(e,
ProfilesMessages.ProfileStore_readingProblems);
} catch (IOException e) {
throw createException(e,
ProfilesMessages.ProfileStore_readingProblems);
} catch (ParserConfigurationException e) {
throw createException(e,
ProfilesMessages.ProfileStore_readingProblems);
}
return handler.getProfiles();
}
/**
* Write the available profiles to the internal XML file.
*
* @param profiles
* List of <code>CustomProfile</code>
* @param file
* File to write
* @param encoding
* the encoding to use
* @throws CoreException
*/
@Override
public void writeProfilesToFile(Collection<IProfile> profiles, File file)
throws CoreException {
try (final OutputStream stream = new FileOutputStream(file)) {
writeProfilesToStream(profiles, stream, ENCODING, versioner);
} catch (IOException e) {
throw createException(e,
ProfilesMessages.ProfileStore_serializingProblems);
}
}
/**
* Save profiles to an XML stream
*
* @param profiles
* the list of <code>CustomProfile</code>
* @param stream
* the stream to write to
* @param encoding
* the encoding to use
* @throws CoreException
*/
public static void writeProfilesToStream(Collection<IProfile> profiles,
OutputStream stream, String encoding, IProfileVersioner versioner)
throws CoreException {
try {
final DocumentBuilderFactory factory = DocumentBuilderFactory
.newInstance();
final DocumentBuilder builder = factory.newDocumentBuilder();
final Document document = builder.newDocument();
final Element rootElement = document.createElement(XML_NODE_ROOT);
document.appendChild(rootElement);
for (final IProfile profile : profiles) {
if (!profile.isBuiltInProfile()) {
final Element profileElement = createProfileElement(profile,
document, versioner);
rootElement.appendChild(profileElement);
}
}
Transformer transformer = TransformerFactory.newInstance()
.newTransformer();
transformer.setOutputProperty(OutputKeys.METHOD, "xml"); //$NON-NLS-1$
transformer.setOutputProperty(OutputKeys.ENCODING, encoding);
transformer.setOutputProperty(OutputKeys.INDENT, "yes"); //$NON-NLS-1$
transformer.transform(new DOMSource(document),
new StreamResult(stream));
} catch (TransformerException e) {
throw createException(e,
ProfilesMessages.ProfileStore_serializingProblems);
} catch (ParserConfigurationException e) {
throw createException(e,
ProfilesMessages.ProfileStore_serializingProblems);
}
}
/*
* Create a new profile element in the specified document. The profile is
* not added to the document by this method.
*/
private static Element createProfileElement(IProfile profile,
Document document, IProfileVersioner versioner) {
final Element element = document.createElement(XML_NODE_PROFILE);
element.setAttribute(XML_ATTRIBUTE_NAME, profile.getName());
element.setAttribute(XML_ATTRIBUTE_PROFILE_FORMATTER,
profile.getFormatterId());
element.setAttribute(XML_ATTRIBUTE_VERSION,
Integer.toString(profile.getVersion()));
for (Map.Entry<String, String> entry : profile.getSettings()
.entrySet()) {
final String key = entry.getKey();
final String value = entry.getValue();
if (value != null) {
final Element setting = document
.createElement(XML_NODE_SETTING);
setting.setAttribute(XML_ATTRIBUTE_ID, key);
setting.setAttribute(XML_ATTRIBUTE_VALUE, value);
element.appendChild(setting);
} else {
DLTKUIPlugin.logErrorMessage(NLS.bind(
ProfilesMessages.ProfileStore_noValueForKey, key));
}
}
return element;
}
private IProfileVersioner versioner;
/*
* Creates a UI exception for logging purposes
*/
private static DLTKUIException createException(Throwable t,
String message) {
return new DLTKUIException(
new Status(IStatus.ERROR, DLTKUIPlugin.PLUGIN_ID, message, t));
}
}