001    /*BEGIN_COPYRIGHT_BLOCK
002     *
003     * Copyright (c) 2001-2010, JavaPLT group at Rice University (drjava@rice.edu)
004     * All rights reserved.
005     * 
006     * Redistribution and use in source and binary forms, with or without
007     * modification, are permitted provided that the following conditions are met:
008     *    * Redistributions of source code must retain the above copyright
009     *      notice, this list of conditions and the following disclaimer.
010     *    * Redistributions in binary form must reproduce the above copyright
011     *      notice, this list of conditions and the following disclaimer in the
012     *      documentation and/or other materials provided with the distribution.
013     *    * Neither the names of DrJava, the JavaPLT group, Rice University, nor the
014     *      names of its contributors may be used to endorse or promote products
015     *      derived from this software without specific prior written permission.
016     * 
017     * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
018     * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
019     * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
020     * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
021     * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
022     * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
023     * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
024     * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
025     * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
026     * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
027     * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
028     *
029     * This software is Open Source Initiative approved Open Source Software.
030     * Open Source Initative Approved is a trademark of the Open Source Initiative.
031     * 
032     * This file is part of DrJava.  Download the current version of this project
033     * from http://www.drjava.org/ or http://sourceforge.net/projects/drjava/
034     * 
035     * END_COPYRIGHT_BLOCK*/
036    
037    package edu.rice.cs.javalanglevels;
038    
039    import edu.rice.cs.javalanglevels.tree.*;
040    import edu.rice.cs.javalanglevels.parser.JExprParser;
041    import java.util.*;
042    import java.io.File;
043    import edu.rice.cs.plt.reflect.JavaVersion;
044    import edu.rice.cs.plt.iter.*;
045    
046    import junit.framework.TestCase;
047    
048    /** We felt that this class should be named Bob because "Bob" was found to be very memorable.  It is a TypeChecker 
049      * for all code segments that rely on some enclosing Data and other context information.  Basically, Bob sits between
050      * the TypeChecker and all other more specific type checkers (such as BodyTypeChecker, ClassBodyTypeChecker, etc)
051      * where Bob maintains the context.
052      */
053    
054    public class Bob extends TypeChecker {
055      /** An incremental list of fields used because forward references among fields are not allowed.*/
056      protected LinkedList<VariableData> _vars;
057      
058      /**Stores what variable datas have been newly given a value in this scope.*/
059      protected LinkedList<VariableData> thingsThatHaveBeenAssigned;
060      
061      /**The context of this type checking--i.e. the data of the enclosing body.*/
062      protected Data _data;
063      
064      /**The list of SymbolDatas corresponding to exceptions thrown in this body. */
065      protected LinkedList<Pair<SymbolData, JExpression>> _thrown;
066    
067      /** Constructor for Bob.
068        * @param data  The data that represents the context.
069        * @param file  The file that corresponds to the source file
070        * @param packageName  The string representing the package name
071        * @param importedFiles  The list of file names that have been specifically imported
072        * @param importedPackages  The list of package names that have been specifically imported
073        * @param vars  The list of fields that have been assigned up to the point where Bob is called.
074        * @param thrown  The list of exceptions that the context is declared to throw
075        */
076      public Bob(Data data, File file, String packageName, LinkedList<String> importedFiles, 
077                 LinkedList<String> importedPackages, LinkedList<VariableData> vars, 
078                 LinkedList<Pair<SymbolData, JExpression>> thrown) {
079        super(file, packageName, importedFiles, importedPackages);
080        if (vars == null) throw new RuntimeException("Bob called with _vars = null!");
081        _data = data;
082        _vars = vars;
083    
084        thingsThatHaveBeenAssigned = new LinkedList<VariableData>();
085        _thrown = thrown;
086      }
087      
088      /** @return the enclosing data*/
089      protected Data _getData() { return _data; }
090      
091      /** @return true iff the first enclosing MethodData or SymbolData is a static method. */
092      protected boolean inStaticMethod() {
093        for (Data d = _data; d != null; d = d.getOuterData()) {
094          if (d instanceof MethodData) { return d.hasModifier("static"); }
095    //      else if (d instanceof SymbolData) { return false; }
096        }
097        return false;
098      }
099      
100      /** Return the symbol data corresponding to the lhs and the namePiece, if possible.
101        * Otherwise, return null.
102        * @param lhs  The left hand side of this complex reference, or null if this is a simple reference
103        * @param namePiece  The String right hand side of this reference
104        * @param jexpr  The JExpression corresponding to this class reference
105        */
106      protected SymbolData findClassReference(TypeData lhs, String namePiece, JExpression jexpr) {
107        SymbolData result;
108        if (lhs == null) {
109          // Do not give an error if the SymbolData could not be found.  This is done later.
110          result = getSymbolData(true, namePiece, _getData(), jexpr, false); 
111    
112        }
113        
114        else if (lhs instanceof PackageData) {
115          // Do not give an error if the SymbolData could not be found
116          result = getSymbolData(lhs.getName() + "." + namePiece, _getData(), jexpr, false); 
117        }
118    
119        else {
120          // Do not give error if it could not be found, but do give an error if the reference is ambiguous.
121          result = getSymbolData(true, namePiece, lhs.getSymbolData(), jexpr, false); 
122    
123          // Don't check for visibility here--check for it wherever this was called from.
124        }
125        
126         return result;
127    
128      }
129    
130    
131        /**
132       * TODO: Move this code to where it is needed?
133       * Do any extra processing of this MethodInvocation, based on what level it is found at.
134       * Here, check if the MethodData is declared to throw any exceptions, add them to the list of Exceptions
135       * @param md  The MethodData of the method being invoked
136       * @param jexpr  The jexpression corresponding to where this method is being invoked from.
137       */
138      protected void handleMethodInvocation(MethodData md, JExpression jexpr) {
139        String[] thrown = md.getThrown();
140        for (int i = 0; i<thrown.length; i++) {
141          _thrown.addLast(new Pair<SymbolData, JExpression>(getSymbolData(thrown[i], _data, jexpr), jexpr));
142        }
143      }
144      
145      /**
146       * Given a ParenthesizedExpressionList, extract its expression array and return
147       * an array with InstanceDatas for each type of the arguments.  Throw an error if a non-instance type is
148       * passed as an argument.
149       * @param pel  The ParenthesizedExpressionList the arguments are stored in.
150       * @return  The InstanceData[] corresponding to the types of the arguments
151       */
152      protected InstanceData[] getArgTypesForInvocation(ParenthesizedExpressionList pel) {
153        Expression[] exprs = pel.getExpressions();
154        InstanceData[] newArgs = new InstanceData[exprs.length];
155        TypeData[] args = new TypeData[exprs.length];
156        ExpressionTypeChecker etc = new ExpressionTypeChecker(_data, _file, _package, _importedFiles, _importedPackages,
157                                                              _vars, _thrown);
158        for (int i = 0; i < exprs.length; i++) {
159          args[i] = exprs[i].visit(etc);
160          if (args[i] == null || !assertFound(args[i], exprs[i])) {
161            return null;
162          }
163          if (!args[i].isInstanceType()) {
164            _addError("Cannot pass a class or interface name as a constructor argument.  " + 
165                      "Perhaps you meant to create an instance or use " + args[i].getName() + ".class", exprs[i]);
166          }
167          newArgs[i] = args[i].getInstanceData(); // getInstanceData() is used in place of a cast
168        }
169        thingsThatHaveBeenAssigned.addAll(etc.thingsThatHaveBeenAssigned);
170        return newArgs;
171      }
172      
173    /**
174     * Assumes type_result is a SymbolData.
175     * Makes sure that the initializedvariable declarator is correct--i.e. the types match.
176     * Also, add the Variable Data corresponding to this initializiation to the _vars list, so
177     * that it can be referenced within this scope.
178     * @param that  The InitializedVariableDeclarator being visited
179     * @param type_result  The TypeData (should be a SymbolData) corresponding to the type on the lhs of the assignment
180     * @param name_result  Not used.
181     * @param initializer_result  The type of what we are initializing the varaible with
182     */
183      public TypeData forInitializedVariableDeclaratorOnly(InitializedVariableDeclarator that, TypeData type_result, 
184                                                           TypeData name_result, TypeData initializer_result) {
185        if (initializer_result != null && assertFound(initializer_result, that.getInitializer())) {
186          if (!initializer_result.isInstanceType()) {
187            _addError("Field or variable " + that.getName().getText() + 
188                      " cannot be initialized with the class or interface name " + initializer_result.getName() + 
189                      ".  Perhaps you meant to create an instance or use " + initializer_result.getName() + ".class", that);
190          }
191          //we know type_result is always a SymbolData.
192          else if (!_isAssignableFrom(type_result.getSymbolData(), initializer_result.getSymbolData())) {
193            _addError("Type: \"" + type_result.getName() + "\" expected, instead found type: \"" + 
194                      initializer_result.getName() + "\".", that);
195          }
196        }
197        Word name = that.getName();
198        String text = that.getName().getText();
199        VariableData vd = _data.getVar(text);
200        if (vd == null) {
201          throw new RuntimeException("Internal Program Error: The field or variable " + text + 
202                                     " was not found in this block.  Please report this bug.");
203        }
204        _vars.addLast(vd);
205        return null;
206      }
207      
208      /*This is not supported at any Language Level.  It should have been caught during the first pass.*/  
209      public TypeData forInstanceInitializer(InstanceInitializer that) {
210        throw new RuntimeException("Internal Program Error: Instance Initializers are not supported." + 
211                                   "  This should have been caught before the Type Checker Pass.  Please report this bug.");
212      }
213    
214      /*This is not supported at any Language Level.  It should have been caught during the first pass.*/  
215      public TypeData forStaticInitializer(StaticInitializer that) {
216        throw new RuntimeException("Internal Program Error: Static Initializers are not supported." +
217                                   "  This should have been caught before the Type Checker Pass.  Please report this bug.");
218      }
219      
220      /*This is not supported at any Language Level.  It should have been caught during the first pass.*/  
221      public TypeData forLabeledStatement(LabeledStatement that) {
222        throw new RuntimeException("Internal Program Error: Labeled Statements are not supported." + 
223                                   "  This should have been caught before the Type Checker Pass.  Please report this bug.");
224      }
225      
226      /*
227       * Visit the expression with a new ExpressionTypeChecker, and return the result of that visitation.
228       * Keep track of what variables get values within the expression.
229       * @param that  The ExpressionStatement we are visiting.
230       * @return  The result of visiting the expression with the ExpressionTypeChecker.
231       */
232      public TypeData forExpressionStatement(ExpressionStatement that) {
233        ExpressionTypeChecker etc = new ExpressionTypeChecker(_data, _file, _package, _importedFiles, _importedPackages,
234                                                              _vars, _thrown);
235        final TypeData expression_result = that.getExpression().visit(etc);
236    
237        //do this so that we can keep track of anything that got assigned
238        thingsThatHaveBeenAssigned.addAll(etc.thingsThatHaveBeenAssigned);
239        
240        return expression_result;
241      }
242      
243      /**
244       * Visit the ThrowStatement's expression to determine what type of Exception is being thrown.
245       * Add the corresponding SymbolData to _thrown.
246       */
247      public TypeData forThrowStatement(ThrowStatement that) {
248        ExpressionTypeChecker etc = new ExpressionTypeChecker(_data, _file, _package, _importedFiles, _importedPackages,
249                                                              _vars, _thrown);
250        final TypeData thrown_result = that.getThrown().visit(etc);
251        thingsThatHaveBeenAssigned.addAll(etc.thingsThatHaveBeenAssigned);
252    
253        forThrowStatementOnly(that, thrown_result);
254        return SymbolData.EXCEPTION.getInstanceData();
255      }
256    
257    
258      /** Make sure that what is being thrown is an instantiation of a class, not a class name, and that
259        * it extends Throwable.  Otherwise, give an error.
260        * @param that  The throw statement we are visiting
261        * @param thrown_result  The TypeData result of visiting the throw statement.  It should be an InstanceData, 
262        *                       unless there is an error in the student's code.
263        */
264      public TypeData forThrowStatementOnly(ThrowStatement that, TypeData thrown_result) {
265        if (thrown_result == null || !assertFound(thrown_result, that.getThrown())) return null;
266      
267        // add the SymbolData even if we're throwing a SymbolData, not an InstanceData
268        _thrown.addLast(new Pair<SymbolData, JExpression>(thrown_result.getSymbolData(), that));
269          
270        //make sure they instantiated what is being thrown.
271        if (!thrown_result.isInstanceType()) {
272          _addError("You cannot throw a class or interface name.  Perhaps you mean to instantiate the exception class " + 
273                    thrown_result.getSymbolData().getName() + " that you are throwing", that);
274          thrown_result = thrown_result.getInstanceData();
275        }
276        
277    //    System.err.println("getSymbolData(\"java.lang.Throwable\", that, false, true) = " + 
278    //                       getSymbolData("java.lang.Throwable", that, false, true));
279        //make sure what is being thrown extends java.lang.Throwable.
280        if (!_isAssignableFrom(getSymbolData("java.lang.Throwable", that, false, true), thrown_result.getSymbolData())) {
281          _addError("You are attempting to throw " + thrown_result.getSymbolData().getName() + 
282                    ", which does not implement the Throwable interface", that);
283        }
284        return thrown_result;
285      }
286      
287      /*This is not supported at any Language Level.  It should have been caught during the first pass.*/  
288      public TypeData forSynchronizedStatement(SynchronizedStatement that) {
289        throw new RuntimeException("SynchronizedStatements are not supported.");
290      }
291    
292    
293      /* Visit the declarator of this formal parameter and return its result.
294       * @param that  The Formal Parameter we are visiting.
295       */
296      public TypeData forFormalParameter(FormalParameter that) {
297        final TypeData declarator_result = that.getDeclarator().visit(this);
298        return declarator_result;
299      }
300    
301      /** Visit each of the declarators of this declaration.
302        * @param that  The VariableDeclaration we are visiting.
303        */
304      public TypeData forVariableDeclaration(VariableDeclaration that) {
305        final TypeData mav_result = that.getMav().visit(this);
306        final TypeData[] declarators_result = makeArrayOfRetType(that.getDeclarators().length);
307        for (int i = 0; i < that.getDeclarators().length; i++) {
308          declarators_result[i] = that.getDeclarators()[i].visit(this);
309        }
310        return null;
311      }
312    
313      /** If this VariableDeclarator is uninitialized, make sure its type can be resolved and visit
314        * its name.
315        * @param that  The UninitializedVariableDeclarator we are visiting.
316        */
317      public TypeData forUninitializedVariableDeclarator(UninitializedVariableDeclarator that) {
318        final TypeData type_result = getSymbolData(that.getType().getName(), _data, that.getType());
319        final TypeData name_result = that.getName().visit(this);
320        return forUninitializedVariableDeclaratorOnly(that, type_result, name_result);
321      }
322      
323      /** If the VariableDeclarator is initialized, things get a little bit more complicated.
324        * Resolve the type and visit the name, like we do for the uninitilized case.
325        * Then, check to see if the initializer is an array initializer.  If so, delegate.
326        * Otherwise, just visit it with an ExpressionTypeChecker.
327        * @param that  The InitializedVariableDeclarator we are visiting.
328        */
329      public TypeData forInitializedVariableDeclarator(InitializedVariableDeclarator that) {
330        final SymbolData type_result = getSymbolData(that.getType().getName(), _data, that.getType());
331        final TypeData name_result = that.getName().visit(this); //we think this is always null
332    
333        TypeData initializer_result;
334        if (that.getInitializer() instanceof ArrayInitializer) {
335          initializer_result = forArrayInitializerHelper((ArrayInitializer) that.getInitializer(), type_result);
336        }
337        else {
338          ExpressionTypeChecker etc = new ExpressionTypeChecker(_data, _file, _package, _importedFiles, _importedPackages,
339                                                                _vars, _thrown);
340          initializer_result = that.getInitializer().visit(etc);
341          thingsThatHaveBeenAssigned.addAll(etc.thingsThatHaveBeenAssigned); //incorporate this list here
342        }
343        return forInitializedVariableDeclaratorOnly(that, type_result, name_result, initializer_result);
344      }
345    
346      /** A variable data can be assigned to if it is not final or if it does not have a value.
347        * (i.e. final variables that already have a value cannot be assigned to.  Everything else can be).
348        */
349      protected boolean canBeAssigned(VariableData vd) {
350        return !vd.isFinal() || !vd.hasValue();
351      }
352      
353      /** Makes sure that the specified type is an array type, and then
354        * examines the elements in the array initializer and makes sure each has a type assignable to
355        * the elementType of the specified array type.  Returns an instance data corresponding to the type of the array.
356        */
357      public TypeData forArrayInitializerHelper(ArrayInitializer that, SymbolData type) {
358        if (type == null) {return null;}
359        if (!(type instanceof ArrayData)) {_addError("You cannot initialize the non-array type " + type.getName() + 
360                                                     " with an array initializer", that); return type.getInstanceData();}
361        
362        SymbolData elementType = ((ArrayData) type).getElementType();
363        VariableInitializerI[] elements = that.getItems();
364        TypeData[] result = makeArrayOfRetType(elements.length);
365        
366        for (int i = 0; i<elements.length; i++) {
367          if (elements[i] instanceof ArrayInitializer) {
368              result[i] = forArrayInitializerHelper((ArrayInitializer) elements[i], elementType);
369          }
370          else {
371            ExpressionTypeChecker etc = new ExpressionTypeChecker(_data, _file, _package, _importedFiles, _importedPackages,
372                                                                  _vars, _thrown);
373            result[i] = elements[i].visit(etc);
374            
375            // Incorporate the things that were assigned in the expression here
376            thingsThatHaveBeenAssigned.addAll(etc.thingsThatHaveBeenAssigned); 
377    
378            if (result[i] != null) {
379              if (assertFound(result[i], (JExpression) that.getItems()[i])) {
380                if (!result[i].getSymbolData().isAssignableTo(elementType, LanguageLevelConverter.OPT.javaVersion())) {
381                  _addError("The elements of this initializer should have type " + elementType.getName() + " but element "
382                              + i + " has type " + result[i].getSymbolData().getName(), (JExpression) that.getItems()[i]);
383                }
384                else {
385                  assertInstanceType(result[i], "The elements of this initializer should all be instances," + 
386                                     " but you have specified the type name " + result[i].getName(), 
387                                     (JExpression) that.getItems()[i]);
388                }
389              }
390            }
391          }
392        }
393        return type.getInstanceData();
394      }
395     
396      /** Look up the SymbolData for this InnerClass within the enclosing data, check for cyclic inheritance,
397        * and then visit everything inside the inner class.
398        * @param that  The InnerClassDef we're visiting
399        */
400      public TypeData forInnerClassDef(InnerClassDef that) {
401        String className = that.getName().getText();
402        
403        // This works because className will never be a qualified name
404        SymbolData sd = _data.getInnerClassOrInterface(className);
405        
406        if (sd == null) throw new RuntimeException("SymbolData is null for class name = " + className);
407    
408        // Check for cyclic inheritance
409        if (checkForCyclicInheritance(sd, new LinkedList<SymbolData>(), that)) { return null; }
410        final TypeData mav_result = that.getMav().visit(this);
411        final TypeData name_result = that.getName().visit(this);
412        final TypeData[] typeParameters_result = makeArrayOfRetType(that.getTypeParameters().length);
413        for (int i = 0; i < that.getTypeParameters().length; i++) {
414          typeParameters_result[i] = that.getTypeParameters()[i].visit(this);
415        }
416        final TypeData superclass_result = that.getSuperclass().visit(this);
417        final TypeData[] interfaces_result = makeArrayOfRetType(that.getInterfaces().length);
418        for (int i = 0; i < that.getInterfaces().length; i++) {
419          interfaces_result[i] = that.getInterfaces()[i].visit(this);
420        }
421        final TypeData body_result = 
422          that.getBody().visit(new ClassBodyTypeChecker(sd, _file, _package, _importedFiles, _importedPackages, _vars, 
423                                                        _thrown));
424        return null;
425      }
426      
427      /** Look for the inner interface inside of the enclosing data.
428        * Then, visit everything that needs to be visited.
429        * @param that  The InnerInterfaceDef that is being visited.
430        */
431      public TypeData forInnerInterfaceDef(InnerInterfaceDef that) {
432        String className = that.getName().getText();
433        SymbolData sd = _data.getInnerClassOrInterface(className); // This works because className will never be a qualified name
434    //    if (sd == null) { System.out.println("I tried to look up " + className + " in " + _data.getName() + " but I got back null");}
435    
436        // Check for cyclic inheritance
437        if (checkForCyclicInheritance(sd, new LinkedList<SymbolData>(), that)) {
438          return null;
439        }
440        final TypeData mav_result = that.getMav().visit(this);
441        final TypeData name_result = that.getName().visit(this);
442        final TypeData[] typeParameters_result = makeArrayOfRetType(that.getTypeParameters().length);
443        for (int i = 0; i < that.getTypeParameters().length; i++) {
444          typeParameters_result[i] = that.getTypeParameters()[i].visit(this);
445        }
446        final TypeData[] interfaces_result = makeArrayOfRetType(that.getInterfaces().length);
447        for (int i = 0; i < that.getInterfaces().length; i++) {
448          interfaces_result[i] = that.getInterfaces()[i].visit(this);
449        }
450    
451        final TypeData body_result = 
452          that.getBody().visit(new InterfaceBodyTypeChecker(sd, _file, _package, _importedFiles, _importedPackages, _vars, 
453                                                            _thrown));
454        return null;
455    
456      }
457        
458      
459      /**
460       * Compare the two lists of variable datas, and if a data is in both lists, mark it as
461       * having been assigned.
462       * @param l1  One of the lists of variable datas
463       * @param l2  The other list of variable datas.
464       */
465      void reassignVariableDatas(LinkedList<VariableData> l1, LinkedList<VariableData> l2) {
466        for (int i = 0; i<l1.size(); i++) { 
467          if (l2.contains(l1.get(i))) {
468            l1.get(i).gotValue();
469            l1.get(i).gotValue();
470          }
471        }
472      }
473      
474      /** Compare a list of variable datas and a list of list of variable datas.
475        * If a variable data is in the list and in each list of the lists of lists, mark it as having been
476        * assigned.
477        * @param tryBlock  The list of variable datas.
478        * @param catchBlocks  The list of list of variable datas.
479        */
480      void reassignLotsaVariableDatas(LinkedList<VariableData> tryBlock, LinkedList<LinkedList<VariableData>> catchBlocks) {
481        for (int i = 0; i<tryBlock.size(); i++) {
482          boolean seenIt = true;
483          for (int j = 0; j<catchBlocks.size(); i++) {
484            if (!catchBlocks.get(j).contains(tryBlock.get(i))) {seenIt = false;}
485          }
486          if (seenIt) {        //find the variable data in vars and give it a value!
487            tryBlock.get(i).gotValue();
488          }
489        }
490      }
491      
492    
493      /** If an exception is thrown but not caught, throw the appropriate error, based on the JExpression.*/
494      public void handleUncheckedException(SymbolData sd, JExpression j) {
495        if (j instanceof MethodInvocation) {
496          _addError("The method " + ((MethodInvocation)j).getName().getText() + " is declared to throw the exception " + 
497                    sd.getName() + " which needs to be caught or declared to be thrown", j);
498          }
499          else if (j instanceof ThrowStatement) {
500            _addError("This statement throws the exception " + sd.getName() + 
501                      " which needs to be caught or declared to be thrown", j);
502          }
503          else if (j instanceof ClassInstantiation) {
504            _addError("The constructor for the class " + ((ClassInstantiation)j).getType().getName() + 
505                      " is declared to throw the exception " + sd.getName() +
506                      " which needs to be caught or declared to be thrown.", j);
507          }
508          else if (j instanceof SuperConstructorInvocation) {
509            _addError("The constructor of this class's super class could throw the exception " + sd.getName() + 
510                      ", so the enclosing constructor needs to be declared to throw it", j);
511          }
512          else if (j instanceof ThisConstructorInvocation) {
513            _addError("This constructor could throw the exception " + sd.getName() + 
514                      ", so this enclosing constructor needs to be declared to throw it", j);
515          }
516          
517          else if (j instanceof BracedBody) { //then this is because of an implicit super constructor reference.
518            _addError("There is an implicit call to the superclass's constructor here.  " + 
519                      "That constructor could throw the exception " + sd.getName() + 
520                      ", so the enclosing constructor needs to be declared to throw it", j);
521          }
522          
523          else {
524            throw new RuntimeException("Internal Program Error: Something besides a method invocation or throw statement" + 
525                                       " threw an exception.  Please report this bug.");
526          }
527      }
528      
529      /** Returns whether the sd is a checked exception, i.e. one that needs to be caught or declared to be thrown.
530        * This is defined as all subclasses of java.lang.Throwable except for subclasses of java.lang.RuntimeException
531        */
532      public boolean isCheckedException(SymbolData sd, JExpression that) {
533        return sd.isSubClassOf(getSymbolData("java.lang.Throwable", _data, that, false)) &&
534          ! sd.isSubClassOf(getSymbolData("java.lang.RuntimeException", _data, that, false)) &&
535          ! sd.isSubClassOf(getSymbolData("java.lang.Error", _data, that, false));
536      }
537      
538      /** Return true if the Exception is a checked exception yet is not caught or declared to be thrown, and false
539        * otherwise.  An exception is a checked if it does not extend either java.lang.RuntimeException or java.lang.Error,
540        * and is not declared to be thrown by the enclosing method.
541        * @param sd  The SymbolData of the Exception we are checking.
542        * @param that  The JExpression passed to getSymbolData for error purposes.
543        */
544      public boolean isUncaughtCheckedException(SymbolData sd, JExpression that) {
545        return isCheckedException(sd, that);
546      }
547      
548      /** Visit each of the items in the body and make sure that none throw uncaught exceptions */
549      public TypeData forBody(Body that) {
550        final TypeData[] items_result = makeArrayOfRetType(that.getStatements().length);
551        for (int i = 0; i < that.getStatements().length; i++) {
552          items_result[i] = that.getStatements()[i].visit(this);
553          //walk over what has been thrown and throw an error if it contains an unchecked exception
554          for (int j = 0; j < this._thrown.size(); j++) {
555            if (isUncaughtCheckedException(this._thrown.get(j).getFirst(), that)) {
556              handleUncheckedException(this._thrown.get(j).getFirst(), this._thrown.get(j).getSecond());
557            }
558          }
559        }
560    
561        return forBodyOnly(that, items_result);
562      }
563    
564      /** Delegate to forBody*/
565      public TypeData forBracedBody(BracedBody that) { return forBody(that); }
566      
567      /** Delegate to forBody*/
568      public TypeData forUnbracedBody(UnbracedBody that) { return forBody(that); }
569      
570      /** Test the methods defined in the enclosing class. */      
571      public static class BobTest extends TestCase {
572        
573        private Bob _b;
574        
575        private SymbolData _sd1;
576        private SymbolData _sd2;
577        private SymbolData _sd3;
578        private SymbolData _sd4;
579        private SymbolData _sd5;
580        private SymbolData _sd6;
581        private ModifiersAndVisibility _publicMav = 
582          new ModifiersAndVisibility(SourceInfo.NO_INFO, new String[] {"public"});
583        private ModifiersAndVisibility _protectedMav = 
584          new ModifiersAndVisibility(SourceInfo.NO_INFO, new String[] {"protected"});
585        private ModifiersAndVisibility _privateMav = 
586          new ModifiersAndVisibility(SourceInfo.NO_INFO, new String[] {"private"});
587        private ModifiersAndVisibility _packageMav = new ModifiersAndVisibility(SourceInfo.NO_INFO, new String[0]);
588        private ModifiersAndVisibility _abstractMav =
589          new ModifiersAndVisibility(SourceInfo.NO_INFO, new String[] {"abstract"});
590        private ModifiersAndVisibility _finalMav =
591          new ModifiersAndVisibility(SourceInfo.NO_INFO, new String[] {"final"});
592        private ModifiersAndVisibility _finalPublicMav =
593          new ModifiersAndVisibility(SourceInfo.NO_INFO, new String[] {"final", "public"});
594        private ModifiersAndVisibility _publicAbstractMav =
595          new ModifiersAndVisibility(SourceInfo.NO_INFO, new String[] {"public", "abstract"});
596        private ModifiersAndVisibility _publicStaticMav =
597          new ModifiersAndVisibility(SourceInfo.NO_INFO, new String[] {"public", "static"});
598        
599        
600        public BobTest() { this(""); }
601        public BobTest(String name) { super(name); }
602        
603        public void setUp() {
604          errors = new LinkedList<Pair<String, JExpressionIF>>();
605          LanguageLevelConverter.symbolTable.clear();
606          _b = new Bob(null, new File(""), "", new LinkedList<String>(), new LinkedList<String>(), 
607                       new LinkedList<VariableData>(), new LinkedList<Pair<SymbolData, JExpression>>());
608          LanguageLevelConverter.OPT = new Options(JavaVersion.JAVA_5, EmptyIterable.<File>make());
609            _b._importedPackages.addFirst("java.lang");
610          _sd1 = new SymbolData("i.like.monkey");
611          _sd2 = new SymbolData("i.like.giraffe");
612          _sd3 = new SymbolData("zebra");
613          _sd4 = new SymbolData("u.like.emu");
614          _sd5 = new SymbolData("");
615          _sd6 = new SymbolData("cebu");
616          _b._data = _sd1;
617        }
618        
619        public void testForInitializedVariableDeclarator() {
620          LanguageLevelVisitor llv =
621            new LanguageLevelVisitor(_b._file, 
622                                     _b._package, 
623                                     _b._importedFiles, 
624                                     _b._importedPackages, new LinkedList<String>(), 
625                                     new Hashtable<String, Pair<TypeDefBase, LanguageLevelVisitor>>(), 
626                                     new Hashtable<String, Pair<SourceInfo, LanguageLevelVisitor>>());
627          
628    //      LanguageLevelConverter.symbolTable.clear();
629          
630          SourceInfo si = SourceInfo.NO_INFO;
631          Expression e1 = new IntegerLiteral(si, 1);
632          Expression e2 = new IntegerLiteral(si, 2);
633          Expression e3 = new PlusExpression(si, new IntegerLiteral(si, 3), new CharLiteral(si, 'e'));
634          Expression e4 = new CharLiteral(si, 'c');
635    
636          ArrayType intArrayType = 
637            new ArrayType(SourceInfo.NO_INFO, "int[]", new PrimitiveType(SourceInfo.NO_INFO, "int"));
638    
639          //make sure it works -- most testing done in testArrayInitializerHelper
640          ArrayData intArray = new ArrayData(SymbolData.INT_TYPE, llv, si);
641          intArray.setIsContinuation(false);
642          symbolTable.remove("int[]");
643          symbolTable.put("int[]", intArray);
644          
645          _b._data.addVar(new VariableData("foozle", _publicMav, intArray, false, _b._data));
646          InitializedVariableDeclarator ivd = 
647            new InitializedVariableDeclarator(SourceInfo.NO_INFO, intArrayType,
648                                              new Word(SourceInfo.NO_INFO, "foozle"),
649                                              new ArrayInitializer(si, new VariableInitializerI[] {e1, e2, e3, e4}));
650    
651          assertEquals("Should return null", null, ivd.visit(_b));
652          assertEquals("There should be no errors", 0, errors.size());
653        }
654        
655        public void testForInitializedVariableDeclaratorOnly() {
656          SymbolData sd1 = SymbolData.DOUBLE_TYPE;
657          SymbolData sd2 = SymbolData.BOOLEAN_TYPE;
658          SymbolData sd3 = SymbolData.INT_TYPE;
659          _b._data.addVar(new VariableData("j", _publicMav, SymbolData.DOUBLE_TYPE, false, _b._data));
660          
661          InitializedVariableDeclarator ivd = 
662            new InitializedVariableDeclarator(SourceInfo.NO_INFO,
663                                              JExprParser.NO_TYPE,
664                                              new Word(SourceInfo.NO_INFO, "j"),
665                                              new DoubleLiteral(SourceInfo.NO_INFO, 1.0));
666          
667    
668          assertEquals("Two assignable types should not throw an error; return null.", null, 
669                       _b.forInitializedVariableDeclaratorOnly(ivd, sd1, sd1, sd3.getInstanceData()));
670          assertEquals("Should be no errors", 0, errors.size());
671          
672          assertEquals("Two unassignable types should throw an error; return null.", null, 
673                       _b.forInitializedVariableDeclaratorOnly(ivd, sd1, sd1, sd2.getInstanceData()));
674          assertEquals("Should now be one error", 1, errors.size());
675          assertEquals("Error message should be correct:", "Type: \"double\" expected, instead found type: \"boolean\".", 
676                       errors.getLast().getFirst());
677    
678          SymbolData foo = new SymbolData("Foo");
679          assertEquals("An initialization from a SymbolData should return null", null, 
680                       _b.forInitializedVariableDeclaratorOnly(ivd, sd1, null, foo));
681          assertEquals("There should be 2 errors", 2, errors.size());
682          assertEquals("Error message should be correct:", 
683                       "Field or variable j cannot be initialized with the class or interface name Foo.  " + 
684                       "Perhaps you meant to create an instance or use Foo.class", errors.getLast().getFirst());
685        }
686            
687        public void testForThrowStatementOnly() {
688          ThrowStatement s = new ThrowStatement(SourceInfo.NO_INFO, new NullLiteral(SourceInfo.NO_INFO));
689          SymbolData exception = _b.getSymbolData("java.lang.Throwable", s, false, true); 
690          InstanceData exceptionInstance = exception.getInstanceData();
691          
692          SymbolData notAnException = new SymbolData("bob");
693          InstanceData naeInstance = notAnException.getInstanceData();
694    
695          
696          assertEquals("When a SymbolData is the thrown type, return its InstanceData", exceptionInstance, 
697                       _b.forThrowStatementOnly(s, exception));
698          assertEquals("There should be 1 error", 1, errors.size());
699          
700          assertEquals("Error message should be correct", 
701                       "You cannot throw a class or interface name.  " + 
702                       "Perhaps you mean to instantiate the exception class java.lang.Throwable that you are throwing", 
703                       errors.get(0).getFirst());
704    
705          assertEquals("When a thrown type does not implement Throwable, return the type anyway", naeInstance, 
706                       _b.forThrowStatementOnly(s, naeInstance));
707          assertEquals("There should be 2 errors", 2, errors.size());
708          assertEquals("Error message should be correct", 
709                       "You are attempting to throw bob, which does not implement the Throwable interface", 
710                       errors.getLast().getFirst());
711        }
712          
713      
714        public void testForArrayInitializerHelper() {
715          LanguageLevelVisitor llv =
716            new LanguageLevelVisitor(_b._file, _b._package, _b._importedFiles, 
717                                     _b._importedPackages, new LinkedList<String>(), 
718                                     new Hashtable<String, Pair<TypeDefBase, LanguageLevelVisitor>>(), 
719                                     new Hashtable<String, Pair<SourceInfo, LanguageLevelVisitor>>());
720    //      LanguageLevelConverter.symbolTable = llv.symbolTable = _b.symbolTable;
721          
722          SourceInfo si = SourceInfo.NO_INFO;
723          
724          Expression e1 = new IntegerLiteral(si, 1);
725          Expression e2 = new IntegerLiteral(si, 2);
726          Expression e3 = new PlusExpression(si, new IntegerLiteral(si, 3), new CharLiteral(si, 'e'));
727          Expression e4 = new CharLiteral(si, 'c');
728          Expression e5 = new DoubleLiteral(si, 5.8);
729          Expression e6 = new SimpleNameReference(SourceInfo.NO_INFO, new Word(SourceInfo.NO_INFO, "int"));
730    
731          ArrayInitializer a1 = new ArrayInitializer(si, new VariableInitializerI[] {e1, e3, e4});
732          ArrayInitializer a2 = new ArrayInitializer(si, new VariableInitializerI[] {e2, e3, e1});
733          
734          Expression nl = new NullLiteral(si);
735    
736          //it works for a one dimensional array
737          ArrayData intArray = new ArrayData(SymbolData.INT_TYPE, llv, si);
738          intArray.setIsContinuation(false);
739          symbolTable.remove("int[]");
740          symbolTable.put("int[]", intArray);
741          
742          ArrayInitializer ia = new ArrayInitializer(si, new VariableInitializerI[] {e1, e2, e3, e4});
743          assertEquals("Should return instance of int[]", intArray.getInstanceData(), 
744                       _b.forArrayInitializerHelper(ia, intArray));
745          assertEquals("There should be no errors", 0, errors.size());
746          
747          //it works for a 2 dimensional array
748          ArrayData intArray2 = new ArrayData(intArray, llv, si);
749          intArray2.setIsContinuation(false);
750          symbolTable.put("int[][]", intArray2);
751          
752          ia = new ArrayInitializer(si, new VariableInitializerI[]{a1, a2});
753          assertEquals("Should return instance of int[][]", intArray2.getInstanceData(), 
754                       _b.forArrayInitializerHelper(ia, intArray2));
755          assertEquals("There should be no errors", 0, errors.size());
756          
757          //it works for a 2 dimensional array with null as its elements
758          ia = new ArrayInitializer(si, new VariableInitializerI[] {nl, nl});
759          assertEquals("Should return instance of int[][]", intArray2.getInstanceData(), 
760                       _b.forArrayInitializerHelper(ia, intArray2));
761          
762          //throw an error if the type passed to the helper is not an array data
763          assertEquals("Should return double", SymbolData.DOUBLE_TYPE.getInstanceData(), 
764                       _b.forArrayInitializerHelper(ia, SymbolData.DOUBLE_TYPE));
765          assertEquals("There should be one error message", 1, errors.size());
766          assertEquals("The error message should be correct", 
767                       "You cannot initialize the non-array type double with an array initializer", 
768                       errors.getLast().getFirst());
769    
770          //throw an error if the type of one of the elements doesn't match
771          ia = new ArrayInitializer(si, new VariableInitializerI[] {e1, e2, e5, e4});
772          assertEquals("Should return instance of int[]", intArray.getInstanceData(), 
773                       _b.forArrayInitializerHelper(ia, intArray));
774          assertEquals("There should be two error messages", 2, errors.size());
775          assertEquals("The error message should be correct", 
776                       "The elements of this initializer should have type int but element 2 has type double", 
777                       errors.getLast().getFirst());
778    
779          //throw an error if null in 1 dimensional int array
780          ia = new ArrayInitializer(si, new VariableInitializerI[] {nl, nl});
781          assertEquals("Should return instance of int[]", intArray.getInstanceData(),
782                       _b.forArrayInitializerHelper(ia, intArray));
783          assertEquals("There should be four error messages", 4, errors.size());
784          assertEquals("The error message should be correct", 
785                       "The elements of this initializer should have type int but element 0 has type null", 
786                       errors.get(2).getFirst());
787          assertEquals("The error message should be correct", 
788                       "The elements of this initializer should have type int but element 1 has type null", 
789                       errors.get(3).getFirst());
790          
791          //should throw error if type name is passed instead of instance
792          ia = new ArrayInitializer(si, new VariableInitializerI[] {e1, e2, e3, e4, e6});
793          assertEquals("Should return instance of int[]", intArray.getInstanceData(), 
794                       _b.forArrayInitializerHelper(ia, intArray));
795          assertEquals("Should now be 5 error messages", 5, errors.size());
796          assertEquals("Error message should be correct", 
797                       "The elements of this initializer should all be instances, but you have specified the type name" + 
798                       " int.  Perhaps you meant to create a new instance of int",
799                       errors.getLast().getFirst());
800        }
801    
802        public void testFindClassReference() {
803          SymbolData string = new SymbolData("java.lang.String");
804          string.setIsContinuation(false);
805          string.setPackage("java.lang");
806          symbolTable.remove("java.lang.String");
807          symbolTable.put("java.lang.String", string);
808          
809          //if lhs is null, just look up SymbolData
810          assertEquals("Should return string", string,
811                       _b.findClassReference(null, "java.lang.String", new NullLiteral(SourceInfo.NO_INFO)));
812          assertEquals("Should not be an error", 0, errors.size());
813          
814          //if SymbolData cannot be found, do not add error--just return null
815          assertEquals("Should return null", null, 
816                       _b.findClassReference(null, "non-existant", new NullLiteral(SourceInfo.NO_INFO)));
817          assertEquals("Should be no errors", 0, errors.size());
818          
819          //if LHS is package data, try to look up fully qualified name
820          assertEquals("Should return string", string,
821                       _b.findClassReference(new PackageData("java.lang"), "String", new NullLiteral(SourceInfo.NO_INFO)));
822          assertEquals("Should not be an error", 0, errors.size());
823          
824          //if symbol data cannot be found, do not give error
825          assertEquals("Should return null", null, 
826                       _b.findClassReference(new PackageData("nonsense"), "non-existant", 
827                                             new NullLiteral(SourceInfo.NO_INFO)));
828          assertEquals("Should be no errors", 0, errors.size());
829          
830          //otherwise, try to look up symbolData from context of lhs
831          SymbolData inner = new SymbolData("java.lang.String$Inner");
832          inner.setIsContinuation(false);
833          inner.setPackage("java.lang");
834          inner.setOuterData(string);
835          string.addInnerClass(inner);
836          assertEquals("Should return inner", inner,
837                       _b.findClassReference(string, "Inner", new NullLiteral(SourceInfo.NO_INFO)));
838          assertEquals("Should be no errors", 0, errors.size());
839          
840          //do not give error if it could not be found
841          assertEquals("Should return null", null, 
842                       _b.findClassReference(string, "non-existant", new NullLiteral(SourceInfo.NO_INFO)));
843          assertEquals("Should be no errors", 0, errors.size());
844        }
845      }
846    }