/**************************************************************************
* ERA - Eclipse Requirements Analysis
* ==============================================
* Copyright (C) 2009-2013 by Georg Blaschke, Christoph P. Neumann
* and Bernd Haberstumpf (http://era.origo.ethz.ch)
**************************************************************************
* Licensed under the Eclipse Public License - v 1.0 (the "License");
* you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.eclipse.org/org/documents/epl-v10.html
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**************************************************************************
*/
package era.foss.tracer;
import java.util.Map;
import java.util.logging.Logger;
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.IResourceDelta;
import org.eclipse.core.resources.IResourceDeltaVisitor;
import org.eclipse.core.resources.IResourceVisitor;
import org.eclipse.core.resources.IncrementalProjectBuilder;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jdt.core.IAnnotation;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IField;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IMemberValuePair;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.ISourceRange;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.CompilationUnit;
/**
* The Class EraTracerIncrementalProjectBuilder.
*/
public class EraTracerIncrementalProjectBuilder extends IncrementalProjectBuilder {
private static final String MARKER_ID = Activator.PLUGIN_ID + ".reqmarker";
private static final Logger logger = Logger.getLogger( EraTracerIncrementalProjectBuilder.class.getSimpleName() );
protected IProject[] build( int kind, @SuppressWarnings("rawtypes") Map args, IProgressMonitor monitor ) throws CoreException {
if( kind == IncrementalProjectBuilder.FULL_BUILD ) {
fullBuild( monitor );
} else {
IResourceDelta delta = getDelta( getProject() );
if( delta == null ) {
fullBuild( monitor );
} else {
incrementalBuild( delta, monitor );
}
}
return null;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.core.resources.IncrementalProjectBuilder#startupOnInitialize()
*/
protected void startupOnInitialize() {
// Informs this builder that it is being started by the build management infrastructure.
}
/*
* (non-Javadoc)
*
* @see org.eclipse.core.resources.IncrementalProjectBuilder#clean(org.eclipse.core.runtime.IProgressMonitor)
*/
protected void clean( IProgressMonitor monitor ) {
IMarker[] markers;
try {
markers = getProject().findMarkers( MARKER_ID, true, IResource.DEPTH_INFINITE );
for( IMarker marker : markers ) {
marker.delete();
}
logger.info( "DELETED " + markers.length + " markers of type " + MARKER_ID );
} catch( CoreException e ) {
// Log the exception and bail out.
}
}
private void fullBuild( final IProgressMonitor monitor ) throws CoreException {
try {
getProject().accept( new EraTracerBuildVisitor() );
} catch( CoreException e ) {
}
}
private static class EraTracerBuildVisitor implements IResourceVisitor {
public boolean visit( IResource res ) {
// ignore anything but files
if( res.getType() != IResource.FILE ) return true;
// ignore anything but Java file
String ext = res.getFileExtension();
ext = (ext == null) ? "" : ext;
if( !ext.equalsIgnoreCase( "java" ) ) return true;
// handle it
IFile file = (IFile)res;
handleJavaFile( file );
// return true to continue visiting children.
return true;
}
}
private void incrementalBuild( IResourceDelta delta, IProgressMonitor monitor ) throws CoreException {
// the visitor does the work.
delta.accept( new EraTracerBuildDeltaVisitor() );
}
private static class EraTracerBuildDeltaVisitor implements IResourceDeltaVisitor {
public boolean visit( IResourceDelta delta ) {
IResource res = delta.getResource();
// ignore anything but files
if( res.getType() != IResource.FILE ) return true;
// ignore anything but Java file
String ext = res.getFileExtension();
ext = (ext == null) ? "" : ext;
if( !ext.equalsIgnoreCase( "java" ) ) return true;
// process the current (java) file
IFile file = (IFile)res;
switch (delta.getKind()) {
case IResourceDelta.ADDED:
handleJavaFile( file );
break;
case IResourceDelta.REMOVED:
logger.info( "Resource " + res.getFullPath() + " was removed." );
break;
case IResourceDelta.CHANGED:
handleJavaFile( file );
break;
}
return true; // visit the children
}
}
private static void handleJavaFile( IFile file ) {
// .java file are the same as ICompilationUnit
// (@see http://help.eclipse.org/indigo/index.jsp?topic=%2Forg.eclipse.jdt.doc.isv%2Fguide%2Fjdt_int_model.htm)
IJavaElement javaelem = JavaCore.create( file );
assert (javaelem instanceof ICompilationUnit);
ICompilationUnit compilunitabstraction = (ICompilationUnit)javaelem;
// transform ICompUnit into CompUnit for offset-to-linenumber transformation
CompilationUnit compilunit = parse( compilunitabstraction );
try {
for( IType itype : compilunitabstraction.getTypes() ) {
for( IAnnotation ianno : itype.getAnnotations() ) {
annoToMarker( ianno, itype.getElementName(), file, compilunit );
}
for( IField ifield : itype.getFields() ) {
for( IAnnotation ianno : ifield.getAnnotations() ) {
annoToMarker( ianno, ifield.getElementName(), file, compilunit );
}
}
for( IMethod imeth : itype.getMethods() ) {
for( IAnnotation ianno : imeth.getAnnotations() ) {
annoToMarker( ianno, imeth.getElementName(), file, compilunit );
}
}
}
} catch( JavaModelException e ) {
e.printStackTrace();
}
logger.info( "Resource " + file.getFullPath() + " has been built." );
}
/**
* @param ianno
* @return boolean indicating whether it was an ERF Requirement Annotation or not
*/
private static boolean annoToMarker( IAnnotation ianno, String elementName, IFile file, CompilationUnit cu ) {
// TODO no foolproof match on Req-Annotation
// (should be based on Annotation-Type, but such seems not to be supported)
if( !ianno.getElementName().equals( "Requirement" ) ) return false;
// MATCHED a Requirement Annotation
IMemberValuePair[] imvp = null;
try {
imvp = ianno.getMemberValuePairs();
} catch( JavaModelException e ) {
logger.warning( "No annotation attributes in '@Requirement' of " + elementName );
return false;
}
// extract requid: from MemberValuePairs
String reqid = null;
for( int i = 0; i < imvp.length; i++ ) {
if( imvp[i].getMemberName().equals( "reqid" ) ) {
reqid = (String)imvp[i].getValue();
}
}
if( reqid == null ) {
logger.warning( "Missing annotation attribute 'reqid' in '@Requirement' annotation of " + elementName );
return false;
}
// extract lineNumber: based on SourceRange/Offset information
int char_start = -1;
int char_end = -1;
int lineNumber = -1;
try {
ISourceRange sr = ianno.getSourceRange();
char_start = sr.getOffset();
char_end = char_start + sr.getLength();
lineNumber = cu.getLineNumber( char_start );
} catch( JavaModelException e ) {
e.printStackTrace();
}
logger.info( "Found reqid: "
+ reqid
+ " at line: "
+ lineNumber
+ " at char_start: "
+ char_start
+ " and char_end: "
+ char_end );
// add the reqid as IMarker of type MARKER_ID
addMarker( file, lineNumber, char_start, char_end, reqid );
return true;
}
/**
* Reads a ICompilationUnit and creates the AST DOM for manipulating the Java source file
*
* @param unit
* @return
*/
private static CompilationUnit parse( ICompilationUnit unit ) {
ASTParser parser = ASTParser.newParser( AST.JLS4 );
parser.setKind( ASTParser.K_COMPILATION_UNIT );
parser.setSource( unit );
parser.setResolveBindings( true );
return (CompilationUnit)parser.createAST( null ); // parse
}
/**
* The charStart and -End is currently ignored!
*
* Helpful information: http://www.ibm.com/developerworks/opensource/tutorials/os-eclipse-plugin-guide/section2.html
* http://help.eclipse.org/indigo/index.jsp?topic=%2Forg.eclipse.platform.doc.isv%2Fguide%2FresAdv_markers.htm
* http://books.gigatux.nl/mirror/eclipseplugins/032142672X/ch14lev1sec2.html
* http://wiki.eclipse.org/FAQ_Why_don't_my_markers_appear_in_the_editor's_vertical_ruler%3F
* http://cubussapiens.hu/2010/11/markers-and-annotations-in-eclipse-for-error-feedback/
*
* @param file
* @param lineNumber
* @param reqid
*/
private static void addMarker( IFile file, int char_start, int char_end, int lineNumber, String reqid ) {
try {
IMarker marker = file.createMarker( MARKER_ID );
// +-> From BOOKMARK
marker.setAttribute( IMarker.MESSAGE, "supa dupa requirement here" );
marker.setAttribute( IMarker.LOCATION, "right here: " + reqid );
// +-> From TEXTMARKER
// WARN: setting the CHAR_START/_END tangles with the LINE_NUMBER !?!
// http://www.eclipse.org/forums/index.php/m/294625/#msg_294625
// marker.setAttribute( IMarker.CHAR_START, char_start );
// marker.setAttribute( IMarker.CHAR_END, char_end );
marker.setAttribute( IMarker.LINE_NUMBER, lineNumber );
// +-> From special ERF REQMARKER
marker.setAttribute( "reqid", reqid );
logger.info( marker.getAttributes().values().toString() );
} catch( CoreException e ) {
e.printStackTrace();
}
}
}