org.aspectj.weaver.bcel.BcelWorld Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of aspectjtools Show documentation
Show all versions of aspectjtools Show documentation
Tools from the AspectJ project
/* *******************************************************************
* Copyright (c) 2002 Palo Alto Research Center, Incorporated (PARC).
* All rights reserved.
* This program and the accompanying materials are made available
* under the terms of the Eclipse Public License v 2.0
* which accompanies this distribution and is available at
* https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.txt
*
* Contributors:
* PARC initial implementation
* Alexandre Vasseur perClause support for @AJ aspects
* ******************************************************************/
package org.aspectj.weaver.bcel;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Modifier;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import org.aspectj.apache.bcel.Constants;
import org.aspectj.apache.bcel.classfile.ClassParser;
import org.aspectj.apache.bcel.classfile.ConstantPool;
import org.aspectj.apache.bcel.classfile.JavaClass;
import org.aspectj.apache.bcel.generic.FieldInstruction;
import org.aspectj.apache.bcel.generic.INVOKEINTERFACE;
import org.aspectj.apache.bcel.generic.Instruction;
import org.aspectj.apache.bcel.generic.InstructionHandle;
import org.aspectj.apache.bcel.generic.InvokeInstruction;
import org.aspectj.apache.bcel.generic.MULTIANEWARRAY;
import org.aspectj.apache.bcel.generic.ObjectType;
import org.aspectj.apache.bcel.generic.Type;
import org.aspectj.apache.bcel.util.ClassLoaderReference;
import org.aspectj.apache.bcel.util.ClassLoaderRepository;
import org.aspectj.apache.bcel.util.ClassPath;
import org.aspectj.apache.bcel.util.NonCachingClassLoaderRepository;
import org.aspectj.apache.bcel.util.Repository;
import org.aspectj.asm.AsmManager;
import org.aspectj.asm.IRelationship;
import org.aspectj.asm.internal.CharOperation;
import org.aspectj.bridge.IMessage;
import org.aspectj.bridge.IMessageHandler;
import org.aspectj.bridge.ISourceLocation;
import org.aspectj.bridge.Message;
import org.aspectj.bridge.MessageUtil;
import org.aspectj.bridge.WeaveMessage;
import org.aspectj.weaver.Advice;
import org.aspectj.weaver.AdviceKind;
import org.aspectj.weaver.AnnotationAJ;
import org.aspectj.weaver.AnnotationOnTypeMunger;
import org.aspectj.weaver.BCException;
import org.aspectj.weaver.Checker;
import org.aspectj.weaver.ICrossReferenceHandler;
import org.aspectj.weaver.IWeavingSupport;
import org.aspectj.weaver.Member;
import org.aspectj.weaver.MemberImpl;
import org.aspectj.weaver.MemberKind;
import org.aspectj.weaver.NewParentTypeMunger;
import org.aspectj.weaver.ReferenceType;
import org.aspectj.weaver.ReferenceTypeDelegate;
import org.aspectj.weaver.ResolvedMember;
import org.aspectj.weaver.ResolvedMemberImpl;
import org.aspectj.weaver.ResolvedType;
import org.aspectj.weaver.ResolvedTypeMunger;
import org.aspectj.weaver.Shadow;
import org.aspectj.weaver.ShadowMunger;
import org.aspectj.weaver.UnresolvedType;
import org.aspectj.weaver.World;
import org.aspectj.weaver.loadtime.definition.Definition;
import org.aspectj.weaver.loadtime.definition.DocumentParser;
import org.aspectj.weaver.model.AsmRelationshipProvider;
import org.aspectj.weaver.patterns.DeclareAnnotation;
import org.aspectj.weaver.patterns.DeclareParents;
import org.aspectj.weaver.patterns.ParserException;
import org.aspectj.weaver.patterns.PatternParser;
import org.aspectj.weaver.patterns.TypePattern;
import org.aspectj.weaver.tools.Trace;
import org.aspectj.weaver.tools.TraceFactory;
public class BcelWorld extends World implements Repository {
private final ClassPathManager classPath;
protected Repository delegate;
private BcelWeakClassLoaderReference loaderRef;
private final BcelWeavingSupport bcelWeavingSupport = new BcelWeavingSupport();
private boolean isXmlConfiguredWorld = false;
private WeavingXmlConfig xmlConfiguration;
private List typeDelegateResolvers;
private static Trace trace = TraceFactory.getTraceFactory().getTrace(BcelWorld.class);
public BcelWorld() {
this("");
}
public BcelWorld(String cp) {
this(makeDefaultClasspath(cp), IMessageHandler.THROW, null);
}
public IRelationship.Kind determineRelKind(ShadowMunger munger) {
AdviceKind ak = ((Advice) munger).getKind();
if (ak.getKey() == AdviceKind.Before.getKey()) {
return IRelationship.Kind.ADVICE_BEFORE;
} else if (ak.getKey() == AdviceKind.After.getKey()) {
return IRelationship.Kind.ADVICE_AFTER;
} else if (ak.getKey() == AdviceKind.AfterThrowing.getKey()) {
return IRelationship.Kind.ADVICE_AFTERTHROWING;
} else if (ak.getKey() == AdviceKind.AfterReturning.getKey()) {
return IRelationship.Kind.ADVICE_AFTERRETURNING;
} else if (ak.getKey() == AdviceKind.Around.getKey()) {
return IRelationship.Kind.ADVICE_AROUND;
} else if (ak.getKey() == AdviceKind.CflowEntry.getKey() || ak.getKey() == AdviceKind.CflowBelowEntry.getKey()
|| ak.getKey() == AdviceKind.InterInitializer.getKey() || ak.getKey() == AdviceKind.PerCflowEntry.getKey()
|| ak.getKey() == AdviceKind.PerCflowBelowEntry.getKey() || ak.getKey() == AdviceKind.PerThisEntry.getKey()
|| ak.getKey() == AdviceKind.PerTargetEntry.getKey() || ak.getKey() == AdviceKind.Softener.getKey()
|| ak.getKey() == AdviceKind.PerTypeWithinEntry.getKey()) {
// System.err.println("Dont want a message about this: "+ak);
return null;
}
throw new RuntimeException("Shadow.determineRelKind: What the hell is it? " + ak);
}
@Override
public void reportMatch(ShadowMunger munger, Shadow shadow) {
if (getCrossReferenceHandler() != null) {
final IRelationship.Kind kind = determineRelKind(munger);
getCrossReferenceHandler().addCrossReference(
munger.getSourceLocation(), // What is being applied?
shadow.getSourceLocation(), // Where is it being applied?
kind == null ? null : kind.getName(), // What kind of advice?
((Advice) munger).hasDynamicTests() // Is a runtime test being stuffed in the code?
);
}
if (!getMessageHandler().isIgnoring(IMessage.WEAVEINFO)) {
reportWeavingMessage(munger, shadow);
}
if (getModel() != null) {
AsmRelationshipProvider.addAdvisedRelationship(getModelAsAsmManager(), shadow, munger);
}
}
/*
* Report a message about the advice weave that has occurred. Some messing about to make it pretty ! This code is just asking
* for an NPE to occur ...
*/
private void reportWeavingMessage(ShadowMunger munger, Shadow shadow) {
Advice advice = (Advice) munger;
AdviceKind aKind = advice.getKind();
// Only report on interesting advice kinds ...
if (aKind == null || advice.getConcreteAspect() == null) {
// We suspect someone is programmatically driving the weaver
// (e.g. IdWeaveTestCase in the weaver testcases)
return;
}
if (!(aKind.equals(AdviceKind.Before) || aKind.equals(AdviceKind.After) || aKind.equals(AdviceKind.AfterReturning)
|| aKind.equals(AdviceKind.AfterThrowing) || aKind.equals(AdviceKind.Around) || aKind.equals(AdviceKind.Softener))) {
return;
}
// synchronized blocks are implemented with multiple monitor_exit instructions in the bytecode
// (one for normal exit from the method, one for abnormal exit), we only want to tell the user
// once we have advised the end of the sync block, even though under the covers we will have
// woven both exit points
if (shadow.getKind() == Shadow.SynchronizationUnlock) {
if (advice.lastReportedMonitorExitJoinpointLocation == null) {
// this is the first time through, let's continue...
advice.lastReportedMonitorExitJoinpointLocation = shadow.getSourceLocation();
} else {
if (areTheSame(shadow.getSourceLocation(), advice.lastReportedMonitorExitJoinpointLocation)) {
// Don't report it again!
advice.lastReportedMonitorExitJoinpointLocation = null;
return;
}
// hmmm, this means some kind of nesting is going on, urgh
advice.lastReportedMonitorExitJoinpointLocation = shadow.getSourceLocation();
}
}
String description = advice.getKind().toString();
String advisedType = shadow.getEnclosingType().getName();
String advisingType = advice.getConcreteAspect().getName();
Message msg = null;
if (advice.getKind().equals(AdviceKind.Softener)) {
msg = WeaveMessage.constructWeavingMessage(WeaveMessage.WEAVEMESSAGE_SOFTENS, new String[] { advisedType,
beautifyLocation(shadow.getSourceLocation()), advisingType, beautifyLocation(munger.getSourceLocation()) },
advisedType, advisingType);
} else {
boolean runtimeTest = advice.hasDynamicTests();
String joinPointDescription = shadow.toString();
msg = WeaveMessage
.constructWeavingMessage(WeaveMessage.WEAVEMESSAGE_ADVISES,
new String[] { joinPointDescription, advisedType, beautifyLocation(shadow.getSourceLocation()),
description, advisingType, beautifyLocation(munger.getSourceLocation()),
(runtimeTest ? " [with runtime test]" : "") }, advisedType, advisingType);
// Boolean.toString(runtimeTest)});
}
getMessageHandler().handleMessage(msg);
}
private boolean areTheSame(ISourceLocation locA, ISourceLocation locB) {
if (locA == null) {
return locB == null;
}
if (locB == null) {
return false;
}
if (locA.getLine() != locB.getLine()) {
return false;
}
File fA = locA.getSourceFile();
File fB = locA.getSourceFile();
if (fA == null) {
return fB == null;
}
if (fB == null) {
return false;
}
return fA.getName().equals(fB.getName());
}
/*
* Ensure we report a nice source location - particular in the case where the source info is missing (binary weave).
*/
private String beautifyLocation(ISourceLocation isl) {
StringBuilder nice = new StringBuilder();
if (isl == null || isl.getSourceFile() == null || isl.getSourceFile().getName().contains("no debug info available")) {
nice.append("no debug info available");
} else {
// can't use File.getName() as this fails when a Linux box encounters a path created on Windows and vice-versa
int takeFrom = isl.getSourceFile().getPath().lastIndexOf('/');
if (takeFrom == -1) {
takeFrom = isl.getSourceFile().getPath().lastIndexOf('\\');
}
int binary = isl.getSourceFile().getPath().lastIndexOf('!');
if (binary != -1 && binary < takeFrom) {
// we have been woven by a binary aspect
String pathToBinaryLoc = isl.getSourceFile().getPath().substring(0, binary + 1);
if (pathToBinaryLoc.contains(".jar")) {
// only want to add the extra info if we're from a jar file
int lastSlash = pathToBinaryLoc.lastIndexOf('/');
if (lastSlash == -1) {
lastSlash = pathToBinaryLoc.lastIndexOf('\\');
}
nice.append(pathToBinaryLoc.substring(lastSlash + 1));
}
}
nice.append(isl.getSourceFile().getPath().substring(takeFrom + 1));
if (isl.getLine() != 0) {
nice.append(":").append(isl.getLine());
}
// if it's a binary file then also want to give the file name
if (isl.getSourceFileName() != null) {
nice.append("(from " + isl.getSourceFileName() + ")");
}
}
return nice.toString();
}
private static List makeDefaultClasspath(String cp) {
List classPath = new ArrayList<>();
classPath.addAll(getPathEntries(cp));
classPath.addAll(getPathEntries(ClassPath.getClassPath()));
return classPath;
}
private static List getPathEntries(String s) {
List ret = new ArrayList<>();
StringTokenizer tok = new StringTokenizer(s, File.pathSeparator);
while (tok.hasMoreTokens()) {
ret.add(tok.nextToken());
}
return ret;
}
public BcelWorld(List classPath, IMessageHandler handler, ICrossReferenceHandler xrefHandler) {
// this.aspectPath = new ClassPathManager(aspectPath, handler);
this.classPath = new ClassPathManager(classPath, handler);
setMessageHandler(handler);
setCrossReferenceHandler(xrefHandler);
// Tell BCEL to use us for resolving any classes
delegate = this;
}
public BcelWorld(ClassPathManager cpm, IMessageHandler handler, ICrossReferenceHandler xrefHandler) {
classPath = cpm;
setMessageHandler(handler);
setCrossReferenceHandler(xrefHandler);
// Tell BCEL to use us for resolving any classes
delegate = this;
}
/**
* Build a World from a ClassLoader, for LTW support
*
* @param loader
* @param handler
* @param xrefHandler
*/
public BcelWorld(ClassLoader loader, IMessageHandler handler, ICrossReferenceHandler xrefHandler) {
classPath = null;
loaderRef = new BcelWeakClassLoaderReference(loader);
setMessageHandler(handler);
setCrossReferenceHandler(xrefHandler);
// Tell BCEL to use us for resolving any classes
// delegate = getClassLoaderRepositoryFor(loader);
}
public void ensureRepositorySetup() {
if (delegate == null) {
delegate = getClassLoaderRepositoryFor(loaderRef);
}
}
public Repository getClassLoaderRepositoryFor(ClassLoaderReference loader) {
if (bcelRepositoryCaching) {
return new ClassLoaderRepository(loader);
} else {
return new NonCachingClassLoaderRepository(loader);
}
}
public void addPath(String name) {
classPath.addPath(name, this.getMessageHandler());
}
// ---- various interactions with bcel
public static Type makeBcelType(UnresolvedType type) {
return Type.getType(type.getErasureSignature());
}
static Type[] makeBcelTypes(UnresolvedType[] types) {
Type[] ret = new Type[types.length];
for (int i = 0, len = types.length; i < len; i++) {
ret[i] = makeBcelType(types[i]);
}
return ret;
}
public static Type[] makeBcelTypes(String[] types) {
if (types == null || types.length==0 ) {
return null;
}
Type[] ret = new Type[types.length];
for (int i=0, len=types.length; i", new ResolvedType[] { INT });
} else if (i instanceof MULTIANEWARRAY) {
MULTIANEWARRAY arrayInstruction = (MULTIANEWARRAY) i;
UnresolvedType ut = null;
short dimensions = arrayInstruction.getDimensions();
ObjectType ot = arrayInstruction.getLoadClassType(cpg);
if (ot != null) {
ut = fromBcel(ot);
ut = UnresolvedType.makeArray(ut, dimensions);
} else {
Type t = arrayInstruction.getType(cpg);
ut = fromBcel(t);
}
ResolvedType[] parms = new ResolvedType[dimensions];
for (int ii = 0; ii < dimensions; ii++) {
parms[ii] = INT;
}
retval = MemberImpl.method(ut, Modifier.PUBLIC, UnresolvedType.VOID, "", parms);
} else if (i.opcode == Constants.NEWARRAY) {
// NEWARRAY arrayInstruction = (NEWARRAY)i;
Type ot = i.getType();
UnresolvedType ut = fromBcel(ot);
retval = MemberImpl.method(ut, Modifier.PUBLIC, UnresolvedType.VOID, "", new ResolvedType[] { INT });
} else {
throw new BCException("Cannot create array construction signature for this non-array instruction:" + i);
}
return retval;
}
public Member makeJoinPointSignatureForMethodInvocation(LazyClassGen cg, InvokeInstruction ii) {
ConstantPool cpg = cg.getConstantPool();
String name = ii.getName(cpg);
String declaring = ii.getClassName(cpg);
UnresolvedType declaringType = null;
String signature = ii.getSignature(cpg);
// 307147
if (name.startsWith("ajc$privMethod$")) {
// The invoke is on a privileged accessor. These may be created for different
// kinds of target, not necessarily just private methods. In bug 307147 it is
// for a private method. This code is identifying the particular case in 307147
try {
declaringType = UnresolvedType.forName(declaring);
String typeNameAsFoundInAccessorName = declaringType.getName().replace('.', '_');
int indexInAccessorName = name.lastIndexOf(typeNameAsFoundInAccessorName);
if (indexInAccessorName != -1) {
String methodName = name.substring(indexInAccessorName+typeNameAsFoundInAccessorName.length()+1);
ResolvedType resolvedDeclaringType = declaringType.resolve(this);
ResolvedMember[] methods = resolvedDeclaringType.getDeclaredMethods();
for (ResolvedMember method: methods) {
if (method.getName().equals(methodName) && method.getSignature().equals(signature) && Modifier.isPrivate(method.getModifiers())) {
return method;
}
}
}
} catch (Exception e) {
// Remove this once confident above code isn't having unexpected side effects
// Added 1.8.7
e.printStackTrace();
}
}
int modifier = (ii instanceof INVOKEINTERFACE) ? Modifier.INTERFACE
: (ii.opcode == Constants.INVOKESTATIC) ? Modifier.STATIC : (ii.opcode == Constants.INVOKESPECIAL && !name
.equals("")) ? Modifier.PRIVATE : 0;
// in Java 1.4 and after, static method call of super class within
// subclass method appears
// as declared by the subclass in the bytecode - but they are not
// see #104212
if (ii.opcode == Constants.INVOKESTATIC) {
ResolvedType appearsDeclaredBy = resolve(declaring);
// look for the method there
for (Iterator iterator = appearsDeclaredBy.getMethods(true, true); iterator.hasNext();) {
ResolvedMember method = iterator.next();
if (Modifier.isStatic(method.getModifiers())) {
if (name.equals(method.getName()) && signature.equals(method.getSignature())) {
// we found it
declaringType = method.getDeclaringType();
break;
}
}
}
}
if (declaringType == null) {
if (declaring.charAt(0) == '[') {
declaringType = UnresolvedType.forSignature(declaring);
} else {
declaringType = UnresolvedType.forName(declaring);
}
}
return MemberImpl.method(declaringType, modifier, name, signature);
}
@Override
public String toString() {
StringBuilder buf = new StringBuilder();
buf.append("BcelWorld(");
// buf.append(shadowMungerMap);
buf.append(")");
return buf.toString();
}
/**
* Retrieve a bcel delegate for an aspect - this will return NULL if the delegate is an EclipseSourceType and not a
* BcelObjectType - this happens quite often when incrementally compiling.
*/
public static BcelObjectType getBcelObjectType(ResolvedType concreteAspect) {
if (concreteAspect == null) {
return null;
}
if (!(concreteAspect instanceof ReferenceType)) { // Might be Missing
return null;
}
ReferenceTypeDelegate rtDelegate = ((ReferenceType) concreteAspect).getDelegate();
if (rtDelegate instanceof BcelObjectType) {
return (BcelObjectType) rtDelegate;
} else {
return null;
}
}
public void tidyUp() {
// At end of compile, close any open files so deletion of those archives
// is possible
classPath.closeArchives();
typeMap.report();
typeMap.demote(true);
// ResolvedType.resetPrimitives();
}
// / The repository interface methods
@Override
public JavaClass findClass(String className) {
return lookupJavaClass(classPath, className);
}
@Override
public JavaClass loadClass(String className) throws ClassNotFoundException {
return lookupJavaClass(classPath, className);
}
@Override
public void storeClass(JavaClass clazz) {
// doesn't need to do anything
}
@Override
public void removeClass(JavaClass clazz) {
throw new RuntimeException("Not implemented");
}
@Override
public JavaClass loadClass(Class clazz) throws ClassNotFoundException {
throw new RuntimeException("Not implemented");
}
@Override
public void clear() {
delegate.clear();
// throw new RuntimeException("Not implemented");
}
/**
* The aim of this method is to make sure a particular type is 'ok'. Some operations on the delegate for a type modify it and
* this method is intended to undo that... see pr85132
*/
@Override
public void validateType(UnresolvedType type) {
ResolvedType result = typeMap.get(type.getSignature());
if (result == null) {
return; // We haven't heard of it yet
}
if (!result.isExposedToWeaver()) {
return; // cant need resetting
}
result.ensureConsistent();
// If we want to rebuild it 'from scratch' then:
// ClassParser cp = new ClassParser(new
// ByteArrayInputStream(newbytes),new String(cs));
// try {
// rt.setDelegate(makeBcelObjectType(rt,cp.parse(),true));
// } catch (ClassFormatException e) {
// e.printStackTrace();
// } catch (IOException e) {
// e.printStackTrace();
// }
}
/**
* Apply a single declare parents - return true if we change the type
*/
private boolean applyDeclareParents(DeclareParents p, ResolvedType onType) {
boolean didSomething = false;
List newParents = p.findMatchingNewParents(onType, true);
if (!newParents.isEmpty()) {
didSomething = true;
BcelObjectType classType = BcelWorld.getBcelObjectType(onType);
// System.err.println("need to do declare parents for: " + onType);
for (ResolvedType newParent : newParents) {
// We set it here so that the imminent matching for ITDs can
// succeed - we still haven't done the necessary changes to the class file
// itself (like transform super calls) - that is done in
// BcelTypeMunger.mungeNewParent()
// classType.addParent(newParent);
onType.addParent(newParent);
ResolvedTypeMunger newParentMunger = new NewParentTypeMunger(newParent, p.getDeclaringType());
newParentMunger.setSourceLocation(p.getSourceLocation());
onType.addInterTypeMunger(new BcelTypeMunger(newParentMunger, getCrosscuttingMembersSet()
.findAspectDeclaringParents(p)), false);
}
}
return didSomething;
}
/**
* Apply a declare @type - return true if we change the type
*/
private boolean applyDeclareAtType(DeclareAnnotation decA, ResolvedType onType, boolean reportProblems) {
boolean didSomething = false;
if (decA.matches(onType)) {
if (onType.hasAnnotation(decA.getAnnotation().getType())) {
// already has it
return false;
}
AnnotationAJ annoX = decA.getAnnotation();
// check the annotation is suitable for the target
boolean isOK = checkTargetOK(decA, onType, annoX);
if (isOK) {
didSomething = true;
ResolvedTypeMunger newAnnotationTM = new AnnotationOnTypeMunger(annoX);
newAnnotationTM.setSourceLocation(decA.getSourceLocation());
onType.addInterTypeMunger(new BcelTypeMunger(newAnnotationTM, decA.getAspect().resolve(this)), false);
decA.copyAnnotationTo(onType);
}
}
return didSomething;
}
/**
* Apply the specified declare @field construct to any matching fields in the specified type.
* @param deca the declare annotation targeting fields
* @param type the type to check for members matching the declare annotation
* @return true if something matched and the type was modified
*/
private boolean applyDeclareAtField(DeclareAnnotation deca, ResolvedType type) {
boolean changedType = false;
ResolvedMember[] fields = type.getDeclaredFields();
for (ResolvedMember field: fields) {
if (deca.matches(field, this)) {
AnnotationAJ anno = deca.getAnnotation();
if (!field.hasAnnotation(anno.getType())) {
field.addAnnotation(anno);
changedType=true;
}
}
}
return changedType;
}
/**
* Checks for an @target() on the annotation and if found ensures it allows the annotation to be attached to the target type
* that matched.
*/
private boolean checkTargetOK(DeclareAnnotation decA, ResolvedType onType, AnnotationAJ annoX) {
if (annoX.specifiesTarget()) {
if ((onType.isAnnotation() && !annoX.allowedOnAnnotationType()) || (!annoX.allowedOnRegularType())) {
return false;
}
}
return true;
}
// Hmmm - very similar to the code in BcelWeaver.weaveParentTypeMungers -
// this code
// doesn't need to produce errors/warnings though as it won't really be
// weaving.
protected void weaveInterTypeDeclarations(ResolvedType onType) {
List declareParentsList = getCrosscuttingMembersSet().getDeclareParents();
if (onType.isRawType()) {
onType = onType.getGenericType();
}
onType.clearInterTypeMungers();
List decpToRepeat = new ArrayList<>();
boolean aParentChangeOccurred = false;
boolean anAnnotationChangeOccurred = false;
// First pass - apply all decp mungers
for (DeclareParents decp : declareParentsList) {
boolean typeChanged = applyDeclareParents(decp, onType);
if (typeChanged) {
aParentChangeOccurred = true;
} else { // Perhaps it would have matched if a 'dec @type' had
// modified the type
if (!decp.getChild().isStarAnnotation()) {
decpToRepeat.add(decp);
}
}
}
// Still first pass - apply all dec @type mungers
for (DeclareAnnotation decA : getCrosscuttingMembersSet().getDeclareAnnotationOnTypes()) {
boolean typeChanged = applyDeclareAtType(decA, onType, true);
if (typeChanged) {
anAnnotationChangeOccurred = true;
}
}
// apply declare @field
for (DeclareAnnotation deca: getCrosscuttingMembersSet().getDeclareAnnotationOnFields()) {
if (applyDeclareAtField(deca,onType)) {
anAnnotationChangeOccurred = true;
}
}
while ((aParentChangeOccurred || anAnnotationChangeOccurred) && !decpToRepeat.isEmpty()) {
anAnnotationChangeOccurred = aParentChangeOccurred = false;
List decpToRepeatNextTime = new ArrayList<>();
for (DeclareParents decp: decpToRepeat) {
if (applyDeclareParents(decp, onType)) {
aParentChangeOccurred = true;
} else {
decpToRepeatNextTime.add(decp);
}
}
for (DeclareAnnotation deca: getCrosscuttingMembersSet().getDeclareAnnotationOnTypes()) {
if (applyDeclareAtType(deca, onType, false)) {
anAnnotationChangeOccurred = true;
}
}
for (DeclareAnnotation deca: getCrosscuttingMembersSet().getDeclareAnnotationOnFields()) {
if (applyDeclareAtField(deca, onType)) {
anAnnotationChangeOccurred = true;
}
}
decpToRepeat = decpToRepeatNextTime;
}
}
@Override
public IWeavingSupport getWeavingSupport() {
return bcelWeavingSupport;
}
@Override
public void reportCheckerMatch(Checker checker, Shadow shadow) {
IMessage iMessage = new Message(checker.getMessage(shadow), shadow.toString(), checker.isError() ? IMessage.ERROR
: IMessage.WARNING, shadow.getSourceLocation(), null, new ISourceLocation[] { checker.getSourceLocation() }, true,
0, -1, -1);
getMessageHandler().handleMessage(iMessage);
if (getCrossReferenceHandler() != null) {
getCrossReferenceHandler()
.addCrossReference(
checker.getSourceLocation(),
shadow.getSourceLocation(),
(checker.isError() ? IRelationship.Kind.DECLARE_ERROR.getName() : IRelationship.Kind.DECLARE_WARNING
.getName()), false);
}
if (getModel() != null) {
AsmRelationshipProvider.addDeclareErrorOrWarningRelationship(getModelAsAsmManager(), shadow, checker);
}
}
public AsmManager getModelAsAsmManager() {
return (AsmManager) getModel(); // For now... always an AsmManager in a bcel environment
}
void raiseError(String message) {
getMessageHandler().handleMessage(MessageUtil.error(message));
}
/**
* These are aop.xml files that can be used to alter the aspects that actually apply from those passed in - and also their scope
* of application to other files in the system.
*
* @param xmlFiles list of File objects representing any aop.xml files passed in to configure the build process
*/
public void setXmlFiles(List xmlFiles) {
if (!isXmlConfiguredWorld && !xmlFiles.isEmpty()) {
raiseError("xml configuration files only supported by the compiler when -xmlConfigured option specified");
return;
}
if (!xmlFiles.isEmpty()) {
xmlConfiguration = new WeavingXmlConfig(this, WeavingXmlConfig.MODE_COMPILE);
}
for (File xmlfile : xmlFiles) {
try {
Definition d = DocumentParser.parse(xmlfile.toURI().toURL());
xmlConfiguration.add(d);
} catch (MalformedURLException e) {
raiseError("Unexpected problem processing XML config file '" + xmlfile.getName() + "' :" + e.getMessage());
} catch (Exception e) {
raiseError("Unexpected problem processing XML config file '" + xmlfile.getName() + "' :" + e.getMessage());
}
}
}
/**
* Add a scoped aspects where the scoping was defined in an aop.xml file and this world is being used in a LTW configuration
*/
public void addScopedAspect(String name, String scope) {
this.isXmlConfiguredWorld = true;
if (xmlConfiguration == null) {
xmlConfiguration = new WeavingXmlConfig(this, WeavingXmlConfig.MODE_LTW);
}
xmlConfiguration.addScopedAspect(name, scope);
}
public void setXmlConfigured(boolean b) {
this.isXmlConfiguredWorld = b;
}
@Override
public boolean isXmlConfigured() {
return isXmlConfiguredWorld && xmlConfiguration != null;
}
public WeavingXmlConfig getXmlConfiguration() {
return xmlConfiguration;
}
@Override
public boolean isAspectIncluded(ResolvedType aspectType) {
if (!isXmlConfigured()) {
return true;
}
return xmlConfiguration.specifiesInclusionOfAspect(aspectType.getName());
}
@Override
public TypePattern getAspectScope(ResolvedType declaringType) {
return xmlConfiguration.getScopeFor(declaringType.getName());
}
@Override
public boolean hasUnsatisfiedDependency(ResolvedType aspectType) {
String aspectName = aspectType.getName();
if (aspectType.hasAnnotations()) {
AnnotationAJ[] annos = aspectType.getAnnotations();
for (AnnotationAJ anno: annos) {
if (anno.getTypeName().equals("org.aspectj.lang.annotation.RequiredTypes")) {
String values = anno.getStringFormOfValue("value"); // Example: "[A,org.foo.Bar]"
if (values != null && values.length() > 2) {
values = values.substring(1,values.length()-1);
StringTokenizer tokenizer = new StringTokenizer(values,",");
boolean anythingMissing = false;
while (tokenizer.hasMoreElements()) {
String requiredTypeName = tokenizer.nextToken();
ResolvedType rt = resolve(UnresolvedType.forName(requiredTypeName));
if (rt.isMissing()) {
if (!getMessageHandler().isIgnoring(IMessage.INFO)) {
getMessageHandler().handleMessage(
MessageUtil.info("deactivating aspect '" + aspectName + "' as it requires type '"
+ requiredTypeName + "' which cannot be found on the classpath"));
}
anythingMissing = true;
if (aspectRequiredTypes == null) {
aspectRequiredTypes = new HashMap<>();
}
// Record that it has an invalid type reference
aspectRequiredTypes.put(aspectName,requiredTypeName);
}
}
if (anythingMissing) {
return true;
}
else {
return false;
}
}
else {
// no value specified for annotation
return false;
}
}
}
}
if (aspectRequiredTypes == null) {
// no aspects require anything, so there can be no unsatisfied dependencies
return false;
}
if (!aspectRequiredTypesProcessed.contains(aspectName)) {
String requiredTypeName = aspectRequiredTypes.get(aspectName);
if (requiredTypeName==null) {
aspectRequiredTypesProcessed.add(aspectName);
return false;
} else {
ResolvedType rt = resolve(UnresolvedType.forName(requiredTypeName));
if (!rt.isMissing()) {
aspectRequiredTypesProcessed.add(aspectName);
aspectRequiredTypes.remove(aspectName);
return false;
} else {
if (!getMessageHandler().isIgnoring(IMessage.INFO)) {
getMessageHandler().handleMessage(
MessageUtil.info("deactivating aspect '" + aspectName + "' as it requires type '"
+ requiredTypeName + "' which cannot be found on the classpath"));
}
aspectRequiredTypesProcessed.add(aspectName);
return true;
}
}
}
return aspectRequiredTypes.containsKey(aspectName);
}
private List aspectRequiredTypesProcessed = new ArrayList<>();
private Map aspectRequiredTypes = null;
public void addAspectRequires(String aspectClassName, String requiredType) {
if (aspectRequiredTypes == null) {
aspectRequiredTypes = new HashMap<>();
}
aspectRequiredTypes.put(aspectClassName,requiredType);
}
/**
* A WeavingXmlConfig is initially a collection of definitions from XML files - once the world is ready and weaving is running
* it will initialize and transform those definitions into an optimized set of values (eg. resolve type patterns and string
* names to real entities). It can then answer questions quickly: (1) is this aspect included in the weaving? (2) Is there a
* scope specified for this aspect and does it include type X?
*
*/
static class WeavingXmlConfig {
final static int MODE_COMPILE = 1;
final static int MODE_LTW = 2;
private int mode;
private boolean initialized = false; // Lazily done
private List definitions = new ArrayList<>();
private List resolvedIncludedAspects = new ArrayList<>();
private Map scopes = new HashMap<>();
// these are not set for LTW mode (exclusion of these fast match patterns is handled before the weaver/world are used)
private List includedFastMatchPatterns = Collections.emptyList();
private List includedPatterns = Collections.emptyList();
private List excludedFastMatchPatterns = Collections.emptyList();
private List excludedPatterns = Collections.emptyList();
private BcelWorld world;
public WeavingXmlConfig(BcelWorld bcelWorld, int mode) {
this.world = bcelWorld;
this.mode = mode;
}
public void add(Definition d) {
definitions.add(d);
}
public void addScopedAspect(String aspectName, String scope) {
ensureInitialized();
resolvedIncludedAspects.add(aspectName);
try {
TypePattern scopePattern = new PatternParser(scope).parseTypePattern();
scopePattern.resolve(world);
scopes.put(aspectName, scopePattern);
if (!world.getMessageHandler().isIgnoring(IMessage.INFO)) {
world.getMessageHandler().handleMessage(
MessageUtil.info("Aspect '" + aspectName + "' is scoped to apply against types matching pattern '"
+ scopePattern.toString() + "'"));
}
} catch (Exception e) {
world.getMessageHandler().handleMessage(
MessageUtil.error("Unable to parse scope as type pattern. Scope was '" + scope + "': " + e.getMessage()));
}
}
public void ensureInitialized() {
if (!initialized) {
try {
resolvedIncludedAspects = new ArrayList<>();
// Process the definitions into something more optimal
for (Definition definition : definitions) {
List aspectNames = definition.getAspectClassNames();
for (String name : aspectNames) {
resolvedIncludedAspects.add(name);
// TODO check for existence?
// ResolvedType resolvedAspect = resolve(UnresolvedType.forName(name));
// if (resolvedAspect.isMissing()) {
// // ERROR
// } else {
// resolvedIncludedAspects.add(resolvedAspect);
// }
String scope = definition.getScopeForAspect(name);
if (scope != null) {
// Resolve the type pattern
try {
TypePattern scopePattern = new PatternParser(scope).parseTypePattern();
scopePattern.resolve(world);
scopes.put(name, scopePattern);
if (!world.getMessageHandler().isIgnoring(IMessage.INFO)) {
world.getMessageHandler().handleMessage(
MessageUtil.info("Aspect '" + name
+ "' is scoped to apply against types matching pattern '"
+ scopePattern.toString() + "'"));
}
} catch (Exception e) {
// TODO definitions should remember which file they came from, for inclusion in this message
world.getMessageHandler().handleMessage(
MessageUtil.error("Unable to parse scope as type pattern. Scope was '" + scope + "': "
+ e.getMessage()));
}
}
}
try {
List includePatterns = definition.getIncludePatterns();
if (includePatterns.size() > 0) {
includedPatterns = new ArrayList<>();
includedFastMatchPatterns = new ArrayList<>();
}
for (String includePattern : includePatterns) {
if (includePattern.endsWith("..*")) {
// from 'blah.blah.blah..*' leave the 'blah.blah.blah.'
includedFastMatchPatterns.add(includePattern.substring(0, includePattern.length() - 2));
} else {
TypePattern includedPattern = new PatternParser(includePattern).parseTypePattern();
includedPatterns.add(includedPattern);
}
}
List excludePatterns = definition.getExcludePatterns();
if (excludePatterns.size() > 0) {
excludedPatterns = new ArrayList<>();
excludedFastMatchPatterns = new ArrayList<>();
}
for (String excludePattern : excludePatterns) {
if (excludePattern.endsWith("..*")) {
// from 'blah.blah.blah..*' leave the 'blah.blah.blah.'
excludedFastMatchPatterns.add(excludePattern.substring(0, excludePattern.length() - 2));
} else {
TypePattern excludedPattern = new PatternParser(excludePattern).parseTypePattern();
excludedPatterns.add(excludedPattern);
}
}
} catch (ParserException pe) {
// TODO definitions should remember which file they came from, for inclusion in this message
world.getMessageHandler().handleMessage(
MessageUtil.error("Unable to parse type pattern: " + pe.getMessage()));
}
}
} finally {
initialized = true;
}
}
}
public boolean specifiesInclusionOfAspect(String name) {
ensureInitialized();
return resolvedIncludedAspects.contains(name);
}
public TypePattern getScopeFor(String name) {
return scopes.get(name);
}
// Can't quite follow the same rules for exclusion as used for loadtime weaving:
// "The set of types to be woven are those types matched by at least one weaver include element and not matched by any
// weaver
// exclude element. If there are no weaver include statements then all non-excluded types are included."
// Since if the weaver is seeing it during this kind of build, the type is implicitly included. So all we should check
// for is exclusion
public boolean excludesType(ResolvedType type) {
if (mode == MODE_LTW) {
return false;
}
String typename = type.getName();
boolean excluded = false;
for (String excludedPattern : excludedFastMatchPatterns) {
if (typename.startsWith(excludedPattern)) {
excluded = true;
break;
}
}
if (!excluded) {
for (TypePattern excludedPattern : excludedPatterns) {
if (excludedPattern.matchesStatically(type)) {
excluded = true;
break;
}
}
}
return excluded;
}
}
@Override
public TypeMap getTypeMap() {
return typeMap;
}
@Override
public boolean isLoadtimeWeaving() {
return false;
}
public void addTypeDelegateResolver(TypeDelegateResolver typeDelegateResolver) {
if (typeDelegateResolvers == null) {
typeDelegateResolvers = new ArrayList<>();
}
typeDelegateResolvers.add(typeDelegateResolver);
}
@Override
public void classWriteEvent(char[][] compoundName) {
typeMap.classWriteEvent(new String(CharOperation.concatWith(compoundName, '.')));
}
/**
* Force demote a type.
*/
public void demote(ResolvedType type) {
typeMap.demote(type);
}
}