001: package org.apache.lucene.store;
002:
003: /**
004: * Licensed to the Apache Software Foundation (ASF) under one or more
005: * contributor license agreements. See the NOTICE file distributed with
006: * this work for additional information regarding copyright ownership.
007: * The ASF licenses this file to You under the Apache License, Version 2.0
008: * (the "License"); you may not use this file except in compliance with
009: * the License. You may obtain a copy of the License at
010: *
011: * http://www.apache.org/licenses/LICENSE-2.0
012: *
013: * Unless required by applicable law or agreed to in writing, software
014: * distributed under the License is distributed on an "AS IS" BASIS,
015: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016: * See the License for the specific language governing permissions and
017: * limitations under the License.
018: */
019:
020: import java.io.IOException;
021: import java.io.File;
022: import java.io.FileNotFoundException;
023: import java.util.Iterator;
024: import java.util.Random;
025: import java.util.Map;
026: import java.util.HashMap;
027: import java.util.ArrayList;
028:
029: /**
030: * This is a subclass of RAMDirectory that adds methods
031: * intented to be used only by unit tests.
032: * @version $Id: RAMDirectory.java 437897 2006-08-29 01:13:10Z yonik $
033: */
034:
035: public class MockRAMDirectory extends RAMDirectory {
036: long maxSize;
037:
038: // Max actual bytes used. This is set by MockRAMOutputStream:
039: long maxUsedSize;
040: double randomIOExceptionRate;
041: Random randomState;
042: boolean noDeleteOpenFile = true;
043:
044: // NOTE: we cannot initialize the Map here due to the
045: // order in which our constructor actually does this
046: // member initialization vs when it calls super. It seems
047: // like super is called, then our members are initialized:
048: Map openFiles;
049:
050: public MockRAMDirectory() {
051: super ();
052: if (openFiles == null) {
053: openFiles = new HashMap();
054: }
055: }
056:
057: public MockRAMDirectory(String dir) throws IOException {
058: super (dir);
059: if (openFiles == null) {
060: openFiles = new HashMap();
061: }
062: }
063:
064: public MockRAMDirectory(Directory dir) throws IOException {
065: super (dir);
066: if (openFiles == null) {
067: openFiles = new HashMap();
068: }
069: }
070:
071: public MockRAMDirectory(File dir) throws IOException {
072: super (dir);
073: if (openFiles == null) {
074: openFiles = new HashMap();
075: }
076: }
077:
078: public void setMaxSizeInBytes(long maxSize) {
079: this .maxSize = maxSize;
080: }
081:
082: public long getMaxSizeInBytes() {
083: return this .maxSize;
084: }
085:
086: /**
087: * Returns the peek actual storage used (bytes) in this
088: * directory.
089: */
090: public long getMaxUsedSizeInBytes() {
091: return this .maxUsedSize;
092: }
093:
094: public void resetMaxUsedSizeInBytes() {
095: this .maxUsedSize = getRecomputedActualSizeInBytes();
096: }
097:
098: /**
099: * Emulate windows whereby deleting an open file is not
100: * allowed (raise IOException).
101: */
102: public void setNoDeleteOpenFile(boolean value) {
103: this .noDeleteOpenFile = value;
104: }
105:
106: public boolean getNoDeleteOpenFile() {
107: return noDeleteOpenFile;
108: }
109:
110: /**
111: * If 0.0, no exceptions will be thrown. Else this should
112: * be a double 0.0 - 1.0. We will randomly throw an
113: * IOException on the first write to an OutputStream based
114: * on this probability.
115: */
116: public void setRandomIOExceptionRate(double rate, long seed) {
117: randomIOExceptionRate = rate;
118: // seed so we have deterministic behaviour:
119: randomState = new Random(seed);
120: }
121:
122: public double getRandomIOExceptionRate() {
123: return randomIOExceptionRate;
124: }
125:
126: void maybeThrowIOException() throws IOException {
127: if (randomIOExceptionRate > 0.0) {
128: int number = Math.abs(randomState.nextInt() % 1000);
129: if (number < randomIOExceptionRate * 1000) {
130: throw new IOException("a random IOException");
131: }
132: }
133: }
134:
135: public synchronized void deleteFile(String name) throws IOException {
136: synchronized (openFiles) {
137: if (noDeleteOpenFile && openFiles.containsKey(name)) {
138: throw new IOException("MockRAMDirectory: file \""
139: + name + "\" is still open: cannot delete");
140: }
141: }
142: super .deleteFile(name);
143: }
144:
145: public IndexOutput createOutput(String name) throws IOException {
146: if (openFiles == null) {
147: openFiles = new HashMap();
148: }
149: synchronized (openFiles) {
150: if (noDeleteOpenFile && openFiles.containsKey(name))
151: throw new IOException("MockRAMDirectory: file \""
152: + name + "\" is still open: cannot overwrite");
153: }
154: RAMFile file = new RAMFile(this );
155: synchronized (this ) {
156: RAMFile existing = (RAMFile) fileMap.get(name);
157: // Enforce write once:
158: if (existing != null && !name.equals("segments.gen"))
159: throw new IOException("file " + name
160: + " already exists");
161: else {
162: if (existing != null) {
163: sizeInBytes -= existing.sizeInBytes;
164: existing.directory = null;
165: }
166:
167: fileMap.put(name, file);
168: }
169: }
170:
171: return new MockRAMOutputStream(this , file);
172: }
173:
174: public IndexInput openInput(String name) throws IOException {
175: RAMFile file;
176: synchronized (this ) {
177: file = (RAMFile) fileMap.get(name);
178: }
179: if (file == null)
180: throw new FileNotFoundException(name);
181: else {
182: synchronized (openFiles) {
183: if (openFiles.containsKey(name)) {
184: Integer v = (Integer) openFiles.get(name);
185: v = new Integer(v.intValue() + 1);
186: openFiles.put(name, v);
187: } else {
188: openFiles.put(name, new Integer(1));
189: }
190: }
191: }
192: return new MockRAMInputStream(this , name, file);
193: }
194:
195: /** Provided for testing purposes. Use sizeInBytes() instead. */
196: public synchronized final long getRecomputedSizeInBytes() {
197: long size = 0;
198: Iterator it = fileMap.values().iterator();
199: while (it.hasNext())
200: size += ((RAMFile) it.next()).getSizeInBytes();
201: return size;
202: }
203:
204: /** Like getRecomputedSizeInBytes(), but, uses actual file
205: * lengths rather than buffer allocations (which are
206: * quantized up to nearest
207: * RAMOutputStream.BUFFER_SIZE (now 1024) bytes.
208: */
209:
210: public final synchronized long getRecomputedActualSizeInBytes() {
211: long size = 0;
212: Iterator it = fileMap.values().iterator();
213: while (it.hasNext())
214: size += ((RAMFile) it.next()).length;
215: return size;
216: }
217:
218: public void close() {
219: if (openFiles == null) {
220: openFiles = new HashMap();
221: }
222: synchronized (openFiles) {
223: if (noDeleteOpenFile && openFiles.size() > 0) {
224: // RuntimeException instead of IOException because
225: // super() does not throw IOException currently:
226: throw new RuntimeException(
227: "MockRAMDirectory: cannot close: there are still open files: "
228: + openFiles);
229: }
230: }
231: }
232:
233: /**
234: * Objects that represent fail-able conditions. Objects of a derived
235: * class are created and registered with the mock directory. After
236: * register, each object will be invoked once for each first write
237: * of a file, giving the object a chance to throw an IOException.
238: */
239: public static class Failure {
240: /**
241: * eval is called on the first write of every new file.
242: */
243: public void eval(MockRAMDirectory dir) throws IOException {
244: }
245:
246: /**
247: * reset should set the state of the failure to its default
248: * (freshly constructed) state. Reset is convenient for tests
249: * that want to create one failure object and then reuse it in
250: * multiple cases. This, combined with the fact that Failure
251: * subclasses are often anonymous classes makes reset difficult to
252: * do otherwise.
253: *
254: * A typical example of use is
255: * Failure failure = new Failure() { ... };
256: * ...
257: * mock.failOn(failure.reset())
258: */
259: public Failure reset() {
260: return this ;
261: }
262:
263: protected boolean doFail;
264:
265: public void setDoFail() {
266: doFail = true;
267: }
268:
269: public void clearDoFail() {
270: doFail = false;
271: }
272: }
273:
274: ArrayList failures;
275:
276: /**
277: * add a Failure object to the list of objects to be evaluated
278: * at every potential failure point
279: */
280: synchronized public void failOn(Failure fail) {
281: if (failures == null) {
282: failures = new ArrayList();
283: }
284: failures.add(fail);
285: }
286:
287: /**
288: * Iterate through the failures list, giving each object a
289: * chance to throw an IOE
290: */
291: synchronized void maybeThrowDeterministicException()
292: throws IOException {
293: if (failures != null) {
294: for (int i = 0; i < failures.size(); i++) {
295: ((Failure) failures.get(i)).eval(this);
296: }
297: }
298: }
299:
300: }
|