...
Here's the declaration:
Code Block |
---|
include('DCL_IncludeInAllClassHeaderFiles.inc'),once
!include('DCL_System_ErrorManager.inc'),once
include('DCL_System_Threading_CriticalSection.inc'),once
DCL_System_PoolItemQueue queue,type
ItemNumber long
InUse bool
end
DCL_System_Pool Class,Type,Module('DCL_System_Pool.CLW'),Link('DCL_System_Pool.CLW',_DCL_Classes_LinkMode_),Dll(_DCL_Classes_DllMode_)
!Errors &DCL_System_ErrorManager
AccessLock &DCL_System_Threading_CriticalSection
StopOnError bool,protected
StopOnErrorMessage cstring(500)
ItemQ &DCL_System_PoolItemQueue
Construct Procedure()
Destruct Procedure()
GetFreeItemCount procedure,long
GetItemNumber procedure,long
Init procedure(long maxItems)
ReleaseItemNumber procedure(long itemNumber)
SetStopOnError procedure(bool StopOnError,<string errorMessage>)
End |
and here's the method code:
Code Block |
---|
Member
Map
End
Include('DCL_System_Pool.inc'),Once
DCL_System_Pool.Construct Procedure()
code
self.ItemQ &= new DCL_System_PoolItemQueue
self.AccessLock &= new DCL_System_Threading_CriticalSection
self.StopOnErrorMessage = 'All pool items are in use'
DCL_System_Pool.Destruct Procedure()
code
!dispose(self.Errors)
free(self.ItemQ)
dispose(self.ItemQ)
dispose(self.AccessLock)
DCL_System_Pool.GetFreeItemCount procedure!,long
FreeItemCount long
x long
code
self.AccessLock.Wait()
loop x = 1 to records(self.ItemQ)
get(self.ItemQ,x)
if not self.ItemQ.InUse then FreeItemCount += 1.
end
self.AccessLock.Release()
return FreeItemCount
DCL_System_Pool.GetItemNumber procedure!,long
x long
retval long
code
self.AccessLock.Wait()
retval = -1
loop x = 1 to records(self.ItemQ)
get(self.ItemQ,x)
if not self.itemq.InUse
self.ItemQ.InUse = true
put(self.ItemQ)
retval = self.ItemQ.ItemNumber
break
end
end
self.AccessLock.Release()
if retval = -1 and self.StopOnError then Stop(self.StopOnErrorMessage).
return retval
DCL_System_Pool.Init procedure(long maxItems)
x long
code
self.AccessLock.Wait()
free(self.ItemQ)
loop x = 1 to maxItems
self.ItemQ.ItemNumber = x
self.ItemQ.InUse = false
add(self.ItemQ)
end
self.AccessLock.Release()
DCL_System_Pool.ReleaseItemNumber procedure(long itemNumber)
code
self.AccessLock.Wait()
self.ItemQ.ItemNumber = itemNumber
get(self.ItemQ,self.ItemQ.ItemNumber)
if not errorcode()
self.itemq.InUse = false
put(self.ItemQ)
end
self.AccessLock.Release()
DCL_System_Pool.SetStopOnError procedure(bool StopOnError,<string errorMessage>)
code
self.StopOnError = StopOnError
if not omitted(errorMessage) then self.StopOnErrorMessage = errorMessage. |
The logic is pretty easy to follow; when you get a pool element it's marked as in use; when you're done with it and release it the flag is cleared. There's also some support for customizing error handling. And to make the class thread safe I've added a critical section.
Clearly even if this is the only place I ever use the class, having it in its own INC and CLW makes the class easier to work with, and more visible within the DCL library.
Inside DCL_System_IO_AsciiFileManager I added five file layouts:
Code Block |
---|
AsciiFileName1 STRING(MaxPathLength),static AsciiFile1 FILE,DRIVER('ASCII','/CLIP = on'),CREATE,NAME(AsciiFileName1),PRE(AsciiFile1) RECORD Txt STRING(ASCII_IO_RECORD_SIZE) END END AsciiFileName2 STRING(MaxPathLength),static AsciiFile2 FILE,DRIVER('ASCII','/CLIP = on'),CREATE,NAME(AsciiFileName2),PRE(AsciiFile2) RECORD Txt STRING(ASCII_IO_RECORD_SIZE) END END AsciiFileName3 STRING(MaxPathLength),static AsciiFile3 FILE,DRIVER('ASCII','/CLIP = on'),CREATE,NAME(AsciiFileName3),PRE(AsciiFile3) RECORD Txt STRING(ASCII_IO_RECORD_SIZE) END END AsciiFileName4 STRING(MaxPathLength),static AsciiFile4 FILE,DRIVER('ASCII','/CLIP = on'),CREATE,NAME(AsciiFileName4),PRE(AsciiFile4) RECORD Txt STRING(ASCII_IO_RECORD_SIZE) END END AsciiFileName5 STRING(MaxPathLength),static AsciiFile5 FILE,DRIVER('ASCII','/CLIP = on'),CREATE,NAME(AsciiFileName5),PRE(AsciiFile5) RECORD Txt STRING(ASCII_IO_RECORD_SIZE) END END |
I also added a class derived from DCL_System_Pool. This class has a constructor that initializes the pool object with five elements, one for each of five layouts:
Code Block |
---|
AsciiFilePool class(DCL_System_Pool) Construct procedure end AsciiFilePool.Construct procedure code self.Init(5) self.SetStopOnError(true,'DCL_System_IO_AsciiFile: All available ASCII files are in use!') |
Finally I added some code to the DCL_System_IO_AsciiFile constructor to get the first available ASCII file (via the pool object) and then one line to the destructor to release the ASCII file.
Code Block |
---|
DCL_System_IO_AsciiFile.Construct PROCEDURE()
CODE
self.Errors &= new DCL_System_ErrorManager
self.PoolItemNumber = AsciiFilePool.GetItemNumber()
execute self.PoolItemNumber
self.Init(AsciiFile1,AsciiFile1:Record,AsciiFileName1)
self.Init(AsciiFile2,AsciiFile2:Record,AsciiFileName2)
self.Init(AsciiFile3,AsciiFile3:Record,AsciiFileName3)
self.Init(AsciiFile4,AsciiFile4:Record,AsciiFileName4)
self.Init(AsciiFile5,AsciiFile5:Record,AsciiFileName5)
else
stop('Unable to initialize the ASCIIFile instance - if you continue the program will probably crash')
end
DCL_System_IO_AsciiFile.Destruct PROCEDURE()
CODE
SELF.CloseFile()
AsciiFilePool.ReleaseItemNumber(self.PoolItemNumber)
dispose(self.Errors)
|
I've updated the GitHub repository with these changes. If you previously used the ASCIIFileManager approach then you'll need to make two simple changes:
Remove the ASCIIFileManager call
Most importantly, remove the leading & from your DCL_System_IO_AsciiFile declaration to change it from a reference to an instance.
I wish I'd made this change sooner - the refactoring wasn't all that difficult, the class is much, much easier to use, and I've added a new pool tool to the kit in the event I need similar functionality somewhere else.