/* ******************************************************************* * Copyright (c) 2004 IBM Corporation. * All rights reserved. * This program and the accompanying materials are made available * under the terms of the Eclipse Public License v1.0 * which accompanies this distribution and is available at * http://www.eclipse.org/legal/epl-v10.html * * ******************************************************************/ package org.aspectj.weaver.patterns; import java.util.Iterator; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import org.aspectj.weaver.Shadow; import org.aspectj.weaver.patterns.Pointcut.MatchesNothingPointcut; /** * Performs term rewriting for pointcut expressions. * * @author colyer * @author clement */ public class PointcutRewriter { private static final boolean WATCH_PROGRESS = false; /** * Set forcerewrite if you want to override the checking for something already in DNF (useful for some testing) Repeated * processing of something already in DNF is expensive (it ends up being done for every pointcut on every incremental compile) - * so let's not do it if we don't have to. See pr113257 */ public Pointcut rewrite(Pointcut pc, boolean forceRewrite) { Pointcut result = pc;// checkPC(result); if (forceRewrite || !isDNF(pc)) { if (WATCH_PROGRESS) { System.out.println("Initial pointcut is ==> " + format(pc)); } result = distributeNot(result);// checkPC(result); if (WATCH_PROGRESS) { System.out.println("Distributing NOT gives ==> " + format(result)); } result = pullUpDisjunctions(result);// checkPC(result); if (WATCH_PROGRESS) { System.out.println("Pull up disjunctions gives ==> " + format(result)); } } else { if (WATCH_PROGRESS) { System.out.println("Not distributing NOTs or pulling up disjunctions, already DNF ==> " + format(pc)); } } result = simplifyAnds(result); // checkPC(result); if (WATCH_PROGRESS) { System.out.println("Simplifying ANDs gives ==> " + format(result)); } result = removeNothings(result); // checkPC(result); if (WATCH_PROGRESS) { System.out.println("Removing nothings gives ==> " + format(result)); } result = sortOrs(result); // checkPC(result); if (WATCH_PROGRESS) { System.out.println("Sorting ORs gives ==> " + format(result)); } return result; } // /** // * Checks pointcuts - used for debugging. // * - this variant checks if the context has been lost, since // * that can indicate an NPE will happen later reporting a message (pr162657). // * Not finished, but helped locate the problem ;) // */ // private void checkPC(Pointcut pc) { // if (isNot(pc)) { // NotPointcut npc = (NotPointcut)pc; // checkPC(npc.getNegatedPointcut()); // if (npc.getSourceContext()==null) { // System.out.println("Lost context for "+npc); // throw new RuntimeException("Lost context"); // } // } else if (isOr(pc)) { // OrPointcut opc = (OrPointcut)pc; // checkPC(opc.getLeft()); // checkPC(opc.getRight()); // if (opc.getSourceContext()==null) { // System.out.println("Lost context for "+opc); // throw new RuntimeException("Lost context"); // } // } else if (isAnd(pc)) { // AndPointcut apc = (AndPointcut)pc; // checkPC(apc.getLeft()); // checkPC(apc.getRight()); // if (apc.getSourceContext()==null) { // System.out.println("Lost context for "+apc); // throw new RuntimeException("Lost context"); // } // } else { // if (pc.getSourceContext()==null) { // System.out.println("Lost context for "+pc); // throw new RuntimeException("Lost context"); // } // } // } public Pointcut rewrite(Pointcut pc) { return rewrite(pc, false); } /** * Check if a pointcut is in DNF - if it is then it should be lots of 'ORs' up the top with 'ANDs' beneath them. */ private boolean isDNF(Pointcut pc) { return isDNFHelper(pc, true); } /** * Helper function for determining DNFness. Records when we have crossed the point of allowing ORs. */ private boolean isDNFHelper(Pointcut pc, boolean canStillHaveOrs) { if (isAnd(pc)) { AndPointcut ap = (AndPointcut) pc; return isDNFHelper(ap.getLeft(), false) && isDNFHelper(ap.getRight(), false); } else if (isOr(pc)) { if (!canStillHaveOrs) { return false; } OrPointcut op = (OrPointcut) pc; return isDNFHelper(op.getLeft(), true) && isDNFHelper(op.getRight(), true); } else if (isNot(pc)) { return isDNFHelper(((NotPointcut) pc).getNegatedPointcut(), canStillHaveOrs); } else { return true; } } /** * Allows formatting of the output pointcut for debugging... */ public static String format(Pointcut p) { String s = p.toString(); // Regex param needs '(' and '*' changing to '.' // s = s.replaceAll("persingleton.pkg1.monitoring.ErrorMonitoring.","M"); // s = s.replaceAll("args.BindingTypePattern.java.lang.Throwable, 0.","Z"); // s = s.replaceAll("within.pkg1.monitoring.DoMonitorErrors+.","X"); // s=s.replaceAll("within.pkg1.monitoring....","Y"); // s=s.replaceAll("if.true.","N"); return s; } // !!X => X // !(X && Y) => !X || !Y // !(X || Y) => !X && !Y private Pointcut distributeNot(Pointcut pc) { if (isNot(pc)) { NotPointcut npc = (NotPointcut) pc; Pointcut notBody = distributeNot(npc.getNegatedPointcut()); if (isNot(notBody)) { // !!X => X return ((NotPointcut) notBody).getNegatedPointcut(); } else if (isAnd(notBody)) { // !(X && Y) => !X || !Y AndPointcut apc = (AndPointcut) notBody; Pointcut newLeft = distributeNot(new NotPointcut(apc.getLeft(), npc.getStart())); Pointcut newRight = distributeNot(new NotPointcut(apc.getRight(), npc.getStart())); return new OrPointcut(newLeft, newRight); } else if (isOr(notBody)) { // !(X || Y) => !X && !Y OrPointcut opc = (OrPointcut) notBody; Pointcut newLeft = distributeNot(new NotPointcut(opc.getLeft(), npc.getStart())); Pointcut newRight = distributeNot(new NotPointcut(opc.getRight(), npc.getStart())); return new AndPointcut(newLeft, newRight); } else { return new NotPointcut(notBody, npc.getStart()); } } else if (isAnd(pc)) { AndPointcut apc = (AndPointcut) pc; Pointcut left = distributeNot(apc.getLeft()); Pointcut right = distributeNot(apc.getRight()); return new AndPointcut(left, right); } else if (isOr(pc)) { OrPointcut opc = (OrPointcut) pc; Pointcut left = distributeNot(opc.getLeft()); Pointcut right = distributeNot(opc.getRight()); return new OrPointcut(left, right); } else { return pc; } } // A && (B || C) => (A && B) || (A && C) // (A || B) && C => (A && C) || (B && C) private Pointcut pullUpDisjunctions(Pointcut pc) { if (isNot(pc)) { NotPointcut npc = (NotPointcut) pc; return new NotPointcut(pullUpDisjunctions(npc.getNegatedPointcut())); } else if (isAnd(pc)) { AndPointcut apc = (AndPointcut) pc; // dive into left and right here... Pointcut left = pullUpDisjunctions(apc.getLeft()); Pointcut right = pullUpDisjunctions(apc.getRight()); if (isOr(left) && !isOr(right)) { // (A || B) && C => (A && C) || (B && C) Pointcut leftLeft = ((OrPointcut) left).getLeft(); Pointcut leftRight = ((OrPointcut) left).getRight(); return pullUpDisjunctions(new OrPointcut(new AndPointcut(leftLeft, right), new AndPointcut(leftRight, right))); } else if (isOr(right) && !isOr(left)) { // A && (B || C) => (A && B) || (A && C) Pointcut rightLeft = ((OrPointcut) right).getLeft(); Pointcut rightRight = ((OrPointcut) right).getRight(); return pullUpDisjunctions(new OrPointcut(new AndPointcut(left, rightLeft), new AndPointcut(left, rightRight))); } else if (isOr(right) && isOr(left)) { // (A || B) && (C || D) => (A && C) || (A && D) || (B && C) || (B && D) Pointcut A = pullUpDisjunctions(((OrPointcut) left).getLeft()); Pointcut B = pullUpDisjunctions(((OrPointcut) left).getRight()); Pointcut C = pullUpDisjunctions(((OrPointcut) right).getLeft()); Pointcut D = pullUpDisjunctions(((OrPointcut) right).getRight()); Pointcut newLeft = new OrPointcut(new AndPointcut(A, C), new AndPointcut(A, D)); Pointcut newRight = new OrPointcut(new AndPointcut(B, C), new AndPointcut(B, D)); return pullUpDisjunctions(new OrPointcut(newLeft, newRight)); } else { return new AndPointcut(left, right); } } else if (isOr(pc)) { OrPointcut opc = (OrPointcut) pc; return new OrPointcut(pullUpDisjunctions(opc.getLeft()), pullUpDisjunctions(opc.getRight())); } else { return pc; } } /** * Returns a NOTted form of the pointcut p - we cope with already NOTted pointcuts. */ public Pointcut not(Pointcut p) { if (isNot(p)) { return ((NotPointcut) p).getNegatedPointcut(); } return new NotPointcut(p); } /** * Passed an array of pointcuts, returns an AND tree with them in. */ public Pointcut createAndsFor(Pointcut[] ps) { if (ps.length == 1) { return ps[0]; // dumb case } if (ps.length == 2) { // recursion exit case return new AndPointcut(ps[0], ps[1]); } // otherwise ... Pointcut[] subset = new Pointcut[ps.length - 1]; for (int i = 1; i < ps.length; i++) { subset[i - 1] = ps[i]; } return new AndPointcut(ps[0], createAndsFor(subset)); } // NOT: execution(* TP.*(..)) => within(TP) && execution(* *(..)) // since this breaks when the pattern matches an interface // NOT: withincode(* TP.*(..)) => within(TP) && withincode(* *(..)) // since this is not correct when an aspect makes an ITD // private Pointcut splitOutWithins(Pointcut pc) { // if (isExecution(pc)) { // KindedPointcut kpc = (KindedPointcut) pc; // SignaturePattern sp = kpc.signature; // TypePattern within = sp.getDeclaringType(); // if (isAnyType(within)) return pc; // SignaturePattern simplified = removeDeclaringTypePattern(sp); // return new AndPointcut(new WithinPointcut(within), // new KindedPointcut(kpc.kind,simplified)); // } else if (isNot(pc)) { // return new NotPointcut(splitOutWithins(((NotPointcut)pc).getNegatedPointcut())); // } else if (isAnd(pc)) { // AndPointcut apc = (AndPointcut) pc; // return new AndPointcut(splitOutWithins(apc.getLeft()), // splitOutWithins(apc.getRight())); // } else if (isOr(pc)) { // OrPointcut opc = (OrPointcut) pc; // return new OrPointcut(splitOutWithins(opc.getLeft()), // splitOutWithins(opc.getRight())); // } else { // return pc; // } // } // private SignaturePattern removeDeclaringTypePattern(SignaturePattern sp) { // return new SignaturePattern( // sp.getKind(), // sp.getModifiers(), // sp.getReturnType(), // TypePattern.ANY, // sp.getName(), // sp.getParameterTypes(), // sp.getThrowsPattern(), // sp.getAnnotationPattern() // ); // } // this finds the root of each && tree and then aggregates all of the branches // into a sorted set: // - duplicates are removed // - A && !A is replaced by a matchesNothingPointcut // - the kind(s) matched by the set are evaluated // - elements are sorted by evaluation complexity // - the result is written out with the least expensive branch leftmost private Pointcut simplifyAnds(Pointcut pc) { if (isNot(pc)) { NotPointcut npc = (NotPointcut) pc; Pointcut notBody = npc.getNegatedPointcut(); if (isNot(notBody)) { // !!X => X return simplifyAnds(((NotPointcut) notBody).getNegatedPointcut()); } else { return new NotPointcut(simplifyAnds(npc.getNegatedPointcut())); } } else if (isOr(pc)) { OrPointcut opc = (OrPointcut) pc; return new OrPointcut(simplifyAnds(opc.getLeft()), simplifyAnds(opc.getRight())); } else if (isAnd(pc)) { return simplifyAnd((AndPointcut) pc); } else { return pc; } } private Pointcut simplifyAnd(AndPointcut apc) { SortedSet nodes = new TreeSet(new PointcutEvaluationExpenseComparator()); collectAndNodes(apc, nodes); // look for A and !A, or IfFalse for (Iterator iter = nodes.iterator(); iter.hasNext();) { Pointcut element = iter.next(); if (element instanceof NotPointcut) { Pointcut body = ((NotPointcut) element).getNegatedPointcut(); if (nodes.contains(body)) { return Pointcut.makeMatchesNothing(body.state); } } if (element instanceof IfPointcut) { if (((IfPointcut) element).alwaysFalse()) { return Pointcut.makeMatchesNothing(element.state); } } // If it can't match anything, the whole AND can't match anything if (element.couldMatchKinds() == Shadow.NO_SHADOW_KINDS_BITS) { return element; } } if (apc.couldMatchKinds() == Shadow.NO_SHADOW_KINDS_BITS) { return Pointcut.makeMatchesNothing(apc.state); } // write out with cheapest on left Iterator iter = nodes.iterator(); Pointcut result = iter.next(); while (iter.hasNext()) { Pointcut right = iter.next(); result = new AndPointcut(result, right); } return result; } private Pointcut sortOrs(Pointcut pc) { SortedSet nodes = new TreeSet(new PointcutEvaluationExpenseComparator()); collectOrNodes(pc, nodes); // write out with cheapest on left Iterator iter = nodes.iterator(); Pointcut result = iter.next(); while (iter.hasNext()) { Pointcut right = iter.next(); result = new OrPointcut(result, right); } return result; } /** * Removes MATCHES_NOTHING pointcuts */ private Pointcut removeNothings(Pointcut pc) { if (isAnd(pc)) { AndPointcut apc = (AndPointcut) pc; Pointcut right = removeNothings(apc.getRight()); Pointcut left = removeNothings(apc.getLeft()); if (left instanceof MatchesNothingPointcut || right instanceof MatchesNothingPointcut) { return new MatchesNothingPointcut(); } return new AndPointcut(left, right); } else if (isOr(pc)) { OrPointcut opc = (OrPointcut) pc; Pointcut right = removeNothings(opc.getRight()); Pointcut left = removeNothings(opc.getLeft()); if (left instanceof MatchesNothingPointcut && !(right instanceof MatchesNothingPointcut)) { return right; } else if (right instanceof MatchesNothingPointcut && !(left instanceof MatchesNothingPointcut)) { return left; } else if (!(left instanceof MatchesNothingPointcut) && !(right instanceof MatchesNothingPointcut)) { return new OrPointcut(left, right); } else if (left instanceof MatchesNothingPointcut && right instanceof MatchesNothingPointcut) { return new MatchesNothingPointcut(); } } return pc; } private void collectAndNodes(AndPointcut apc, Set nodesSoFar) { Pointcut left = apc.getLeft(); Pointcut right = apc.getRight(); if (isAnd(left)) { collectAndNodes((AndPointcut) left, nodesSoFar); } else { nodesSoFar.add(left); } if (isAnd(right)) { collectAndNodes((AndPointcut) right, nodesSoFar); } else { nodesSoFar.add(right); } } private void collectOrNodes(Pointcut pc, Set nodesSoFar) { if (isOr(pc)) { OrPointcut opc = (OrPointcut) pc; collectOrNodes(opc.getLeft(), nodesSoFar); collectOrNodes(opc.getRight(), nodesSoFar); } else { nodesSoFar.add(pc); } } private boolean isNot(Pointcut pc) { return (pc instanceof NotPointcut); } private boolean isAnd(Pointcut pc) { return (pc instanceof AndPointcut); } private boolean isOr(Pointcut pc) { return (pc instanceof OrPointcut); } }