001: /*
002: * Copyright 2002-2007 the original author or authors.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * 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 org.springframework.util;
018:
019: import java.io.BufferedReader;
020: import java.io.BufferedWriter;
021: import java.io.IOException;
022: import java.io.InputStream;
023: import java.io.OutputStream;
024: import java.io.Reader;
025: import java.io.Writer;
026: import java.util.Date;
027: import java.util.Enumeration;
028: import java.util.Properties;
029:
030: /**
031: * Default implementation of the {@link PropertiesPersister} interface.
032: * Follows the native parsing of <code>java.util.Properties</code>.
033: *
034: * <p>Allows for reading from any Reader and writing to any Writer, for example
035: * to specify a charset for a properties file. This is a capability that standard
036: * <code>java.util.Properties</code> unfortunately lacks up until JDK 1.5:
037: * You can only load files using the ISO-8859-1 charset there.
038: *
039: * <p>Loading from and storing to a stream delegates to <code>Properties.load</code>
040: * and <code>Properties.store</code>, respectively, to be fully compatible with
041: * the Unicode conversion as implemented by the JDK Properties class. On JDK 1.6,
042: * <code>Properties.load/store</code> will also be used for readers/writers,
043: * effectively turning this class into a plain backwards compatibility adapter.
044: *
045: * <p>The persistence code that works with Reader/Writer follows the JDK's parsing
046: * strategy but does not implement Unicode conversion, because the Reader/Writer
047: * should already apply proper decoding/encoding of characters. If you use prefer
048: * to escape unicode characters in your properties files, do <i>not</i> specify
049: * an encoding for a Reader/Writer (like ReloadableResourceBundleMessageSource's
050: * "defaultEncoding" and "fileEncodings" properties).
051: *
052: * <p>As of Spring 1.2.2, this implementation also supports properties XML files,
053: * through the <code>loadFromXml</code> and <code>storeToXml</code> methods.
054: * The default implementations delegate to JDK 1.5's corresponding methods,
055: * throwing an exception if running on an older JDK. Those implementations
056: * could be subclassed to apply custom XML handling on JDK 1.4, for example.
057: *
058: * @author Juergen Hoeller
059: * @since 10.03.2004
060: * @see java.util.Properties
061: * @see java.util.Properties#load
062: * @see java.util.Properties#store
063: * @see org.springframework.context.support.ReloadableResourceBundleMessageSource#setPropertiesPersister
064: * @see org.springframework.context.support.ReloadableResourceBundleMessageSource#setDefaultEncoding
065: * @see org.springframework.context.support.ReloadableResourceBundleMessageSource#setFileEncodings
066: */
067: public class DefaultPropertiesPersister implements PropertiesPersister {
068:
069: // Determine whether Properties.load(Reader) is available (on JDK 1.6+)
070: private static final boolean loadFromReaderAvailable = ClassUtils
071: .hasMethod(Properties.class, "load",
072: new Class[] { Reader.class });
073:
074: // Determine whether Properties.store(Writer, String) is available (on JDK 1.6+)
075: private static final boolean storeToWriterAvailable = ClassUtils
076: .hasMethod(Properties.class, "store", new Class[] {
077: Writer.class, String.class });
078:
079: public void load(Properties props, InputStream is)
080: throws IOException {
081: props.load(is);
082: }
083:
084: public void load(Properties props, Reader reader)
085: throws IOException {
086: if (loadFromReaderAvailable) {
087: // On JDK 1.6+
088: props.load(reader);
089: } else {
090: // Fall back to manual parsing.
091: doLoad(props, reader);
092: }
093: }
094:
095: protected void doLoad(Properties props, Reader reader)
096: throws IOException {
097: BufferedReader in = new BufferedReader(reader);
098: while (true) {
099: String line = in.readLine();
100: if (line == null) {
101: return;
102: }
103: line = StringUtils.trimLeadingWhitespace(line);
104: if (line.length() > 0) {
105: char firstChar = line.charAt(0);
106: if (firstChar != '#' && firstChar != '!') {
107: while (endsWithContinuationMarker(line)) {
108: String nextLine = in.readLine();
109: line = line.substring(0, line.length() - 1);
110: if (nextLine != null) {
111: line += StringUtils
112: .trimLeadingWhitespace(nextLine);
113: }
114: }
115: int separatorIndex = line.indexOf("=");
116: if (separatorIndex == -1) {
117: separatorIndex = line.indexOf(":");
118: }
119: String key = (separatorIndex != -1 ? line
120: .substring(0, separatorIndex) : line);
121: String value = (separatorIndex != -1) ? line
122: .substring(separatorIndex + 1) : "";
123: key = StringUtils.trimTrailingWhitespace(key);
124: value = StringUtils.trimLeadingWhitespace(value);
125: props.put(unescape(key), unescape(value));
126: }
127: }
128: }
129: }
130:
131: protected boolean endsWithContinuationMarker(String line) {
132: boolean evenSlashCount = true;
133: int index = line.length() - 1;
134: while (index >= 0 && line.charAt(index) == '\\') {
135: evenSlashCount = !evenSlashCount;
136: index--;
137: }
138: return !evenSlashCount;
139: }
140:
141: protected String unescape(String str) {
142: StringBuffer outBuffer = new StringBuffer(str.length());
143: for (int index = 0; index < str.length();) {
144: char c = str.charAt(index++);
145: if (c == '\\') {
146: c = str.charAt(index++);
147: if (c == 't') {
148: c = '\t';
149: } else if (c == 'r') {
150: c = '\r';
151: } else if (c == 'n') {
152: c = '\n';
153: } else if (c == 'f') {
154: c = '\f';
155: }
156: }
157: outBuffer.append(c);
158: }
159: return outBuffer.toString();
160: }
161:
162: public void store(Properties props, OutputStream os, String header)
163: throws IOException {
164: props.store(os, header);
165: }
166:
167: public void store(Properties props, Writer writer, String header)
168: throws IOException {
169: if (storeToWriterAvailable) {
170: // On JDK 1.6+
171: props.store(writer, header);
172: } else {
173: // Fall back to manual parsing.
174: doStore(props, writer, header);
175: }
176: }
177:
178: protected void doStore(Properties props, Writer writer,
179: String header) throws IOException {
180: BufferedWriter out = new BufferedWriter(writer);
181: if (header != null) {
182: out.write("#" + header);
183: out.newLine();
184: }
185: out.write("#" + new Date());
186: out.newLine();
187: for (Enumeration keys = props.keys(); keys.hasMoreElements();) {
188: String key = (String) keys.nextElement();
189: String val = props.getProperty(key);
190: out.write(escape(key, true) + "=" + escape(val, false));
191: out.newLine();
192: }
193: out.flush();
194: }
195:
196: protected String escape(String str, boolean isKey) {
197: int len = str.length();
198: StringBuffer outBuffer = new StringBuffer(len * 2);
199: for (int index = 0; index < len; index++) {
200: char c = str.charAt(index);
201: switch (c) {
202: case ' ':
203: if (index == 0 || isKey) {
204: outBuffer.append('\\');
205: }
206: outBuffer.append(' ');
207: break;
208: case '\\':
209: outBuffer.append("\\\\");
210: break;
211: case '\t':
212: outBuffer.append("\\t");
213: break;
214: case '\n':
215: outBuffer.append("\\n");
216: break;
217: case '\r':
218: outBuffer.append("\\r");
219: break;
220: case '\f':
221: outBuffer.append("\\f");
222: break;
223: default:
224: if ("=: \t\r\n\f#!".indexOf(c) != -1) {
225: outBuffer.append('\\');
226: }
227: outBuffer.append(c);
228: }
229: }
230: return outBuffer.toString();
231: }
232:
233: public void loadFromXml(Properties props, InputStream is)
234: throws IOException {
235: try {
236: props.loadFromXML(is);
237: } catch (NoSuchMethodError err) {
238: throw new IOException(
239: "Cannot load properties XML file - not running on JDK 1.5+: "
240: + err.getMessage());
241: }
242: }
243:
244: public void storeToXml(Properties props, OutputStream os,
245: String header) throws IOException {
246: try {
247: props.storeToXML(os, header);
248: } catch (NoSuchMethodError err) {
249: throw new IOException(
250: "Cannot store properties XML file - not running on JDK 1.5+: "
251: + err.getMessage());
252: }
253: }
254:
255: public void storeToXml(Properties props, OutputStream os,
256: String header, String encoding) throws IOException {
257: try {
258: props.storeToXML(os, header, encoding);
259: } catch (NoSuchMethodError err) {
260: throw new IOException(
261: "Cannot store properties XML file - not running on JDK 1.5+: "
262: + err.getMessage());
263: }
264: }
265:
266: }
|