Add Deprecated Annotations and Task Tags to your DSL

Some programming languages allow to discourage the usage of program element. For this purpose, Java provides the annotation @deprecated. It can be added to classes, fields and methods to express that the element should not be used any longer. There are many possible reasons, e.g., the element is unsafe or it has been superseded and may cease to exist in the future. When a deprecated element is used the compilers shows a warning. The Eclipse IDE marks all calls to deprecated methods and accesses to deprecated classes or fields.

Java developers using Eclipse JDT are probably also familiar to the task tags //XXX, //TODO and //FIXME. They offer a convenient way to mark positions in the source code that should be remembered for later reviews or adaptions. Eclipse lists all task tags found in the source code in a special view. By double clicking the entry in this view the IDE jumps to the corresponding position in the source code.

Both, the task tags and the deprecated-annotation are useful tools when creating Java programs in Eclipse. When working with large scale textual models, they can be beneficial, too. Xtext, however, does not provide such a feature out of the box. Though, it can be built with little effort by means of its validation framework.

This post describes how this can be done in such a way that it can be attached to different model elements with little effort.

First of all, we need rules in a DSL’s grammar that provides model elements in which we expect the task markers and the deprecated annotations. By adding a call to these rules these features can be enabled for all the model elements of your DSL you want to provide them for. The following example grammar uses a generic description element for that purpose:

Model:
	contents+=Elements*;
Elements:
	Entity | Relation;
Entity:
	(description=DescriptionElement)? 
	'entity' name=ID ';';
Relation:
	(description=DescriptionElement)? 
	'relation' left=[Entity|ID] '->' right=[Entity|ID] ';';
	
// The generic description element, which can be attached to any other 
// language element
DescriptionElement:
	text = DESCRIPTION; 

// Used for description texts
terminal DESCRIPTION: 
	('/#' -> '#/');

The next step is to provide markers for each task tag and show warnings for each user of model element that are marked as deprecated. Such a behaviour can be implemented by means of Xtext’s validation framework using appropriate checkers.

The following snippet shows an implementation of such a checker. It is run for each occurrence of DescriptionElement and scans its textual contents for occurrences of @todo and @deprecated.

For each occurence of @todo is interpreted as a task tag. A warning is added for the model element that contains the DescriptionElement. The warning is labelled with a default text. An individual description for the marker can also be provided by adding a colon followed by some text (e.g. @todo: Needs review).

Furthermore, the checker also finds occurrences of the string @deprecated and interprets them as deprecated annotations. This is done by adding warnings to all model elements that reference the deprecated model element. An individual description can also be added providing further information about the reason of depreaction (@deprecated: Please use element el3 in the future).

public class AnnotationExampleJavaValidator extends AbstractAnnotationExampleJavaValidator {

	...

	private String ERROR_CODE__DESCRIPTION__TODO = "TODO";
	private String ERROR_CODE__DEPRECATED = "DEPRECATION";

	@Check
	public void checkDescription(DescriptionElement descriptionElement) {
		EObject object = descriptionElement.eContainer();
		
		String description = descriptionElement.getText();
		// Find all parts of the texts that start with "@" and run until the 
		// end of the line or and ends with : 
		Pattern p = Pattern.compile("@[\\S&&[^:\\r\\n]]*[:\\r\\n]");
		Matcher m = p.matcher(description);
		while ( m.find() ) {
			String matchedText = description.substring(
				m.start()+1, 
				m.end()-1);
			String additionalInformation = null;
			// Reads the description text after the : until the end of
			//  the line 
			if (description.charAt(m.end()-1) == ':') {
				additionalInformation = description.
					substring(m.end()).
					split("\\n")
					[0];
			}
			
			// Add task tags
			if (matchedText.equals("todo")) {
				String errorMessage = 
					ERROR_CODE__DESCRIPTION__TODO + 
					(additionalInformation != null ? 
						(" " + additionalInformation) : 
						""
					);
				// Display a warning for the model element the task tag was 
				// assigned to
				warning(
					errorMessage, 
					object, 
					object.eClass().getClassifierID(), 
					ERROR_CODE__DESCRIPTION__TODO);	
				
			// Add deprecated warnings
			} else if (matchedText.equals("deprecated")) {
				// Look for an attribute named "name" of type String 
				String deprecatedElementName = getName(object);
				String errorMessage = MessageFormat.format(
					"{0}: The element {1} is deprecated", 
					ERROR_CODE__DEPRECATED, 
					deprecatedElementName != null ? 
						(" \"" + deprecatedElementName + "\"") : 
						"",
					additionalInformation != null ? 
						(" (" + additionalInformation + ")") : 
						""
					); 
				
				// Find all incoming references to the deprecated element 
				Collection<Setting> crossReferences = 
					EcoreUtil.UsageCrossReferencer.find(
						object, 
						object.eResource().getResourceSet());
				for (Setting crossReference : crossReferences) {
					// Display a warning on each referencing objects
					warning(
						errorMessage, 
						crossReference.getEObject(), 
						crossReference.getEStructuralFeature().
							getFeatureID(), 
						ERROR_CODE__DEPRECATED);
				}
				
			} else {
				// do whatever else
			}
		}
	}

	/**
	 * Returns the value of the attribute "name" of type String for the 
	 * given object
	 * @param object The model element
	 * @return The value of the name attribute or null
	 */
	private String getName(EObject object) {
		for (EAttribute attr : object.eClass().getEAllAttributes()) {
			if (
				attr.getName().equals("name") && 
				attr.getEAttributeType().getInstanceClass().equals(
					String.class)) 
			{
				return (String)object.eGet(attr);
			}
		}
		return null;
	}	
}

The following screenshot shows an example using the DSL with a task marker and a deprecated model element.
Screenshot

The source code of this post is available here.

Advertisements
  1. July 3rd, 2011

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: