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 jdk.internal.util.xml.impl.XMLStreamWriter; 29 import jdk.internal.util.xml.impl.XMLStreamWriterImpl; 30 import jdk.internal.util.xml.impl.XMLStreamException; 31 import jdk.internal.util.xml.impl.SAXParser; 32 import jdk.internal.util.xml.impl.SAXParserImp; 33 import java.io.*; 34 import java.util.InvalidPropertiesFormatException; 35 import java.util.Iterator; 36 import java.util.Properties; 37 import java.util.Set; 38 import jdk.internal.org.xml.sax.Attributes; 39 import jdk.internal.org.xml.sax.InputSource; 40 import jdk.internal.org.xml.sax.SAXException; 41 import jdk.internal.org.xml.sax.SAXParseException; 42 import jdk.internal.org.xml.sax.helpers.DefaultHandler; 43 44 /** 45 * A class used to aid in Properties load and save in XML. This class is 46 * re-implemented using a subset of SAX 47 * 48 * @author Joe Wang 49 * @since 8 50 */ 51 class PropertiesDefaultHandler extends DefaultHandler { 52 53 // Elements specified in the properties.dtd 54 private static final String ELEMENT_ROOT = "properties"; 55 private static final String ELEMENT_COMMENT = "comment"; 56 private static final String ELEMENT_ENTRY = "entry"; 57 private static final String ATTR_KEY = "key"; 58 // The required DTD URI for exported properties 59 private static final String PROPS_DTD_DECL = 60 "<!DOCTYPE properties SYSTEM \"http://java.sun.com/dtd/properties.dtd\">"; 61 private static final String PROPS_DTD_URI = 62 "http://java.sun.com/dtd/properties.dtd"; 63 private static final String PROPS_DTD = 64 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" 65 + "<!-- DTD for properties -->" 66 + "<!ELEMENT properties ( comment?, entry* ) >" 67 + "<!ATTLIST properties" 68 + " version CDATA #FIXED \"1.0\">" 69 + "<!ELEMENT comment (#PCDATA) >" 70 + "<!ELEMENT entry (#PCDATA) >" 71 + "<!ATTLIST entry " 72 + " key CDATA #REQUIRED>"; 73 /** 74 * Version number for the format of exported properties files. 75 */ 76 private static final String EXTERNAL_XML_VERSION = "1.0"; 77 private Properties properties; 78 79 public void load(Properties props, InputStream in) 80 throws IOException, InvalidPropertiesFormatException, 81 UnsupportedEncodingException{ 82 this.properties = props; 83 84 try { 85 SAXParser parser = new SAXParserImp(); 86 parser.parse(in, this); 87 } catch (SAXException saxe) { 88 throw new InvalidPropertiesFormatException(saxe); 89 } 90 91 /** 92 * String xmlVersion = propertiesElement.getAttribute("version"); if 93 * (xmlVersion.compareTo(EXTERNAL_XML_VERSION) > 0) throw new 94 * InvalidPropertiesFormatException( "Exported Properties file format 95 * version " + xmlVersion + " is not supported. This java installation 96 * can read" + " versions " + EXTERNAL_XML_VERSION + " or older. You" + 97 * " may need to install a newer version of JDK."); 98 */ 99 } 100 101 public void store(Properties props, OutputStream os, String comment, 102 String encoding) 103 throws IOException { 104 try { 105 XMLStreamWriter writer = new XMLStreamWriterImpl(os, encoding); 106 writer.writeStartDocument(); 107 writer.writeDTD(PROPS_DTD_DECL); 108 writer.writeStartElement(ELEMENT_ROOT); 109 if (comment != null && comment.length() > 0) { 110 writer.writeStartElement(ELEMENT_COMMENT); 111 writer.writeCharacters(comment); 112 writer.writeEndElement(); 113 } 114 Set keys = props.keySet(); 115 Iterator i = keys.iterator(); 116 while (i.hasNext()) { 117 String key = (String) i.next(); 118 String val = props.getProperty(key); 119 writer.writeStartElement(ELEMENT_ENTRY); 120 writer.writeAttribute(ATTR_KEY, key); 121 writer.writeCharacters(val); 122 writer.writeEndElement(); 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 if (rootElem < 2) { 154 rootElem++; 155 } 156 if (rootElem == 1 && !rootElm.equalsIgnoreCase(qName)) { 157 fatalError(new SAXParseException("Document root element \"" + qName 158 + "\", must match DOCTYPE root \"" + rootElm + "\"", null)); 159 } 160 if (!ALLOWED_ELEMENTS.contains(qName)) { 161 fatalError(new SAXParseException("Element type \"" + qName + "\" must be declared.", null)); 162 } 163 if (qName.equalsIgnoreCase(ELEMENT_ENTRY)) { 164 validEntry = true; 165 key = attributes.getValue(ATTR_KEY); 166 if (key == null) { 167 fatalError(new SAXParseException("Attribute \"key\" is required and must be specified for element type \"entry\"", null)); 168 } 169 } else if (qName.equalsIgnoreCase(ALLOWED_COMMENT)) { 170 if (sawComment) { 171 fatalError(new SAXParseException("Only one comment element may be allowed. " 172 + "The content of element type \"properties\" must match \"(comment?,entry*)\"", null)); 173 } 174 sawComment = true; 175 } 176 } 177 178 @Override 179 public void characters(char[] ch, int start, int length) throws SAXException { 180 if (validEntry) { 181 buf.append(ch, start, length); 182 } 183 } 184 185 @Override 186 public void endElement(String uri, String localName, String qName) throws SAXException { 187 if (!ALLOWED_ELEMENTS.contains(qName)) { 188 fatalError(new SAXParseException("Element: " + qName + " is invalid, must match \"(comment?,entry*)\".", null)); 189 } 190 191 if (validEntry) { 192 properties.setProperty(key, buf.toString()); 193 buf.delete(0, buf.length()); 194 validEntry = false; 195 } 196 } 197 198 @Override 199 public void notationDecl(String name, String publicId, String systemId) 200 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 }