Auto release executable/symbol file handle after it's not accessed for
certain time (3 seconds). Fixed the bug that the file is locked (thus
cannot be changed) after debug, or just after being accessed in
Executable view.

diff --git a/org.eclipse.cdt.debug.edc/src/org/eclipse/cdt/debug/edc/internal/FileAccessor.java b/org.eclipse.cdt.debug.edc/src/org/eclipse/cdt/debug/edc/internal/FileAccessor.java
new file mode 100644
index 0000000..78a3356
--- /dev/null
+++ b/org.eclipse.cdt.debug.edc/src/org/eclipse/cdt/debug/edc/internal/FileAccessor.java
@@ -0,0 +1,163 @@
+/*******************************************************************************

+ * Copyright (c) 2011 Nokia and others.

+ * All rights reserved. This program and the accompanying materials

+ * are made available under the terms of the Eclipse Public License v1.0

+ * which accompanies this distribution, and is available at

+ * http://www.eclipse.org/legal/epl-v10.html

+ *

+ * Contributors:

+ *   Nokia - Initial API and implementation. Aug 29, 2011

+ *******************************************************************************/

+

+package org.eclipse.cdt.debug.edc.internal;

+

+import java.io.File;

+import java.io.IOException;

+import java.io.RandomAccessFile;

+import java.util.Collections;

+import java.util.HashMap;

+import java.util.Map;

+

+import org.eclipse.cdt.debug.edc.internal.symbols.files.FileStatistics;

+import org.eclipse.cdt.utils.ERandomAccessFile;

+

+/**

+ * This class manages open, close and random access of a file. Currently only

+ * read operation is allowed.

+ * 

+ * The most important trait of the class is the file will be auto closed after

+ * it's not accessed for a certain period. This is intended for cases like this:

+ * release an executable/symbol file after debug so that user can rebuild it.

+ */

+public class FileAccessor {

+	/**

+	 * Global cache 

+	 */

+	static private Map<File, FileAccessor> accessors = Collections.synchronizedMap(new HashMap<File, FileAccessor>());

+

+	/**

+	 * Timeout on when to close the file (release the file handle).

+	 */

+	static final private long TIME_OUT = 3000;	// ms

+	

+	private final boolean DEBUG = false;

+

+	private RandomAccessFile raFile = null;

+	private File file = null;

+		

+	private long fileAccessTime = 0;

+	private Boolean accessingFile = true;

+

+	/**

+	 * Monitor thread: if a file has not been accessed for certain time, close it.

+	 */

+	class MonitorThread extends Thread {

+

+		public MonitorThread() {

+			super();

+			setDaemon(true);

+			setName("File Monitor");

+		}

+

+		@Override

+		public void run() {

+			while (true) {

+				synchronized (accessingFile) {

+					if (fileAccessTime != 0 && 

+							System.currentTimeMillis() > fileAccessTime + TIME_OUT) 

+					{

+						close();

+						fileAccessTime = 0;

+						break;  // exit the thread

+					}

+				}

+				

+				try {

+					sleep(300);

+				} catch (InterruptedException e) {

+					break;

+				}

+			}

+		}

+	}

+

+	/**

+	 * Get the {@link FileAccessor} object for the given file.

+	 * 

+	 * @param osFile

+	 *            can be null.

+	 * @return cached {@link FileAccessor} object for the file, or create a new

+	 *         one if not cached. <code>null</code> if osFile is null.

+	 */

+	static public FileAccessor getFileAccessor(File osFile) {

+		if (osFile == null)

+			return null;

+		

+		FileAccessor ret = accessors.get(osFile);

+		if (ret == null) {

+			ret = new FileAccessor(osFile);

+			accessors.put(osFile, ret);

+		}

+		

+		return ret;

+	}

+	

+	private FileAccessor(File osFile) {

+		this.file = osFile;

+	}

+

+	public void dispose() {

+		synchronized (accessingFile) {

+			close();

+		}

+		

+		accessors.remove(file);

+	}

+

+	/**

+	 * Read bytes from given offset in the file.

+	 * 

+	 * @param buffer

+	 *            caller allocated buffer to hold data.

+	 * @param sourceOffset

+	 *            offset in the file (in bytes)

+	 * @param count

+	 *            number of bytes to read

+	 * @throws IOException file access failure.

+	 */

+	public void read(byte[] buffer, long sourceOffset, int count) throws IOException {

+		synchronized (accessingFile) {

+			ensureOpen();

+			

+			raFile.seek(sourceOffset);

+			raFile.read(buffer, 0, count);

+			

+			fileAccessTime = System.currentTimeMillis();

+		}

+	}

+

+	private void ensureOpen() throws IOException {

+		if (raFile == null && file != null) {

+			if (DEBUG) System.out.println("Thread " + Thread.currentThread().getName() + ": Opening " + file);

+			FileStatistics.executablesOpened++;

+			FileStatistics.executablesOpen++;

+			raFile = new ERandomAccessFile(file, "r");

+			

+			new MonitorThread().start();	// start the monitor thread

+		}

+	}

+

+	private void close() {

+		if (raFile != null && file != null) {

+			try {

+				if (DEBUG) System.out.println("File Monitor Thread " + Thread.currentThread().getId() + ": closing " + file);

+				FileStatistics.executablesOpen--;

+				raFile.close();

+				raFile = null;

+			} catch (IOException e) {

+				// ignore

+			}

+		}

+	}

+	

+}

diff --git a/org.eclipse.cdt.debug.edc/src/org/eclipse/cdt/debug/edc/internal/FileStreamBuffer.java b/org.eclipse.cdt.debug.edc/src/org/eclipse/cdt/debug/edc/internal/FileStreamBuffer.java
index 9c76fd5..e198233 100644
--- a/org.eclipse.cdt.debug.edc/src/org/eclipse/cdt/debug/edc/internal/FileStreamBuffer.java
+++ b/org.eclipse.cdt.debug.edc/src/org/eclipse/cdt/debug/edc/internal/FileStreamBuffer.java
@@ -17,6 +17,7 @@
 
 package org.eclipse.cdt.debug.edc.internal;
 
+import java.io.File;
 import java.io.IOException;
 import java.io.RandomAccessFile;
 import java.nio.BufferUnderflowException;
@@ -26,36 +27,77 @@
 
 /**
  * This implementation of IStreamBuffer works on file content.
+ * 
+ * Each object of this class is either based on a {@link File} object or
+ * {@link RandomAccessFile} object.
  */
 public class FileStreamBuffer extends StreamBufferBase {
 	private final boolean DEBUG = false;
-	private RandomAccessFile file;
+
+	private RandomAccessFile raFile = null;
 	
-	
+	private File osFile = null;
+	private FileAccessor fileAccessor = null;
+
 	/**
-	 * Wrap in-memory content.
-	 * @param content
+	 * This will keep the file open. Caller is supposed to close the file.
+	 * 
+	 * @param file
 	 * @param order
 	 */
 	public FileStreamBuffer(RandomAccessFile file, ByteOrder order) throws IOException {
 		super(order, 0, file.length());
-		this.file = file;
-	}
-	public FileStreamBuffer(RandomAccessFile file, ByteOrder order, long position, long size) {
-		super(order, position, size);
-		this.file = file;
+		this.raFile = file;
 	}
 	
+	public FileStreamBuffer(RandomAccessFile file, ByteOrder order, long position, long size) {
+		super(order, position, size);
+		this.raFile = file;
+	}
+
+	/**
+	 * The underlying file will be auto opened for access, and auto-closed after
+	 * it's not accessed for certain time.
+	 * 
+	 * @param file
+	 * @param order
+	 */
+	public FileStreamBuffer(File file, ByteOrder order) {
+		super(order, 0, file.length());
+		this.osFile = file;
+	}
+	
+	/**
+	 * Refer to {@link #FileStreamBuffer(File, ByteOrder)}.
+	 * 
+	 * @param file
+	 * @param order
+	 * @param position
+	 * @param size
+	 */
+	public FileStreamBuffer(File file, ByteOrder order, long position, long size) {
+		super(order, position, size);
+		this.osFile = file;
+	}
+
 	/* (non-Javadoc)
 	 * @see org.eclipse.cdt.debug.edc.internal.StreamBufferBase#fetchPage(byte[], int, int)
 	 */
 	@Override
 	protected void fetchPage(byte[] buffer, long sourceOffset, int count) {
 		try {
-			if (DEBUG) System.out.print("Reading "+ sourceOffset + " x "+ count + "... ");
-			file.seek(sourceOffset);
-			file.read(buffer, 0, count);
-			if (DEBUG) System.out.println("done");
+			if (osFile != null) {
+				if (fileAccessor == null)
+					fileAccessor = FileAccessor.getFileAccessor(osFile);
+			
+				fileAccessor.read(buffer, sourceOffset, count);
+			}
+			else {
+				if (DEBUG) System.out.print("Reading "+ sourceOffset + " x "+ count + "... ");
+				raFile.seek(sourceOffset);
+				raFile.read(buffer, 0, count);
+				if (DEBUG) System.out.println("done");				
+			}
 		} catch (IOException e) {
 			BufferUnderflowException be = new BufferUnderflowException();
 			be.initCause(e);
@@ -68,6 +110,9 @@
 	 */
 	@Override
 	protected IStreamBuffer createSubBuffer(long offset, long size) {
-		return new FileStreamBuffer(file, order, offset, size);
+		if (osFile != null)
+			return new FileStreamBuffer(osFile, order, offset, size);
+		else
+			return new FileStreamBuffer(raFile, order, offset, size);
 	}
 }
diff --git a/org.eclipse.cdt.debug.edc/src/org/eclipse/cdt/debug/edc/internal/symbols/elf/BufferedRandomReadAccessFile.java b/org.eclipse.cdt.debug.edc/src/org/eclipse/cdt/debug/edc/internal/symbols/elf/BufferedRandomReadAccessFile.java
index ae5d7d9..e1f258e 100644
--- a/org.eclipse.cdt.debug.edc/src/org/eclipse/cdt/debug/edc/internal/symbols/elf/BufferedRandomReadAccessFile.java
+++ b/org.eclipse.cdt.debug.edc/src/org/eclipse/cdt/debug/edc/internal/symbols/elf/BufferedRandomReadAccessFile.java
@@ -11,6 +11,7 @@
 
 package org.eclipse.cdt.debug.edc.internal.symbols.elf;
 
+import java.io.File;
 import java.io.IOException;
 import java.io.RandomAccessFile;
 import java.nio.BufferUnderflowException;
@@ -43,11 +44,11 @@
 	 * @throws IOException 
 	 */
 	public BufferedRandomReadAccessFile(String file, boolean isle) throws IOException {
-		this.file = new RandomAccessFile(file, "r"); //$NON-NLS-1$
+		File f = new File(file);
 		// for ELF-aware reading
-		this.buffer = new FileStreamBuffer(this.file,  isle ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN);
+		this.buffer = new FileStreamBuffer(f,  isle ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN);
 		// for native DataInput APIs
-		this.bigEndianBuffer = new FileStreamBuffer(this.file, ByteOrder.BIG_ENDIAN);
+		this.bigEndianBuffer = new FileStreamBuffer(f, ByteOrder.BIG_ENDIAN);
 	}
 
 	/* (non-Javadoc)
@@ -239,7 +240,8 @@
 	 * @see java.io.Closeable#close()
 	 */
 	public void close() throws IOException {
-		file.close();
+		if (file != null)
+			file.close();
 	}
 
 	/* (non-Javadoc)
diff --git a/org.eclipse.cdt.debug.edc/src/org/eclipse/cdt/debug/edc/internal/symbols/files/SectionMapper.java b/org.eclipse.cdt.debug.edc/src/org/eclipse/cdt/debug/edc/internal/symbols/files/SectionMapper.java
index 098fd97..25fae54 100644
--- a/org.eclipse.cdt.debug.edc/src/org/eclipse/cdt/debug/edc/internal/symbols/files/SectionMapper.java
+++ b/org.eclipse.cdt.debug.edc/src/org/eclipse/cdt/debug/edc/internal/symbols/files/SectionMapper.java
@@ -82,6 +82,10 @@
 	private void close() {

 		if (!mappedBuffers.isEmpty())

 			throw new IllegalStateException("cannot close file; mapped buffers open");

+		closeFile();

+	}

+	

+	private void closeFile() {

 		if (efile != null) {

 			try {

 				FileStatistics.log("Closing " + hostFile.toFile());

@@ -98,9 +102,6 @@
 	 * @see org.eclipse.cdt.debug.edc.internal.symbols.exe.IExecutableSectionMapper#getSectionBuffer(org.eclipse.cdt.debug.edc.internal.symbols.exe.SectionInfo)

 	 */

 	public IStreamBuffer getSectionBuffer(SectionInfo section) throws IOException {

-		

-		ensureOpen();

-

 		IStreamBuffer buffer = loadedSections.get(section);

 		if (buffer == null) {

 			buffer = loadSection(section);

@@ -145,8 +146,10 @@
 	private IStreamBuffer loadSectionIntoFileStreamBuffer(SectionInfo section) throws IOException {

 		IStreamBuffer buffer = null;

 		try {

-			buffer = new FileStreamBuffer(efile, isLE ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN, 

+			// Note this will release the hostFile handle after it's not accessed for certain time.

+			buffer = new FileStreamBuffer(hostFile.toFile(), isLE ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN, 

 					section.fileOffset, section.sectionSize);

+

 			mappedBuffers.put(section, buffer);

 			FileStatistics.currentMemoryMappedBuffers += buffer.capacity();

 			FileStatistics.totalMemoryMappedBuffers += buffer.capacity();

@@ -158,6 +161,8 @@
 

 	private IStreamBuffer loadSectionIntoHeap(SectionInfo section)

 			throws IOException {

+		ensureOpen();

+

 		IStreamBuffer buffer;

 		// try to load the section into memory because it will

 		// be faster

@@ -168,6 +173,10 @@
 										0 , (bytesRead != -1) ? bytesRead : 0);

 		FileStatistics.currentHeapAllocatedBuffers += data.length;

 		FileStatistics.totalHeapAllocatedBuffers += data.length;

+		

+		// To avoid locking the file, we close the file after loading a section.

+		closeFile();

+		

 		return buffer;

 	}