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(/*@non_null*/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 }