SoMoX/Change Impact Case Study

Aus SDQ-Wiki
Wechseln zu: Navigation, Suche

When changing SoMoX input data model from the SISSy AST to the MoDisco AST model, we have performed a case study on change impact analyses (Lehnert et al. http://www.db-thueringen.de/servlets/DocumentServlet?id=19544).

After unsuccessfully searching for a public available implementation of an according approach, we have decided to implement a query according to Lee et al. (http://dl.acm.org/citation.cfm?id=832261.833279).


While we are working on providing an Eclipse plugin to ease the analysis application, below, the code of the query used in the case study is provided below.

  package org.somox.changeimpact;
  
  import java.util.ArrayList;
  import java.util.Collection;
  
  import org.eclipse.emf.common.util.TreeIterator;
  import org.eclipse.emf.ecore.EObject;
  import org.eclipse.gmt.modisco.infra.query.core.exception.ModelQueryExecutionException;
  import org.eclipse.gmt.modisco.infra.query.core.java.IJavaModelQuery;
  import org.eclipse.gmt.modisco.infra.query.core.java.ParameterValueList;
  import org.eclipse.gmt.modisco.java.ASTNode;
  import org.eclipse.gmt.modisco.java.AbstractMethodDeclaration;
  import org.eclipse.gmt.modisco.java.AbstractMethodInvocation;
  import org.eclipse.gmt.modisco.java.AbstractTypeDeclaration;
  import org.eclipse.gmt.modisco.java.ClassInstanceCreation;
  import org.eclipse.gmt.modisco.java.ImportDeclaration;
  import org.eclipse.gmt.modisco.java.Model;
  import org.eclipse.gmt.modisco.java.NamedElement;
  import org.eclipse.gmt.modisco.java.SingleVariableAccess;
  import org.eclipse.gmt.modisco.java.Type;
  import org.eclipse.gmt.modisco.java.TypeAccess;
  import org.eclipse.gmt.modisco.java.VariableDeclaration;
  
  /**
   * The impact query published in the study thesis.<br>
   * It detects type accesses, method invocations, import statements and variable accesses 
   * from a source area to a target area. The area is defined as the starting package name. 
   * @author Oliver Burkhardt
   *
   */
  public class ImpactQuery2 implements
  		IJavaModelQuery<Model, Collection<ASTNode>> {
  	private static final String SOURCE = "org.somox";
  	private static final String TARGET = "de.fzi.gast";
  
  	public Collection<ASTNode> evaluate(final Model context,
  			final ParameterValueList parameterValues)
  			throws ModelQueryExecutionException {
  
  		Collection<ASTNode> result = new ArrayList<ASTNode>();
  		TreeIterator<EObject> it = context.eAllContents();
  
  		while (it.hasNext()) {
  			ASTNode element = (ASTNode) it.next();
  
  			//has to be handled before checking for source area
  			if (element instanceof ImportDeclaration) {
  				handleImportDeclaration(element, result);
  			}
  
  			if (!isInSourceArea(element)) {
  				continue;
  			}
  			if (element instanceof TypeAccess) {
  				handleTypeAccess(element, result);
  
  			} else if (element instanceof AbstractMethodInvocation) {
  				handleMethodInvocation(element, result);
  
  			} else if (element instanceof SingleVariableAccess) {
  				handleSingleVariableAccess(element, result);
  
  			}
  		}
  		return result;
  
  	}
  
  	/**
  	 * Handles an import declaration.
  	 * @param element The import statement to consider.
  	 * @param result the result set
  	 */
  	private void handleImportDeclaration(Object element,
  			Collection<ASTNode> result) {
  		ImportDeclaration importDecl = (ImportDeclaration) element;
  		NamedElement importedElement = importDecl.getImportedElement();
  		if (isInTargetArea(importedElement)) {
  			result.add(importDecl);
  		}
  
  	}
  
  	private boolean isInSourceArea(ASTNode element) {
  		return computeFullQualifiedName(element).startsWith(SOURCE);
  	}
  
  	/**
  	 * Computes for a given variable access, the location (type) of the accessed variable.
  	 * @param element
  	 * @param result
  	 */
  	private void handleSingleVariableAccess(Object element,
  			Collection<ASTNode> result) {
  		SingleVariableAccess svaAccess = (SingleVariableAccess) element;
  		VariableDeclaration variable = svaAccess.getVariable();
  		if (variable == null) {
  			return;
  		}
  		Type typeContainer = getAbstractTypeDeclarationContainer(variable);
  		if (typeContainer != null & isInTargetArea(typeContainer)) {
  			result.add(svaAccess);
  		}
  	}
  
  	/**
  	 * Computes the container (type) for a given {@link ASTNode} object.
  	 * @param input The {@link ASTNode} object
  	 * @return The type the input is contained in. Null if the {@link ASTNode} is not contained in a type.
  	 */
  	private Type getAbstractTypeDeclarationContainer(ASTNode input) {
  		ASTNode node = input;
  		while (!(node.eContainer() instanceof AbstractTypeDeclaration)) {
  			if (! (node.eContainer() instanceof ASTNode)) {
  				return null;
  			}
  			node = (ASTNode) node.eContainer();
  		}
  		return (Type) node.eContainer();
  	}
  
  	/**
  	 * Handles a method invocation. If the accessed method in the target area, then the method invocation is added to the result list.
  	 * @param element The object to handle as method invocation.
  	 * @param result The result set.
  	 */
  	private void handleMethodInvocation(Object element,
  			Collection<ASTNode> result) {
  		AbstractMethodInvocation methodInvoc = (AbstractMethodInvocation) element;
  		AbstractMethodDeclaration method = methodInvoc.getMethod();
  
  		if (isInTargetArea(method)) {
  			result.add(methodInvoc);
  		}
  	}
  
  	/**
  	 * Handles a type access. The access is added to the the result list.
  	 * @param element The element to handle as type access.
  	 * @param result The result set.
  	 */
  	private void handleTypeAccess(Object element, Collection<ASTNode> result) {
  		TypeAccess access = (TypeAccess) element;
  
  		// Type accesses in ClassInstanceCreations are not handled, because else they would be counted twice.
  		if (access.eContainer() instanceof ClassInstanceCreation) {
  			return;
  		}
  		
  		if (access.getType() instanceof AbstractTypeDeclaration) {
  			AbstractTypeDeclaration atd = (AbstractTypeDeclaration) access
  					.getType();
  			if(atd != null & isInTargetArea(atd)){
  				result.add(access);
  			}
  		}
  	}
  
  	/**
  	 * Returns if the {@link ASTNode} object is in the target area.
  	 * @param type The {@link ASTNode} input object.
  	 * @return if the input is in the target area
  	 */
  	private boolean isInTargetArea(ASTNode type) {
  		return computeFullQualifiedName(type).startsWith(TARGET);
  	}
  
  	/**
  	 * Computes the full qualified name for an {@link ASTNode} object
  	 * @param input the {@link ASTNode} object
  	 * @return the full qualified name
  	 */
  	private static String computeFullQualifiedName(ASTNode input) {
  		EObject pack = input;
  
  		String result = "";
  
  		if (pack instanceof NamedElement) {
  			result = ((NamedElement) pack).getName();
  		}
  
  		while (pack != null) {
  			if (pack.eContainer() != null
  					&& pack.eContainer() instanceof NamedElement) {
  				pack = pack.eContainer();
  				result = ((NamedElement) pack).getName() + "." + result;
  			} else {
  				pack = pack.eContainer();
  			}
  		}
  		return result;
  	}
  }