001: /**************************************************************************/
002: /* N I C E */
003: /* A high-level object-oriented research language */
004: /* (c) Daniel Bonniot 2000 */
005: /* */
006: /* This program is free software; you can redistribute it and/or modify */
007: /* it under the terms of the GNU General Public License as published by */
008: /* the Free Software Foundation; either version 2 of the License, or */
009: /* (at your option) any later version. */
010: /* */
011: /**************************************************************************/package bossa.modules;
012:
013: import bossa.util.*;
014: import bossa.syntax.*;
015: import gnu.bytecode.*;
016: import gnu.expr.*;
017: import nice.tools.code.Import;
018: import nice.tools.typing.PrimitiveType;
019:
020: import java.util.*;
021: import java.util.jar.*;
022: import java.io.*;
023:
024: import bossa.util.Location;
025: import gnu.expr.ClassExp;
026:
027: /**
028: A Nice package.
029:
030: @version $Date: 2005/03/24 14:47:07 $
031: @author Daniel Bonniot (bonniot@users.sourceforge.net)
032: */
033: public class Package implements mlsub.compilation.Module, Located {
034: /****************************************************************
035: * Loading
036: ****************************************************************/
037:
038: public static Package make(String name, Compilation compilation,
039: boolean isRoot) {
040: return make(new LocatedString(name, Location.option),
041: compilation, isRoot);
042: }
043:
044: public static Package make(LocatedString lname,
045: Compilation compilation, boolean isRoot) {
046: String name = lname.toString();
047:
048: Package res = (Package) compilation.packages.get(name);
049: if (res != null)
050: return res;
051:
052: return new Package(lname, compilation, isRoot);
053: }
054:
055: /****************************************************************
056: * Single Constructor
057: ****************************************************************/
058:
059: private Package(LocatedString name, Compilation compilation,
060: boolean isRoot) {
061: this .name = name;
062: this .compilation = compilation;
063: this .isRoot = isRoot;
064:
065: compilation.packages.put(name.toString(), this );
066:
067: packageScope = new nice.tools.visibility.Scope(getName(), null);
068:
069: source = compilation.locator.find(this );
070: if (source == null)
071: User.error(name, "Could not find package " + name);
072:
073: boolean shouldReload = compilation.recompileAll || isRoot
074: && compilation.recompileCommandLine;
075:
076: loadImports(shouldReload);
077:
078: prepareCodeGeneration();
079:
080: read(shouldReload);
081: }
082:
083: private void loadImports(boolean shouldReload) {
084: Set opens = new TreeSet();
085: opens.add("java.lang");
086: opens.add("java.util");
087:
088: imports = source.getImports(opens, shouldReload);
089:
090: if (!name.toString().equals("nice.lang")
091: && !Debug.ignorePrelude)
092: imports.add(new LocatedString("nice.lang",
093: bossa.util.Location.nowhere()));
094:
095: setOpens(opens);
096:
097: List p = this .getImports();
098: for (Iterator i = p.iterator(); i.hasNext();) {
099: Package imported = (Package) i.next();
100: source.someImportModified(imported.lastModification());
101: if (imported != this )
102: packageScope.addImplicitOpen(imported.packageScope);
103: }
104:
105: packageScope.addImplicitOpen(compilation.javaScope);
106: }
107:
108: /**
109: @param shouldReload reload if the source if available.
110: **/
111: private void read(boolean shouldReload) {
112: bossa.syntax.Node.setPackage(this );
113:
114: compilation.progress(this , "parsing");
115:
116: definitions = new ArrayList();
117: source.getDefinitions(definitions, shouldReload);
118: this .ast = bossa.syntax.dispatch.createAST(this , definitions);
119: definitions = null;
120: compilation.addNumberOfDeclarations(ast.numberOfDeclarations());
121:
122: if (compiling())
123: // Inform compilation that at least one package is going to generate code
124: compilation.recompilationNeeded = true;
125: }
126:
127: void setOpens(Set opens) {
128: // when we import a Nice package, we also open it.
129: for (Iterator i = imports.iterator(); i.hasNext();)
130: opens.add(((LocatedString) i.next()).toString());
131:
132: int len = opens.size();
133: this .opens = (String[]) opens.toArray(new String[len + 1]);
134: // We must guarantee that this package is the first element of 'open'.
135: this .opens[len] = this .opens[0];
136: this .opens[0] = this .name.toString();
137: }
138:
139: public long lastModification() {
140: return source.lastModification;
141: }
142:
143: private void readAlternatives() {
144: for (Method method = source.getBytecode().getMethods(); method != null; method = method
145: .getNext())
146: bossa.syntax.dispatch.readImportedAlternative(source
147: .getBytecode(), method, location(), compiledModule);
148: }
149:
150: /****************************************************************
151: * Package dependencies
152: ****************************************************************/
153:
154: public List /* of Package */getRequirements() {
155: return getImports();
156: }
157:
158: public String[] listImplicitPackages() {
159: return opens;
160: }
161:
162: /** List of the packages implicitely opened, a la 'import pkg.*'.
163: The first element must be this package itself.
164: */
165: private String[] opens;
166: private List /* of LocatedString */imports;
167: private List /* of Package */importedPackages;
168:
169: private List getImports() {
170: if (importedPackages == null)
171: computeImportedPackages();
172:
173: return importedPackages;
174: }
175:
176: private void computeImportedPackages() {
177: importedPackages = new ArrayList(imports.size());
178:
179: for (Iterator i = imports.iterator(); i.hasNext();) {
180: LocatedString s = (LocatedString) i.next();
181: Package p = make(s, compilation, false);
182: if (!importedPackages.contains(p))
183: importedPackages.add(p);
184: }
185: }
186:
187: /****************************************************************
188: * Progress
189: ****************************************************************/
190:
191: /** The average contribution to compile time of the different phases.
192: The sum should be 1.0
193: */
194: private static float PROGRESS_SCOPE = 0.04f, PROGRESS_LOAD = 0.04f,
195: PROGRESS_TYPED_RESOLVE = 0.04f,
196: PROGRESS_LOCAL_RESOLVE = 0.04f, PROGRESS_TYPECHECK = 0.50f,
197: PROGRESS_GENERATE_CODE = 0.10f,
198: PROGRESS_SAVE_INTERFACE = 0.04f, PROGRESS_LINK = 0.20f;
199:
200: void addProgress(float weight) {
201: compilation.addProgress(weight * ast.numberOfDeclarations());
202: }
203:
204: void addGlobalProgress(float weight) {
205: compilation.addProgress(weight
206: * compilation.getNumberOfDeclarations());
207: }
208:
209: /****************************************************************
210: * Passes
211: ****************************************************************/
212:
213: private boolean scoped = false;
214:
215: static nice.tools.util.Chronometer scopeChrono = nice.tools.util.Chronometer
216: .make("Scope");
217: static nice.tools.util.Chronometer resolveChrono = nice.tools.util.Chronometer
218: .make("Resolve");
219: static nice.tools.util.Chronometer readAltChrono = nice.tools.util.Chronometer
220: .make("Read alternatives");
221: static nice.tools.util.Chronometer tresolveChrono = nice.tools.util.Chronometer
222: .make("Typed resolve");
223: static nice.tools.util.Chronometer lresolveChrono = nice.tools.util.Chronometer
224: .make("Local resolve");
225: static nice.tools.util.Chronometer typecheckChrono = nice.tools.util.Chronometer
226: .make("Typecheck");
227:
228: public void scope() {
229: if (scoped)
230: return;
231: scoped = true;
232:
233: scopeChrono.start();
234: try {
235: ast.buildScope();
236: } finally {
237: scopeChrono.stop();
238: }
239:
240: addProgress(PROGRESS_SCOPE);
241: }
242:
243: public void load() {
244: resolveChrono.start();
245: try {
246: ast.resolveScoping();
247: } finally {
248: resolveChrono.stop();
249: }
250:
251: readAltChrono.start();
252: try {
253: // this must be done before freezing
254: if (!compiling())
255: readAlternatives();
256: } finally {
257: readAltChrono.stop();
258: }
259:
260: addProgress(PROGRESS_LOAD);
261: }
262:
263: public void typedResolve() {
264: tresolveChrono.start();
265: try {
266: ast.typedResolve();
267: } finally {
268: tresolveChrono.stop();
269: }
270:
271: addProgress(PROGRESS_TYPED_RESOLVE);
272: }
273:
274: public void localResolve() {
275: tresolveChrono.start();
276: try {
277: ast.localResolve();
278: } finally {
279: tresolveChrono.stop();
280: }
281:
282: addProgress(PROGRESS_LOCAL_RESOLVE);
283: }
284:
285: public void compile() {
286: compilation.exitIfErrors();
287: generateCode();
288:
289: addProgress(PROGRESS_GENERATE_CODE);
290:
291: saveInterface();
292:
293: addProgress(PROGRESS_SAVE_INTERFACE);
294: }
295:
296: public static void startNewCompilation() {
297: // Perform resets to discard static information gathered during the
298: // previous compilation. This is a workaround, full non-staticness
299: // would be better.
300:
301: mlsub.typing.Typing.startNewCompilation();
302: nice.tools.code.Types.reset();
303: nice.tools.code.SpecialTypes.init();
304: bossa.syntax.dispatch.resetAlternatives();
305: bossa.syntax.dispatch.resetDispatchTest();
306: bossa.syntax.dispatch.resetTypeDefinitionMappings();
307: bossa.syntax.dispatch.resetConstructorsMap();
308: bossa.syntax.dispatch.resetJavaClasses();
309: PrimitiveType.reset();
310: gnu.bytecode.Type.reset();
311: }
312:
313: public void freezeGlobalContext() {
314: contextFrozen = true;
315:
316: mlsub.typing.Typing.createInitialContext();
317: }
318:
319: public void unfreezeGlobalContext() {
320: contextFrozen = false;
321: mlsub.typing.Typing.releaseInitialContext();
322: }
323:
324: private static boolean contextFrozen;
325:
326: public static boolean contextFrozen() {
327: return contextFrozen;
328: }
329:
330: public void typecheck() {
331: // An interface file does not have to be typecheked.
332: // It is known to be type correct from previous compilation!
333: // We still call ast.typechecking, but with a value telling
334: // that only bookeeping tasks are needed, and we don't advertise
335: // this pass.
336: if (compiling())
337: compilation.progress(this , "typechecking");
338:
339: typecheckChrono.start();
340: try {
341: ast.typechecking(compiling());
342: } finally {
343: typecheckChrono.stop();
344: }
345:
346: addProgress(PROGRESS_TYPECHECK);
347: }
348:
349: public void link() {
350: if (!isRoot)
351: return;
352:
353: // If at least one package is recompiled, the root will also be recompiled
354: if (compiling()) {
355: compilation.progress(this , "linking");
356: bossa.syntax.dispatch.testCoverage(this );
357:
358: finishCompilation();
359:
360: compilation.exitIfErrors();
361: }
362:
363: addGlobalProgress(PROGRESS_LINK);
364:
365: // Write the archive even if nothing was compiled.
366: // This is useful to bundle the application after it was compiled.
367: writeArchive();
368:
369: compilation.locator.save();
370: }
371:
372: /**
373: Save the dispatch class generated during link,
374: and ask imported packages to do so too.
375: */
376: private void finishCompilation() {
377: gnu.expr.Package pkg = compilePackages(null);
378:
379: try {
380: pkg.compileToFiles();
381: pkg = null;
382: } catch (IOException e) {
383: User.error(this .name,
384: "Error during creation of bytecode files:\n" + e);
385: }
386: }
387:
388: /**
389: Recursive traversal of the import graph.
390:
391: Root package are compiled first.
392:
393: Setting module to false allows to reclaim memory,
394: and avoids infinite recursion.
395: */
396: private gnu.expr.Package compilePackages(gnu.expr.Package res) {
397: if (dispatchClass == null)
398: return res;
399:
400: if (compiling())
401: // Force generation of the package class.
402: // We might not need to do that forever, but at the moment, a compiled
403: // package is ignored when "fun.class" is missing.
404: getImplementationClass();
405:
406: // The implementation class is null if this package was up-to-date.
407: if (implementationClass != null)
408: this Pkg.addClass(implementationClass);
409:
410: this Pkg.addClass(dispatchClass);
411: this Pkg.directory = source.getOutputDirectory();
412:
413: this .implementationClass = null;
414: this .dispatchClass = null;
415:
416: this Pkg.next = res;
417: res = this Pkg;
418:
419: // Allow memory to be reclaimed early.
420: this Pkg = null;
421: ast = null;
422:
423: for (Iterator i = getImports().iterator(); i.hasNext();)
424: res = ((Package) i.next()).compilePackages(res);
425:
426: return res;
427: }
428:
429: private void saveInterface() {
430: // do not save the interface
431: // if this package already comes from an interface file
432: if (!compiling())
433: return;
434:
435: File dir = source.getOutputDirectory();
436:
437: try {
438: PrintWriter f = new PrintWriter(new BufferedWriter(
439: new FileWriter(new File(dir, "package.nicei"))));
440: f.print("package " + name + ";\n\n");
441:
442: for (Iterator i = getImports().iterator(); i.hasNext();) {
443: Package m = (Package) i.next();
444: f.print("import " + m.getName() + ";\n");
445: }
446: f.println();
447:
448: for (int i = 0; i < opens.length; i++) {
449: f.print("import "
450: + opens[i]
451: + ".*"
452: + (Import.isStrictPackage(opens[i]) ? "(!)"
453: : "") + ";\n");
454: }
455: f.println();
456:
457: ast.printInterface(f);
458: f.close();
459: } catch (IOException e) {
460: User.warning(name,
461: "Could not save the interface of package " + name);
462: }
463: }
464:
465: /****************************************************************
466: * Archive
467: ****************************************************************/
468:
469: private void writeArchive() {
470: if (compilation.output == null)
471: return;
472:
473: File jarFile = createJarFile();
474:
475: try {
476: JarOutputStream jarStream = createJarStream(jarFile);
477: if (!compilation.excludeRuntime)
478: writeRuntime(compilation.runtimeFile, jarStream);
479: // The link was performed iff this package (root) was recompiled.
480: this .addToArchive(jarStream, compiling());
481: jarStream.close();
482: } catch (Exception e) {
483: // The jar file was not completed
484: // it must be corrupt, so it's cleaner to delete it
485: jarFile.delete();
486:
487: if (e instanceof RuntimeException)
488: throw (RuntimeException) e;
489: else
490: User.error(this , "Error while writing archive ("
491: + e.getMessage() + ")", e);
492: }
493: }
494:
495: private File createJarFile() {
496: if (!compilation.output.endsWith(".jar"))
497: compilation.output = compilation.output + ".jar";
498:
499: File jarFile = new File(compilation.output);
500:
501: // Create the directory if necessary
502: File parent = jarFile.getParentFile();
503: // The parent is null (i.e. current dir) if it is not specified.
504: if (parent != null)
505: parent.mkdirs();
506:
507: return jarFile;
508: }
509:
510: private JarOutputStream createJarStream(File jarFile)
511: throws IOException {
512: Manifest manifest = new Manifest();
513: manifest.getMainAttributes().put(
514: Attributes.Name.MANIFEST_VERSION, "1.0");
515: manifest.getMainAttributes().put(Attributes.Name.MAIN_CLASS,
516: this .name + ".dispatch");
517: return new JarOutputStream(new FileOutputStream(jarFile),
518: manifest);
519: }
520:
521: /**
522: Write all base classes that are needed to run generated code.
523: */
524: private static void writeRuntime(String runtimeJar,
525: JarOutputStream jarStream) throws IOException {
526: JarFile runtime = null;
527:
528: if (runtimeJar != null)
529: try {
530: runtime = new JarFile(runtimeJar);
531: } catch (java.util.zip.ZipException e) {
532: }
533:
534: if (runtime == null) {
535: Internal
536: .warning("Runtime was not found. The archive is not self-contained");
537: return;
538: }
539:
540: // add individual classes
541: String[] classes = { "gnu/mapping/Procedure.class",
542: "gnu/mapping/Procedure0.class",
543: "gnu/mapping/Procedure1.class",
544: "gnu/mapping/Procedure2.class",
545: "gnu/mapping/Procedure3.class",
546: "gnu/mapping/ProcedureN.class",
547: "gnu/mapping/Named.class",
548: "gnu/mapping/Printable.class",
549: "gnu/mapping/WrongArguments.class",
550: "gnu/expr/ModuleBody.class",
551: "gnu/expr/ModuleMethod.class" };
552:
553: for (int i = 0; i < classes.length; i++) {
554: JarEntry entry = runtime.getJarEntry(classes[i]);
555: if (entry == null)
556: System.out.println("Runtime: " + classes[i]
557: + " not found");
558: else
559: addEntry(classes[i], runtime.getInputStream(entry),
560: jarStream);
561: }
562: }
563:
564: private boolean addedToArchive;
565:
566: private void addToArchive(JarOutputStream jarStream,
567: boolean linkPerformed) throws IOException {
568: if (addedToArchive)
569: return;
570: addedToArchive = true;
571:
572: compilation.progress(this , "writing in archive");
573:
574: String packagePrefix = getName().replace('.', '/') + "/";
575: Content.Stream[] classes = source.getClasses(linkPerformed);
576:
577: for (int i = 0; i < classes.length; i++)
578: addEntry(packagePrefix + classes[i].name,
579: classes[i].stream, jarStream);
580:
581: for (Iterator i = getImports().iterator(); i.hasNext();)
582: ((Package) i.next()).addToArchive(jarStream, linkPerformed);
583: }
584:
585: private static void addEntry(String name, InputStream data,
586: JarOutputStream out) throws IOException {
587: out.putNextEntry(new JarEntry(name));
588: copy(data, out);
589: }
590:
591: private static void copy(InputStream in, OutputStream out)
592: throws IOException {
593: int size = in.available();
594: if (size < 1024)
595: size = 1024;
596: byte[] buf = new byte[size];
597:
598: try {
599: int read;
600: do {
601: read = in.read(buf);
602: if (read > 0)
603: out.write(buf, 0, read);
604: } while (read != -1);
605: } finally {
606: in.close();
607: }
608: }
609:
610: /****************************************************************
611: * Code generation
612: ****************************************************************/
613:
614: /** The name of the class package functions and method implementations
615: are stored in.
616: **/
617: static final String packageClassName = "fun";
618:
619: public ClassExp getClassExp(Object def) {
620: if (compiling())
621: return bossa.syntax.dispatch.NiceClass_createClassExp(def);
622:
623: String name = bossa.syntax.dispatch.NiceClass_getName(def)
624: .toString();
625: ClassType classe = source.readClass(name);
626: if (classe == null)
627: Internal.error("Compiled class " + def + " was not found");
628: importMethods(def, classe);
629:
630: ClassExp res = new ClassExp(classe);
631: addUserClass(res);
632: return res;
633: }
634:
635: /** Load methods compiled in a class (custom constructors for now). */
636: private void importMethods(Object def, ClassType classe) {
637: for (Method method = classe.getMethods(); method != null; method = method
638: .getNext()) {
639: Definition d = bossa.syntax.dispatch
640: .NiceClass_importMethod(def, method);
641: if (d != null)
642: definitions.add(d);
643: }
644: }
645:
646: public void addUserClass(gnu.expr.ClassExp classe) {
647: this Pkg.addClass(classe);
648:
649: // A class only needs an outer frame if we are compiling the package.
650: if (compiling())
651: classe.outer = getImplementationClass();
652: }
653:
654: public void addGlobalVar(gnu.expr.Declaration decl, boolean constant) {
655: if (!compiling())
656: // The code is already there
657: {
658: decl.setSimple(false);
659: decl.field = source.getBytecode().getField(decl.getName());
660:
661: if (decl.field == null)
662: Internal
663: .error(
664: this ,
665: "The compiled file is not consistant with the interface file for global variable "
666: + decl.getName());
667: } else {
668: getImplementationClass().addDeclaration(decl);
669: if (constant)
670: decl.setFlag(Declaration.IS_CONSTANT);
671:
672: decl.setFlag(Declaration.STATIC_SPECIFIED
673: | Declaration.TYPE_SPECIFIED);
674: }
675: }
676:
677: private gnu.expr.Package this Pkg;
678: private ClassExp implementationClass, dispatchClass;
679:
680: private ClassExp createClassExp(String name) {
681: ClassExp res = new ClassExp();
682: res.setName(name);
683: res.setSimple(true);
684: res.body = QuoteExp.voidExp;
685: res.needsConstructor = true;
686: return res;
687: }
688:
689: /**
690: * Transform the name of a class to its
691: * fully qualified name.
692: */
693: public static String className(String name) {
694: return name;
695: }
696:
697: static {
698: nice.tools.code.NiceInterpreter.init();
699: }
700:
701: private void prepareCodeGeneration() {
702: this Pkg = new gnu.expr.Package(getName());
703:
704: dispatchClass = createClassExp(name + ".dispatch");
705: dispatchClass.addBytecodeAttribute(MiscAttr.synthetic());
706: }
707:
708: /** Creates the implementation class lazily. */
709: private ClassExp getImplementationClass() {
710: if (implementationClass == null)
711: implementationClass = createClassExp(name + "."
712: + packageClassName);
713:
714: return implementationClass;
715: }
716:
717: /**
718: * Creates bytecode for the alternatives defined in the module.
719: */
720: private void generateCode() {
721: if (compiling())
722: compilation.progress(this , "generating code");
723:
724: ast.compile(compiling());
725: }
726:
727: public ClassType createClass(String name) {
728: // If we use new ClassType(), we may end up with two different
729: // objects representing this class.
730: // However if the class exists but is invalid, we create a new one.
731:
732: String className = this .name + "." + name;
733: ClassType res;
734: try {
735: res = ClassType.make(className);
736: } catch (java.lang.LinkageError e) {
737: res = new ClassType(className);
738: }
739:
740: res.setExisting(false);
741: return res;
742: }
743:
744: public Method lookupDispatchClassMethod(ClassType clas,
745: String name, String attribute, String value) {
746: if (clas == null)
747: clas = source.getDispatch();
748: return lookupClassMethod(clas, name, attribute, value);
749: }
750:
751: /**
752: @return the bytecode method with this (unique) name
753: if the package has not been recompiled.
754: */
755: private Method lookupClassMethod(ClassType clas, String name,
756: String attribute, String value) {
757: if (clas == null)
758: return null;
759:
760: name = nice.tools.code.Strings.escape(name);
761:
762: for (Method m = clas.getDeclaredMethods(); m != null; m = m
763: .getNext())
764: if (m.getName().equals(name)
765: // in rare cases, the method name might have been made unique
766: // in the bytecode by appending "$..."
767: // but names appended with "$$..." may not be matched because
768: // that are escape characters
769: || m.getName().startsWith(name)
770: && m.getName().charAt(name.length()) == '$'
771: && m.getName().charAt(name.length() + 1) != '$') {
772: MiscAttr attr = (MiscAttr) Attribute.get(m, attribute);
773: if (attr != null && new String(attr.data).equals(value))
774: return m;
775: }
776:
777: return null;
778: }
779:
780: /** Add a method to this package and return an expression to refer it. */
781: public ReferenceExp addMethod(LambdaExp method,
782: boolean packageMethod) {
783: ClassExp classe = packageMethod ? getImplementationClass()
784: : dispatchClass;
785: return classe.addMethod(method);
786: }
787:
788: public String bytecodeName() {
789: return name.toString();
790: }
791:
792: /****************************************************************
793: * Misc
794: ****************************************************************/
795:
796: public Location location() {
797: return name.location();
798: }
799:
800: public String getName() {
801: return name.toString();
802: }
803:
804: public String toString() {
805: return "package " + name;
806: }
807:
808: public AST getDefinitions() {
809: return ast;
810: }
811:
812: public LocatedString name;
813:
814: /** True if this package was specified on the command line. */
815: private boolean isRoot;
816:
817: /** The list of definitions. Used to add global definitions on the fly. */
818: private List definitions;
819:
820: private AST ast;
821:
822: /** The "source" where this package resides. */
823: private Content source;
824:
825: /** The compilation that is in process. */
826: Compilation compilation;
827:
828: public Compilation getCompilation() {
829: return compilation;
830: }
831:
832: bossa.syntax.Module compiledModule = null;
833:
834: nice.tools.visibility.Scope packageScope;
835:
836: /**
837: @return true if this package was loaded from an interface file,
838: not a source file
839: */
840: public boolean interfaceFile() {
841: return !compiling();
842: }
843:
844: /**
845: @return true if this package is recompiled.
846: */
847: public boolean compiling() {
848: return source.sourceRead;
849: }
850:
851: /****************************************************************
852: * Static link to the current Compilation object (not thread safe!)
853: ****************************************************************/
854:
855: public static Compilation currentCompilation;
856: }
|