blob: 5bbf1ff92ee7dc4ed4dff3dfe123c168b31f462e [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2016 xored software, Inc. 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:
* xored software, Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.dltk.core;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.RandomAccessFile;
import java.io.Reader;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.core.runtime.QualifiedName;
import org.eclipse.core.runtime.content.IContentDescription;
import org.eclipse.core.runtime.content.ITextContentDescriber;
import org.eclipse.dltk.utils.CharArraySequence;
public abstract class ScriptContentDescriber implements ITextContentDescriber {
@Override
public QualifiedName[] getSupportedOptions() {
return new QualifiedName[] { DLTKContentTypeManager.DLTK_VALID };
}
private final static int BUFFER_LENGTH = 2 * 1024;
private final static int HEADER_LENGTH = 4 * 1024;
private final static int FOOTER_LENGTH = 4 * 1024;
private static boolean checkHeader(File file, Pattern[] headerPatterns,
Pattern[] footerPatterns) throws FileNotFoundException, IOException {
FileInputStream reader = null;
try {
reader = new FileInputStream(file);
byte buf[] = new byte[BUFFER_LENGTH + 1];
int res = reader.read(buf);
if (res == -1 || res == 0) {
return false;
}
String header = new String(buf);
if (checkBufferForPatterns(header, headerPatterns)) {
return true;
}
if (file.length() < BUFFER_LENGTH && footerPatterns != null) {
if (checkBufferForPatterns(header, footerPatterns)) {
return true;
}
}
return false;
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
}
}
}
}
private static boolean checkFooter(File file, Pattern[] footerPatterns)
throws FileNotFoundException, IOException {
RandomAccessFile raFile = new RandomAccessFile(file, "r"); //$NON-NLS-1$
try {
long len = BUFFER_LENGTH;
long fileSize = raFile.length();
long offset = fileSize - len;
if (offset < 0) {
offset = 0;
}
raFile.seek(offset);
byte buf[] = new byte[BUFFER_LENGTH + 1];
int code = raFile.read(buf);
if (code != -1) {
String content = new String(buf, 0, code);
if (checkBufferForPatterns(content, footerPatterns)) {
return true;
}
}
return false;
} finally {
raFile.close();
}
}
private static boolean checkBufferForPatterns(CharSequence header,
Pattern[] patterns) {
if (patterns == null) {
return false;
}
for (int i = 0; i < patterns.length; i++) {
Matcher m = patterns[i].matcher(header);
if (m.find()) {
return true;
}
}
return false;
}
public static boolean checkPatterns(File file, Pattern[] headerPatterns,
Pattern[] footerPatterns) {
try {
if (checkHeader(file, headerPatterns, footerPatterns)) {
return true;
}
if (footerPatterns != null && file.length() > BUFFER_LENGTH
&& checkFooter(file, footerPatterns)) {
return true;
}
} catch (FileNotFoundException e) {
return false;
} catch (IOException e) {
return false;
}
return false;
}
/**
* Reads the specified number of bytes from the specified reader. Calls
* {@link Reader#read(char[], int, int)} multiple times until EOF of the
* specified number of bytes is read. Also catches {@link IOException} and
* return -1 on it.
*
* @param reader
* @param bufffer
* @param offset
* @param len
* @return
*/
private static int read(Reader reader, char[] bufffer, int offset, int len) {
try {
int count = 0;
while (len > 0) {
final int result = reader.read(bufffer, offset, len);
if (result > 0) {
offset += result;
len -= result;
count += result;
} else if (result < 0) {
if (count == 0) {
return result;
} else {
break;
}
}
}
return count;
} catch (IOException e) {
if (DLTKCore.DEBUG) {
e.printStackTrace(); // ignore
}
return -1;
}
}
public static boolean checkPatterns(Reader reader,
Pattern[] headerPatterns, Pattern[] footerPatterns) {
/*
* There is no need to use BufferedReader here since a) we read blocks,
* b) implementation provided by eclipse.core already do buffering.
*/
final int bufferSize = Math.max(HEADER_LENGTH, FOOTER_LENGTH);
char[] buffer = new char[bufferSize];
int len = read(reader, buffer, 0, HEADER_LENGTH);
if (len > 0) {
if (headerPatterns != null && headerPatterns.length > 0) {
if (checkBufferForPatterns(new CharArraySequence(buffer, len),
headerPatterns)) {
return true;
}
}
}
if (footerPatterns != null && footerPatterns.length > 0) {
char[] prevBuffer = new char[bufferSize];
int prevLen = 0;
for (;;) {
final char[] tempBuffer = buffer;
buffer = prevBuffer;
prevBuffer = tempBuffer;
//
final int savedLen = prevLen;
prevLen = len;
len = savedLen;
//
len = read(reader, buffer, 0, bufferSize);
if (len <= 0) {
final CharSequence footer;
if (savedLen >= FOOTER_LENGTH) {
footer = new CharArraySequence(buffer, savedLen
- FOOTER_LENGTH, FOOTER_LENGTH);
} else {
int footerLength = prevLen;
if (savedLen > 0) {
footerLength += savedLen;
}
if (footerLength > FOOTER_LENGTH) {
footerLength = FOOTER_LENGTH;
}
int prevOffset = Math.max(prevLen - footerLength, 0);
int prevSize = Math.min(prevLen, footerLength);
if (savedLen > 0) {
System.arraycopy(buffer, 0, buffer, footerLength
- savedLen, savedLen);
prevOffset += savedLen;
prevSize -= savedLen;
}
if (prevSize > 0) {
System.arraycopy(prevBuffer, prevOffset, buffer, 0,
prevSize);
}
footer = new CharArraySequence(buffer, footerLength);
}
if (checkBufferForPatterns(footer, footerPatterns)) {
return true;
}
}
break;
}
}
return false;
}
@Override
public int describe(InputStream contents, IContentDescription description)
throws IOException {
return describe(new InputStreamReader(contents), description);
}
@Override
public int describe(Reader contents, IContentDescription description)
throws IOException {
Pattern[] header = getHeaderPatterns();
Pattern[] footer = getFooterPatterns();
if (checkPatterns(contents, header, footer)) {
if (description != null) {
description.setProperty(DLTKContentTypeManager.DLTK_VALID,
Boolean.TRUE);
}
return VALID;
}
return INDETERMINATE;
}
/**
* Returns an array of patterns that will be used to scan file headers to
* determine the script content type.
*/
protected abstract Pattern[] getHeaderPatterns();
/**
* Returns an array of patterns that will be used to scan file footers to
* determine the script content type.
*
* <p>
* Default implementation returns <code>null</code>. Subclasses are free to
* override if they wish to provide footer patterns.
* </p>
*/
protected Pattern[] getFooterPatterns() {
return null;
}
}