KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > core > output2 > FileMapStorage


1 /*
2  * The contents of this file are subject to the terms of the Common Development
3  * and Distribution License (the License). You may not use this file except in
4  * compliance with the License.
5  *
6  * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
7  * or http://www.netbeans.org/cddl.txt.
8  *
9  * When distributing Covered Code, include this CDDL Header Notice in each file
10  * and include the License file at http://www.netbeans.org/cddl.txt.
11  * If applicable, add the following below the CDDL Header, with the fields
12  * enclosed by brackets [] replaced by your own identifying information:
13  * "Portions Copyrighted [year] [name of copyright owner]"
14  *
15  * The Original Software is NetBeans. The Initial Developer of the Original
16  * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
17  * Microsystems, Inc. All Rights Reserved.
18  */

19 package org.netbeans.core.output2;
20
21 import java.util.logging.Logger JavaDoc;
22 import org.openide.util.NbBundle;
23
24 import java.io.*;
25 import java.nio.ByteBuffer JavaDoc;
26 import java.nio.channels.FileChannel JavaDoc;
27 import org.openide.util.Exceptions;
28
29 /**
30  * An implementation of the Storage interface over a memory mapped file.
31  *
32  */

33 class FileMapStorage implements Storage {
34     /** A file channel for writing the mapped file */
35     private FileChannel JavaDoc writeChannel;
36     /** A file channel for reading the mapped file */
37     private FileChannel JavaDoc readChannel;
38     /** The base number of bytes to allocate when a getWriteBuffer for writing is
39      * needed. */

40     private static final int BASE_BUFFER_SIZE = 8196;
41     /**
42      * max possible range to map.. 20 MB
43      */

44     private static final long MAX_MAP_RANGE = 1024 * 1024 * 20;
45     /**
46      * The byte getWriteBuffer that write operations write into. Actual buffers are
47      * provided for writing by calling master.slice(); this getWriteBuffer simply
48      * pre-allocates a fairly large chunk of memory to reduce repeated
49      * allocations.
50      */

51     private ByteBuffer JavaDoc master;
52     /** A byte getWriteBuffer mapped to the contents of the output file, from which
53      * content is read. */

54     private ByteBuffer JavaDoc contents;
55     /** The number of bytes from the file that have been are currently mapped
56      * into the contents ByteBuffer. This will be checked on calls that read,
57      * and if more than the currently mapped bytes are requested, the
58      * contents bufffer will be replaced by a larger one */

59     private long mappedRange;
60     
61     /**
62      * start of the mapped range..
63      */

64     private long mappedStart;
65     /**
66      * The currently in use buffer.
67      */

68     private ByteBuffer JavaDoc buffer = null;
69     /**
70      * The number of bytes that have been written.
71      */

72     protected int bytesWritten = 0;
73     /**
74      * The file we are writing to.
75      */

76     private File outfile = null;
77     
78     private int outstandingBufferCount = 0;
79
80     FileMapStorage() {
81         init();
82     }
83
84     private void init() {
85         contents = null;
86         mappedRange = -1;
87         mappedStart = 0;
88         master = ByteBuffer.allocateDirect (BASE_BUFFER_SIZE);
89         readChannel = null;
90         writeChannel = null;
91         buffer = null;
92         bytesWritten = 0;
93     }
94
95     /**
96      * Ensure that the output file exists.
97      */

98     private void ensureFileExists() throws IOException {
99         if (outfile == null) {
100             String JavaDoc outdir = System.getProperty("java.io.tmpdir"); //NOI18N
101
if (!outdir.endsWith(File.separator)) {
102                 outdir += File.separator;
103             }
104             File dir = new File (outdir);
105             if (!dir.exists() || !dir.canWrite()) {
106                 //Handle the (unlikely) case we cannot write to the system
107
//temporary directory
108
IllegalStateException JavaDoc ise = new IllegalStateException JavaDoc ("Cannot" + //NOI18N
109
" write to " + outdir); //NOI18N
110
Exceptions.attachLocalizedMessage(ise,
111                                                   NbBundle.getMessage(OutWriter.class,
112                                                                       "FMT_CannotWrite",
113                                                                       outdir));
114                 throw ise;
115             }
116             //#47196 - if user holds down F9, many threads can enter this method
117
//simultaneously and all try to create the same file
118
synchronized (FileMapStorage.class) {
119                 StringBuilder JavaDoc fname = new StringBuilder JavaDoc(outdir)
120                         .append("output").append(Long.toString(System.currentTimeMillis())); //NOI18N
121
outfile = new File (fname.toString());
122                 while (outfile.exists()) {
123                     fname.append('x'); //NOI18N
124
outfile = new File(fname.toString());
125                 }
126                 outfile.createNewFile();
127                 outfile.deleteOnExit();
128             }
129         }
130     }
131     
132     public String JavaDoc toString() {
133         return outfile == null ? "[unused or disposed FileMapStorage]" : outfile.getPath();
134     }
135
136     /**
137      * Get a FileChannel opened for writing against the output file.
138      */

139     private FileChannel JavaDoc writeChannel() {
140         try {
141             if (writeChannel == null) {
142                 ensureFileExists();
143                 FileOutputStream fos = new FileOutputStream(outfile, true);
144                 writeChannel = fos.getChannel();
145             }
146             return writeChannel;
147         } catch (FileNotFoundException fnfe) {
148             fnfe.printStackTrace(); //XXX
149
} catch (IOException ioe) {
150             ioe.printStackTrace(); //XXX
151
}
152         return null;
153     }
154
155     /**
156      * Fetch a FileChannel for readin the file.
157      */

158     private FileChannel JavaDoc readChannel() {
159         //TODO may be better to use RandomAccessFile and a single bidirectional
160
//FileChannel rather than maintaining two separate ones.
161
if (readChannel == null) {
162             try {
163                 ensureFileExists();
164                 FileInputStream fis = new FileInputStream (outfile);
165                 readChannel = fis.getChannel();
166             } catch (Exception JavaDoc e) {
167                 e.printStackTrace();
168             }
169         }
170         return readChannel;
171     }
172
173     /**
174      * Fetch a getWriteBuffer of a specified size to use for appending new data to the
175      * end of the file.
176      */

177     public synchronized ByteBuffer JavaDoc getWriteBuffer (int size) throws IOException {
178         if (master.capacity() - master.position() < size) {
179             int newSize = Math.max (BASE_BUFFER_SIZE * 2,
180                 size + BASE_BUFFER_SIZE);
181             
182             master = ByteBuffer.allocateDirect (newSize);
183         }
184
185         if (buffer == null) {
186             buffer = master.slice();
187         } else {
188             int charsRemaining = AbstractLines.toCharIndex(buffer.capacity() - buffer.position());
189
190             if (charsRemaining < size) {
191                 buffer.flip();
192                 buffer = master.slice();
193             }
194         }
195         outstandingBufferCount++;
196         return buffer;
197     }
198
199     /**
200      * Dispose of a ByteBuffer which has been acquired for writing by one of
201      * the write methods, writing its contents to the file.
202      */

203     public int write (ByteBuffer JavaDoc bb, boolean addNewLine) throws IOException {
204         synchronized (this) {
205             if (bb == buffer) {
206                 buffer = null;
207             }
208         }
209         int position = size();
210         int byteCount = bb.position();
211         bb.flip();
212         if (writeChannel().isOpen()) { //If a thread was terminated while writing, it will be closed
213
writeChannel().write (bb);
214             if (addNewLine) {
215                 writeChannel().write(ByteBuffer.wrap(OutWriter.lineSepBytes));
216             }
217             synchronized (this) {
218                 bytesWritten += byteCount + (addNewLine ? OutWriter.lineSepBytes.length : 0);
219                 outstandingBufferCount--;
220             }
221         }
222         return position;
223     }
224
225     public synchronized void dispose() {
226         if (Controller.LOG) {
227             Controller.log ("Disposing file map storage");
228             Controller.logStack();
229         }
230         if (writeChannel != null && writeChannel.isOpen()) {
231             try {
232                 writeChannel.close();
233                 writeChannel = null;
234             } catch (Exception JavaDoc e) {
235                 Exceptions.printStackTrace(e);
236             }
237         }
238         if (readChannel != null && readChannel.isOpen()) {
239             try {
240                 readChannel.close();
241                 readChannel = null;
242             } catch (Exception JavaDoc e) {
243                 Exceptions.printStackTrace(e);
244             }
245         }
246         if (outfile != null && outfile.exists()) {
247             try {
248                 outfile.delete();
249                 outfile = null;
250             } catch (Exception JavaDoc e) {
251                 Exceptions.printStackTrace(e);
252             }
253         }
254         buffer = null;
255         contents = null;
256     }
257
258     /**
259      * Get a byte buffer representing the a getText of the contents of the
260      * output file. This is optimized to possibly map more of the output file
261      * into memory if it is not already mapped.
262      */

263     public ByteBuffer JavaDoc getReadBuffer(int start, int byteCount) throws IOException {
264         ByteBuffer JavaDoc cont;
265         synchronized (this) {
266             //XXX Some optimizations possible here:
267
// - Don't map the entire file, just what is requested (perhaps if the mapped
268
// start - currentlyMappedStart > someThreshold
269
// - Use RandomAccessFile and use one buffer for reading and writing (this may
270
// cause contention problems blocking repaints)
271
cont = this.contents;
272             if (cont == null || start + byteCount > mappedRange || start < mappedStart) {
273                 FileChannel JavaDoc ch = readChannel();
274                 long offset = start + byteCount;
275                 mappedStart = Math.max((long)0, (long)(start - (MAX_MAP_RANGE /2)));
276                 long prevMappedRange = mappedRange;
277                 mappedRange = Math.min(ch.size(), start + (MAX_MAP_RANGE / 2));
278                 try {
279                     try {
280                         cont = ch.map(FileChannel.MapMode.READ_ONLY,
281                             mappedStart, mappedRange - mappedStart);
282                         this.contents = cont;
283                     } catch (IOException ioe) {
284                         Logger.getAnonymousLogger().info("Failed to memory map output file for " + //NOI18N
285
"reading. Trying to read it normally."); //NOI18N
286
Exceptions.printStackTrace(ioe);
287
288                         //If a lot of processes have crashed with mapped files (generally when testing),
289
//this exception may simply be that the memory cannot be allocated for mapping.
290
//Try to do it non-mapped
291
cont = ByteBuffer.allocate((int) (mappedRange - mappedStart));
292                         ch.position(mappedStart).read(cont);
293                         this.contents = cont;
294                     }
295                 } catch (IOException ioe) {
296                     Logger.getAnonymousLogger().info("Failed to read output file. Start:" + start + " bytes reqd=" + //NOI18N
297
byteCount + " mapped range=" + mappedRange + //NOI18N
298
" previous mapped range=" + prevMappedRange + //NOI18N
299
" channel size: " + ch.size()); //NOI18N
300
throw ioe;
301                 }
302             }
303             if (start - mappedStart > cont.limit() - byteCount) {
304                 cont.position(Math.max(0, cont.limit() - byteCount));
305             } else {
306                 cont.position((int) (start - mappedStart));
307             }
308         }
309         int limit = Math.min(cont.limit(), byteCount);
310         try {
311             return (ByteBuffer JavaDoc) cont.slice().limit(limit);
312         } catch (Exception JavaDoc e) {
313             throw new IllegalStateException JavaDoc ("Error setting limit to " + limit //NOI18N
314
+ " contents size = " + cont.limit() + " requested: read " + //NOI18N
315
"buffer from " + start + " to be " + byteCount + " bytes"); //NOI18N
316
}
317     }
318
319     public synchronized int size() {
320         return bytesWritten;
321     }
322
323     public void flush() throws IOException {
324         if (buffer != null) {
325             if (Controller.LOG) Controller.log("FILEMAP STORAGE flush(): " + outstandingBufferCount);
326             write (buffer, false);
327             writeChannel.force(false);
328             buffer = null;
329         }
330     }
331
332     public void close() throws IOException {
333         if (writeChannel != null) {
334             flush();
335             writeChannel.close();
336             writeChannel = null;
337             if (Controller.LOG) Controller.log("FILEMAP STORAGE CLOSE. Outstanding buffer count: " + outstandingBufferCount);
338         }
339     }
340
341     public boolean isClosed() {
342         return writeChannel == null || !writeChannel.isOpen();
343     }
344 }
345
Popular Tags