blob: 6a29258f57aabe53d0368866b025766fa209b6b8 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007, 2012 IBM Corporation 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:
* IBM Corporation - initial API and implementation
*
*******************************************************************************/
package org.eclipse.jst.jsp.core.internal.util;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.Reference;
import java.lang.ref.SoftReference;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.IllegalCharsetNameException;
import java.nio.charset.UnsupportedCharsetException;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import org.eclipse.core.filebuffers.FileBuffers;
import org.eclipse.core.filebuffers.ITextFileBuffer;
import org.eclipse.core.filebuffers.LocationKind;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.QualifiedName;
import org.eclipse.core.runtime.content.IContentDescription;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jst.jsp.core.internal.Logger;
public class FileContentCache {
private static class CacheEntry {
String contents;
long modificationStamp = IResource.NULL_STAMP;
IPath contentPath;
CacheEntry(IPath path) {
this.contentPath = path;
modificationStamp = getModificationStamp(path);
contents = readContents(path);
}
private IFile getFile(IPath path) {
if (path.segmentCount() > 1) {
return ResourcesPlugin.getWorkspace().getRoot().getFile(path);
}
return null;
}
boolean isStale() {
if (modificationStamp == IResource.NULL_STAMP) {
return true;
}
long newStamp = getModificationStamp(contentPath);
return newStamp > modificationStamp;
}
/**
* @param name
* @param contents2
* @return
*/
private String detectCharset(String name, byte[] contents) throws IOException {
IContentDescription description = Platform.getContentTypeManager().getDescriptionFor(new ByteArrayInputStream(contents), name, new QualifiedName[]{IContentDescription.CHARSET});
if (description != null) {
String charset = description.getCharset();
if (charset == null && description.getContentType() != null)
charset = description.getContentType().getDefaultCharset();
if (charset != null)
return charset;
}
return ResourcesPlugin.getEncoding();
}
private long getModificationStamp(IPath filePath) {
IFile f = getFile(filePath);
if (f != null && f.isAccessible()) {
return f.getModificationStamp();
}
File file = filePath.toFile();
if (file.exists())
return file.lastModified();
return IResource.NULL_STAMP;
}
/**
* Read once and store the contents to determine the encoding and reuse as input
*/
private String readContents(IPath filePath) {
if (DEBUG)
System.out.println(getClass().getName() + " readContents: " + filePath); //$NON-NLS-1$
InputStream is = null;
try {
IFile f = getFile(filePath);
if (f != null && f.isAccessible()) {
is = f.getContents();
}
else {
is = new FileInputStream(filePath.toFile());
}
ByteArrayOutputStream store = new ByteArrayOutputStream();
byte[] readBuffer = new byte[8092];
int n = is.read(readBuffer);
while (n > 0) {
store.write(readBuffer, 0, n);
n = is.read(readBuffer);
}
byte[] bytes = store.toByteArray();
String charset = detectCharset(filePath.lastSegment(), bytes);
ByteBuffer buffer = ByteBuffer.wrap(bytes);
try {
CharBuffer charBuffer = Charset.forName(charset).decode(buffer);
return charBuffer.toString();
}
catch (IllegalCharsetNameException e) {
return new String(bytes, charset);
}
catch (UnsupportedCharsetException e) {
return new String(bytes, charset);
}
}
catch (CoreException e) {
Logger.logException(e);
// out of sync
}
catch (Exception e) {
// Logger.logException(e);
}
finally {
try {
if (is != null) {
is.close();
}
}
catch (Exception e) {
// nothing to do
}
}
return null;
}
}
static final boolean DEBUG = false;
static FileContentCache instance = new FileContentCache();
public static FileContentCache getInstance() {
return instance;
}
static class LimitedHashMap extends LinkedHashMap {
private static final long serialVersionUID = 1L;
protected boolean removeEldestEntry(java.util.Map.Entry eldest) {
return size() > 25; //completely arbitrary number
}
}
private LinkedHashMap fContentMap;
private FileContentCache() {
super();
fContentMap = new LimitedHashMap();
}
private void cleanup() {
synchronized (fContentMap) {
Iterator iterator = fContentMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry entry = (Map.Entry) iterator.next();
if (entry.getValue() != null && ((Reference) entry.getValue()).get() == null) {
iterator.remove();
}
}
}
}
public String getContents(IPath filePath) {
if (DEBUG)
System.out.println(getClass().getName() + "#getContents: " + filePath); //$NON-NLS-1$
/*
* Use an open file buffer if one exists for contents in dirty
* editors. LocationKind.IFILE will only apply to workspace paths, but
* they're far more likely to be open in an editor and modified than
* files outside the workspace (LocationKind.LOCATION). We'll use
* LocationKind.NORMALIZE and let the framework sort things out,
* unless it proves to be a performance problem.
*
* Do not cause a file buffer to be opened by calling connect()
*/
ITextFileBuffer existingBuffer = FileBuffers.getTextFileBufferManager().getTextFileBuffer(
filePath, LocationKind.NORMALIZE);
if (existingBuffer != null) {
IDocument document = existingBuffer.getDocument();
if (document != null) {
return document.get();
}
}
//check the cache
CacheEntry entry = null;
Object o = fContentMap.get(filePath);
if (o instanceof Reference) {
entry = (CacheEntry) ((Reference) o).get();
}
if (entry == null || entry.isStale()) {
if (DEBUG && entry != null && entry.isStale())
System.out.println(getClass().getName() + " stale contents: " + filePath); //$NON-NLS-1$
entry = new CacheEntry(filePath);
synchronized (fContentMap) {
fContentMap.put(filePath, new SoftReference(entry));
}
}
cleanup();
return entry.contents;
}
}