/*******************************************************************************
* Copyright (c) 2016 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
* Zend Technologies
*******************************************************************************/
package org.eclipse.php.internal.core.builder;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExecutableExtension;
import org.eclipse.dltk.ast.declarations.ModuleDeclaration;
import org.eclipse.dltk.compiler.task.ITaskReporter;
import org.eclipse.dltk.core.IScriptProject;
import org.eclipse.dltk.core.builder.AbstractBuildParticipantType;
import org.eclipse.dltk.core.builder.IBuildContext;
import org.eclipse.dltk.core.builder.IBuildParticipant;
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.php.core.PHPToolkitUtil;
import org.eclipse.php.core.compiler.ast.nodes.Comment;
import org.eclipse.php.core.compiler.ast.nodes.PHPModuleDeclaration;
import org.eclipse.php.core.compiler.ast.nodes.Scalar;
import org.eclipse.php.internal.core.Logger;
import org.eclipse.php.internal.core.PHPCoreConstants;
import org.eclipse.php.internal.core.preferences.TaskPatternsProvider;
import org.eclipse.php.internal.core.preferences.TaskTagsProvider;
import org.eclipse.wst.sse.core.internal.provisional.tasks.TaskTag;
/**
* This builder firstly finds all todo task tags in comments and secondly
* creates (and removes) the task markers that will eventually show up in the
* task view.
*
* @author blind
*/
public class TaskTagBuildParticipantFactory extends AbstractBuildParticipantType implements IExecutableExtension {
@Override
public IBuildParticipant createBuildParticipant(IScriptProject project) throws CoreException {
if (natureId != null) {
return new TaskTagBuildParticipantParticipant(project);
}
return null;
}
protected String natureId = null;
public void setInitializationData(IConfigurationElement config, String propertyName, Object data)
throws CoreException {
natureId = config.getAttribute("nature"); //$NON-NLS-1$
}
private static class TaskTagBuildParticipantParticipant implements IBuildParticipant {
private IScriptProject project;
public TaskTagBuildParticipantParticipant(IScriptProject project) {
this.project = project;
}
private void checkForTodo(Pattern[] todos, List<Scalar> foundTaskTags, int commentStart, String comment) {
ArrayList<Matcher> matchers = createMatcherList(todos, comment);
int startPosition = 0;
Matcher matcher = getMinimalMatcher(matchers, startPosition);
while (matcher != null) {
int startIndex = matcher.start();
int endIndex = matcher.end();
foundTaskTags.add(new Scalar(commentStart + startIndex, commentStart + endIndex, matcher.group(),
Scalar.TYPE_STRING));
startPosition = endIndex;
matcher = getMinimalMatcher(matchers, startPosition);
}
}
private ArrayList<Matcher> createMatcherList(Pattern[] todos, String content) {
ArrayList<Matcher> list = new ArrayList<Matcher>(todos.length);
for (int i = 0; i < todos.length; i++) {
list.add(i, todos[i].matcher(content));
}
return list;
}
private Matcher getMinimalMatcher(ArrayList<Matcher> matchers, int startPosition) {
Matcher minimal = null;
int size = matchers.size();
for (int i = 0; i < size;) {
Matcher tmp = (Matcher) matchers.get(i);
if (tmp.find(startPosition)) {
if (minimal == null || tmp.start() < minimal.start()) {
minimal = tmp;
}
i++;
} else {
matchers.remove(i);
size--;
}
}
return minimal;
}
/**
* Get the task priority according to the preferences
*
* @return
*/
private int getTaskPriority(TaskTag[] taskTags, String taskStr) {
String taskTagLowerCase = taskStr.toLowerCase();
for (int i = 0; i < taskTags.length; i++) {
TaskTag taskTag = taskTags[i];
if (taskTag.getTag().toLowerCase().equals(taskTagLowerCase)) {
return taskTag.getPriority();
}
}
return TaskTag.PRIORITY_NORMAL;
}
/**
* Reports the task
*
* @param document
* @param file
* @param taskReporter
* @param offset
* @param nextTaskTag
* @param priority
* @throws BadLocationException
* @throws CoreException
*/
private void reportTask(IDocument document, IFile file, ITaskReporter taskReporter, int offset,
Scalar nextTaskTag, int priority) throws BadLocationException, CoreException {
int lineNumber = document.getLineOfOffset(offset);
String taskStr = getTaskStr(document, lineNumber, offset, nextTaskTag);
// the end of the string to be highlighted
int charEnd = offset + taskStr.length();
// report the task
createMarker(file, taskStr, lineNumber, priority, offset, charEnd);
}
/**
* Creates a PHP task marker based on the given information
*
* @param file
* @param taskStr
* @param lineNumber
* @param priority
* @param offset
* @param charEnd
* @throws CoreException
*/
private void createMarker(IFile file, String taskStr, int lineNumber, int priority, int offset, int charEnd)
throws CoreException {
IMarker marker = file.createMarker(PHPCoreConstants.PHP_MARKER_TYPE);
if (!marker.exists()) {
return;
}
marker.setAttribute(IMarker.TASK, true);
marker.setAttribute(IMarker.LINE_NUMBER, lineNumber + 1);
marker.setAttribute(IMarker.CHAR_START, offset);
marker.setAttribute(IMarker.CHAR_END, charEnd);
marker.setAttribute(IMarker.MESSAGE, taskStr);
marker.setAttribute(IMarker.USER_EDITABLE, false);
marker.setAttribute(IMarker.PRIORITY, priority);
}
/**
* Gets the Task message from the document
*
* @param document
* @param lineNumber
* @param offset
* @param nextTaskTag
* @return
* @throws BadLocationException
*/
private String getTaskStr(IDocument document, int lineNumber, int offset, Scalar nextTaskTag)
throws BadLocationException {
// get line info to identify the end of the task
IRegion lineInformation = document.getLineInformation(lineNumber);
int lineStart = lineInformation.getOffset();
int lineEnd = lineStart + lineInformation.getLength();
// identify the actual end of the task: either end of line or begin
// of the next token
int taskEnd;
if (nextTaskTag != null) {
taskEnd = Math.min(nextTaskTag.start(), lineEnd);
} else {
taskEnd = lineEnd;
}
String taskStr = document.get(offset, taskEnd - offset);
taskStr = taskStr.trim();
return taskStr;
}
@Override
public void build(IBuildContext context) throws CoreException {
if (Boolean.TRUE.equals(context.get(ParserBuildParticipantFactory.IN_LIBRARY_FOLDER))) {
// skip syntax check for code inside library folders
return;
}
if (context.getFile() == null) {
// skip when building external module
return;
}
if (context.getFile().getType() != IResource.FILE || !(PHPToolkitUtil.isPHPFile(context.getFile()))) {
// process only PHP files
return;
}
// remove the markers currently existing for this file
try {
context.getFile().deleteMarkers(PHPCoreConstants.PHP_MARKER_TYPE, false, IResource.DEPTH_INFINITE);
} catch (CoreException e) {
}
final ModuleDeclaration moduleDeclaration = (ModuleDeclaration) context
.get(IBuildContext.ATTR_MODULE_DECLARATION);
if (moduleDeclaration instanceof PHPModuleDeclaration) {
try {
Pattern[] todos;
TaskTag[] taskTags;
if (project != null && project.getProject() != null) {
todos = TaskPatternsProvider.getInstance().getPatternsForProject(project.getProject());
taskTags = TaskTagsProvider.getInstance().getProjectTaskTags(project.getProject());
} else {
todos = TaskPatternsProvider.getInstance().getPatternsForWorkspace();
taskTags = TaskTagsProvider.getInstance().getWorkspaceTaskTags();
}
if (taskTags == null) {
taskTags = TaskTagsProvider.getInstance().getWorkspaceTaskTags();
}
IDocument document = new Document(new String(context.getContents()));
for (Comment comment : ((PHPModuleDeclaration) moduleDeclaration).getCommentList()) {
List<Scalar> foundTaskTags = new ArrayList<Scalar>();
checkForTodo(todos, foundTaskTags, comment.start(),
document.get(comment.start(), comment.end() - comment.start()));
if (foundTaskTags.isEmpty()) {
comment.setTaskTags(null);
} else {
comment.setTaskTags(foundTaskTags);
for (int i = 0; i < foundTaskTags.size(); i++) {
Scalar foundTaskTag = foundTaskTags.get(i);
String taskKeyword = document.get(foundTaskTag.start(),
foundTaskTag.end() - foundTaskTag.start());
int priority = getTaskPriority(taskTags, taskKeyword);
reportTask(document, context.getFile(), context.getTaskReporter(), foundTaskTag.start(),
i + 1 < foundTaskTags.size() ? foundTaskTags.get(i + 1) : null, priority);
}
}
}
} catch (Exception e) {
Logger.logException(e);
}
}
}
}
}