001: /* Licensed to the Apache Software Foundation (ASF) under one or more
002: * contributor license agreements. See the NOTICE file distributed with
003: * this work for additional information regarding copyright ownership.
004: * The ASF licenses this file to You under the Apache License, Version 2.0
005: * (the "License"); you may not use this file except in compliance with
006: * the License. You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package java.util.prefs;
018:
019: import java.io.BufferedInputStream;
020: import java.io.BufferedWriter;
021: import java.io.File;
022: import java.io.FileInputStream;
023: import java.io.FileOutputStream;
024: import java.io.IOException;
025: import java.io.InputStream;
026: import java.io.OutputStream;
027: import java.io.OutputStreamWriter;
028: import java.io.StringReader;
029: import java.nio.channels.FileChannel;
030: import java.nio.channels.FileLock;
031: import java.security.AccessController;
032: import java.security.PrivilegedAction;
033: import java.security.PrivilegedActionException;
034: import java.security.PrivilegedExceptionAction;
035: import java.util.Properties;
036: import java.util.StringTokenizer;
037:
038: import javax.xml.parsers.DocumentBuilder;
039: import javax.xml.parsers.DocumentBuilderFactory;
040: import javax.xml.parsers.FactoryConfigurationError;
041: import javax.xml.parsers.ParserConfigurationException;
042: import javax.xml.transform.TransformerException;
043:
044: import org.apache.harmony.prefs.internal.nls.Messages;
045: import org.apache.xpath.XPathAPI;
046: import org.w3c.dom.Document;
047: import org.w3c.dom.Element;
048: import org.w3c.dom.NodeList;
049: import org.xml.sax.EntityResolver;
050: import org.xml.sax.ErrorHandler;
051: import org.xml.sax.InputSource;
052: import org.xml.sax.SAXException;
053: import org.xml.sax.SAXParseException;
054:
055: /**
056: * Utility class for the Preferences import/export from XML file.
057: *
058: */
059: class XMLParser {
060:
061: /*
062: * Constant - the specified DTD URL
063: */
064: static final String PREFS_DTD_NAME = "http://java.sun.com/dtd/preferences.dtd"; //$NON-NLS-1$
065:
066: /*
067: * Constant - the DTD string
068: */
069: static final String PREFS_DTD = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" //$NON-NLS-1$
070: + " <!ELEMENT preferences (root)>" //$NON-NLS-1$
071: + " <!ATTLIST preferences EXTERNAL_XML_VERSION CDATA \"0.0\" >" //$NON-NLS-1$
072: + " <!ELEMENT root (map, node*) >" //$NON-NLS-1$
073: + " <!ATTLIST root type (system|user) #REQUIRED >" //$NON-NLS-1$
074: + " <!ELEMENT node (map, node*) >" //$NON-NLS-1$
075: + " <!ATTLIST node name CDATA #REQUIRED >" //$NON-NLS-1$
076: + " <!ELEMENT map (entry*) >" //$NON-NLS-1$
077: + " <!ELEMENT entry EMPTY >" //$NON-NLS-1$
078: + " <!ATTLIST entry key CDATA #REQUIRED value CDATA #REQUIRED >"; //$NON-NLS-1$
079:
080: /*
081: * Constant - the specified header
082: */
083: static final String HEADER = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"; //$NON-NLS-1$
084:
085: /*
086: * Constant - the specified DOCTYPE
087: */
088: static final String DOCTYPE = "<!DOCTYPE preferences SYSTEM"; //$NON-NLS-1$
089:
090: /*
091: * empty string array constant
092: */
093: private static final String[] EMPTY_SARRAY = new String[0];
094:
095: /*
096: * Constant - used by FilePreferencesImpl, which is default implementation of Linux platform
097: */
098: private static final String FILE_PREFS = "<!DOCTYPE map SYSTEM 'http://java.sun.com/dtd/preferences.dtd'>"; //$NON-NLS-1$
099:
100: /*
101: * Constant - specify the DTD version
102: */
103: private static final float XML_VERSION = 1.0f;
104:
105: /*
106: * DOM builder
107: */
108: private static final DocumentBuilder builder;
109:
110: /*
111: * specify the indent level
112: */
113: private static int indent = -1;
114:
115: /*
116: * init DOM builder
117: */
118: static {
119: DocumentBuilderFactory factory = DocumentBuilderFactory
120: .newInstance();
121: factory.setValidating(true);
122: try {
123: builder = factory.newDocumentBuilder();
124: } catch (ParserConfigurationException e) {
125: throw new Error(e);
126: }
127: builder.setEntityResolver(new EntityResolver() {
128: public InputSource resolveEntity(String publicId,
129: String systemId) throws SAXException, IOException {
130: if (systemId.equals(PREFS_DTD_NAME)) {
131: InputSource result = new InputSource(
132: new StringReader(PREFS_DTD));
133: result.setSystemId(PREFS_DTD_NAME);
134: return result;
135: }
136: // prefs.1=Invalid DOCTYPE declaration: {0}
137: throw new SAXException(Messages.getString(
138: "prefs.1", systemId)); //$NON-NLS-1$
139: }
140: });
141: builder.setErrorHandler(new ErrorHandler() {
142: public void warning(SAXParseException e)
143: throws SAXException {
144: throw e;
145: }
146:
147: public void error(SAXParseException e) throws SAXException {
148: throw e;
149: }
150:
151: public void fatalError(SAXParseException e)
152: throws SAXException {
153: throw e;
154: }
155: });
156: }
157:
158: private XMLParser() {// empty constructor
159: }
160:
161: /***************************************************************************
162: * utilities for Preferences export
163: **************************************************************************/
164: static void exportPrefs(Preferences prefs, OutputStream stream,
165: boolean withSubTree) throws IOException,
166: BackingStoreException {
167: indent = -1;
168: BufferedWriter out = new BufferedWriter(new OutputStreamWriter(
169: stream, "UTF-8")); //$NON-NLS-1$
170: out.write(HEADER);
171: out.newLine();
172: out.newLine();
173:
174: out.write(DOCTYPE);
175: out.write(" '"); //$NON-NLS-1$
176: out.write(PREFS_DTD_NAME);
177: out.write("'>"); //$NON-NLS-1$
178: out.newLine();
179: out.newLine();
180:
181: flushStartTag(
182: "preferences", new String[] { "EXTERNAL_XML_VERSION" }, new String[] { String.valueOf(XML_VERSION) }, out); //$NON-NLS-1$ //$NON-NLS-2$
183: flushStartTag(
184: "root", new String[] { "type" }, new String[] { prefs.isUserNode() ? "user" : "system" }, out); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
185: flushEmptyElement("map", out); //$NON-NLS-1$
186:
187: StringTokenizer ancestors = new StringTokenizer(prefs
188: .absolutePath(), "/"); //$NON-NLS-1$
189: exportNode(ancestors, prefs, withSubTree, out);
190:
191: flushEndTag("root", out); //$NON-NLS-1$
192: flushEndTag("preferences", out); //$NON-NLS-1$
193: out.flush();
194: out = null;
195: }
196:
197: private static void exportNode(StringTokenizer ancestors,
198: Preferences prefs, boolean withSubTree, BufferedWriter out)
199: throws IOException, BackingStoreException {
200: if (ancestors.hasMoreTokens()) {
201: String name = ancestors.nextToken();
202: flushStartTag(
203: "node", new String[] { "name" }, new String[] { name }, out); //$NON-NLS-1$ //$NON-NLS-2$
204: if (ancestors.hasMoreTokens()) {
205: flushEmptyElement("map", out); //$NON-NLS-1$
206: exportNode(ancestors, prefs, withSubTree, out);
207: } else {
208: exportEntries(prefs, out);
209: if (withSubTree) {
210: exportSubTree(prefs, out);
211: }
212: }
213: flushEndTag("node", out); //$NON-NLS-1$
214: }
215: }
216:
217: private static void exportSubTree(Preferences prefs,
218: BufferedWriter out) throws BackingStoreException,
219: IOException {
220: String[] names = prefs.childrenNames();
221: if (names.length > 0) {
222: for (int i = 0; i < names.length; i++) {
223: Preferences child = prefs.node(names[i]);
224: flushStartTag(
225: "node", new String[] { "name" }, new String[] { names[i] }, out); //$NON-NLS-1$ //$NON-NLS-2$
226: exportEntries(child, out);
227: exportSubTree(child, out);
228: flushEndTag("node", out); //$NON-NLS-1$
229: }
230: }
231: }
232:
233: private static void exportEntries(Preferences prefs,
234: BufferedWriter out) throws BackingStoreException,
235: IOException {
236: String[] keys = prefs.keys();
237: String[] values = new String[keys.length];
238: for (int i = 0; i < keys.length; i++) {
239: values[i] = prefs.get(keys[i], null);
240: }
241: exportEntries(keys, values, out);
242: }
243:
244: private static void exportEntries(String[] keys, String[] values,
245: BufferedWriter out) throws IOException {
246: if (keys.length == 0) {
247: flushEmptyElement("map", out); //$NON-NLS-1$
248: return;
249: }
250: flushStartTag("map", out); //$NON-NLS-1$
251: for (int i = 0; i < keys.length; i++) {
252: if (values[i] != null) {
253: flushEmptyElement(
254: "entry", new String[] { "key", "value" }, new String[] { keys[i], values[i] }, out); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
255: }
256: }
257: flushEndTag("map", out); //$NON-NLS-1$
258: }
259:
260: private static void flushEndTag(String tagName, BufferedWriter out)
261: throws IOException {
262: flushIndent(indent--, out);
263: out.write("</"); //$NON-NLS-1$
264: out.write(tagName);
265: out.write(">"); //$NON-NLS-1$
266: out.newLine();
267: }
268:
269: private static void flushEmptyElement(String tagName,
270: BufferedWriter out) throws IOException {
271: flushIndent(++indent, out);
272: out.write("<"); //$NON-NLS-1$
273: out.write(tagName);
274: out.write(" />"); //$NON-NLS-1$
275: out.newLine();
276: indent--;
277: }
278:
279: private static void flushEmptyElement(String tagName,
280: String[] attrKeys, String[] attrValues, BufferedWriter out)
281: throws IOException {
282: flushIndent(++indent, out);
283: out.write("<"); //$NON-NLS-1$
284: out.write(tagName);
285: flushPairs(attrKeys, attrValues, out);
286: out.write(" />"); //$NON-NLS-1$
287: out.newLine();
288: indent--;
289: }
290:
291: private static void flushPairs(String[] attrKeys,
292: String[] attrValues, BufferedWriter out) throws IOException {
293: for (int i = 0; i < attrKeys.length; i++) {
294: out.write(" "); //$NON-NLS-1$
295: out.write(attrKeys[i]);
296: out.write("=\""); //$NON-NLS-1$
297: out.write(htmlEncode(attrValues[i]));
298: out.write("\""); //$NON-NLS-1$
299: }
300: }
301:
302: private static void flushIndent(int ind, BufferedWriter out)
303: throws IOException {
304: for (int i = 0; i < ind; i++) {
305: out.write(" "); //$NON-NLS-1$
306: }
307: }
308:
309: private static void flushStartTag(String tagName,
310: String[] attrKeys, String[] attrValues, BufferedWriter out)
311: throws IOException {
312: flushIndent(++indent, out);
313: out.write("<"); //$NON-NLS-1$
314: out.write(tagName);
315: flushPairs(attrKeys, attrValues, out);
316: out.write(">"); //$NON-NLS-1$
317: out.newLine();
318: }
319:
320: private static void flushStartTag(String tagName, BufferedWriter out)
321: throws IOException {
322: flushIndent(++indent, out);
323: out.write("<"); //$NON-NLS-1$
324: out.write(tagName);
325: out.write(">"); //$NON-NLS-1$
326: out.newLine();
327: }
328:
329: private static String htmlEncode(String s) {
330: StringBuffer sb = new StringBuffer();
331: char c;
332: for (int i = 0; i < s.length(); i++) {
333: c = s.charAt(i);
334: switch (c) {
335: case '<':
336: sb.append("<"); //$NON-NLS-1$
337: break;
338: case '>':
339: sb.append(">"); //$NON-NLS-1$
340: break;
341: case '&':
342: sb.append("&"); //$NON-NLS-1$
343: break;
344: case '\\':
345: sb.append("'"); //$NON-NLS-1$
346: break;
347: case '"':
348: sb.append("""); //$NON-NLS-1$
349: break;
350: default:
351: sb.append(c);
352: }
353: }
354: return sb.toString();
355: }
356:
357: /***************************************************************************
358: * utilities for Preferences import
359: **************************************************************************/
360: static void importPrefs(InputStream in) throws IOException,
361: InvalidPreferencesFormatException {
362: try {
363: // load XML document
364: Document doc = builder.parse(new InputSource(in));
365:
366: // check preferences' export version
367: Element preferences;
368: preferences = doc.getDocumentElement();
369: String version = preferences
370: .getAttribute("EXTERNAL_XML_VERSION"); //$NON-NLS-1$
371: if (version != null
372: && Float.parseFloat(version) > XML_VERSION) {
373: // prefs.2=This preferences exported version is not supported:{0}
374: throw new InvalidPreferencesFormatException(Messages
375: .getString("prefs.2", version)); //$NON-NLS-1$
376: }
377:
378: // check preferences root's type
379: Element root = (Element) preferences.getElementsByTagName(
380: "root").item(0); //$NON-NLS-1$
381: Preferences prefsRoot = null;
382: String type = root.getAttribute("type"); //$NON-NLS-1$
383: if (type.equals("user")) { //$NON-NLS-1$
384: prefsRoot = Preferences.userRoot();
385: } else {
386: prefsRoot = Preferences.systemRoot();
387: }
388:
389: // load node
390: loadNode(prefsRoot, root);
391: } catch (FactoryConfigurationError e) {
392: throw new InvalidPreferencesFormatException(e);
393: } catch (SAXException e) {
394: throw new InvalidPreferencesFormatException(e);
395: } catch (TransformerException e) {
396: throw new InvalidPreferencesFormatException(e);
397: }
398: }
399:
400: private static void loadNode(Preferences prefs, Element node)
401: throws TransformerException {
402: // load preferences
403: NodeList children = XPathAPI.selectNodeList(node, "node"); //$NON-NLS-1$
404: NodeList entries = XPathAPI.selectNodeList(node, "map/entry"); //$NON-NLS-1$
405: int childNumber = children.getLength();
406: Preferences[] prefChildren = new Preferences[childNumber];
407: int entryNumber = entries.getLength();
408: synchronized (((AbstractPreferences) prefs).lock) {
409: if (((AbstractPreferences) prefs).isRemoved()) {
410: return;
411: }
412: for (int i = 0; i < entryNumber; i++) {
413: Element entry = (Element) entries.item(i);
414: String key = entry.getAttribute("key"); //$NON-NLS-1$
415: String value = entry.getAttribute("value"); //$NON-NLS-1$
416: prefs.put(key, value);
417: }
418: // get children preferences node
419: for (int i = 0; i < childNumber; i++) {
420: Element child = (Element) children.item(i);
421: String name = child.getAttribute("name"); //$NON-NLS-1$
422: prefChildren[i] = prefs.node(name);
423: }
424: }
425:
426: // load children nodes after unlock
427: for (int i = 0; i < childNumber; i++) {
428: loadNode(prefChildren[i], (Element) children.item(i));
429: }
430: }
431:
432: /***************************************************************************
433: * utilities for FilePreferencesImpl, which is default implementation of Linux platform
434: **************************************************************************/
435: /**
436: * load preferences from file, if cannot load, create a new one FIXME: need
437: * lock or not?
438: *
439: * @param file the XML file to be read
440: * @return Properties instance which indicates the preferences key-value pairs
441: */
442: static Properties loadFilePrefs(final File file) {
443: return AccessController
444: .doPrivileged(new PrivilegedAction<Properties>() {
445: public Properties run() {
446: return loadFilePrefsImpl(file);
447: }
448: });
449:
450: // try {
451: // //FIXME: lines below can be deleted, because it is not required to
452: // persistent at the very beginning
453: // flushFilePrefs(file, result);
454: // } catch (IOException e) {
455: // e.printStackTrace();
456: // }
457: }
458:
459: static Properties loadFilePrefsImpl(final File file) {
460: Properties result = new Properties();
461: if (!file.exists()) {
462: file.getParentFile().mkdirs();
463: } else if (file.canRead()) {
464: InputStream in = null;
465: FileLock lock = null;
466: try {
467:
468: FileInputStream istream = new FileInputStream(file);
469: in = new BufferedInputStream(istream);
470: FileChannel channel = istream.getChannel();
471: lock = channel.lock(0L, Long.MAX_VALUE, true);
472: Document doc = builder.parse(in);
473: NodeList entries = XPathAPI.selectNodeList(doc
474: .getDocumentElement(), "entry"); //$NON-NLS-1$
475: int length = entries.getLength();
476: for (int i = 0; i < length; i++) {
477: Element node = (Element) entries.item(i);
478: String key = node.getAttribute("key"); //$NON-NLS-1$
479: String value = node.getAttribute("value"); //$NON-NLS-1$
480: result.setProperty(key, value);
481: }
482: return result;
483: } catch (Exception e) {
484: e.printStackTrace();
485: } finally {
486: try {
487: lock.release();
488: } catch (Exception e) {//ignore
489: }
490: try {
491: in.close();
492: } catch (Exception e) {//ignore
493: }
494: }
495: } else {
496: file.delete();
497: }
498: return result;
499: }
500:
501: /**
502: *
503: * @param file
504: * @param prefs
505: * @throws PrivilegedActionException
506: */
507: static void flushFilePrefs(final File file, final Properties prefs)
508: throws PrivilegedActionException {
509: AccessController
510: .doPrivileged(new PrivilegedExceptionAction<Object>() {
511: public Object run() throws IOException {
512: flushFilePrefsImpl(file, prefs);
513: return null;
514: }
515: });
516: }
517:
518: static void flushFilePrefsImpl(File file, Properties prefs)
519: throws IOException {
520: BufferedWriter out = null;
521: FileLock lock = null;
522: try {
523: FileOutputStream ostream = new FileOutputStream(file);
524: out = new BufferedWriter(new OutputStreamWriter(ostream,
525: "UTF-8")); //$NON-NLS-1$
526: FileChannel channel = ostream.getChannel();
527: lock = channel.lock();
528: out.write(HEADER);
529: out.newLine();
530: out.write(FILE_PREFS);
531: out.newLine();
532: if (prefs.size() == 0) {
533: exportEntries(EMPTY_SARRAY, EMPTY_SARRAY, out);
534: } else {
535: String[] keys = prefs.keySet().toArray(
536: new String[prefs.size()]);
537: int length = keys.length;
538: String[] values = new String[length];
539: for (int i = 0; i < length; i++) {
540: values[i] = prefs.getProperty(keys[i]);
541: }
542: exportEntries(keys, values, out);
543: }
544: out.flush();
545: } finally {
546: try {
547: lock.release();
548: } catch (Exception e) {//ignore
549: }
550: try {
551: if (null != out) {
552: out.close();
553: }
554: } catch (Exception e) {//ignore
555: }
556: }
557: }
558: }
|