001    /* Copyright 2000, 2001, Compaq Computer Corporation */
002    
003    package escjava.reader;
004    
005    import javafe.reader.CachedReader;
006    import javafe.reader.Reader;
007    import javafe.ast.CompilationUnit;
008    import javafe.genericfile.*;
009    import javafe.util.ErrorSet;
010    import javafe.util.Location;
011    import javafe.ast.Modifiers;
012    import javafe.tc.TypeSig;
013    import javafe.tc.OutsideEnv;
014    import javafe.ast.*;
015    import escjava.ast.*;
016    import escjava.AnnotationHandler;
017    import escjava.RefinementSequence;
018    
019    import java.util.*;
020    
021    /**
022     * RefinementCachedReader caches compilation units that have been read,
023     * as does its super class.  However, before doing so, it retrieves
024     * all files of a refinement sequence, combines them and caches the
025     * combined result against all of the file names.  Thus if any file in
026     * the refinement sequence is read again, the refinement combination will
027     * be produced.
028     *
029     * <p> Reads from GenericFiles with null canonicalIDs are not cached.
030     */
031    
032    public class RefinementCachedReader extends CachedReader
033    {
034        /***************************************************
035         *                                                 *
036         * Creation:                                       *
037         *                                                 *
038         **************************************************/
039    
040        /**
041         * The underlying Reader whose results we are caching.
042         */
043        //@ invariant underlyingReader != null;
044    
045        protected /*@ non_null @*/ AnnotationHandler annotationHandler = new AnnotationHandler();
046    
047        /**
048         * Creating a cached version of a Reader:
049         */
050        //@ requires reader != null;
051        public RefinementCachedReader(Reader reader) {
052            super(reader);
053    
054            //+@ set cache.keyType = \type(String);
055            //+@ set cache.elementType = \type(Object);
056        }
057    
058    
059        /***************************************************
060         *                                                 *
061         * The Cache itself:                               *
062         *                                                 *
063         **************************************************/
064    
065            // Inherited from the super class
066    
067    
068        /***************************************************
069         *                                                 *
070         * Caching methods:                                *
071         *                                                 *
072         **************************************************/
073    
074        public CompilationUnit isAlreadyRead(GenericFile target) {
075            return (CompilationUnit)get(target);
076        }
077    
078        /***************************************************
079         *                                                 *
080         * Reading:                                        *
081         *                                                 *
082         **************************************************/
083    
084        /**
085         * Attempt to read and parse a CompilationUnit from target.
086         * Any errors encountered are reported via javafe.util.ErrorSet.
087         * Null is returned iff an error was encountered.<p>
088         *
089         *
090         * By default, we attempt to read only a spec (e.g., specOnly is set
091         * in the resulting CompilationUnit) to save time.  If avoidSpec is
092         * true, we attempt to return a non-spec, although this may not
093         * always be possible.<p>
094         *
095         *
096         * The results of this function (including null results, but not
097         * the action of reporting error messages) are cached.
098         * Only the value of avoidSpec used the first time a given file is
099         * read is used.  This may result in a spec being returned
100         * unnecessarily when avoidSpec is true.<p>
101         *
102         * Target must be non-null.<p>
103         */
104        public CompilationUnit read(GenericFile target, boolean avoidSpec) {
105            // Note - reading has the side effect of caching
106            CompilationUnit cu = (CompilationUnit)get(target);
107            if (cu != null) {
108                cu.duplicate = true;
109                return cu;
110    /*
111    Don't complain, but don't do it twice either. ???
112                CompilationUnit cu = (CompilationUnit)get(target);
113                if (cu != null) {
114                    // If we are processing a package, we do not want to complain
115                    // about multiple files from a refinement sequence.
116                    Name n = cu.pkgName;
117                    if (n != null && escjava.Main.options().
118                            packagesToProcess.contains(n.printName()))
119                        return null;
120                }
121                ErrorSet.caution("Duplicate command-line file " +
122                                    "(or part of a refinement sequence): " +
123                                    target.getHumanName());
124    */
125                
126                //return null;
127            }
128    
129            // not cached - read and do refinement combination
130            refinementSequence = null;
131            cu = super.read(target,avoidSpec);
132            if (cu == null) return null;
133            CompilationUnit result = readRefinements(cu,avoidSpec);
134            if (result == null) {
135                // Some error occurred (already reported).  In particular, this
136                // null return can happen if the type declared in this file has
137                // already been loaded by (declared in) another file.
138                return result;
139            }
140    
141            // Do anything to pragmas that must be done before type signatures
142            // are created - includes, for example, handling model imports.
143            if (result != null) annotationHandler.handlePragmas(result);
144    
145            // Put the composite CU in the cache for all elements of the RS.
146            Iterator i = refinementSequence.iterator();
147            while (i.hasNext()) {
148                CompilationUnit rcu = (CompilationUnit)i.next();
149                put(rcu.sourceFile(),result);
150            }
151            return result;
152        }
153    
154        protected ArrayList refinementSequence;
155    
156        public CompilationUnit readRefinements(/*@ non_null @*/ CompilationUnit cu, boolean avoidSpec) {
157         
158                // Get and parse the package name
159                Name pkgName = cu.pkgName;
160                String pkg = pkgName == null ? "" : pkgName.printName();
161                String[] pkgStrings = pkgName == null ? new String[0] : 
162                                            pkgName.toStrings();
163    
164    
165                // Look through all the types in the compilation unit and
166                // find the public one.  That is the one whose name should
167                // be used to find the refinement files.
168                TypeDeclVec types = cu.elems;
169                Identifier type = null;
170                for (int i=0; i<types.size(); ++i) {
171                    TypeDecl td = types.elementAt(i);
172                    if (Modifiers.isPublic(td.modifiers)) {
173                        type = td.id;
174                        break;
175                    }
176                }
177    
178                if (type == null) {
179                    String s = cu.sourceFile().getLocalName();
180                    int p = s.indexOf('.');
181                    type = Identifier.intern(s.substring(0,p));
182                }
183    
184                // Check one of the ids to see if the type is already loaded.
185                if (types.size() != 0) {
186                    String typeToCheck;
187                    if (type != null) typeToCheck = type.toString();
188                    else typeToCheck = types.elementAt(0).id.toString();
189                    TypeSig sig = TypeSig.lookup(pkgStrings,typeToCheck);
190                    if (sig != null && sig.isPreloaded()) {
191                        CompilationUnit pcu = sig.getCompilationUnit();
192                        ErrorSet.caution("Type " + pkg + 
193                            (pkgName==null?"":".") + typeToCheck + 
194                            " in " + cu.sourceFile().getHumanName() + 
195                            " is already loaded from " + 
196                            pcu.sourceFile().getHumanName());
197                        return null;
198                    }
199                }
200    
201                // See if there is a java file for this type.
202                // Note that if there is no public type, then type == null,
203                // and we don't look for a java file.
204                GenericFile javafile = null;
205                if (type == null) {
206                    // No public type declaration
207                    // So there are no other files to be found
208                    // Have to do the refinement processing, because that makes
209                    // a (singleton) composite set of pragma modifiers
210    
211                    String s = cu.sourceFile().getLocalName();
212                    if (s.endsWith(".java")) javafile = cu.sourceFile();
213                    
214                } else {
215                    javafile = ((EscTypeReader)OutsideEnv.reader).
216                            findSrcFile(pkgStrings,type.toString()+".java");
217                    if (javafile == null &&
218                            cu.sourceFile().getLocalName().endsWith(".java")) {
219                        javafile = cu.sourceFile();
220                        ErrorSet.caution(Location.createWholeFileLoc(javafile),
221                            "Using given file as the .java file, even though it is not the java file for " + pkg + (pkgName==null?"":".") + type + " on the classpath");
222                    }
223    
224                }
225    
226                // Now find the refinement sequence belonging to the given type.
227                // If there is none, or if type is null, then a refinement sequence
228                // consisting of just the one compilation unit cu is returned.
229                // Note that this parses each of the files in the RS.
230                // Note also that 'cu' need not be in its own RS if it isn't,
231                // then it is not part of the list returned.
232                refinementSequence = getRefinementSequence(pkgStrings, type, cu, avoidSpec);
233                    // Error occurred (already reported) such that we don't
234                    // want to add a new compilation unit to the environment
235                if (refinementSequence == null) return null;
236    
237                if (javafe.util.Info.on) {
238                    java.util.Iterator i = refinementSequence.iterator();
239                    System.out.print("Refinement Sequence: [");
240                    while (i.hasNext()) {
241                            System.out.print(" "+ ((CompilationUnit)i.next()).
242                                            sourceFile().getHumanName());
243                    }
244                    System.out.println(" ]");
245                }
246    
247                // Now find the compilation unit for the java file.  If it is
248                // already in the RS or is the same as cu, we don't read it again.  
249                CompilationUnit javacu = null;
250                if (javafile != null) {
251                    if (javafile.getCanonicalID().equals(cu.sourceFile().getCanonicalID())) javacu = cu;
252                    else for (int i=0; javacu == null && i<refinementSequence.size(); ++i) {
253                        CompilationUnit rcu = (CompilationUnit)refinementSequence.get(i);
254                        if (rcu.sourceFile().getCanonicalID().equals(javafile.getCanonicalID()))
255                                javacu = rcu;
256                    }
257                }
258                //System.out.println("HAVE " + cu.sourceFile().getHumanName());
259                if (javacu == null) {
260                    // We don't already have a CU for the java file (that is, it
261                    // is not cu or in the RS) so we need to parse the source or
262                    // binary file.
263    
264                    // Note - if we are not using any implementation or 
265                    // specs from the source file, why not just read the 
266                    // binary if it is available?   FIXME
267                    if (javafile != null) {
268                        // We have a .java source file so read from it.
269                        javafe.util.Info.out("Reading source file "
270                            + javafile.getHumanName());
271                        ErrorSet.caution("The file " + javafile.getHumanName() + 
272                            " is not in the refinement sequence that begins with " +
273                            cu.sourceFile().getHumanName() + 
274                            "; it is used to generate a class signature, but no refinements within it are used.");
275                        javacu = underlyingReader.read(javafile, false);
276                            // The false above means only read a signature and not
277                            // the implementation or the annotations.
278                    } else {
279                        javacu = getCombinedBinaries(pkgName,pkgStrings,refinementSequence);
280                    }
281                }
282    
283    // FIXME - really want a routine that reads binary if up to date otherwise 
284    // source, simply to get signature.  Read java with bodies if it is one of the
285    // files to be checked.  The above should be able to be greatly improved!!!!
286         
287                CompilationUnit newcu = new RefinementSequence(refinementSequence,
288                                                    javacu,annotationHandler);
289    
290                
291                javafe.util.Info.out("Constructed refinement sequence");
292                return newcu;
293            }
294    
295        CompilationUnit getCombinedBinaries(/*null*/ Name pkgName, 
296                                            /*@ non_null @*/ String[] pkg, 
297                                            /*@ non_null @*/ ArrayList rs) 
298        {
299                CompilationUnit combination = null;
300                java.util.List failures = new java.util.LinkedList();
301                Iterator i = rs.iterator();
302                while (i.hasNext()) {
303                    CompilationUnit cu = (CompilationUnit)i.next();
304                    TypeDeclVec tdv = cu.elems;
305                    for (int j=0; j<tdv.size(); ++j) {
306                        TypeDecl td = tdv.elementAt(j);
307                        Identifier id = td.id;
308                        boolean found = false;
309                        if (combination != null) {
310                            for (int k = combination.elems.size()-1; k>=0; --k) {
311                                if (combination.elems.elementAt(k).id == id) {
312                                    found = true;
313                                    break;
314                                }
315                            }
316                        }
317                        if (!found) {
318                            GenericFile javafile = ((EscTypeReader)OutsideEnv.reader).
319                                findBinFile(pkg,id.toString()+".class");
320                            if (javafile != null) {
321                                javafe.util.Info.out("Reading class file "
322                                    + javafile.getHumanName());
323                                CompilationUnit
324                                    javacu = ((EscTypeReader)OutsideEnv.reader).
325                                        binaryReader.read(javafile, false); 
326                                if (combination == null)
327                                    combination = javacu;
328                                else {
329                                    TypeDeclVec ntdv = javacu.elems;
330                                    for (int n=0; n<ntdv.size(); ++n) {
331                                        combination.elems.addElement(ntdv.elementAt(n));
332                                    }
333                                }
334                            } else {
335                                    failures.add(
336                                            (pkgName==null? id.toString() :
337                                                pkgName.printName() + "." + id));
338                            }
339                        }
340                    }
341                }
342                if (combination != null && failures.size() != 0) {
343                    // FIXME - should marak the source location for these
344                    String s = "Failed to find some but not all binary files: ";
345                    Iterator ii = failures.iterator();
346                    while (ii.hasNext()) s += ii.next();
347                    ErrorSet.error(s);
348                }
349                return combination;
350            }
351    
352    
353            // result is a list of CompilationUnits
354            // result will contain something, perhaps just the given cu
355            //@ ensures \result != null;
356            ArrayList getRefinementSequence(/*@ non_null @*/ String[] pkgStrings,
357                                            Identifier type, 
358                                            /*@ non_null @*/ CompilationUnit cu, 
359                                            boolean avoidSpec) {
360                ArrayList refinements = new ArrayList();
361                GenericFile mrcufile;
362                GenericFile gf = cu.sourceFile();
363                String gfid = gf.getCanonicalID();
364                if (type == null) {
365                    mrcufile = gf;
366                } else {
367                    mrcufile =
368                        ((EscTypeReader)OutsideEnv.reader).findFirst(
369                                pkgStrings,type.toString());
370                }
371                javafe.util.Info.out( mrcufile==null ? "No MRCU found" :
372                            "Found MRCU " + mrcufile);
373                // If no MRCU is found in the sourcepath, then we presume that
374                // the file on the command line is that.
375                GenericFile gfile = (mrcufile == null) ? gf : mrcufile ;
376                CompilationUnit ccu;
377                boolean foundCommandLineFileInRS = false;
378                while(gfile != null) {
379                    if (gfile.getCanonicalID().equals(gfid)) {
380                        // Avoid parsing a file twice
381                        ccu = cu;
382                        foundCommandLineFileInRS = true;
383                    } else {
384                        ccu = underlyingReader.read(gfile,avoidSpec);
385                    }
386                    annotationHandler.parseAllRoutineSpecs(ccu);
387                    refinements.add(ccu);
388                    gfile = findRefined(pkgStrings,ccu);
389                    if (gfile != null) {
390                      if (!gfile.getLocalName().startsWith(type.toString() + ".")){
391                            ErrorSet.caution("The refinement file " + 
392                                gfile.getHumanName() +
393                                " in the sequence beginning with " + 
394                                mrcufile.getHumanName() +
395                                " has a prefix that does not match the type name "
396                                + type);
397                      }
398                      for (int i=0; i<refinements.size(); ++i) {
399                        if ( ((CompilationUnit)refinements.get(i)).sourceFile().
400                            getCanonicalID().equals( gfile.getCanonicalID() )) {
401                            ErrorSet.error(gfile.getHumanName() + 
402                                    " is circularly referenced in a refinement sequence");
403                            gfile = null;
404                            break;
405                        }
406                      }
407                    }
408                }
409                if (!foundCommandLineFileInRS) {
410                    String pkg = cu.pkgName == null ? "" : 
411                                    cu.pkgName.printName() + ".";
412                    if (refinements.size() == 0) {
413                        // If no refinement sequence was found, we simply use the
414                        // file on the command line, even if it is not on the
415                        // classpath.
416                        refinements.add(cu);
417                    } else {
418                        StringBuffer err = new StringBuffer(
419                            "The command-line argument " 
420                            + cu.sourceFile().getHumanName() 
421                            + " was not in the refinement sequence for type " 
422                            + pkg + type.toString() + ":");
423                        for (int k = 0; k<refinements.size(); ++k) {
424                            err.append(" ");
425                            err.append(((CompilationUnit)refinements.get(k)).
426                                            sourceFile().getHumanName());
427                        }
428                        // If the command-line file is not in the refinement
429                        //  sequence, we use the refinement sequence, since,
430                        //  if the type was referenced from another class it
431                        //  is the refinement sequence that would be found.
432                        //
433                        ErrorSet.error(err.toString());
434                    }
435                }
436                javafe.util.Info.out("Found refinement sequence files");
437                return refinements;
438            }
439    
440        public static GenericFile findRefined(/*@ non_null @*/ String[] pkgStrings, 
441                                              /*@ non_null @*/ CompilationUnit cu) 
442        {
443                LexicalPragmaVec v = cu.lexicalPragmas;
444                for (int i=0; i<v.size(); ++i) {
445                    if (v.elementAt(i) instanceof RefinePragma) {
446                        RefinePragma rp = (RefinePragma)v.elementAt(i);
447                        String filename = rp.filename;
448                            // FIXME - what if we are refining a class file ???
449                        GenericFile gf = ((EscTypeReader)OutsideEnv.reader).findSrcFile(pkgStrings,filename);
450                        if (gf == null) ErrorSet.error(rp.loc,
451                            "Could not find file referenced in refine annotation: " + filename);
452                        return gf;
453         
454        // FIXME - hsould be able to have refined files that are not in regular files as well
455                    }
456                }
457                return null;
458            }
459    
460    }