Autoload.st
author Claus Gittinger <cg@exept.de>
Mon, 08 Oct 2001 11:09:47 +0200
changeset 6075 57f3ae8d2a28
parent 5903 fa9072bf1506
child 6081 fedbf974cfe8
permissions -rw-r--r--
renamed: #allSelectorsAndMethodsDo: into: #instAndClassSelectorsAndMethodsDo:

"
 COPYRIGHT (c) 1991 by Claus Gittinger
	      All Rights Reserved

 This software is furnished under a license and may be used
 only in accordance with the terms of that license and with the
 inclusion of the above copyright notice.   This software may not
 be provided or otherwise made available to, or used by, any
 other person.  No title to or ownership of the software is
 hereby transferred.
"

"{ Package: 'stx:libbasic' }"

nil subclass:#Autoload
	instanceVariableNames:''
	classVariableNames:'LazyLoading AutoloadFailedSignal LoadedClasses'
	poolDictionaries:''
	category:'Kernel-Classes'
!

!Autoload class methodsFor:'documentation'!

copyright
"
 COPYRIGHT (c) 1991 by Claus Gittinger
	      All Rights Reserved

 This software is furnished under a license and may be used
 only in accordance with the terms of that license and with the
 inclusion of the above copyright notice.   This software may not
 be provided or otherwise made available to, or used by, any
 other person.  No title to or ownership of the software is
 hereby transferred.
"
!

documentation
"
    In memory limited systems (as my 8Mb 386 is) all seldom-used classes are made
    subclasses of this class. 
    Autoload catches all messages and files-In the corresponding code
    when first used. 
    Then the caught message is resent to the (now existing) class.

    Class files are searched along the searchPath (see Smalltalk),
    and any of binary-classFile, byteCode-classFile or sourceFile are
    loaded, whichever is found first. For binaries to be loaded,
    these must be found in the 'bin' directory. Sources must be found
    in the 'source' directory.
    (Usually, Autoload classes finds the source file and loads that one).

    When started, the patches startup script arranges for the abbreviation
    file 'include/abbrev.stc' to be read and installs autoload stubs for
    all classes listed in that file, which do not exists already.
    The abbreviation file is maintained by the production environment
    and updated by makefile rules - therefore, it should not be edited
    manually.

    Late addition: above comment is no longer true - I have made now almost
    all Demos & Goodies be autoloaded ... even for big systems.

    [class variables:]
        
	LazyLoading             <Boolean>       if true, the loaded classes 
						methods will NOT be compiled at 
						autoload time, but instead when 
						first called. This allows for a 
						faster load. However, expect short 
						pauses later when the methods are
						first executed.
                                     
	AutoloadFailedSignal    <Signal>        signal raised if an autoloaded 
						classes source is not available.

	LoadedClasses           <Collection>    set of classes that heve been
						autoloaded (for later unload)

    [see also:]
	Smalltalk

    [author:]
	Claus Gittinger
"
! !

!Autoload class methodsFor:'initialization'!

initialize
    "initialize the failure-signal"

    AutoloadFailedSignal isNil ifTrue:[
	LazyLoading := false.

	AutoloadFailedSignal := Object errorSignal newSignalMayProceed:true.
	AutoloadFailedSignal nameClass:self message:#autoloadFailedSignal.
	AutoloadFailedSignal notifierString:'autoload failed'.

	self class changeClassTo:AutoloadMetaclass.
	self setSuperclass:nil.
	ObjectMemory flushCaches.
    ].

    self ~~ Autoload ifTrue:[
	^ self doesNotUnderstand:(Message selector:#initialize)
    ].

    "Modified: / 20.5.1997 / 19:06:25 / cg"
    "Modified: / 3.8.1999 / 14:23:30 / stefan"
! !

!Autoload class methodsFor:'Signal constants'!

autoloadFailedSignal
    "return the signal raised when an autoload fails"

    ^ AutoloadFailedSignal
! !

!Autoload class methodsFor:'adding/removing autoloaded classes'!

addClass:aClassName
    "given a name, install a corresponding autoload class stub for it.
     Return the (autoload-) stubClass or nil, if no new class was created."

    ^ self addClass:aClassName inCategory:'autoloaded-Classes'

    "
     Autoload addClass:'Clock'
    "

    "Modified: 24.4.1996 / 19:54:16 / cg"
!

addClass:aClassName inCategory:aCategory
    "given a name, install a corresponding autoload class stub for it.
     Return the (autoload-) stubClass or nil, if no new class was created."

    |nameSymbol cls|

    nameSymbol := aClassName asSymbol.
    (Smalltalk at:nameSymbol) isNil ifTrue:[
	cls := Autoload 
		subclass:nameSymbol
		instanceVariableNames:''
		classVariableNames:''
		poolDictionaries:''
		category:aCategory.
    ].
    ^ cls

    "
     Autoload addClass:'Clock' inCategory:'autoloaded-Demos'
    "

    "Modified: 24.4.1996 / 19:54:20 / cg"
!

removeClass:aClass
    "remove a class from the list of loaded classes"

    LoadedClasses remove:aClass ifAbsent:[]

    "Modified: 24.4.1996 / 19:54:35 / cg"
! !

!Autoload class methodsFor:'fileout'!

basicFileOutDefinitionOn:aStream withNameSpace:forceNameSpace withPackage:withPackage
    "print an expression to define myself on aStream.
     Since autoloaded classes dont know their real definition, simply
     output some comment string making things clear in the browser."

    |myName fileName nm package|

    (self == Autoload) ifTrue:[
	^ super 
	    basicFileOutDefinitionOn:aStream 
	    withNameSpace:forceNameSpace
	    withPackage:withPackage
    ].

    myName := self name.
    aStream nextPutAll:'"' ; nextPutLine:'Notice from Autoload:'; cr;
	    spaces:4; nextPutLine:myName , ' is not yet loaded.'; cr.
    aStream nextPutAll:'to load, execute: '.
    aStream cr; cr; spaces:4; nextPutLine:myName  , ' autoload'.
    aStream cr; nextPutLine:'or use the browsers load-function (in the class menu)'.
    aStream nextPutLine:'(double-click on the class will also load it).'.
    "
     the following is simply informative ...
     actually, its a hack & kludge - there ought to be a method for this
     in Smalltalk 
     (knowing the details of loading here is no good coding style)
    "
    fileName := Smalltalk fileNameForClass:myName.
    (ObjectFileLoader notNil and:[Smalltalk loadBinaries]) ifTrue:[
	(nm := Smalltalk libraryFileNameOfClass:myName) notNil ifTrue:[
	    nm := nm , ' (a classLibrary, possibly including more classes)'
	] ifFalse:[
	    nm := Smalltalk getBinaryFileName:(fileName , '.so').
	    nm isNil ifTrue:[
		nm := Smalltalk getBinaryFileName:(fileName , '.o')
	    ].
	    nm notNil ifTrue:[
		nm := nm , ' (a classBinary)'
	    ]
	].
    ].
    nm isNil ifTrue:[
	package := self package.
	package notNil ifTrue:[
	    nm := Smalltalk getSourceFileName:((package copyReplaceAll:$: with:$/) , '/' , fileName , '.st').
	].
	nm isNil ifTrue:[
	    nm := Smalltalk getSourceFileName:(fileName , '.st').
	].
    ].
    nm notNil ifTrue:[
	aStream cr; nextPutLine:'When accessed, ' , myName , ' will automatically be loaded'.
	aStream nextPutLine:'from: '; spaces:4; nextPutAll:nm.
	nm asFilename isSymbolicLink ifTrue:[
	    aStream cr; cr.
	    aStream nextPutLine:'which is a link to: '; spaces:4; 
		    nextPutAll:(nm asFilename linkInfo path).
	]
    ] ifFalse:[
	package := self package.
	(Smalltalk at:#SourceCodeManager) notNil ifTrue:[
	    aStream nextPutAll:'
When accessed, I''ll ask the sourceCodeManager to load the code 
from ''' , fileName , '.st'' in the ''' , package , ''' package.'.
	] ifFalse:[
	    aStream nextPutAll:'
When accessed, I''ll search for a file named 
''' , fileName , '.st'' in the ''' , package , ''' package.

Packages are tried along the packagePath, which is:
' , ((Smalltalk packagePath asStringCollection collect:[:each| '   ' , each]) asStringWith:Character cr). 
	]
    ].
    aStream cr; nextPutAll:'"'.

    "Created: / 5.1.1997 / 14:31:33 / cg"
    "Modified: / 6.6.1999 / 15:17:30 / cg"
! !

!Autoload class methodsFor:'lazy compilation'!

compileLazy
    "return the lazy loading flag - if on, fileIn is much faster,
     but pauses are to be expected later, since methods are compiled
     when first executed."

    ^ LazyLoading
!

compileLazy:aBoolean
    "turn on/off lazy loading - if on, fileIn is much faster,
     but pauses are to be expected later, since methods are compiled
     when first executed.
     If you like it, add a line to your startup file."

    LazyLoading := aBoolean
! !

!Autoload class methodsFor:'loading'!

autoload
    "use this to force loading
     - it is defined a noop in all non-autoloading classes"

    |mySelf myName myNameSpace newClass oldMeta project prevMode 
     package|

    mySelf := self.
    myName := self name asSymbol.
    myNameSpace := self nameSpace.

    "/ recheck - in case my class somehow came into the system
    "/ (by loading a binary...)
    (newClass := Smalltalk at:myName) ~~ self ifTrue:[
        (newClass isBehavior and:[newClass isLoaded]) ifTrue:[
            ('Autoload [info]: already loaded: ', myName , '.') infoPrintCR.
            self class becomeSameAs:newClass class.
            self becomeSameAs:newClass.

            ObjectMemory flushCaches.
            ^ newClass
        ].
    ].

    "load it"
    (Object infoPrinting and:[Smalltalk silentLoading ~~ true]) ifTrue:[
        Transcript showCR:('autoloading ', myName , ' ...'); endEntry.
    ].

    [
        "remove myself - to avoid recompilation"
        Smalltalk at:myName put:nil.

        prevMode := ClassCategoryReader sourceMode.
"/
"/ no- do not do this; it may lead to trouble ...
"/        ClassCategoryReader sourceMode:#reference.

        "/
        "/ in order to not get a package of private (or whatever),
        "/ temporarily set the currentProject to nil.
        "/ we will later set the classes package to something useful
        "/
        Project notNil ifTrue:[
            project := Project current.
            Project setProject:nil.
        ].

        package := self package.
        Class nameSpaceQuerySignal answer:myNameSpace "Smalltalk" 
        do:[
            Class packageQuerySignal answer:package 
            do:[
                (Smalltalk 
                    fileInClass:myName
                    package:package
                    initialize:false 
                    lazy:LazyLoading
                    silent:nil) isNil 
                ifTrue:[
                    "/ temporary: try without stx/package prefix
                    "/ this will vanish as soon as source-directory
                    "/ is always guaranteed to contain an stx-subtree
                    ((package startsWith:'stx/') 
                    or:[package startsWith:'stx:']) ifTrue:[
                        package := package copyFrom:5.
                        Smalltalk 
                            fileInClass:myName
                            package:package
                            initialize:false 
                            lazy:LazyLoading
                            silent:nil.
                    ]
                ]
            ]
        ].
        ClassCategoryReader sourceMode:prevMode.
        project notNil ifTrue:[
            Project setProject:project.
        ].
    ] valueOnUnwindDo:[
        ClassCategoryReader sourceMode:prevMode.
        project notNil ifTrue:[
            Project setProject:project.
        ].
        Smalltalk at:myName put:mySelf.
    ].

    "did it work ?"
    newClass := Smalltalk at:myName.
    Smalltalk at:myName put:mySelf.   "will be undone by become:"

    "no - report the error"
    newClass isNil ifTrue:[
        "
         this signal is raised, if an autoloaded class
         cannot be loaded. Usually, this happends when
         some sourcefile is missing, not readable or if 
         an entry is missing in the abbreviation file.
         Check for a readable file named <myName>.st 
         in the 'source' directory and (if its a long fileName) 
         for a corresponding entry in the abbreviation file
         'include/abbrev.stc'.
         Finally, your searchpath could be set wrong -
         both 'source' and 'include' directories must be found in
         one of the directories named in systemPath.

         In the debugger, press 'abort' to continue execution.
        " 
        AutoloadFailedSignal
            raiseRequestWith:self
            errorString:('autoload of ' , myName , ' failed').
        ^ nil
    ].

    newClass setPackage:package.
    newClass instAndClassSelectorsAndMethodsDo:[:sel :mthd | mthd setPackage:package].

    "/
    "/ autoloaded classes get their package from the revision (if present)
    "/ this only happens with autoloaded sourceFiles which have no package
    "/ info encoded. (binary classes have it)
    "/ If there is no such information, give it my package (if I have one)
    "/
"/    newClass setPackageFromRevision.
"/
"/    newPackage := newClass package.
"/    (newPackage isNil or:[newPackage = 'no package']) ifTrue:[
"/        (package notNil and:[package ~= 'no package']) ifTrue:[
"/            newClass setPackage:package.
"/        ].
"/    ].

    LoadedClasses isNil ifTrue:[
        LoadedClasses := IdentitySet new.
    ].
    LoadedClasses add:newClass.

    "wow - it worked. now the big trick ..."

"/    newClass class setSoleInstance:self.   "/ will be undone by become ...
    oldMeta := self class.

"/ self setName:(self name , ' (auto)').

    self becomeSameAs:newClass.
    oldMeta becomeSameAs:newClass class.

    ObjectMemory flushCaches.
    LoadedClasses rehash.

    newClass initializeWithAllPrivateClasses.
    newClass postAutoload.

    Smalltalk changed:#classDefinition with:newClass.
    ^ newClass

    "Modified: / 5.3.1999 / 12:59:13 / cg"
! !

!Autoload class methodsFor:'message catching'!

basicNew
    "catch basicNew - load the class and send it to the real one"

    ^ self doesNotUnderstand:(Message selector:#basicNew)

    "Modified: 24.4.1996 / 19:53:10 / cg"
!

basicNew:arg
    "catch basicNew: - load the class and send it to the real one"

    ^ self doesNotUnderstand:(Message selector:#basicNew: with:arg)

    "Modified: 24.4.1996 / 19:53:16 / cg"
!

comment
    "return the classes comment.
     Autoloaded classes have no comment; but I myself have one"

    (self == Autoload) ifTrue:[^ super comment].
    ^ 'not yet loaded'
!

doesNotUnderstand:aMessage
    "caught a message; load the class and retry the message"

    |newClass|

    self ~~ Autoload ifTrue:[
	newClass := self autoload.
	newClass notNil ifTrue:[
	    ^ aMessage sendTo:newClass
	]
    ].
    ^ super doesNotUnderstand:aMessage

    "Modified: 24.4.1996 / 19:53:27 / cg"
!

loadAndResendMessage
    "common helper to autoload a class and define a subclass of it by sending
     the sender-message again.
     This is invoked by all #subClass... definition messages."

    |sel args newClass|

    sel := thisContext sender selector.
    args := thisContext sender args.

    "take care: subclassing Autoload must still be possible"
    (self == Autoload) ifTrue:[
	^ self perform:sel inClass:Autoload class superclass withArguments:args
    ].

    newClass := self autoload.
    newClass notNil ifTrue:[
	^ newClass perform:sel withArguments:args
    ].
    ^ nil

    "Created: 8.2.1997 / 19:42:47 / cg"
!

new
    "catch new - load the class and resend #new to the real one"

    ^ self doesNotUnderstand:(Message selector:#new)

    "Modified: 26.5.1997 / 14:59:52 / cg"
!

new:arg
    "catch new: - load the class and resend #new: it to the real one"

    ^ self doesNotUnderstand:(Message selector:#new: with:arg)

    "Modified: 26.5.1997 / 14:59:58 / cg"
!

readFrom:aStream 
    "catch  - load the class and send it to the real one"

    ^ self doesNotUnderstand:(Message selector:#readFrom: with:aStream)

    "Created: 22.5.1996 / 23:44:23 / stefan"
!

readFrom:aStream onError:exceptionBlock
    "catch  - load the class and send it to the real one"

    ^ self doesNotUnderstand:(Message selector:#readFrom:onError: with:aStream with:exceptionBlock)

    "Created: 22.5.1996 / 23:03:39 / stefan"
!

readFromString:aString
    "catch  - load the class and send it to the real one"

    ^ self doesNotUnderstand:(Message selector:#readFromString: with:aString)

    "Created: 22.5.1996 / 23:46:15 / stefan"
!

readFromString:aString onError:exceptionBlock
    "catch  - load the class and send it to the real one"

    ^ self doesNotUnderstand:(Message selector:#readFromString:onError: with:aString with:exceptionBlock)

    "Created: 22.5.1996 / 23:04:49 / stefan"
!

respondsTo:aSelector
    "catch respondsTo: - load the class and resend #respondsTo: it to the real one"

    self == Autoload ifTrue:[
	^ super respondsTo:aSelector
    ].
    ^ self doesNotUnderstand:(Message selector:#respondsTo: with:aSelector)

    "Modified: 26.5.1997 / 14:59:58 / cg"
! !

!Autoload class methodsFor:'queries'!

accepts:aSignal
    "redefined to avoid recursive autoload"

    |newClass|

    self isBeingLoaded ifFalse:[
	newClass := self autoload.
	newClass notNil ifTrue:[
	    ^ newClass accepts:aSignal
	].
    ].
    ^ false
!

isBehavior
    "return true if the recevier is some kind of class.
     Autoloaded classes are definitely; therefore return true."

    ^ true
!

isBeingLoaded
    ^ (self ~~ Autoload) and:[(Smalltalk at:self name) isNil ]

    "Modified: / 16.2.1998 / 11:57:35 / stefan"
!

isBuiltInClass
    "return true if this class is known by the run-time-system.
     Returns false here."

    ^ false

    "Created: 10.12.1995 / 00:28:22 / cg"
    "Modified: 23.4.1996 / 15:55:36 / cg"
!

isLoaded
    "return true, if the class has been loaded.
     Autoload itself is loaded, subclasses are not.
     This allows testing wether a class has already been loaded."

    ^ (self == Autoload)

    "Modified: / 16.2.1998 / 11:57:35 / stefan"
!

loadedClasses
    "return the set of classes that were autoloaded"

    ^ LoadedClasses ? #()
!

privateClassesOrAll:allOfThem
    "an autoloaded class has none"

    ^ #()
!

wasAutoloaded:aClass
    "return true, if aClass was autoloaded"

    ^ LoadedClasses notNil and:[LoadedClasses includes:aClass]
! !

!Autoload class methodsFor:'documentation'!

version
    ^ '$Header: /cvs/stx/stx/libbasic/Autoload.st,v 1.110 2001-10-08 09:09:47 cg Exp $'
! !
Autoload initialize!