001: /*******************************************************************************
002: * Copyright (c) 2000, 2006 IBM Corporation and others.
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * IBM Corporation - initial API and implementation
010: *******************************************************************************/package org.eclipse.jdt.internal.ui.preferences.formatter;
011:
012: import java.io.ByteArrayInputStream;
013: import java.io.ByteArrayOutputStream;
014: import java.io.File;
015: import java.io.FileInputStream;
016: import java.io.FileOutputStream;
017: import java.io.IOException;
018: import java.io.InputStream;
019: import java.io.OutputStream;
020: import java.io.UnsupportedEncodingException;
021: import java.util.ArrayList;
022: import java.util.Collection;
023: import java.util.HashMap;
024: import java.util.Iterator;
025: import java.util.List;
026: import java.util.Map;
027:
028: import javax.xml.parsers.DocumentBuilder;
029: import javax.xml.parsers.DocumentBuilderFactory;
030: import javax.xml.parsers.ParserConfigurationException;
031: import javax.xml.parsers.SAXParser;
032: import javax.xml.parsers.SAXParserFactory;
033: import javax.xml.transform.OutputKeys;
034: import javax.xml.transform.Transformer;
035: import javax.xml.transform.TransformerException;
036: import javax.xml.transform.TransformerFactory;
037: import javax.xml.transform.dom.DOMSource;
038: import javax.xml.transform.stream.StreamResult;
039:
040: import org.eclipse.core.runtime.CoreException;
041: import org.eclipse.core.runtime.IStatus;
042: import org.eclipse.core.runtime.preferences.IEclipsePreferences;
043: import org.eclipse.core.runtime.preferences.IScopeContext;
044:
045: import org.eclipse.jdt.ui.JavaUI;
046:
047: import org.eclipse.jdt.internal.ui.JavaPlugin;
048: import org.eclipse.jdt.internal.ui.JavaUIException;
049: import org.eclipse.jdt.internal.ui.JavaUIStatus;
050: import org.eclipse.jdt.internal.ui.preferences.formatter.ProfileManager.CustomProfile;
051: import org.eclipse.jdt.internal.ui.preferences.formatter.ProfileManager.Profile;
052:
053: import org.w3c.dom.Document;
054: import org.w3c.dom.Element;
055: import org.xml.sax.Attributes;
056: import org.xml.sax.InputSource;
057: import org.xml.sax.SAXException;
058: import org.xml.sax.helpers.DefaultHandler;
059:
060: /**
061: * Can load/store profiles from/to profilesKey
062: */
063: public class ProfileStore {
064:
065: /** The default encoding to use */
066: public static final String ENCODING = "UTF-8"; //$NON-NLS-1$
067:
068: protected static final String VERSION_KEY_SUFFIX = ".version"; //$NON-NLS-1$
069:
070: /**
071: * A SAX event handler to parse the xml format for profiles.
072: */
073: private final static class ProfileDefaultHandler extends
074: DefaultHandler {
075:
076: private List fProfiles;
077: private int fVersion;
078:
079: private String fName;
080: private Map fSettings;
081: private String fKind;
082:
083: public void startElement(String uri, String localName,
084: String qName, Attributes attributes)
085: throws SAXException {
086:
087: if (qName.equals(XML_NODE_SETTING)) {
088:
089: final String key = attributes
090: .getValue(XML_ATTRIBUTE_ID);
091: final String value = attributes
092: .getValue(XML_ATTRIBUTE_VALUE);
093: fSettings.put(key, value);
094:
095: } else if (qName.equals(XML_NODE_PROFILE)) {
096:
097: fName = attributes.getValue(XML_ATTRIBUTE_NAME);
098: fKind = attributes.getValue(XML_ATTRIBUTE_PROFILE_KIND);
099: if (fKind == null) //Can only be an CodeFormatterProfile created pre 3.3M2
100: fKind = ProfileVersioner.CODE_FORMATTER_PROFILE_KIND;
101:
102: fSettings = new HashMap(200);
103:
104: } else if (qName.equals(XML_NODE_ROOT)) {
105:
106: fProfiles = new ArrayList();
107: try {
108: fVersion = Integer.parseInt(attributes
109: .getValue(XML_ATTRIBUTE_VERSION));
110: } catch (NumberFormatException ex) {
111: throw new SAXException(ex);
112: }
113:
114: }
115: }
116:
117: public void endElement(String uri, String localName,
118: String qName) {
119: if (qName.equals(XML_NODE_PROFILE)) {
120: fProfiles.add(new CustomProfile(fName, fSettings,
121: fVersion, fKind));
122: fName = null;
123: fSettings = null;
124: fKind = null;
125: }
126: }
127:
128: public List getProfiles() {
129: return fProfiles;
130: }
131:
132: }
133:
134: /**
135: * Identifiers for the XML file.
136: */
137: private final static String XML_NODE_ROOT = "profiles"; //$NON-NLS-1$
138: private final static String XML_NODE_PROFILE = "profile"; //$NON-NLS-1$
139: private final static String XML_NODE_SETTING = "setting"; //$NON-NLS-1$
140:
141: private final static String XML_ATTRIBUTE_VERSION = "version"; //$NON-NLS-1$
142: private final static String XML_ATTRIBUTE_ID = "id"; //$NON-NLS-1$
143: private final static String XML_ATTRIBUTE_NAME = "name"; //$NON-NLS-1$
144: private final static String XML_ATTRIBUTE_PROFILE_KIND = "kind"; //$NON-NLS-1$
145: private final static String XML_ATTRIBUTE_VALUE = "value"; //$NON-NLS-1$
146:
147: private final IProfileVersioner fProfileVersioner;
148: private final String fProfilesKey;
149: private final String fProfilesVersionKey;
150:
151: public ProfileStore(String profilesKey,
152: IProfileVersioner profileVersioner) {
153: fProfilesKey = profilesKey;
154: fProfileVersioner = profileVersioner;
155: fProfilesVersionKey = profilesKey + VERSION_KEY_SUFFIX;
156: }
157:
158: /**
159: * @return Returns the collection of profiles currently stored in the preference store or
160: * <code>null</code> if the loading failed. The elements are of type {@link ProfileManager.CustomProfile}
161: * and are all updated to the latest version.
162: * @throws CoreException
163: */
164: public List readProfiles(IScopeContext scope) throws CoreException {
165: return readProfilesFromString(scope.getNode(JavaUI.ID_PLUGIN)
166: .get(fProfilesKey, null));
167: }
168:
169: public void writeProfiles(Collection profiles,
170: IScopeContext instanceScope) throws CoreException {
171: ByteArrayOutputStream stream = new ByteArrayOutputStream(2000);
172: try {
173: writeProfilesToStream(profiles, stream, ENCODING,
174: fProfileVersioner);
175: String val;
176: try {
177: val = stream.toString(ENCODING);
178: } catch (UnsupportedEncodingException e) {
179: val = stream.toString();
180: }
181: IEclipsePreferences uiPreferences = instanceScope
182: .getNode(JavaUI.ID_PLUGIN);
183: uiPreferences.put(fProfilesKey, val);
184: uiPreferences.putInt(fProfilesVersionKey, fProfileVersioner
185: .getCurrentVersion());
186: } finally {
187: try {
188: stream.close();
189: } catch (IOException e) { /* ignore */
190: }
191: }
192: }
193:
194: public List readProfilesFromString(String profiles)
195: throws CoreException {
196: if (profiles != null && profiles.length() > 0) {
197: byte[] bytes;
198: try {
199: bytes = profiles.getBytes(ENCODING);
200: } catch (UnsupportedEncodingException e) {
201: bytes = profiles.getBytes();
202: }
203: InputStream is = new ByteArrayInputStream(bytes);
204: try {
205: List res = readProfilesFromStream(new InputSource(is));
206: if (res != null) {
207: for (int i = 0; i < res.size(); i++) {
208: fProfileVersioner.update((CustomProfile) res
209: .get(i));
210: }
211: }
212: return res;
213: } finally {
214: try {
215: is.close();
216: } catch (IOException e) { /* ignore */
217: }
218: }
219: }
220: return null;
221: }
222:
223: /**
224: * Read the available profiles from the internal XML file and return them
225: * as collection or <code>null</code> if the file is not a profile file.
226: * @param file The file to read from
227: * @return returns a list of <code>CustomProfile</code> or <code>null</code>
228: * @throws CoreException
229: */
230: public List readProfilesFromFile(File file) throws CoreException {
231: try {
232: final FileInputStream reader = new FileInputStream(file);
233: try {
234: return readProfilesFromStream(new InputSource(reader));
235: } finally {
236: try {
237: reader.close();
238: } catch (IOException e) { /* ignore */
239: }
240: }
241: } catch (IOException e) {
242: throw createException(
243: e,
244: FormatterMessages.CodingStyleConfigurationBlock_error_reading_xml_message);
245: }
246: }
247:
248: /**
249: * Load profiles from a XML stream and add them to a map or <code>null</code> if the source is not a profile store.
250: * @param inputSource The input stream
251: * @return returns a list of <code>CustomProfile</code> or <code>null</code>
252: * @throws CoreException
253: */
254: public static List readProfilesFromStream(InputSource inputSource)
255: throws CoreException {
256:
257: final ProfileDefaultHandler handler = new ProfileDefaultHandler();
258: try {
259: final SAXParserFactory factory = SAXParserFactory
260: .newInstance();
261: final SAXParser parser = factory.newSAXParser();
262: parser.parse(inputSource, handler);
263: } catch (SAXException e) {
264: throw createException(
265: e,
266: FormatterMessages.CodingStyleConfigurationBlock_error_reading_xml_message);
267: } catch (IOException e) {
268: throw createException(
269: e,
270: FormatterMessages.CodingStyleConfigurationBlock_error_reading_xml_message);
271: } catch (ParserConfigurationException e) {
272: throw createException(
273: e,
274: FormatterMessages.CodingStyleConfigurationBlock_error_reading_xml_message);
275: }
276: return handler.getProfiles();
277: }
278:
279: /**
280: * Write the available profiles to the internal XML file.
281: * @param profiles List of <code>CustomProfile</code>
282: * @param file File to write
283: * @param encoding the encoding to use
284: * @throws CoreException
285: */
286: public void writeProfilesToFile(Collection profiles, File file,
287: String encoding) throws CoreException {
288: final OutputStream stream;
289: try {
290: stream = new FileOutputStream(file);
291: try {
292: writeProfilesToStream(profiles, stream, encoding,
293: fProfileVersioner);
294: } finally {
295: try {
296: stream.close();
297: } catch (IOException e) { /* ignore */
298: }
299: }
300: } catch (IOException e) {
301: throw createException(
302: e,
303: FormatterMessages.CodingStyleConfigurationBlock_error_serializing_xml_message);
304: }
305: }
306:
307: /**
308: * Save profiles to an XML stream
309: * @param profiles the list of <code>CustomProfile</code>
310: * @param stream the stream to write to
311: * @param encoding the encoding to use
312: * @throws CoreException
313: */
314: public static void writeProfilesToStream(Collection profiles,
315: OutputStream stream, String encoding,
316: IProfileVersioner profileVersioner) throws CoreException {
317:
318: try {
319: final DocumentBuilderFactory factory = DocumentBuilderFactory
320: .newInstance();
321: final DocumentBuilder builder = factory
322: .newDocumentBuilder();
323: final Document document = builder.newDocument();
324:
325: final Element rootElement = document
326: .createElement(XML_NODE_ROOT);
327: rootElement.setAttribute(XML_ATTRIBUTE_VERSION, Integer
328: .toString(profileVersioner.getCurrentVersion()));
329:
330: document.appendChild(rootElement);
331:
332: for (final Iterator iter = profiles.iterator(); iter
333: .hasNext();) {
334: final Profile profile = (Profile) iter.next();
335: if (profile.isProfileToSave()) {
336: final Element profileElement = createProfileElement(
337: profile, document, profileVersioner);
338: rootElement.appendChild(profileElement);
339: }
340: }
341:
342: Transformer transformer = TransformerFactory.newInstance()
343: .newTransformer();
344: transformer.setOutputProperty(OutputKeys.METHOD, "xml"); //$NON-NLS-1$
345: transformer
346: .setOutputProperty(OutputKeys.ENCODING, encoding);
347: transformer.setOutputProperty(OutputKeys.INDENT, "yes"); //$NON-NLS-1$
348: transformer.transform(new DOMSource(document),
349: new StreamResult(stream));
350: } catch (TransformerException e) {
351: throw createException(
352: e,
353: FormatterMessages.CodingStyleConfigurationBlock_error_serializing_xml_message);
354: } catch (ParserConfigurationException e) {
355: throw createException(
356: e,
357: FormatterMessages.CodingStyleConfigurationBlock_error_serializing_xml_message);
358: }
359: }
360:
361: /*
362: * Create a new profile element in the specified document. The profile is not added
363: * to the document by this method.
364: */
365: private static Element createProfileElement(Profile profile,
366: Document document, IProfileVersioner profileVersioner) {
367: final Element element = document
368: .createElement(XML_NODE_PROFILE);
369: element.setAttribute(XML_ATTRIBUTE_NAME, profile.getName());
370: element.setAttribute(XML_ATTRIBUTE_VERSION, Integer
371: .toString(profile.getVersion()));
372: element.setAttribute(XML_ATTRIBUTE_PROFILE_KIND,
373: profileVersioner.getProfileKind());
374:
375: final Iterator keyIter = profile.getSettings().keySet()
376: .iterator();
377:
378: while (keyIter.hasNext()) {
379: final String key = (String) keyIter.next();
380: final String value = (String) profile.getSettings()
381: .get(key);
382: if (value != null) {
383: final Element setting = document
384: .createElement(XML_NODE_SETTING);
385: setting.setAttribute(XML_ATTRIBUTE_ID, key);
386: setting.setAttribute(XML_ATTRIBUTE_VALUE, value);
387: element.appendChild(setting);
388: } else {
389: JavaPlugin
390: .logErrorMessage("ProfileStore: Profile does not contain value for key " + key); //$NON-NLS-1$
391: }
392: }
393: return element;
394: }
395:
396: /*
397: * Creates a UI exception for logging purposes
398: */
399: private static JavaUIException createException(Throwable t,
400: String message) {
401: return new JavaUIException(JavaUIStatus.createError(
402: IStatus.ERROR, message, t));
403: }
404: }
|