001    /*
002     * JastAddJ is covered by the modified BSD License. You should have received a copy of the
003     * modified BSD license with this compiler.
004     * 
005     * Copyright (c) 2011, Jesper Öqvist <jesper.oqvist@cs.lth.se>
006     * All rights reserved.
007     */
008    
009    aspect TryWithResources {
010        /**
011         * Syntactic classification for TWR resource declarations.
012         */
013        eq ResourceDeclaration.getTypeAccess().nameType() = NameType.TYPE_NAME;
014    
015        /**
016         * Inherit the lookupType attribute in ResourceDeclaration.
017         */
018        inh TypeDecl ResourceDeclaration.lookupType(String packageName, String typeName);
019    
020        /**
021         * Type checking for TWR.
022         */
023        public void ResourceDeclaration.typeCheck() {
024                TypeDecl typeAutoCloseable = lookupType("java.lang", "AutoCloseable");
025                if (typeAutoCloseable == null)
026                        error("java.lang.AutoCloseable not found");
027                else if (!getTypeAccess().type().instanceOf(typeAutoCloseable))
028                        error("Resource specification must declare an AutoCloseable resource");
029        }
030    
031        /*coll Set<TypeDecl> Stmt.uncaughtExceptions()
032                [new HashSet<TypeDecl>()] with add;
033    
034        MethodAccess contributes exceptionCollection()
035                to Stmt.uncaughtExceptions();*/
036    
037        /**
038         * Exception error checks.
039         */
040        public void TryWithResources.exceptionHandling() {
041    
042                // Check exception handling of exceptions on auto closing of resource
043                for (ResourceDeclaration resource : getResourceList()) {
044                        MethodDecl close = lookupClose(resource);
045                        if (close == null) continue;
046                        for (Access exception : close.getExceptionList()) {
047                                TypeDecl exceptionType = exception.type();
048                                if (!twrHandlesException(exceptionType))
049                                        error("automatic closing of resource "+resource.name()+
050                                                        " may raise the uncaught exception "+exceptionType.fullName()+"; "+
051                                                        "it must be caught or declared as being thrown");
052                        }
053                }
054        }
055    
056        /**
057         * This attribute computes whether or not the TWR statement
058         * has a catch clause which handles the exception.
059         */
060        syn boolean TryWithResources.catchHandlesException(TypeDecl exceptionType) {
061                for (int i = 0; i < getNumCatchClause(); i++)
062                        if (getCatchClause(i).handles(exceptionType))
063                                return true;
064                return false;
065        }
066    
067        /**
068         * Returns true if exceptions of type exceptionType are handled
069         * in the try-with-resources statement or any containing statement
070         * within the directly enclosing method or initializer block.
071         */
072        syn boolean TryWithResources.twrHandlesException(TypeDecl exceptionType) {
073                if (catchHandlesException(exceptionType))
074                        return true;
075                if (hasFinally() && !getFinally().canCompleteNormally())
076                        return true;
077                return handlesException(exceptionType);
078        }
079    
080        /**
081         * Inherit the handlesException attribute from methoddecl.
082         */
083        inh lazy boolean TryWithResources.handlesException(TypeDecl exceptionType);
084    
085        eq TryWithResources.getResource(int i).handlesException(TypeDecl exceptionType) =
086                twrHandlesException(exceptionType);
087    
088        eq TryWithResources.getBlock().handlesException(TypeDecl exceptionType) =
089                twrHandlesException(exceptionType);
090    
091    
092        /**
093         * Lookup the close method declaration for the resource which is being used.
094         */
095        syn MethodDecl TryWithResources.lookupClose(ResourceDeclaration resource) {
096                TypeDecl resourceType = resource.getTypeAccess().type();
097                for (MethodDecl method : (Collection<MethodDecl>) resourceType.memberMethods("close")) {
098                        if (method.getNumParameter() == 0) {
099                                return method;
100                       }
101               }
102               return null;
103               /* We can't throw a runtime exception here. If there is no close method it
104                * likely means that the resource type is not a subtype of java.lang.AutoCloseable
105                * and type checking will report this error.
106                */
107               //throw new RuntimeException("close() not found for resource type "+resourceType.fullName());
108       }
109    
110       inh lazy TypeDecl TryWithResources.typeError();
111       inh lazy TypeDecl TryWithResources.typeRuntimeException();
112    
113       eq TryWithResources.getCatchClause(int childIndex).reachableCatchClause(TypeDecl exceptionType) {
114               for (int i = 0; i < childIndex; i++)
115                       if (getCatchClause(i).handles(exceptionType))
116                               return false;
117               if (catchableException(exceptionType))
118                       return true;
119               if (exceptionType.mayCatch(typeError()) || exceptionType.mayCatch(typeRuntimeException()))
120                       return true;
121               return false;
122       }
123    
124       /**
125        * Variable lookup attribute.
126        */
127       eq TryWithResources.getBlock().lookupVariable(String name) = localLookup(name);
128       syn lazy SimpleSet TryWithResources.localLookup(String name) {
129               VariableDeclaration v = localVariableDeclaration(name);
130               if (v != null) return v;
131               return lookupVariable(name);
132       }
133       syn lazy VariableDeclaration TryWithResources.localVariableDeclaration(String name) {
134               for (ResourceDeclaration resource : getResourceList())
135                       if (resource.declaresVariable(name))
136                               return resource;
137               return null;
138       }
139    
140       TryWithResources implements VariableScope;
141       inh lazy SimpleSet TryWithResources.lookupVariable(String name);
142       eq TryWithResources.getResource(int i).outerScope() = this;
143    
144       inh boolean VariableDeclaration.resourcePreviouslyDeclared(String name);
145       inh boolean TryWithResources.resourcePreviouslyDeclared(String name);
146       eq TryWithResources.getResource(int index).resourcePreviouslyDeclared(String name) {
147               for (int i = 0; i < index; ++i) {
148                       if (getResource(i).name().equals(name))
149                               return true;
150               }
151               return false;
152       }
153       eq BodyDecl.getChild(int i).resourcePreviouslyDeclared(String name) = false;
154       eq Program.getChild(int i).resourcePreviouslyDeclared(String name) = false;
155    
156       public void ResourceDeclaration.nameCheck() {
157               // Special name check for resource specification
158               if (resourcePreviouslyDeclared(name()))
159                       error("A resource with the name "+name()+
160                                       " has already been declared in this try statement.");
161    
162               // Do regular name check for variable declaration
163               super.nameCheck();
164       }
165    
166       // Definite Assignment
167       eq TryWithResources.isDAafter(Variable v) =
168               getBlock().isDAafter(v);
169    
170       // 16.2.2 7th bullet
171       eq TryWithResources.getResource(int index).isDAbefore(Variable v) =
172               index == 0 ? isDAbefore(v) : getResource(index - 1).isDAafter(v);
173       eq TryWithResources.getBlock().isDAbefore(Variable v) =
174               getNumResource() == 0 ? isDAbefore(v) :
175               getResource(getNumResource() - 1).isDAafter(v);
176    
177       /**
178        * Returns true if the try-with-resources statement can throw
179        * an exception of type (or a subtype of) catchType.
180        */
181       protected boolean TryWithResources.reachedException(TypeDecl catchType) {
182               boolean found = false;
183               // found is true if the exception type is caught by a catch clause
184               for(int i = 0; i < getNumCatchClause() && !found; i++)
185                       if(getCatchClause(i).handles(catchType))
186                               found = true;
187               // if an exception is thrown in the block and the exception is not caught and
188               // either there is no finally block or the finally block can complete normally
189               if(!found && (!hasFinally() || getFinally().canCompleteNormally()) )
190                       if(catchableException(catchType))
191                               return true;
192               // even if the exception is caught by the catch clauses they may 
193               // throw new exceptions
194               for(int i = 0; i < getNumCatchClause(); i++)
195                       if(getCatchClause(i).reachedException(catchType))
196                               return true;
197               return hasFinally() && getFinally().reachedException(catchType);
198       }
199    
200       /**
201        * True if the automatic closing of resources in this try-with-resources statement
202        * may throw an exception of type catchType.
203        */
204       syn boolean TryWithResources.resourceClosingException(TypeDecl catchType) {
205               for (ResourceDeclaration resource : getResourceList()) {
206                       MethodDecl close = lookupClose(resource);
207                       if (close == null) continue;
208                       for (Access exception : close.getExceptionList()) {
209                               TypeDecl exceptionType = exception.type();
210                               if (catchType.mayCatch(exception.type()))
211                                       return true;
212                       }
213               }
214               return false;
215       }
216    
217       /**
218        * True if the resource initialization of this try-with-resources statement
219        * may throw an exception of type catchType.
220        */
221       syn boolean TryWithResources.resourceInitializationException(TypeDecl catchType) {
222               for (ResourceDeclaration resource : getResourceList()) {
223                       if (resource.reachedException(catchType))
224                               return true;
225               }
226               return false;
227       }
228    
229       /**
230        * @see AST.TryStmt#catchableException(TypeDecl) TryStmt.catchableException(TypeDecl)
231        */ 
232       eq TryWithResources.catchableException(TypeDecl type) =
233               getBlock().reachedException(type) ||
234                       resourceClosingException(type) ||
235                       resourceInitializationException(type);
236    
237       eq ResourceModifiers.isFinal() = true;
238    }
239    
240    aspect PrettyPrint {
241       /**
242        * Pretty printing of try-with-resources
243        */
244       public void TryWithResources.toString(StringBuffer sb) {
245               sb.append(indent() + "try (");
246               for (ResourceDeclaration resource : getResourceList()) {
247                       sb.append(resource.toString());
248               }
249               sb.append(") ");
250               getBlock().toString(sb);
251               for (CatchClause cc : getCatchClauseList()) {
252                       sb.append(" ");
253                       cc.toString(sb);
254               }
255               if (hasFinally()) {
256                       sb.append(" finally ");
257                       getFinally().toString(sb);
258               }
259       }
260    
261    }
262