001: /*
002: * Copyright (C) 2006 Joe Walnes.
003: * Copyright (C) 2007 XStream Committers.
004: * All rights reserved.
005: *
006: * The software in this package is published under the terms of the BSD
007: * style license a copy of which has been included with this distribution in
008: * the LICENSE.txt file.
009: *
010: * Created on 13. June 2006 by Guilherme Silveira
011: */
012: package com.thoughtworks.xstream.persistence;
013:
014: import java.io.File;
015: import java.io.FileInputStream;
016: import java.io.FileNotFoundException;
017: import java.io.FileOutputStream;
018: import java.io.FilenameFilter;
019: import java.io.IOException;
020: import java.io.InputStream;
021: import java.io.OutputStream;
022: import java.util.Iterator;
023: import java.util.Map;
024: import java.util.Map.Entry;
025:
026: import com.thoughtworks.xstream.XStream;
027: import com.thoughtworks.xstream.io.StreamException;
028:
029: /**
030: * The default naming strategy is based on the key's toString method and escapes
031: * non digit, non a-z, A-Z characters. In order to change the
032: * escaping/unescaping algorithm, simply extend this class and rewrite its
033: * getName/extractKey methods.
034: *
035: * @author Guilherme Silveira
036: */
037: // TODO cache keys?
038: public class FileStreamStrategy implements StreamStrategy {
039:
040: private final FilenameFilter filter;
041:
042: private final XStream xstream;
043:
044: private final File baseDirectory;
045:
046: public FileStreamStrategy(File baseDirectory) {
047: this (baseDirectory, new XStream());
048: }
049:
050: public FileStreamStrategy(File baseDirectory, XStream xstream) {
051: this .baseDirectory = baseDirectory;
052: this .xstream = xstream;
053: this .filter = new FilenameFilter() {
054: public boolean accept(File dir, String name) {
055: return new File(dir, name).isFile()
056: && isValid(dir, name);
057: }
058: };
059: }
060:
061: protected boolean isValid(File dir, String name) {
062: return name.endsWith(".xml");
063: }
064:
065: /**
066: * Given a filename, the unescape method returns the key which originated
067: * it.
068: *
069: * @param name
070: * the filename
071: * @return the original key
072: */
073: protected String extractKey(String name) {
074: return unescape(name.substring(0, name.length() - 4));
075: }
076:
077: protected String unescape(String name) {
078: StringBuffer buffer = new StringBuffer();
079: int currentValue = -1;
080: // do we have a regex master to do it?
081: char[] array = name.toCharArray();
082: for (int i = 0; i < array.length; i++) {
083: char c = array[i];
084: if (c == '_' && currentValue != -1) {
085: if (currentValue == 0) {
086: buffer.append('_');
087: } else {
088: buffer.append((char) currentValue);
089: }
090: currentValue = -1;
091: } else if (c == '_') {
092: currentValue = 0;
093: } else if (currentValue != -1) {
094: currentValue = currentValue * 16
095: + Integer.parseInt(String.valueOf(c), 16);
096: } else {
097: buffer.append(c);
098: }
099: }
100: return buffer.toString();
101: }
102:
103: /**
104: * Given a key, the escape method returns the filename which shall be used.
105: *
106: * @param key
107: * the key
108: * @return the desired and escaped filename
109: */
110: protected String getName(Object key) {
111: return escape(key.toString()) + ".xml";
112: }
113:
114: protected String escape(String key) {
115: // do we have a regex master to do it?
116: StringBuffer buffer = new StringBuffer();
117: char[] array = key.toCharArray();
118: for (int i = 0; i < array.length; i++) {
119: char c = array[i];
120: if (Character.isDigit(c) || (c >= 'A' && c <= 'Z')
121: || (c >= 'a' && c <= 'z')) {
122: buffer.append(c);
123: } else if (c == '_') {
124: buffer.append("__");
125: } else {
126: buffer.append("_" + (Integer.toHexString(c)) + "_");
127: }
128: }
129: return buffer.toString();
130: }
131:
132: class XmlMapEntriesIterator implements Iterator {
133:
134: private File[] files = baseDirectory.listFiles(filter);
135:
136: private int position = -1;
137:
138: private File current = null;
139:
140: public boolean hasNext() {
141: return position + 1 < files.length;
142: }
143:
144: public void remove() {
145: if (current == null) {
146: throw new IllegalStateException();
147: }
148: // removes without loading
149: current.delete();
150: }
151:
152: public Object next() {
153: return new Map.Entry() {
154:
155: private File file = current = files[++position];
156:
157: private String key = extractKey(file.getName());
158:
159: public Object getKey() {
160: return key;
161: }
162:
163: public Object getValue() {
164: return readFile(file);
165: }
166:
167: public Object setValue(Object value) {
168: return put(key, value);
169: }
170:
171: public boolean equals(Object obj) {
172: if (!(obj instanceof Entry)) {
173: return false;
174: }
175: Entry e2 = (Entry) obj;
176: // TODO local cache value instead of calling getValue twice
177: return (key == null ? e2.getKey() == null : key
178: .equals(e2.getKey()))
179: && (getValue() == null ? e2.getValue() == null
180: : getValue().equals(e2.getValue()));
181: }
182:
183: };
184: }
185:
186: }
187:
188: private void writeFile(File file, Object value) {
189: try {
190: OutputStream os = new FileOutputStream(file);
191: try {
192: this .xstream.toXML(value, os);
193: } finally {
194: os.close();
195: }
196: } catch (IOException e) {
197: throw new StreamException(e);
198: }
199: }
200:
201: private File getFile(String filename) {
202: return new File(this .baseDirectory, filename);
203: }
204:
205: private Object readFile(File file) {
206: try {
207: InputStream is = new FileInputStream(file);
208: try {
209: return this .xstream.fromXML(is);
210: } finally {
211: is.close();
212: }
213: } catch (FileNotFoundException e) {
214: // not found... file.exists might generate a sync problem
215: return null;
216: } catch (IOException e) {
217: throw new StreamException(e);
218: }
219: }
220:
221: public Object put(Object key, Object value) {
222: Object oldValue = get(key);
223: String filename = getName(key);
224: writeFile(new File(baseDirectory, filename), value);
225: return oldValue;
226: }
227:
228: public Iterator iterator() {
229: return new XmlMapEntriesIterator();
230: }
231:
232: public int size() {
233: return baseDirectory.list(filter).length;
234: }
235:
236: public boolean containsKey(Object key) {
237: // faster lookup
238: File file = getFile(getName(key));
239: return file.exists();
240: }
241:
242: public Object get(Object key) {
243: return readFile(getFile(getName(key)));
244: }
245:
246: public Object remove(Object key) {
247: // faster lookup
248: File file = getFile(getName(key));
249: Object value = null;
250: if (file.exists()) {
251: value = readFile(file);
252: file.delete();
253: }
254: return value;
255: }
256:
257: }
|