/******************************************************************************* * 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; } }