/******************************************************************************* * Copyright (c) 2001, 2008 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 * Jens Lukowski/Innoopract - initial renaming/restructuring * *******************************************************************************/ package org.eclipse.wst.sse.core.internal.tasks; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.nio.charset.CharacterCodingException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IMarker; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IConfigurationElement; import org.eclipse.core.runtime.IExecutableExtension; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.Document; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IRegion; import org.eclipse.wst.sse.core.internal.Logger; import org.eclipse.wst.sse.core.internal.document.DocumentReader; import org.eclipse.wst.sse.core.internal.ltk.modelhandler.IModelHandler; import org.eclipse.wst.sse.core.internal.ltk.parser.RegionParser; import org.eclipse.wst.sse.core.internal.ltk.parser.StructuredDocumentRegionHandler; import org.eclipse.wst.sse.core.internal.ltk.parser.StructuredDocumentRegionParser; import org.eclipse.wst.sse.core.internal.modelhandler.ModelHandlerRegistry; import org.eclipse.wst.sse.core.internal.provisional.document.IEncodedDocument; import org.eclipse.wst.sse.core.internal.provisional.tasks.IFileTaskScanner; import org.eclipse.wst.sse.core.internal.provisional.tasks.TaskTag; import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument; import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion; import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegion; import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegionList; /** * A delegate to create IMarker.TASKs for "todos" and similar comments. */ public abstract class StructuredFileTaskScanner implements IFileTaskScanner, IExecutableExtension { // the list of attributes for the new tasks for the current file protected List fNewMarkerAttributeMaps = null; List oldMarkers = null; private long time0; private String runtimeMarkerType; public StructuredFileTaskScanner() { super(); fNewMarkerAttributeMaps = new ArrayList(); if (Logger.DEBUG_TASKS) { System.out.println(getClass().getName() + " instance created"); //$NON-NLS-1$ } } /** * Returns the attributes with which a newly created marker will be * initialized. Modified from the method in MarkerRulerAction * * @return the initial marker attributes */ protected Map createInitialMarkerAttributes(String text, int documentLine, int startOffset, int length, int priority) { Map attributes = new HashMap(6); // marker line numbers are 1-based attributes.put(IMarker.LINE_NUMBER, new Integer(documentLine + 1)); attributes.put(IMarker.TASK, getMarkerType()); attributes.put(IMarker.CHAR_START, new Integer(startOffset)); attributes.put(IMarker.CHAR_END, new Integer(startOffset + length)); attributes.put(IMarker.MESSAGE, text); attributes.put(IMarker.USER_EDITABLE, Boolean.FALSE); switch (priority) { case IMarker.PRIORITY_HIGH : { attributes.put(IMarker.PRIORITY, new Integer(IMarker.PRIORITY_HIGH)); } break; case IMarker.PRIORITY_LOW : { attributes.put(IMarker.PRIORITY, new Integer(IMarker.PRIORITY_LOW)); } break; default : { attributes.put(IMarker.PRIORITY, new Integer(IMarker.PRIORITY_NORMAL)); } } return attributes; } /* * (non-Javadoc) * * @see org.eclipse.wst.sse.core.internal.provisional.tasks.IFileTaskScanner#getMarkerType() */ public String getMarkerType() { if (runtimeMarkerType != null) return runtimeMarkerType; return org.eclipse.core.resources.IMarker.TASK; } private String detectCharset(IFile file) { if (file.getType() == IResource.FILE && file.isAccessible()) { try { return file.getCharset(true); } catch (CoreException e) { Logger.logException(e); } } return ResourcesPlugin.getEncoding(); } /** * @param document * @param documentRegion * @param comment */ protected void findTasks(IDocument document, TaskTag[] taskTags, IStructuredDocumentRegion documentRegion, ITextRegion comment) { if (isCommentRegion(documentRegion, comment)) { int startOffset = documentRegion.getStartOffset(comment); int endOffset = documentRegion.getTextEndOffset(comment); try { int startLine = document.getLineOfOffset(startOffset); int endLine = document.getLineOfOffset(endOffset); for (int lineNumber = startLine; lineNumber <= endLine; lineNumber++) { IRegion line = document.getLineInformation(lineNumber); int begin = Math.max(startOffset, line.getOffset()); int end = Math.min(endOffset, line.getOffset() + line.getLength()); int length = end - begin; /* XXX: This generates a lot of garbage objects */ String commentedText = getCommentedText(document, begin, length); String comparisonText = commentedText.toLowerCase(Locale.ENGLISH); for (int i = 0; i < taskTags.length; i++) { int tagIndex = comparisonText.indexOf(taskTags[i].getTag().toLowerCase(Locale.ENGLISH)); if (tagIndex >= 0) { String markerDescription = commentedText.substring(tagIndex); int markerOffset = begin + tagIndex; int markerLength = end - markerOffset; fNewMarkerAttributeMaps.add(createInitialMarkerAttributes(markerDescription, lineNumber, markerOffset, markerLength, taskTags[i].getPriority())); } } } } catch (BadLocationException e) { Logger.logException(e); } } } private void findTasks(IFile file, final TaskTag[] taskTags, final IProgressMonitor monitor) { try { IModelHandler handler = ModelHandlerRegistry.getInstance().getHandlerFor(file); // records if the optimized streamish parse was possible boolean didStreamParse = false; final IEncodedDocument defaultDocument = handler.getDocumentLoader().createNewStructuredDocument(); if (defaultDocument instanceof IStructuredDocument) { RegionParser parser = ((IStructuredDocument) defaultDocument).getParser(); if (parser instanceof StructuredDocumentRegionParser) { didStreamParse = true; String charset = detectCharset(file); StructuredDocumentRegionParser documentParser = (StructuredDocumentRegionParser) parser; final IDocument textDocument = new Document(); setDocumentContent(textDocument, file.getContents(true), charset); monitor.beginTask("", textDocument.getLength()); documentParser.reset(new DocumentReader(textDocument)); documentParser.addStructuredDocumentRegionHandler(new StructuredDocumentRegionHandler() { public void nodeParsed(IStructuredDocumentRegion documentRegion) { ITextRegionList regions = documentRegion.getRegions(); for (int j = 0; j < regions.size(); j++) { ITextRegion comment = regions.get(j); findTasks(textDocument, taskTags, documentRegion, comment); } // disconnect the document regions if (documentRegion.getPrevious() != null) { documentRegion.getPrevious().setPrevious(null); documentRegion.getPrevious().setNext(null); } if (monitor.isCanceled()) { textDocument.set(""); //$NON-NLS-1$ } monitor.worked(documentRegion.getLength()); } public void resetNodes() { } }); documentParser.getDocumentRegions(); } } if (!didStreamParse) { // Use a StructuredDocument IEncodedDocument document = handler.getDocumentLoader().createNewStructuredDocument(file); monitor.beginTask("", document.getLength()); if (document instanceof IStructuredDocument) { IStructuredDocumentRegion documentRegion = ((IStructuredDocument) document).getFirstStructuredDocumentRegion(); while (documentRegion != null) { ITextRegionList regions = documentRegion.getRegions(); for (int j = 0; j < regions.size(); j++) { ITextRegion comment = regions.get(j); findTasks(document, taskTags, documentRegion, comment); } monitor.worked(documentRegion.getLength()); documentRegion = documentRegion.getNext(); } } } } catch (CoreException e) { Logger.logException("Exception with " + file.getFullPath().toString(), e); //$NON-NLS-1$ } catch (CharacterCodingException e) { Logger.log(Logger.INFO, "StructuredFileTaskScanner encountered CharacterCodingException reading " + file.getFullPath()); //$NON-NLS-1$ } catch (Exception e) { Logger.logException("Exception with " + file.getFullPath().toString(), e); //$NON-NLS-1$ } monitor.done(); } protected String getCommentedText(IDocument document, int begin, int length) throws BadLocationException { return document.get(begin, length); } protected abstract boolean isCommentRegion(IStructuredDocumentRegion region, ITextRegion textRegion); public synchronized Map[] scan(IFile file, TaskTag[] taskTags, IProgressMonitor monitor) { fNewMarkerAttributeMaps.clear(); if (monitor.isCanceled() || !shouldScan(file)) { return new Map[0]; } if (Logger.DEBUG_TASKSPERF) { time0 = System.currentTimeMillis(); } if (taskTags.length > 0) { findTasks(file, taskTags, monitor); } if (Logger.DEBUG_TASKSPERF) { System.out.println("" + (System.currentTimeMillis() - time0) + "ms for " + file.getFullPath()); //$NON-NLS-1$ //$NON-NLS-2$ } return (Map[]) fNewMarkerAttributeMaps.toArray(new Map[fNewMarkerAttributeMaps.size()]); } /** * Sets the document content from this stream and closes the stream */ protected void setDocumentContent(IDocument document, InputStream contentStream, String charset) { Reader in = null; try { in = new BufferedReader(new InputStreamReader(contentStream, charset), 2048); StringBuffer buffer = new StringBuffer(2048); char[] readBuffer = new char[2048]; int n = in.read(readBuffer); while (n > 0) { buffer.append(readBuffer, 0, n); n = in.read(readBuffer); } document.set(buffer.toString()); } catch (IOException x) { } finally { if (in != null) { try { in.close(); } catch (IOException x) { } } } } /* * (non-Javadoc) * * @see org.eclipse.core.runtime.IExecutableExtension#setInitializationData(org.eclipse.core.runtime.IConfigurationElement, * java.lang.String, java.lang.Object) */ public void setInitializationData(IConfigurationElement config, String propertyName, Object data) throws CoreException { if (data != null && data instanceof String) { runtimeMarkerType = data.toString(); } } boolean shouldScan(IResource r) { // skip "dot" files String s = r.getName(); return s.length() == 0 || s.charAt(0) != '.'; } public void shutdown(IProject project) { if (Logger.DEBUG_TASKS) { System.out.println(this + " shutdown for " + project.getName()); //$NON-NLS-1$ } } public void startup(IProject project) { if (Logger.DEBUG_TASKS) { System.out.println(this + " startup for " + project.getName()); //$NON-NLS-1$ } if (Logger.DEBUG_TASKSPERF) { time0 = System.currentTimeMillis(); } if (Logger.DEBUG_TASKSPERF) { System.out.println("" + (System.currentTimeMillis() - time0) + "ms loading prefs for " + project.getName()); //$NON-NLS-1$ //$NON-NLS-2$ } } }