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.
042: *
043: * <p>The persistence code that works with Reader/Writer follows the JDK's parsing
044: * strategy but does not implement Unicode conversion, because the Reader/Writer
045: * should already apply proper decoding/encoding of characters. If you use prefer
046: * to escape unicode characters in your properties files, do <i>not</i> specify
047: * an encoding for a Reader/Writer (like ReloadableResourceBundleMessageSource's
048: * "defaultEncoding" and "fileEncodings" properties).
049: *
050: * <p>As of Spring 1.2.2, this implementation also supports properties XML files,
051: * through the <code>loadFromXml</code> and <code>storeToXml</code> methods.
052: * The default implementations delegate to JDK 1.5's corresponding methods,
053: * throwing an exception if running on an older JDK. Those implementations
054: * could be subclassed to apply custom XML handling on JDK 1.4, for example.
055: *
056: * @author Juergen Hoeller
057: * @since 10.03.2004
058: * @see java.util.Properties
059: * @see java.util.Properties#load
060: * @see java.util.Properties#store
061: * @see org.springframework.context.support.ReloadableResourceBundleMessageSource#setPropertiesPersister
062: * @see org.springframework.context.support.ReloadableResourceBundleMessageSource#setDefaultEncoding
063: * @see org.springframework.context.support.ReloadableResourceBundleMessageSource#setFileEncodings
064: */
065: public class DefaultPropertiesPersister implements PropertiesPersister {
066:
067: public void load(Properties props, InputStream is)
068: throws IOException {
069: props.load(is);
070: }
071:
072: public void load(Properties props, Reader reader)
073: throws IOException {
074: BufferedReader in = new BufferedReader(reader);
075: while (true) {
076: String line = in.readLine();
077: if (line == null) {
078: return;
079: }
080: line = StringUtils.trimLeadingWhitespace(line);
081: if (line.length() > 0) {
082: char firstChar = line.charAt(0);
083: if (firstChar != '#' && firstChar != '!') {
084: while (endsWithContinuationMarker(line)) {
085: String nextLine = in.readLine();
086: line = line.substring(0, line.length() - 1);
087: if (nextLine != null) {
088: line += StringUtils
089: .trimLeadingWhitespace(nextLine);
090: }
091: }
092: int separatorIndex = line.indexOf("=");
093: if (separatorIndex == -1) {
094: separatorIndex = line.indexOf(":");
095: }
096: String key = (separatorIndex != -1 ? line
097: .substring(0, separatorIndex) : line);
098: String value = (separatorIndex != -1) ? line
099: .substring(separatorIndex + 1) : "";
100: key = StringUtils.trimTrailingWhitespace(key);
101: value = StringUtils.trimLeadingWhitespace(value);
102: props.put(unescape(key), unescape(value));
103: }
104: }
105: }
106: }
107:
108: protected boolean endsWithContinuationMarker(String line) {
109: boolean evenSlashCount = true;
110: int index = line.length() - 1;
111: while (index >= 0 && line.charAt(index) == '\\') {
112: evenSlashCount = !evenSlashCount;
113: index--;
114: }
115: return !evenSlashCount;
116: }
117:
118: protected String unescape(String str) {
119: StringBuffer outBuffer = new StringBuffer(str.length());
120: for (int index = 0; index < str.length();) {
121: char c = str.charAt(index++);
122: if (c == '\\') {
123: c = str.charAt(index++);
124: if (c == 't') {
125: c = '\t';
126: } else if (c == 'r') {
127: c = '\r';
128: } else if (c == 'n') {
129: c = '\n';
130: } else if (c == 'f') {
131: c = '\f';
132: }
133: }
134: outBuffer.append(c);
135: }
136: return outBuffer.toString();
137: }
138:
139: public void store(Properties props, OutputStream os, String header)
140: throws IOException {
141: props.store(os, header);
142: }
143:
144: public void store(Properties props, Writer writer, String header)
145: throws IOException {
146: BufferedWriter out = new BufferedWriter(writer);
147: if (header != null) {
148: out.write("#" + header);
149: out.newLine();
150: }
151: out.write("#" + new Date());
152: out.newLine();
153: for (Enumeration keys = props.keys(); keys.hasMoreElements();) {
154: String key = (String) keys.nextElement();
155: String val = props.getProperty(key);
156: out.write(escape(key, true) + "=" + escape(val, false));
157: out.newLine();
158: }
159: out.flush();
160: }
161:
162: protected String escape(String str, boolean isKey) {
163: int len = str.length();
164: StringBuffer outBuffer = new StringBuffer(len * 2);
165: for (int index = 0; index < len; index++) {
166: char c = str.charAt(index);
167: switch (c) {
168: case ' ':
169: if (index == 0 || isKey) {
170: outBuffer.append('\\');
171: }
172: outBuffer.append(' ');
173: break;
174: case '\\':
175: outBuffer.append("\\\\");
176: break;
177: case '\t':
178: outBuffer.append("\\t");
179: break;
180: case '\n':
181: outBuffer.append("\\n");
182: break;
183: case '\r':
184: outBuffer.append("\\r");
185: break;
186: case '\f':
187: outBuffer.append("\\f");
188: break;
189: default:
190: if ("=: \t\r\n\f#!".indexOf(c) != -1) {
191: outBuffer.append('\\');
192: }
193: outBuffer.append(c);
194: }
195: }
196: return outBuffer.toString();
197: }
198:
199: public void loadFromXml(Properties props, InputStream is)
200: throws IOException {
201: try {
202: props.loadFromXML(is);
203: } catch (NoSuchMethodError err) {
204: throw new IOException(
205: "Cannot load properties XML file - not running on JDK 1.5+: "
206: + err.getMessage());
207: }
208: }
209:
210: public void storeToXml(Properties props, OutputStream os,
211: String header) throws IOException {
212: try {
213: props.storeToXML(os, header);
214: } catch (NoSuchMethodError err) {
215: throw new IOException(
216: "Cannot store properties XML file - not running on JDK 1.5+: "
217: + err.getMessage());
218: }
219: }
220:
221: public void storeToXml(Properties props, OutputStream os,
222: String header, String encoding) throws IOException {
223: try {
224: props.storeToXML(os, header, encoding);
225: } catch (NoSuchMethodError err) {
226: throw new IOException(
227: "Cannot store properties XML file - not running on JDK 1.5+: "
228: + err.getMessage());
229: }
230: }
231:
232: }
|