1 /* 2 * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package jdk.internal.util.xml; 27 28 import java.io.*; 29 import java.util.InvalidPropertiesFormatException; 30 import java.util.Properties; 31 import jdk.internal.org.xml.sax.Attributes; 32 import jdk.internal.org.xml.sax.InputSource; 33 import jdk.internal.org.xml.sax.SAXException; 34 import jdk.internal.org.xml.sax.SAXParseException; 35 import jdk.internal.org.xml.sax.helpers.DefaultHandler; 36 import jdk.internal.util.xml.impl.SAXParserImpl; 37 import jdk.internal.util.xml.impl.XMLStreamWriterImpl; 38 39 /** 40 * A class used to aid in Properties load and save in XML. This class is 41 * re-implemented using a subset of SAX 42 * 43 * @author Joe Wang 44 * @since 8 45 */ 46 public class PropertiesDefaultHandler extends DefaultHandler { 47 48 // Elements specified in the properties.dtd 49 private static final String ELEMENT_ROOT = "properties"; 50 private static final String ELEMENT_COMMENT = "comment"; 51 private static final String ELEMENT_ENTRY = "entry"; 52 private static final String ATTR_KEY = "key"; 53 // The required DTD URI for exported properties 54 private static final String PROPS_DTD_DECL = 55 "<!DOCTYPE properties SYSTEM \"http://java.sun.com/dtd/properties.dtd\">"; 56 private static final String PROPS_DTD_URI = 57 "http://java.sun.com/dtd/properties.dtd"; 58 private static final String PROPS_DTD = 59 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" 60 + "<!-- DTD for properties -->" 61 + "<!ELEMENT properties ( comment?, entry* ) >" 62 + "<!ATTLIST properties" 63 + " version CDATA #FIXED \"1.0\">" 64 + "<!ELEMENT comment (#PCDATA) >" 65 + "<!ELEMENT entry (#PCDATA) >" 66 + "<!ATTLIST entry " 67 + " key CDATA #REQUIRED>"; 68 /** 69 * Version number for the format of exported properties files. 70 */ 71 private static final String EXTERNAL_XML_VERSION = "1.0"; 72 private Properties properties; 73 74 public void load(Properties props, InputStream in) 75 throws IOException, InvalidPropertiesFormatException, 76 UnsupportedEncodingException { 77 this.properties = props; 78 79 try { 80 SAXParser parser = new SAXParserImpl(); 81 parser.parse(in, this); 82 } catch (SAXException saxe) { 83 throw new InvalidPropertiesFormatException(saxe); 84 } 85 86 /** 87 * String xmlVersion = propertiesElement.getAttribute("version"); if 88 * (xmlVersion.compareTo(EXTERNAL_XML_VERSION) > 0) throw new 89 * InvalidPropertiesFormatException( "Exported Properties file format 90 * version " + xmlVersion + " is not supported. This java installation 91 * can read" + " versions " + EXTERNAL_XML_VERSION + " or older. You" + 92 * " may need to install a newer version of JDK."); 93 */ 94 } 95 96 public void store(Properties props, OutputStream os, String comment, String encoding) 97 throws IOException 98 { 99 try { 100 XMLStreamWriter writer = new XMLStreamWriterImpl(os, encoding); 101 writer.writeStartDocument(); 102 writer.writeDTD(PROPS_DTD_DECL); 103 writer.writeStartElement(ELEMENT_ROOT); 104 if (comment != null && comment.length() > 0) { 105 writer.writeStartElement(ELEMENT_COMMENT); 106 writer.writeCharacters(comment); 107 writer.writeEndElement(); 108 } 109 110 for (String key : props.stringPropertyNames()) { 111 String val = props.getProperty(key); 112 writer.writeStartElement(ELEMENT_ENTRY); 113 writer.writeAttribute(ATTR_KEY, key); 114 writer.writeCharacters(val); 115 writer.writeEndElement(); 116 } 117 118 writer.writeEndElement(); 119 writer.writeEndDocument(); 120 writer.close(); 121 } catch (XMLStreamException e) { 122 if (e.getCause() instanceof UnsupportedEncodingException) { 123 throw (UnsupportedEncodingException) e.getCause(); 124 } 125 throw new IOException(e); 126 } 127 128 } 129 //////////////////////////////////////////////////////////////////// 130 // Validate while parsing 131 //////////////////////////////////////////////////////////////////// 132 final static String ALLOWED_ELEMENTS = "properties, comment, entry"; 133 final static String ALLOWED_COMMENT = "comment"; 134 //////////////////////////////////////////////////////////////////// 135 // Handler methods 136 //////////////////////////////////////////////////////////////////// 137 StringBuffer buf = new StringBuffer(); 138 boolean sawComment = false; 139 boolean validEntry = false; 140 int rootElem = 0; 141 String key; 142 String rootElm; 143 144 @Override 145 public void startElement(String uri, String localName, String qName, Attributes attributes) 146 throws SAXException 147 { 148 if (rootElem < 2) { 149 rootElem++; 150 } 151 152 if (rootElm == null) { 153 fatalError(new SAXParseException("An XML properties document must contain" 154 + " the DOCTYPE declaration as defined by java.util.Properties.", null)); 155 } 156 157 if (rootElem == 1 && !rootElm.equals(qName)) { 158 fatalError(new SAXParseException("Document root element \"" + qName 159 + "\", must match DOCTYPE root \"" + rootElm + "\"", null)); 160 } 161 if (!ALLOWED_ELEMENTS.contains(qName)) { 162 fatalError(new SAXParseException("Element type \"" + qName + "\" must be declared.", null)); 163 } 164 if (qName.equals(ELEMENT_ENTRY)) { 165 validEntry = true; 166 key = attributes.getValue(ATTR_KEY); 167 if (key == null) { 168 fatalError(new SAXParseException("Attribute \"key\" is required and must be specified for element type \"entry\"", null)); 169 } 170 } else if (qName.equals(ALLOWED_COMMENT)) { 171 if (sawComment) { 172 fatalError(new SAXParseException("Only one comment element may be allowed. " 173 + "The content of element type \"properties\" must match \"(comment?,entry*)\"", null)); 174 } 175 sawComment = true; 176 } 177 } 178 179 @Override 180 public void characters(char[] ch, int start, int length) throws SAXException { 181 if (validEntry) { 182 buf.append(ch, start, length); 183 } 184 } 185 186 @Override 187 public void endElement(String uri, String localName, String qName) throws SAXException { 188 if (!ALLOWED_ELEMENTS.contains(qName)) { 189 fatalError(new SAXParseException("Element: " + qName + " is invalid, must match \"(comment?,entry*)\".", null)); 190 } 191 192 if (validEntry) { 193 properties.setProperty(key, buf.toString()); 194 buf.delete(0, buf.length()); 195 validEntry = false; 196 } 197 } 198 199 @Override 200 public void notationDecl(String name, String publicId, String systemId) throws SAXException { 201 rootElm = name; 202 } 203 204 @Override 205 public InputSource resolveEntity(String pubid, String sysid) 206 throws SAXException, IOException { 207 { 208 if (sysid.equals(PROPS_DTD_URI)) { 209 InputSource is; 210 is = new InputSource(new StringReader(PROPS_DTD)); 211 is.setSystemId(PROPS_DTD_URI); 212 return is; 213 } 214 throw new SAXException("Invalid system identifier: " + sysid); 215 } 216 } 217 218 @Override 219 public void error(SAXParseException x) throws SAXException { 220 throw x; 221 } 222 223 @Override 224 public void fatalError(SAXParseException x) throws SAXException { 225 throw x; 226 } 227 228 @Override 229 public void warning(SAXParseException x) throws SAXException { 230 throw x; 231 } 232 }