Skip to content

Commit

Permalink
Fix Aspect overriding
Browse files Browse the repository at this point in the history
When an aspect extends another one and redefine a method, all call to
the redefined method are redirected to the new version.
  • Loading branch information
fcoulon committed Jun 28, 2016
1 parent 4d196cc commit cf8ba3b
Show file tree
Hide file tree
Showing 3 changed files with 186 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import org.eclipse.xtext.builder.EclipseResourceFileSystemAccess2
import org.eclipse.xtext.generator.IGenerator
import org.eclipse.xtext.generator.OutputConfigurationProvider
import org.eclipse.xtext.resource.DerivedStateAwareResource
import fr.inria.diverse.melange.utils.AspectOverrider

class MelangeBuilder
{
Expand All @@ -38,6 +39,7 @@ class MelangeBuilder
@Inject extension ModelingElementExtensions
@Inject extension ModelTypeExtensions
@Inject extension EcoreExtensions
@Inject extension AspectOverrider
private static final Logger log = Logger.getLogger(MelangeBuilder)

def void generateAll(Resource res, IProject project, IProgressMonitor monitor) {
Expand Down Expand Up @@ -102,6 +104,7 @@ class MelangeBuilder
monitor.worked(40)
sub.subTask("Copying aspects for " + l.name)
l.createExternalAspects
l.generateAspectJ
monitor.worked(40)
sub.subTask("Updating dependencies for " + l.name)
eclipseHelper.addDependencies(project, #[l.externalRuntimeName])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ import org.eclipse.emf.ecore.resource.Resource
import org.eclipse.jdt.core.JavaCore
import org.eclipse.pde.internal.core.natures.PDE
import org.eclipse.xtext.util.MergeableManifest
import org.eclipse.core.resources.IProjectDescription
import org.eclipse.core.resources.ICommand

/**
* A collection of utilities around Eclipse's APIs to manage the creation,
Expand Down Expand Up @@ -417,4 +419,32 @@ class EclipseProjectHelper

return project
}

def void addNature(IProject project, String natureID) {
try {
val IProjectDescription desc = project.getDescription();
val natures = desc.getNatureIds();
val newNatures = newArrayList(natureID)
newNatures.addAll(natures)
desc.setNatureIds(newNatures);
project.setDescription(desc, new NullProgressMonitor());
} catch (CoreException e) {
e.printStackTrace();
}
}

def void addBuilder(IProject project, String builderID) {
try {
val IProjectDescription projectDescription = project.getDescription();
val ICommand[] buildSpec = projectDescription.getBuildSpec();
val ICommand command = projectDescription.newCommand();
command.setBuilderName(builderID);
val newBuildSpect = newArrayList(command)
newBuildSpect.addAll(buildSpec)
projectDescription.setBuildSpec(newBuildSpect);
project.setDescription( projectDescription, new NullProgressMonitor() );
} catch (CoreException e) {
e.printStackTrace();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
package fr.inria.diverse.melange.utils

import com.google.common.collect.HashMultimap
import com.google.common.collect.SetMultimap
import com.google.inject.Inject
import fr.inria.diverse.melange.ast.AspectExtensions
import fr.inria.diverse.melange.ast.LanguageExtensions
import fr.inria.diverse.melange.eclipse.EclipseProjectHelper
import fr.inria.diverse.melange.metamodel.melange.Language
import java.nio.file.Files
import java.nio.file.Paths
import java.util.List
import org.eclipse.core.resources.IProject
import org.eclipse.core.resources.ResourcesPlugin
import org.eclipse.xtext.common.types.JvmDeclaredType
import org.eclipse.xtext.common.types.JvmOperation
import org.eclipse.core.resources.IResource

class AspectOverrider {

@Inject extension LanguageExtensions
@Inject extension AspectExtensions
@Inject EclipseProjectHelper eclipseHelper

def void generateAspectJ(Language l){
val packageName = l.aspectsNamespace
val aspectName = "_MelangeDispatcher"
val targetProject = ResourcesPlugin.workspace.root.getProject(l.externalRuntimeName)
val targetAspectFolder = targetProject.locationURI.path + "/src-gen/" + packageName.replaceAll("\\.","/")


val aspects = l.semantics.map[it.aspectTypeRef.type as JvmDeclaredType]
val advices = generateAdvices(aspects)
if(!advices.isEmpty){
val fileContent = new StringBuilder
fileContent.append("package "+packageName + ";\n\n")
fileContent.append("public aspect "+aspectName+" {\n")
fileContent.append(advices)
fileContent.append("}\n")

val path = Paths.get(targetAspectFolder+"/"+aspectName+".aj");
val writer = Files.newBufferedWriter(path)
writer.write(fileContent.toString)
writer.flush

targetProject.convertToAspectJ
targetProject.refreshLocal(IResource.DEPTH_INFINITE, null);
}
}

/**
* Generate AspectJ advice to intercept methods calls when some
* K3 aspects override others K3 aspects
*/
private def String generateAdvices(List<JvmDeclaredType> aspects) {

val content = new StringBuilder
val SetMultimap<JvmOperation, JvmOperation> res = HashMultimap.create

// Compute hierarchy
val SetMultimap<JvmDeclaredType, JvmDeclaredType> overriders = HashMultimap.create
aspects.forEach[ cls |
val ancestors = cls.allSuperAspects
if(!ancestors.isEmpty)
overriders.putAll(cls,ancestors)
]

// Find overrides
overriders.keySet.forEach[ childCls |
val ancestors = overriders.get(childCls)
childCls.members.filter(JvmOperation).forEach[ m |
ancestors.filter[hasMethod(m)].forEach[ superCls |
// add a new rule
val target = superCls.members.filter(JvmOperation).findFirst[isEqual(m)]
res.storeRule(m,target)
]
]
]

//Generate AspectJ advice
if(res.keySet.size > 0) {

res.keySet.forEach[ m1 |

val returnType = m1.returnType.type.qualifiedName
val calledMethodFqn = m1.qualifiedName
val parameters = m1.parameters.map[parameterType.type.qualifiedName+" "+name].join(",")
val parameterTypes = m1.parameters.map[parameterType.type.qualifiedName].join(",")
val parameterNames = m1.parameters.map[name].join(",")

res.get(m1).forEach[ m2 |
val targetMethodFqn = m2.qualifiedName
content.append(" "+returnType+" around("+parameters+") : call("+returnType+" "+targetMethodFqn+"("+parameterTypes+")) && args("+parameterNames+") {\n")
if(returnType != "void")
content.append(" return "+calledMethodFqn+"("+parameterNames+");\n")
else
content.append(" "+calledMethodFqn+"("+parameterNames+");\n")
content.append(" }\n")
]
]

return content.toString
}

return ""
}

private def void storeRule(SetMultimap<JvmOperation, JvmOperation> storage, JvmOperation called, JvmOperation hidden){
val colider = storage.keySet.findFirst[isEqual(called)]
if(colider === null || colider === called){
storage.put(called,hidden)
}
else if(called.declaringType.allSuperAspects.contains(colider.declaringType)){
val toMove = storage.get(colider)
storage.removeAll(colider)
storage.putAll(called,toMove)
storage.put(called,hidden)
}
}

private def boolean hasMethod(JvmDeclaredType cls, JvmOperation method) {
cls.members.filter(JvmOperation).exists[isEqual(method)]
}

private def boolean isEqual(JvmOperation m1, JvmOperation m2) {
m1.simpleName == m2.simpleName
&& m1.parameters.size == m2.parameters.size
&& m1.parameters.forall[param |
val index = m1.parameters.indexOf(param)
m2.parameters.get(index).parameterType.type == param.parameterType.type
]
}

private def List<JvmDeclaredType> getAllSuperAspects(JvmDeclaredType cls) {
val res = newArrayList
if(!cls.hasAspectAnnotation)
return res

var current = cls.superTypes.head.type as JvmDeclaredType
while(current !== null && current.hasAspectAnnotation){
res.add(current)
current = current.superTypes.head.type as JvmDeclaredType
}

return res
}

private def void convertToAspectJ(IProject prj){
eclipseHelper.addDependencies(prj,#["org.aspectj.runtime"])
eclipseHelper.addNature(prj,"org.eclipse.ajdt.ui.ajnature")
eclipseHelper.addBuilder(prj,"org.eclipse.ajdt.core.ajbuilder")
}
}

0 comments on commit cf8ba3b

Please sign in to comment.