'From Cuis 5.0 [latest update: #4056] on 4 March 2020 at 3:30:31 pm'!

!Feature methodsFor: 'testing' stamp: 'KenD 3/3/2020 07:51:49'!
= another

	self == another ifTrue: [ ^ true ].
	self class == another class ifFalse: [ ^false ].
	^ self name = another name and: [
		self version = another version and: [
			self revision = another revision ]]! !

!Feature methodsFor: 'testing' stamp: 'KenD 3/3/2020 07:52:25'!
hash
	"Hash is reimplemented because = is implemented."
	
	^ (self feature name) ifNil: [0] ifNotNil: [ :myName | myName hash ]! !


!FeatureRequirement methodsFor: 'accessing' stamp: 'KenD 3/3/2020 11:14:53'!
pathName
	
	^ pathName "May be nil"! !

!FeatureRequirement methodsFor: 'requires' stamp: 'KenD 3/3/2020 13:05:17'!
assureRequirement
	"Answer a CodePackageFile if I am satisfied by package found at pathName"
	
	"Transcript print: self name; print: ' checking: '; print: pathName; newLine. "
	
	pathName asFileEntry readStreamDo: [ :stream | | codePkgFile fSpec |
		codePkgFile := CodePackageFile buildFileStream: stream packageName: self packageFileName fullName: pathName.
		fSpec := codePkgFile featureSpec.
		(fSpec notNil and: [fSpec provides satisfies: self]) ifTrue: [ ^codePkgFile ]   
	].
	^ nil "failed"! !

!FeatureRequirement methodsFor: 'requires' stamp: 'KenD 3/3/2020 13:44:53'!
findCodePackageFileAsReqOf: mainFeatureOrNil
	"Look in known places for packages providing required feature.
	Answer CodePackageFile if search was successful."
	
	| packageFileName entry |
	pathName ifNotNil: [
		pathName asFileEntry exists ifFalse: [ ^ false ]].
	packageFileName _ self packageFileName.
	(mainFeatureOrNil ifNil: [ self ]) placesToLookForPackagesDo: [ :directory |
		entry _ directory // packageFileName.
		entry exists ifTrue: [
			pathName _ entry pathName.
			self assureRequirement ifNotNil: [ :codePkgFile | ^codePkgFile ].
			pathName _ nil]].
	^ nil! !

!FeatureRequirement methodsFor: 'requires' stamp: 'KenD 3/3/2020 12:11:20'!
satisfyRequirementsAndInstallSelf
	"Like #require, but install me even if already satisified (i.e. installed)"

	self satisfyRequirementsAndLoadThem.
	self install! !

!FeatureRequirement methodsFor: 'private' stamp: 'KenD 3/4/2020 15:29:59'!
satisfyRequirementsAndLoadThem
	"See if all the transitive closure of requirements can be met and install them if so.
	Note: does NOT load package for self, see #require and #satisfyRequirementsAndInstallSelf"

	| packagesToLoad |
	
	"Preflight before load 
	   Build transitive closure as load order where Feature comes 
	       before its required features.
	Note: packagesToLoad is an OrderedCollection of CodePackageFile's"
	[packagesToLoad _ self requireUnlessIn: OrderedCollection new main: nil requiringFeature: nil]
		on: FeatureRequirementUnsatisfied  
		do: [ :error | 
			error defaultAction. 
			^self "exit"
		].
	
"	Transcript print: 'packagesToLoad: {'.
	packagesToLoad do: [ :x |Transcript print: x packageName asString, ' ' ].
	Transcript print: '}'; newLine.  "

	"Now load required packages before packages that require them"
	packagesToLoad reverseDo: [ :codePkgFile |
		(codePkgFile provides requirementOfMe isAlreadySatisfied)
			ifFalse: [ codePkgFile privateInstallFeature ]
"			ifTrue: [ Transcript print: codePkgFile packageName asString, ' is already satisfied'; newLine] "
	].

	"Nota Bene: self is _not_ installed; see usage: #require and #satisfyRequirementsAndInstall"! !


!CodePackageFile methodsFor: 'accessing' stamp: 'KenD 3/3/2020 11:57:26'!
packageName

	^packageName ! !

!CodePackageFile methodsFor: 'private' stamp: 'KenD 3/4/2020 15:29:24'!
addToLoad: toLoad withMyRequirements: myRequirements

	"Add self to OrderedCollection 'toLoad' before any requirements I have"
	toLoad do: [ :codePkgToLoad | 
		(myRequirements anySatisfy: [ :aRequirement | 
			(codePkgToLoad provides) satisfies: aRequirement ])
		ifTrue: [
"			Transcript show: ('queueing ', self packageName asString, 
				' before: ', codePkgToLoad packageName asString); newLine.  "
			toLoad add: self before: codePkgToLoad.
			^ toLoad
		]
	].
"	Transcript show: ('queueing ', self packageName asString); newLine.  "
	toLoad addLast: self.
	^ toLoad! !

!CodePackageFile methodsFor: 'private' stamp: 'KenD 3/4/2020 15:29:01'!
privateInstallFeature
	"Called by #FeatureRequirement>>satisfyRequirementsAndLoadThem
	 Preconditions have been satisfied.  Install the required package."
	
	| aFileEntry |
	aFileEntry := self fullName asFileEntry.
"	Transcript print: 'Installing from: ', self fullName; newLine. "
	aFileEntry readStreamDo: [ :aStream | | existing |
		existing := CodePackage named: self packageName createIfAbsent: false registerIfNew: false.
		(existing isNil
		or: [ existing hasUnsavedChanges not
			or: [ self confirm: 'If you install this package, there are unsaved changes that will be lost.', 
								String newLineString, 'Continue?' ]]) 
		ifTrue: [ 
			self installFileStream: aStream
				 packageName: self packageName
				 fullName: self fullName ]
	]! !


!FeatureRequirement methodsFor: 'requires' stamp: 'KenD 3/3/2020 11:52:49'!
require
	"See if all the transitive closure of requirements can be met and install if so"

	self satisfyRequirementsAndLoadThem.
	self isAlreadySatisfied ifFalse: [ self install ]! !

!FeatureRequirement methodsFor: 'requires' stamp: 'KenD 3/4/2020 15:30:19'!
requireUnlessIn: toLoad main: mainFeatureOrNil requiringFeature: requiringFeatureOrNil
	"See if all the requirements can be met and answer the load order"
	"Note: #toLoad is an OrderedCollection of CodePackageFile's"

	| codePkgFileForMe |
	self isAlreadySatisfied ifFalse: [
		(toLoad anySatisfy: [ :codePkgFile | (codePkgFile featureSpec provides) satisfies: self]) 
		  ifFalse: [ 
			(codePkgFileForMe := self findCodePackageFileAsReqOf: mainFeatureOrNil)
				ifNotNil: [ | otherRequirements |
					otherRequirements := codePkgFileForMe featureSpec requires.
					otherRequirements ifNotNil: [
						otherRequirements do: [ :requires | 
							requires requireUnlessIn: toLoad main: (mainFeatureOrNil ifNil: [self]) requiringFeature: self ]].
					self == mainFeatureOrNil ifFalse: [
						codePkgFileForMe addToLoad: toLoad withMyRequirements: otherRequirements ].
					]
				ifNil: [ | failureMessage |
					failureMessage := 'Could not find package supplying: ', self printString, String newLineString,
						'Required by: ', (requiringFeatureOrNil ifNil: [ self ]) printString, String newLineString,
						'For installing: ', (mainFeatureOrNil ifNil: [ self ]) printString, String newLineString.
					FeatureRequirementUnsatisfied signal: failureMessage.
				]]]
"	ifTrue: [ Transcript print: self name, ' is already satisfied'; newLine ]" .

	^ toLoad! !


!CodePackageFile methodsFor: 'initialization' stamp: 'KenD 3/4/2020 14:15:28'!
buildFileStream: aFileStream packageName: pkName fullName: fullFileName
	"Just build the CodePackageFile object. Don't install the code."

	| classesDefined classesExtended classesToDeleteButCant classesToReallyDelete packageInMemory |
	packageName _ CodePackageFile packageNameFrom: pkName.
	fullName _ fullFileName.
	"This will most likely be updated with the actual data from the file, calling #provides:"
	featureSpec provides: (Feature name: pkName version: 1 revision: 0).
	"Don't register a package!!"
	packageInMemory _ CodePackage
		named: packageName
		createIfAbsent: true
		registerIfNew: false.
	self buildFrom: aFileStream.
	"Compute stuff no longer in package: Should be removed from system."
	classesDefined _ Set new.
	classesExtended _ Set new.
	classes do: [ :pseudoClass |
		pseudoClass hasDefinition
			ifTrue: [ classesDefined add: pseudoClass name ]
			ifFalse: [ classesExtended add: pseudoClass name ]].
	classesToRemove _ packageInMemory classNames difference: classesDefined.
	"Add here:
		- classes in classesToDelete, that #allCallsOn answers selectors that aren't in classesToDelete or methodsToRemove
		- classes with #subclasses that aren't in classesToDelete.
		- classes with existing instances (#instanceCount)? Not really sure... Maybe sole instance referenced from classVar or such...
		- something else I forgot?
	Warning: This search for stuff that can't be removed must be iterated again until it doesn't find any more."
	classesToDeleteButCant _ classesToRemove intersection: classesExtended.
	classesToReallyDelete _ classesToRemove difference: classesToDeleteButCant.
	"Methods. Could also mean classes that can't be deleted!! (include in the iteration)
	Warn if deleting last implementor of sent messages?"
	methodsToRemove _ packageInMemory methods asSet difference: self allMethodReferences.
	methodsToRemove _ methodsToRemove reject: [ :methodReference | classesToReallyDelete includes: methodReference classSymbol ].

"
	'=============' print.
	('classesToRemove: ', classesToRemove printString) print.
	('classesToDeleteButCant: ', classesToDeleteButCant printString) print.
	('classesToReallyDelete: ', classesToReallyDelete printString) print.
	'=============' print.
	'methodsToRemove: ' print.
	methodsToRemove do: [ :methodReference | methodReference print ].
	'=============' print.
"! !


!CodePackageFile class methodsFor: 'services' stamp: 'KenD 3/3/2020 12:11:54'!
installPackage: aFileEntry

	| fullName pkName |
	fullName _ aFileEntry pathName.
	pkName _ CodePackageFile packageNameFrom: fullName.
	(FeatureRequirement name: pkName)
		pathName: fullName;
		satisfyRequirementsAndInstallSelf "Install with all requirements"! !

!methodRemoval: CodePackageFile #privateInstallFeature: stamp: 'KenD 3/4/2020 15:27:20'!
CodePackageFile removeSelector: #privateInstallFeature:!
!methodRemoval: FeatureRequirement #checkRequirement stamp: 'KenD 3/4/2020 15:27:17'!
FeatureRequirement removeSelector: #checkRequirement!
!methodRemoval: FeatureRequirement #satisfyRequirementsAndInstall stamp: 'KenD 3/4/2020 15:27:17'!
FeatureRequirement removeSelector: #satisfyRequirementsAndInstall!
!methodRemoval: FeatureRequirement #findPackageFileAsReqOf: stamp: 'KenD 3/4/2020 15:27:17'!
FeatureRequirement removeSelector: #findPackageFileAsReqOf:!
!methodRemoval: FeatureRequirement #addToLoad:withMyRequirements: stamp: 'KenD 3/4/2020 15:27:17'!
FeatureRequirement removeSelector: #addToLoad:withMyRequirements:!

!FeatureRequirement reorganize!
('accessing' maxVersion minRevision minVersion name packageFileName pathName pathName: requirements)
('requires' assureRequirement findCodePackageFileAsReqOf: require requireUnlessIn:main:requiringFeature: satisfyRequirementsAndInstallSelf)
('testing' isAlreadySatisfied sameNameAs:)
('printing' printDetailsOn: printOn:)
('private' install placesToLookForPackagesDo: satisfyRequirementsAndLoadThem)
('comparing' = hash)
('initialization' name:minVersion:minRevision:maxVersion:)
!

