/******************************************************************************* * Copyright (c) 2000, 2008 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.wst.jsdt.internal.ui.preferences.formatter; 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.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; 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.preferences.IEclipsePreferences; import org.eclipse.core.runtime.preferences.IScopeContext; import org.eclipse.wst.jsdt.internal.ui.JavaScriptPlugin; import org.eclipse.wst.jsdt.internal.ui.JavaUIException; import org.eclipse.wst.jsdt.internal.ui.JavaUIStatus; import org.eclipse.wst.jsdt.internal.ui.preferences.formatter.ProfileManager.CustomProfile; import org.eclipse.wst.jsdt.internal.ui.preferences.formatter.ProfileManager.Profile; import org.eclipse.wst.jsdt.ui.JavaScriptUI; 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 { /** The default encoding to use */ public static final String ENCODING= "UTF-8"; //$NON-NLS-1$ protected static final String VERSION_KEY_SUFFIX= ".version"; //$NON-NLS-1$ /** * A SAX event handler to parse the xml format for profiles. */ private final static class ProfileDefaultHandler extends DefaultHandler { private List fProfiles; private int fVersion; private String fName; private Map fSettings; private String fKind; 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); fKind= attributes.getValue(XML_ATTRIBUTE_PROFILE_KIND); if (fKind == null) //Can only be an CodeFormatterProfile created pre 3.3M2 fKind= ProfileVersioner.CODE_FORMATTER_PROFILE_KIND; fSettings= new HashMap(200); } else if (qName.equals(XML_NODE_ROOT)) { fProfiles= new ArrayList(); try { fVersion= Integer.parseInt(attributes.getValue(XML_ATTRIBUTE_VERSION)); } catch (NumberFormatException ex) { throw new SAXException(ex); } } } public void endElement(String uri, String localName, String qName) { if (qName.equals(XML_NODE_PROFILE)) { fProfiles.add(new CustomProfile(fName, fSettings, fVersion, fKind)); fName= null; fSettings= null; fKind= null; } } public List 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_KIND= "kind"; //$NON-NLS-1$ private final static String XML_ATTRIBUTE_VALUE= "value"; //$NON-NLS-1$ private final IProfileVersioner fProfileVersioner; private final String fProfilesKey; private final String fProfilesVersionKey; public ProfileStore(String profilesKey, IProfileVersioner profileVersioner) { fProfilesKey= profilesKey; fProfileVersioner= profileVersioner; fProfilesVersionKey= profilesKey + VERSION_KEY_SUFFIX; } /** * @return Returns the collection of profiles currently stored in the preference store or * <code>null</code> if the loading failed. The elements are of type {@link ProfileManager.CustomProfile} * and are all updated to the latest version. * @throws CoreException */ public List readProfiles(IScopeContext scope) throws CoreException { return readProfilesFromString(scope.getNode(JavaScriptUI.ID_PLUGIN).get(fProfilesKey, null)); } public void writeProfiles(Collection profiles, IScopeContext instanceScope) throws CoreException { ByteArrayOutputStream stream= new ByteArrayOutputStream(2000); try { writeProfilesToStream(profiles, stream, ENCODING, fProfileVersioner); String val; try { val= stream.toString(ENCODING); } catch (UnsupportedEncodingException e) { val= stream.toString(); } IEclipsePreferences uiPreferences = instanceScope.getNode(JavaScriptUI.ID_PLUGIN); uiPreferences.put(fProfilesKey, val); uiPreferences.putInt(fProfilesVersionKey, fProfileVersioner.getCurrentVersion()); } finally { try { stream.close(); } catch (IOException e) { /* ignore */ } } } public List readProfilesFromString(String profiles) throws CoreException { if (profiles != null && profiles.length() > 0) { byte[] bytes; try { bytes= profiles.getBytes(ENCODING); } catch (UnsupportedEncodingException e) { bytes= profiles.getBytes(); } InputStream is= new ByteArrayInputStream(bytes); try { List res= readProfilesFromStream(new InputSource(is)); if (res != null) { for (int i= 0; i < res.size(); i++) { fProfileVersioner.update((CustomProfile) res.get(i)); } } return res; } finally { try { is.close(); } catch (IOException e) { /* ignore */ } } } return null; } /** * 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 */ public List readProfilesFromFile(File file) throws CoreException { try { final FileInputStream reader= new FileInputStream(file); try { return readProfilesFromStream(new InputSource(reader)); } finally { try { reader.close(); } catch (IOException e) { /* ignore */ } } } catch (IOException e) { throw createException(e, FormatterMessages.CodingStyleConfigurationBlock_error_reading_xml_message); } } /** * 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 */ public static List readProfilesFromStream(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, FormatterMessages.CodingStyleConfigurationBlock_error_reading_xml_message); } catch (IOException e) { throw createException(e, FormatterMessages.CodingStyleConfigurationBlock_error_reading_xml_message); } catch (ParserConfigurationException e) { throw createException(e, FormatterMessages.CodingStyleConfigurationBlock_error_reading_xml_message); } 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 */ public void writeProfilesToFile(Collection profiles, File file, String encoding) throws CoreException { final OutputStream stream; try { stream= new FileOutputStream(file); try { writeProfilesToStream(profiles, stream, encoding, fProfileVersioner); } finally { try { stream.close(); } catch (IOException e) { /* ignore */ } } } catch (IOException e) { throw createException(e, FormatterMessages.CodingStyleConfigurationBlock_error_serializing_xml_message); } } /** * 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 profiles, OutputStream stream, String encoding, IProfileVersioner profileVersioner) 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); rootElement.setAttribute(XML_ATTRIBUTE_VERSION, Integer.toString(profileVersioner.getCurrentVersion())); document.appendChild(rootElement); for(final Iterator iter= profiles.iterator(); iter.hasNext();) { final Profile profile= (Profile)iter.next(); if (profile.isProfileToSave()) { final Element profileElement= createProfileElement(profile, document, profileVersioner); 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, FormatterMessages.CodingStyleConfigurationBlock_error_serializing_xml_message); } catch (ParserConfigurationException e) { throw createException(e, FormatterMessages.CodingStyleConfigurationBlock_error_serializing_xml_message); } } /* * Create a new profile element in the specified document. The profile is not added * to the document by this method. */ private static Element createProfileElement(Profile profile, Document document, IProfileVersioner profileVersioner) { final Element element= document.createElement(XML_NODE_PROFILE); element.setAttribute(XML_ATTRIBUTE_NAME, profile.getName()); element.setAttribute(XML_ATTRIBUTE_VERSION, Integer.toString(profile.getVersion())); element.setAttribute(XML_ATTRIBUTE_PROFILE_KIND, profileVersioner.getProfileKind()); final Iterator keyIter= profile.getSettings().keySet().iterator(); while (keyIter.hasNext()) { final String key= (String)keyIter.next(); final String value= (String)profile.getSettings().get(key); 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 { JavaScriptPlugin.logErrorMessage("ProfileStore: Profile does not contain value for key " + key); //$NON-NLS-1$ } } return element; } /* * Creates a UI exception for logging purposes */ private static JavaUIException createException(Throwable t, String message) { return new JavaUIException(JavaUIStatus.createError(IStatus.ERROR, message, t)); } }