    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    }