// TODO: rewrite this, using refreshMethod() instead of SavedMethodDecls aspect LockedMethodAccess { /* A locked method access is a method access that does not obey the normal method lookup * rules, but instead immediately binds to its target. For every locked method access, * we also cache whether or not it is a monomorphic call. */ private SavedMethodDecl MethodAccess.targetMethod = null; private boolean MethodAccess.isMonoCall = false; private SavedMethodDecl MethodAccess.setTargetMethod(SavedMethodDecl smd, boolean isMonoCall) { this.targetMethod = smd; this.isMonoCall = isMonoCall; return smd; } public MethodAccess.MethodAccess(MethodDecl target, List args, boolean isMonoCall) { this(target.name(), args); setTargetMethod(target.save(), isMonoCall); } public MethodAccess MethodDecl.createLockedAccess(List args, boolean isMonoCall) { return new MethodAccess(this, args, isMonoCall); } public MethodAccess MethodDecl.createLockedAccess(List args) { return createLockedAccess(args, false); } /* If we just store the method declaration an access should bind to, we get into trouble with * ParMethodDecls: we might store a reference to some ParMethodDecl, then somebody flushes the * caches, and a new ParMethodDecl will be created the next time we ask for it. Then inherited * attributes won't work anymore, and things generally become unpleasant. * * So we store a SavedMethodDecl instead: this is either a wrapper around a regular MethodDecl, * or it represents a ParMethodDecl by its genericMethodDecl() and its list of arguments.*/ class SavedMethodDecl { private MethodDecl md; public SavedMethodDecl(MethodDecl md) { this.md = md; } public MethodDecl getDecl() { return md; } public boolean isFinal() { return md.isFinal(); } public boolean isStatic() { return md.isStatic(); } } class SavedParMethodDecl extends SavedMethodDecl { private ArrayList parms; public SavedParMethodDecl(ParMethodDecl pmd) { super(pmd.genericMethodDecl()); parms = new ArrayList(); for(Access acc : pmd.getTypeArguments()) parms.add(acc.type()); } public MethodDecl getDecl() { ArrayList parmtypes = new ArrayList(); for(TypeDecl std : parms) parmtypes.add(std.refresh()); return ((GenericMethodDecl)super.getDecl()).lookupParMethodDecl(parmtypes); } } class SavedMethodDeclSubstituted extends SavedMethodDecl { private TypeDecl host; public SavedMethodDeclSubstituted(MethodDeclSubstituted mds) { super(mds.getOriginal()); host = mds.hostType(); } public MethodDecl getDecl() { TypeDecl host = this.host.refresh(); MethodDecl md = super.getDecl(); host.localMethodsSignatureMap(); for(BodyDecl bd : host.getBodyDecls()) if(bd instanceof MethodDeclSubstituted && ((MethodDeclSubstituted)bd).getOriginal() == md) return (MethodDecl)bd; throw new Error("no such method"); } } public SavedMethodDecl MethodDecl.save() { return new SavedMethodDecl(this); } public SavedParMethodDecl ParMethodDecl.save() { return new SavedParMethodDecl(this); } public SavedMethodDeclSubstituted MethodDeclSubstituted.save() { return new SavedMethodDeclSubstituted(this); } refine LookupMethod eq MethodAccess.decls() = targetMethod == null ? refined() : targetMethod.getDecl(); refine PrettyPrint public void MethodAccess.toString(StringBuffer s) { if(targetMethod == null) { refined(s); } else { s.append("[["); refined(s); s.append("]]"); } } // introducing locked method accesses public ASTNode MethodAccess.lockMethodNames(Collection endangered) { if(endangered.contains(name())) lock(); return super.lockMethodNames(endangered); } public ASTNode MethodAccess.lock() { return targetMethod == null ? lock(decl()) : this; } public ASTNode MethodAccess.lock(MethodDecl md) { assert md != null && !md.isUnknown(); setTargetMethod(md.save(), isMonoCall()); return this; } public boolean MethodAccess.isLocked() { return targetMethod != null; } // eliminating locked method accesses inh TypeDecl MethodAccess.enclosingType(); public void MethodAccess.eliminateLockedNames() { if(targetMethod != null) unlock(); super.eliminateLockedNames(); } public Access MethodAccess.unlock() { Expr qual = getQualifier(); MethodDecl target = targetMethod.getDecl(); boolean isMonoCall = this.isMonoCall; setTargetMethod(null, false); flushCache(); if(fromSource()) setID(target.name()); if(decl().equals(target)) { return this; } else if(!fromSource()) { throw new RefactoringException("cannot adjust method access in compiled code"); } else { return unlock(qual, target, isMonoCall); } } public Access MethodAccess.unlock(Expr qual, MethodDecl target, boolean isMonoCall) { if((qual == null ? inStaticContext() : qual.staticContextQualifier()) && !target.isStatic()) throw new RefactoringException("cannot access instance method in static context"); MethodAccessInfo acc = accessMethod(target); if(acc == null) { if((qual == null || qual.isPure()) && target.isStatic()) { TypeDecl host = target.hostType(); // since the target is static, we can access it through the raw type if(host.isGenericType()) host = ((GenericTypeDecl)host).rawType(); if(host.accessibleFrom(hostType()) && target.accessibleFrom(hostType())) if (qual == null || (qual!=null && qual.type() != host)) { this.flushCaches(); Access hostAccess = host.createLockedAccess(); affectedByUnlock(hostAccess, this); return (Access) replace(qual!=null ? parentDot() : this).with(hostAccess.qualifiesAccess(this)); } else return qual!=null ? parentDot() : this; } else if(qual != null && (qual.isThisAccess() || qual.isSuperAccess())) { acc = parentDot().accessMethod(target); if(acc != null) { this.flushCaches(); return (Access) replace(parentDot()).with(acc.eliminate(this, null, enclosingType(), isMonoCall, inStaticContext(), (List) getArgs().fullCopyAndDetach())); } } throw new RefactoringException("cannot access method "+target.name()); } this.flushCaches(); Access res = acc.eliminate(this, qual, enclosingType(), isMonoCall, inStaticContext(), (List) getArgs().fullCopyAndDetach()); if(res == null) throw new RefactoringException("cannot access method "+target.name()); return res; } // eliminating locked method accesses public Access MethodAccessInfo.eliminate(MethodAccess original, Expr qualifier, TypeDecl enclosing, boolean isMonoCall, boolean inStaticCtxt, List args) { if(qualifier == null) return eliminate(original, enclosing, isMonoCall, inStaticCtxt, args); else return eliminateQualified(original, qualifier, enclosing, isMonoCall, inStaticCtxt, args); } public Access MethodAccessInfo.eliminate(MethodAccess original, TypeDecl enclosing, boolean isMonoCall, boolean inStaticCtxt, List args) { if(!directlyVisible) { if(target.isStatic()) return (Access) original.replace(original).with( computeStaticAccess(original, enclosing, args)); if(inStaticCtxt) throw new RefactoringException("cannot access non-static method in static context"); MethodAccess ma = constructAccess(original, args); ma.flushCaches(); if(isMonoCall && !target.isPrivate()) { // need to construct a super call if(bend instanceof ClassDecl && source == ((ClassDecl)bend).superclass()) { if(bend == enclosing) { ma.affectedByUnlock(ma); return (Access)original.replace(original).with(new SuperAccess("super").qualifiesAccess(ma)); } else { Access bendAccess = bend.createLockedAccess(); ma.affectedByUnlock(bendAccess, ma); return (Access)original.replace(original).with(bendAccess.qualifiesAccess(new SuperAccess("super").qualifiesAccess(ma))); } } throw new RefactoringException("cannot construct monomorphic call"); } else { if(source != bend && target.isPrivate()) throw new RefactoringException("cannot access private method here"); if(bend == enclosing) { return (Access)original.replace(original).with(ma); } else { Access bendAccess = bend.createLockedAccess(); ma.affectedByUnlock(bendAccess, ma); return (Access)original.replace(original).with(bendAccess.qualifiesAccess(new ThisAccess("this").qualifiesAccess(ma))); } /* Note: We do not distiguish whether source == bend or not, but since we are constructing a virtuall call * there is no semantic difference between ((A)this).m() and this.m(), and ((A)B.this).m() and * B.this.m() anyway. * * TODO: this might lead to problems with overloading*/ } } else { return (Access)original.replace(original).with(constructAccess(original, args)); } } public Access MethodAccessInfo.computeStaticAccess(MethodAccess original, TypeDecl enclosing, List args) { Access sourceAccess = source.createLockedAccess(); Access methodAccess = constructAccess(original, args); methodAccess.affectedByUnlock(sourceAccess, methodAccess); return sourceAccess.qualifiesAccess(methodAccess); } protected MethodAccess MethodAccessInfo.constructAccess(MethodAccess original, List args) { // check if any of the competitors is more specific than the target for // this argument list; then we need casts for(MethodDecl cand : competitors) if(cand.applicableTo(args)) if(cand.moreSpecificThan(target) || !target.moreSpecificThan(cand)) return (MethodAccess) original.replace(original).with(new MethodAccess(target.name(), insertCasts(target, args))); return original; } public static List MethodAccessInfo.insertCasts(Callable target, List args) { List new_args = new List(); for(int i=0;i varargs = new List(); while(i args) { MethodAccess ma = constructAccess(original, args); if(!directlyVisible) { if(!target.isStatic()) throw new RefactoringException("cannot access method"); if(inStaticCtxt) if(qualifier.isTypeAccess() && qualifier.type().equals(source)) return ma.parentDot(); else throw new RefactoringException("cannot access method"); if(source == bend && source.equals(qualifier.type())) return ma.parentDot(); else if(!qualifier.isTypeAccess() && qualifier.type().equals(bend)) { ma.parentDot().flushCaches(); Access sourceAccess = source.createLockedAccess(); ma.affectedByUnlock(sourceAccess, ma); return (Access) ma.parentDot().replace(ma.parentDot()).with( new ParExpr(new CastExpr(sourceAccess, qualifier)).qualifiesAccess(ma)); } throw new RefactoringException("cannot access method"); } else { if(!target.accessibleFrom(enclosing)) throw new RefactoringException("method not accessible"); return ma.parentDot(); } } /* A special case are element-value pairs in annotations: they also refer to methods, so we have to lock them; * but they are much easier to adjust. */ private MethodDecl ElementValuePair.targetMethod = null; private MethodDecl ElementValuePair.setTargetMethod(MethodDecl smd) { return targetMethod = smd; } public ASTNode ElementValuePair.lockMethodNames(Collection endangered) { if(endangered.contains(getName())) return lock(); else return super.lockMethodNames(endangered); } public ASTNode ElementValuePair.lock() { Iterator iter = enclosingAnnotationDecl().memberMethods(getName()).iterator(); if(iter.hasNext()) setTargetMethod(iter.next()); return this; } public void ElementValuePair.eliminateLockedNames() { if(targetMethod != null) { MethodDecl target = targetMethod; setTargetMethod(null); setName(target.name()); Iterator iter = enclosingAnnotationDecl().memberMethods(getName()).iterator(); if(!iter.hasNext() || iter.next() != target) throw new RefactoringException("cannot unlock element-value pair"); } super.eliminateLockedNames(); } /* NOTE: Locked method accesses only fix the static declaration a method resolves to; they do not consider * possible polymorphic dispatch. One could think that it would be safer to actually compute the set * of all targets a call could possibly resolve to, and fix that whole set. This would not actually * be an improvement, however, since this set would be an overapproximation, and we still could not * guarantee invariance of run-time dispatch. Instead, we have to guard against changes in dispatch * behaviour on a refactoring-specific basis. */ refine Uses protected void MethodAccess.collect__MethodDecl_Uses(MethodDecl decl, String name, Collection col) { super.collect__MethodDecl_Uses(decl, name, col); if ((targetMethod != null && targetMethod.getDecl() == decl) || (name.equals(name()) && decl() == decl)) col.add(this); } }