1 /* 2 * Copyright (c) 2012, 2013, 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.Map.Entry; 31 import java.util.Properties; 32 import jdk.internal.org.xml.sax.Attributes; 33 import jdk.internal.org.xml.sax.InputSource; 34 import jdk.internal.org.xml.sax.SAXException; 35 import jdk.internal.org.xml.sax.SAXParseException; 36 import jdk.internal.org.xml.sax.helpers.DefaultHandler; 37 import jdk.internal.util.xml.impl.SAXParserImpl; 38 import jdk.internal.util.xml.impl.XMLStreamWriterImpl; 39 40 /** 41 * A class used to aid in Properties load and save in XML. This class is 42 * re-implemented using a subset of SAX 43 * 44 * @author Joe Wang 45 * @since 8 46 */ 47 public class PropertiesDefaultHandler extends DefaultHandler { 48 49 // Elements specified in the properties.dtd 50 private static final String ELEMENT_ROOT = "properties"; 51 private static final String ELEMENT_COMMENT = "comment"; 52 private static final String ELEMENT_ENTRY = "entry"; 53 private static final String ATTR_KEY = "key"; 54 // The required DTD URI for exported properties 55 private static final String PROPS_DTD_DECL = 56 "<!DOCTYPE properties SYSTEM \"http://java.sun.com/dtd/properties.dtd\">"; 57 private static final String PROPS_DTD_URI = 58 "http://java.sun.com/dtd/properties.dtd"; 59 private static final String PROPS_DTD = 60 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" 61 + "<!-- DTD for properties -->" 62 + "<!ELEMENT properties ( comment?, entry* ) >" 63 + "<!ATTLIST properties" 64 + " version CDATA #FIXED \"1.0\">" 65 + "<!ELEMENT comment (#PCDATA) >" 66 + "<!ELEMENT entry (#PCDATA) >" 67 + "<!ATTLIST entry " 68 + " key CDATA #REQUIRED>"; 69 /** 70 * Version number for the format of exported properties files. 71 */ 72 private static final String EXTERNAL_XML_VERSION = "1.0"; 73 private Properties properties; 74 75 public void load(Properties props, InputStream in) 76 throws IOException, InvalidPropertiesFormatException, UnsupportedEncodingException 77 { 78 this.properties = props; 79 80 try { 81 SAXParser parser = new SAXParserImpl(); 82 parser.parse(in, this); 83 } catch (SAXException saxe) { 84 throw new InvalidPropertiesFormatException(saxe); 85 } 86 87 /** 88 * String xmlVersion = propertiesElement.getAttribute("version"); if 89 * (xmlVersion.compareTo(EXTERNAL_XML_VERSION) > 0) throw new 90 * InvalidPropertiesFormatException( "Exported Properties file format 91 * version " + xmlVersion + " is not supported. This java installation 92 * can read" + " versions " + EXTERNAL_XML_VERSION + " or older. You" + 93 * " may need to install a newer version of JDK."); 94 */ 95 } 96 97 public void store(Properties props, OutputStream os, String comment, String encoding) 98 throws IOException 99 { 100 try { 101 XMLStreamWriter writer = new XMLStreamWriterImpl(os, encoding); 102 writer.writeStartDocument(); 103 writer.writeDTD(PROPS_DTD_DECL); 104 writer.writeStartElement(ELEMENT_ROOT); 105 if (comment != null && comment.length() > 0) { 106 writer.writeStartElement(ELEMENT_COMMENT); 107 writer.writeCharacters(comment); 108 writer.writeEndElement(); 109 } 110 111 synchronized(props) { 112 for (Entry<Object, Object> e : props.entrySet()) { 113 final Object k = e.getKey(); 114 final Object v = e.getValue(); 115 if (k instanceof String && v instanceof String) { 116 writer.writeStartElement(ELEMENT_ENTRY); 117 writer.writeAttribute(ATTR_KEY, (String)k); 118 writer.writeCharacters((String)v); 119 writer.writeEndElement(); 120 } 121 } 122 } 123 124 writer.writeEndElement(); 125 writer.writeEndDocument(); 126 writer.close(); 127 } catch (XMLStreamException e) { 128 if (e.getCause() instanceof UnsupportedEncodingException) { 129 throw (UnsupportedEncodingException) e.getCause(); 130 } 131 throw new IOException(e); 132 } 133 134 } 135 //////////////////////////////////////////////////////////////////// 136 // Validate while parsing 137 //////////////////////////////////////////////////////////////////// 138 final static String ALLOWED_ELEMENTS = "properties, comment, entry"; 139 final static String ALLOWED_COMMENT = "comment"; 140 //////////////////////////////////////////////////////////////////// 141 // Handler methods 142 //////////////////////////////////////////////////////////////////// 143 StringBuffer buf = new StringBuffer(); 144 boolean sawComment = false; 145 boolean validEntry = false; 146 int rootElem = 0; 147 String key; 148 String rootElm; 149 150 @Override 151 public void startElement(String uri, String localName, String qName, Attributes attributes) 152 throws SAXException 153 { 154 if (rootElem < 2) { 155 rootElem++; 156 } 157 158 if (rootElm == null) { 159 fatalError(new SAXParseException("An XML properties document must contain" 160 + " the DOCTYPE declaration as defined by java.util.Properties.", null)); 161 } 162 163 if (rootElem == 1 && !rootElm.equals(qName)) { 164 fatalError(new SAXParseException("Document root element \"" + qName 165 + "\", must match DOCTYPE root \"" + rootElm + "\"", null)); 166 } 167 if (!ALLOWED_ELEMENTS.contains(qName)) { 168 fatalError(new SAXParseException("Element type \"" + qName + "\" must be declared.", null)); 169 } 170 if (qName.equals(ELEMENT_ENTRY)) { 171 validEntry = true; 172 key = attributes.getValue(ATTR_KEY); 173 if (key == null) { 174 fatalError(new SAXParseException("Attribute \"key\" is required and must be specified for element type \"entry\"", null)); 175 } 176 } else if (qName.equals(ALLOWED_COMMENT)) { 177 if (sawComment) { 178 fatalError(new SAXParseException("Only one comment element may be allowed. " 179 + "The content of element type \"properties\" must match \"(comment?,entry*)\"", null)); 180 } 181 sawComment = true; 182 } 183 } 184 185 @Override 186 public void characters(char[] ch, int start, int length) throws SAXException { 187 if (validEntry) { 188 buf.append(ch, start, length); 189 } 190 } 191 192 @Override 193 public void endElement(String uri, String localName, String qName) throws SAXException { 194 if (!ALLOWED_ELEMENTS.contains(qName)) { 195 fatalError(new SAXParseException("Element: " + qName + " is invalid, must match \"(comment?,entry*)\".", null)); 196 } 197 198 if (validEntry) { 199 properties.setProperty(key, buf.toString()); 200 buf.delete(0, buf.length()); 201 validEntry = false; 202 } 203 } 204 205 @Override 206 public void notationDecl(String name, String publicId, String systemId) throws SAXException { 207 rootElm = name; 208 } 209 210 @Override 211 public InputSource resolveEntity(String pubid, String sysid) 212 throws SAXException, IOException { 213 { 214 if (sysid.equals(PROPS_DTD_URI)) { 215 InputSource is; 216 is = new InputSource(new StringReader(PROPS_DTD)); 217 is.setSystemId(PROPS_DTD_URI); 218 return is; 219 } 220 throw new SAXException("Invalid system identifier: " + sysid); 221 } 222 } 223 224 @Override 225 public void error(SAXParseException x) throws SAXException { 226 throw x; 227 } 228 229 @Override 230 public void fatalError(SAXParseException x) throws SAXException { 231 throw x; 232 } 233 234 @Override 235 public void warning(SAXParseException x) throws SAXException { 236 throw x; 237 } 238 }