home *** CD-ROM | disk | FTP | other *** search
/ Visual Basic 4 Power Pack / Visual_Basic4_Power_Pack.bin / vb4files / dbawarco / dbawarco.cls < prev    next >
Encoding:
Text File  |  1996-11-20  |  40.5 KB  |  1,151 lines

  1. VERSION 1.0 CLASS
  2. BEGIN
  3.   MultiUse = -1  'True
  4. END
  5. Attribute VB_Name = "DBAwareCollection"
  6. Attribute VB_Creatable = True
  7. Attribute VB_Exposed = True
  8. Option Explicit
  9. '
  10. ' DBAwareCollection is a database-aware Collection class
  11. '     supplemental for Microsoft Visual Basic 4.0
  12. '
  13. ' (c) Copyright 1995 Abe Fitzpatrick,
  14. '     All Rights Reserved
  15. '     Cannot be distributed or sold without permission
  16. '
  17. ' Application Requirements (Database level)
  18. '   Each of the supported Tables must have a Column as follows:
  19. '       Name: ObjectID
  20. '       Type: Counter
  21. '   Table "DBAwareObjectLinks"
  22. '   Application Tables must be defined or attached to same
  23. '       Database used to contain Table "DBAwareObjectLinks"
  24. '
  25. ' Application Requirements (Project level)
  26. '   DBAwareCollection.cls
  27. '   DBAwareObjectLink.cls
  28. '
  29. ' Application Requirements (Class Module level)
  30. '   Public ObjectID as Long
  31. '       (must be mapped to Table.ObjectID)
  32. '   Private MyCollection As New DBAwareCollection
  33. '   Public MyDatabase as Database
  34. '   Private Sub Class_Initialize:
  35. '       ObjectID = -1
  36. '       possibly more, as needed
  37. '   Public Function InitializeFromRecordSet(RecordSet) as <Me>
  38. '   Public Function InitializeRecordSet(RecordSet) As Long (value of Err)
  39. '   Public Function NewInstanceOfMyClass() as <Me>
  40. '   Public Function ObjectType() As String
  41. '   Public Function TableName() as String
  42. '   Public Function <CollectionName>(Optional ByVal ObjectID As Variant) As Variant
  43. '
  44. ' Application Requirements (General)
  45. '   Application must prepare a Database object for use
  46. '   Application must provide DBAwareCollection with the prepared
  47. '       Database object using any of the following methods:
  48. '       1.  the Public Property Set Database method,
  49. '       2.  the Database:= named parameter of the
  50. '               InstantiateFromDatabase method
  51. '       3.  the Database:= named parameter of the
  52. '               SetDatabaseParameters method
  53. '
  54. ' Application Recommendations (General)
  55. '   Each object which contains a DBAwareCollection should
  56. '       have a corresponding method named "Add<ContainedObjectType>"
  57. '       (e.g., "AddPerson", "AddAddress", etc.)
  58.  
  59. Private pvtSampleObject As Object
  60. Private pvtParentObject As Object
  61. Private pvtCollection As Collection
  62. Private pvtDBAwareObjectLink As New DBAwareObjectLink
  63. Private pvtSampleObjectTableName As String
  64. Private pvtSampleObjectObjectType As String
  65. Private pvtParentObjectTableName As String
  66. Private pvtParentObjectObjectType As String
  67. Private pvtDBAwareObjectLinkTableName As String
  68.  
  69. Private pvtDatabase As Database
  70. Private pvtRecordSet As RecordSet
  71. Private pvtSQLStatement As String
  72. Private pvtWhereClause As String
  73. Private pvtOrderByClause As String
  74. Private pvtCollectionEmulationMode As Boolean
  75. Private pvtRecordSetProvidedByUser As Boolean
  76.  
  77. Private pvtDBHasBeenReferenced As Long
  78. Private pvtHighestObjectID As Long
  79. Private RC As Long
  80.  
  81. Private Const pvtReceiverDoesNotSupportThisMethod = 438
  82. Public Function Add(Optional ByVal Item As Variant, Optional ByVal Key As Variant, Optional Parent As Variant, Optional ByVal After As Variant, Optional ByVal NoInsert As Variant) As DBAwareCollection
  83. Attribute Add.VB_Description = "Add an item to the collection.  Automatically inserts the item into the associated table.  See the VB Programmer's Manual for details"
  84. ' Add the new Item to the collection and
  85. '   return the collection
  86.  
  87.     Dim tempSuppressInsert As Boolean
  88.  
  89.     On Local Error Resume Next
  90.         
  91. ' bullet-proofing
  92.     If IsMissing(Item) Or IsMissing(Parent) Then
  93.         pvtErrorMessage pvtName & " cannot process the '.Add' method for this object because either the 'Item:=' or the 'Parent:=' parameter is missing"
  94.         Set Add = Me
  95.         Exit Function
  96.     End If
  97.  
  98. ' support database-free emulation of the VB Collection Class
  99.     tempSuppressInsert = False
  100.     If Item.TableName = "" Or Err = 438 Then
  101.         pvtCollectionEmulationMode = True
  102.         tempSuppressInsert = True
  103.     End If
  104.     If Not IsMissing(NoInsert) Then
  105.         If NoInsert = True Then
  106.             tempSuppressInsert = True
  107.         End If
  108.     End If
  109.  
  110. ' if in an Insert-capable mode
  111.     If tempSuppressInsert = False Then
  112.  
  113. ' if Item.ObjectID doesn't already have a value
  114. '   (meaning that it has never been inserted in
  115. '   the database),
  116.         If Item.ObjectID <= 0 Then
  117.         
  118. ' insert Item and set Item.ObjectID
  119.             Item.ObjectID = pvtDBInsert( _
  120.                                  Item:=Item)
  121.         End If
  122.  
  123. ' else, if the ObjectID doesn't already have a value
  124. '   assign an artificial ObjectID
  125.     Else
  126.         pvtHighestObjectID = pvtHighestObjectID + 1
  127.         Item.ObjectID = pvtHighestObjectID
  128.     End If
  129.             
  130. ' save the HighestObjectID encountered
  131.     If Item.ObjectID > pvtHighestObjectID Then
  132.         pvtHighestObjectID = Item.ObjectID
  133.     End If
  134.  
  135. ' use the Key:= if it was provided and it was
  136. '   of Type(Long)
  137.     If IsMissing(Key) Or Key = 0 Or Err = 13 Then
  138.         pvtAddItemToCollection _
  139.             Item:=Item, _
  140.             Key:=CStr(Item.ObjectID), _
  141.             After:=After
  142.  
  143. ' else, use the Item.ObjectID
  144.     Else
  145.         pvtAddItemToCollection _
  146.             Item:=Item, _
  147.             Key:=Key, _
  148.             After:=After
  149.     End If
  150.  
  151. ' link the Item to its Parent object
  152. '   (in the database)
  153.     If Not IsMissing(Parent) And pvtCollectionEmulationMode = False Then
  154.         RC = pvtDBAwareObjectLink.LinkParentObjectToChildObject( _
  155.             Parent:=Parent, _
  156.             Child:=Item)
  157.     End If
  158.         
  159.     Set Add = Me
  160. End Function
  161.  
  162.  
  163. Public Function AddWithoutDBInsert(Optional ByVal Item As Variant, Optional ByVal Key As Variant, Optional Parent As Variant, Optional ByVal After As Variant, Optional ByVal NoInsert As Variant) As DBAwareCollection
  164. Attribute AddWithoutDBInsert.VB_Description = "Add an item to the collection.  Does not automatically insert the item into the associated table"
  165. ' Add Item to the DBAwareCollection, but without
  166. '   inserting it into the database
  167.  
  168.     Set AddWithoutDBInsert = Add( _
  169.                                     Item:=Item, _
  170.                                     Key:=Key, _
  171.                                     Parent:=Parent, _
  172.                                     After:=After, _
  173.                                     NoInsert:=True)
  174.  
  175. End Function
  176.  
  177. Public Function CloneRecordSet() As RecordSet
  178. Attribute CloneRecordSet.VB_Description = "Returns a Clone of the internally maintained RecordSet object"
  179.     Set CloneRecordSet = pvtRecordSet.Clone()
  180. End Function
  181.  
  182. Public Function CollectionIndex(Optional ByVal Item As Variant) As Long
  183. Attribute CollectionIndex.VB_Description = "Returns the index (1 - n) of the item in the collection"
  184. ' Return the Collection Index of Item
  185.  
  186.     Dim tempItem As Object
  187.     Dim I As Long
  188.  
  189.     On Local Error Resume Next
  190.     
  191.     I = 1
  192.     For Each tempItem In pvtCollection
  193.         If tempItem.ObjectID = Item.ObjectID Then
  194.             If Err = 0 Then ' for some reason this doesn't work if placed in the above statement as an "And"
  195.                 CollectionIndex = I
  196.                 Exit Function
  197.             End If
  198.         End If
  199.         
  200.         I = I + 1
  201.     Next tempItem
  202.  
  203.     CollectionIndex = -1
  204. End Function
  205.  
  206. Public Function Count() As Long
  207. Attribute Count.VB_Description = "Returns a count of the number of items currently in the collection.  See the VB Programmer's Manual for details"
  208.     Count = pvtCollection.Count
  209. End Function
  210.  
  211. Public Property Set Database(Database As Database)
  212. Attribute Database.VB_Description = "Sets the database property"
  213.  
  214.     If Not IsMissing(Database) Then
  215.         pvtReceiveGeneralParameters _
  216.             Database:=Database
  217.             
  218.         pvtCollectionEmulationMode = False
  219.     End If
  220.  
  221. End Property
  222.  
  223. Public Function DatabaseHasBeenReferenced() As Long
  224. Attribute DatabaseHasBeenReferenced.VB_Description = "Returns turue or false, depending on whether or not the DBAwareCollection has referenced the database to attempt to instantiate the collection of contained objects"
  225. ' Returns aBoolean, depending on whether or not the
  226. '   Database has been referenced as of yet for this
  227. '   DBAwareCollection
  228.     
  229.     DatabaseHasBeenReferenced = pvtDBHasBeenReferenced
  230. End Function
  231.  
  232. Public Function InstantiateFromDatabase(Optional ByVal Database As Variant, Optional ByVal SampleObject As Variant, Optional ByVal Parent As Variant, Optional ByVal WhereClause As Variant, Optional ByVal SQL As Variant, Optional ByVal OrderByClause As Variant) As DBAwareCollection
  233. Attribute InstantiateFromDatabase.VB_Description = "Returns a DBAwareCollection which has been instantiated with a collection of instantiated objects, according to the contents of the associated table"
  234. ' Returns a DBAwareCollection of objects which have been
  235. '   instantiated from data found in a database
  236. '   table meeting the criteria specified in any of
  237. '   the following methods:
  238. '       a complete SQL statement can be provided;
  239. '       a Where Clause can be provided;
  240. '       a Parent Object can be provided;
  241.  
  242.     Dim tempRow As Object
  243.     Dim newChildObject As Object
  244.     Dim tempIndex As Long
  245.     
  246.     On Local Error Resume Next
  247.     
  248.     Set InstantiateFromDatabase = Nothing
  249.     pvtRecordSetProvidedByUser = False
  250.  
  251. ' test SampleObject for Database-readiness
  252.     If Not IsMissing(SampleObject) Then
  253.         If (SampleObject.TableName = "" Or Err = 438) Then
  254.             pvtCollectionEmulationMode = True
  255.         End If
  256.     End If
  257.  
  258.     pvtReceiveGeneralParameters _
  259.         Database:=Database, _
  260.         SampleObject:=SampleObject, _
  261.         Parent:=Parent, _
  262.         WhereClause:=WhereClause, _
  263.         OrderByClause:=OrderByClause, _
  264.         SQL:=SQL
  265.  
  266. ' determine the usability of the parameters
  267.     If Not pvtCheckDatabase() _
  268.     Or Not pvtCheckSQLAccessibility() _
  269.     Then
  270.         Exit Function
  271.     End If
  272.  
  273. ' open a RecordSet containing the desired rows
  274.     Set pvtRecordSet = pvtDBSelect( _
  275.                             pvtCreateSQLStatement())
  276.  
  277. ' create the objects from the contents of the
  278. '   RecordSet
  279.     Set pvtCollection = _
  280.         pvtInstantiateObjectsFromRecordSet( _
  281.             RecordSet:=pvtRecordSet, _
  282.             Collection:=pvtCollection)
  283.  
  284. InstantiateFromDatabase_Exit:
  285.     Set InstantiateFromDatabase = Me
  286. End Function
  287.  
  288.  
  289. Public Function Item(Optional ByVal ObjectID As Variant) As Variant
  290. Attribute Item.VB_Description = "Returns either the entire DBAwareCollection (as a collection) or a specific item.  See the VB Programmer's Manual for details"
  291. ' Returns either the entire collection or a
  292. '   specific item in the collection
  293.  
  294.     On Local Error Resume Next
  295.  
  296. ' determine the usability of the current state
  297.     If Not pvtCollectionEmulationMode Then
  298.         If Not pvtCheckDatabase() _
  299.         Or Not pvtCheckSQLAccessibility() _
  300.         Or Not pvtCheckCollection() _
  301.         Then
  302.             Exit Function
  303.         End If
  304.     End If
  305.     
  306. ' check for a request for a specific Object
  307.     If Not IsMissing(ObjectID) Then
  308.         Set Item = pvtCollection.Item(ObjectID)
  309.         If Err = 5 Then
  310.             Set Item = Nothing
  311.             Exit Function
  312.         End If
  313.     Else
  314.         Set Item = Me
  315.     End If
  316. End Function
  317.  
  318.  
  319. Public Function Name() As String
  320. Attribute Name.VB_Description = "Returns the name of the DBAwareCollection"
  321. ' Returns "DBAwareCollection", the name of
  322. '   this object
  323.     
  324.     Name = pvtName
  325. End Function
  326.  
  327. Private Function pvtAddItemToCollection(Optional ByVal Item As Variant, Optional ByVal Key As Variant, Optional ByVal After As Variant) As Collection
  328. Attribute pvtAddItemToCollection.VB_Description = "(Private) adds an item to the internally managed collection"
  329. ' Return the DBAwareCollection after having added
  330. '   Item.  Take into account the impact of the
  331. '   After parameter
  332.  
  333.     Dim tempAfter As Long
  334.     
  335.     On Local Error Resume Next
  336.     
  337. ' set default After value
  338.     tempAfter = pvtCollection.Count
  339.     
  340. ' use any specified After value
  341.     If Not IsMissing(After) Then
  342.         If After <= pvtCollection.Count Then
  343.             tempAfter = After
  344.         End If
  345.     End If
  346.     
  347. ' insert somewhere after the first item
  348.     If tempAfter > 0 Then
  349.         pvtCollection.Add _
  350.             Item:=Item, _
  351.             Key:=CStr(Item.ObjectID), _
  352.             After:=tempAfter
  353.             
  354. ' insert before the first item
  355.     ElseIf pvtCollection.Count > 0 Then
  356.         pvtCollection.Add _
  357.             Item:=Item, _
  358.             Key:=CStr(Item.ObjectID), _
  359.             Before:=1
  360.             
  361. ' insert as the first item
  362.     Else
  363.         pvtCollection.Add _
  364.             Item:=Item, _
  365.             Key:=CStr(Item.ObjectID)
  366.     End If
  367.  
  368.     Set pvtAddItemToCollection = pvtCollection
  369. End Function
  370.  
  371. Private Function pvtBuildSQLStatementFromWhereClause(Optional WhereClause As Variant) As String
  372. Attribute pvtBuildSQLStatementFromWhereClause.VB_Description = "(Private) returns an SQL Select statement which includes a user-specified Where clause.  The SQL statement should be appropriate for retrieving all of the items contained within the specified parent object"
  373. ' Return an SQL Statement which uses WhereClause to
  374. '   select the desired rows
  375.     
  376.     Dim SQLStatement As String
  377.     
  378.     On Local Error Resume Next
  379.     
  380. ' ask the SampleObject for certain critical services
  381.     pvtSampleObjectTableName = pvtSampleObject.TableName
  382.     If Err = pvtReceiverDoesNotSupportThisMethod Then
  383.         pvtErrorMessage "Object does not support the method 'TableName'." & vbCrLf & "Object cannot be supported by " & pvtName & " without this method."
  384.     End If
  385.  
  386.     pvtSampleObjectObjectType = pvtSampleObject.ObjectType
  387.     If Err = pvtReceiverDoesNotSupportThisMethod Then
  388.         pvtErrorMessage "Object does not support the method 'ObjectType'." & vbCrLf & "Object cannot be supported by " & pvtName & " without this method."
  389.     End If
  390.     
  391.     On Local Error Resume Next
  392.     
  393.     SQLStatement = _
  394.         "SELECT DISTINCTROW " & _
  395.         pvtSampleObjectTableName & ".* FROM " & _
  396.         pvtSampleObjectTableName & " WHERE " & _
  397.         WhereClause
  398.     SQLStatement = SQLStatement & _
  399.         pvtConcatenateOrderByClause( _
  400.             SQL:=SQLStatement, _
  401.             OrderByClause:=pvtOrderByClause)
  402.        
  403.     pvtBuildSQLStatementFromWhereClause = SQLStatement
  404. End Function
  405.  
  406.  
  407. Private Function pvtBuildSQLStatementFromParent(Optional ByVal Parent As Variant) As String
  408. Attribute pvtBuildSQLStatementFromParent.VB_Description = "(Private) returns an SQL Select statement which can be used to retrieve all of the items contained within the specified parent object"
  409. ' Return an SQL Statement which retrieves rows
  410. '   of the child table based on the value of
  411. '   the Parent object
  412.     
  413.     Dim SQLStatement As String
  414.     
  415. ' ask the SampleObject for certain critical services
  416.     pvtSampleObjectTableName = pvtSampleObject.TableName
  417.     If Err = pvtReceiverDoesNotSupportThisMethod Then
  418.         pvtErrorMessage "The provided 'Sample' object does not support the method 'TableName'." & vbCrLf & "Object cannot be supported by " & pvtName & " without this method."
  419.     End If
  420.     
  421.     pvtSampleObjectObjectType = pvtSampleObject.ObjectType
  422.     If Err = pvtReceiverDoesNotSupportThisMethod Then
  423.         pvtErrorMessage "The provided 'Sample' object does not support the method 'ObjectType'." & vbCrLf & "Object cannot be supported by " & pvtName & " without this method."
  424.     End If
  425.     
  426.     pvtParentObjectTableName = pvtParentObject.TableName
  427.     If Err = pvtReceiverDoesNotSupportThisMethod Then
  428.         pvtErrorMessage "The provided 'Parent' object does not support the method 'TableName'." & vbCrLf & "Object cannot be supported by " & pvtName & " without this method."
  429.     End If
  430.     
  431.     pvtParentObjectObjectType = pvtParentObject.ObjectType
  432.     If Err = pvtReceiverDoesNotSupportThisMethod Then
  433.         pvtErrorMessage "The provided 'Parent' object does not support the method 'ObjectType'." & vbCrLf & "Object cannot be supported by " & pvtName & " without this method."
  434.     End If
  435.     
  436.     pvtDBAwareObjectLinkTableName = pvtDBAwareObjectLink.TableName()
  437.     If Err = pvtReceiverDoesNotSupportThisMethod Then
  438.         pvtErrorMessage "The DBAwareObjectLink Object is invalid (is missing method 'TableName'.)" & vbCrLf & "Object cannot be supported by " & pvtName & " without this method."
  439.     End If
  440.         
  441.     On Local Error Resume Next
  442.     
  443. ' (SQL Statement modeled in MS Access)
  444. 'SELECT DISTINCTROW
  445. '        Persons.* FROM (DBAwareObjectLinks INNER JOIN
  446. '        Company ON DBAwareObjectLinks.FromObjectID =
  447. '       Company.ObjectID) INNER JOIN
  448. '       Persons ON DBAwareObjectLinks.ToObjectID =
  449. '       Persons.ObjectID WHERE ((DBAwareObjectLinks.FromObjectType="
  450. '       Company") AND (DBAwareObjectLinks.ToObjectType="
  451. '       Person") AND (
  452. '       Company.ObjectID=
  453. '       1));
  454.     SQLStatement = _
  455.         "SELECT DISTINCTROW " & _
  456.         pvtSampleObjectTableName & ".* FROM (" & pvtDBAwareObjectLinkTableName & " INNER JOIN " & _
  457.         pvtParentObjectTableName & " ON " & pvtDBAwareObjectLinkTableName & ".FromObjectID = " & _
  458.         pvtParentObjectTableName & ".ObjectID) INNER JOIN " & _
  459.         pvtSampleObjectTableName & " ON " & pvtDBAwareObjectLinkTableName & ".ToObjectID = " & _
  460.         pvtSampleObjectTableName & ".ObjectID WHERE ((" & pvtDBAwareObjectLinkTableName & ".FromObjectType='"
  461.     SQLStatement = SQLStatement & _
  462.         pvtParentObjectObjectType & "') AND (" & pvtDBAwareObjectLinkTableName & ".ToObjectType='" & _
  463.         pvtSampleObjectObjectType & "') AND (" & _
  464.         pvtParentObjectTableName & ".ObjectID=" & _
  465.         CStr(pvtParentObject.ObjectID) & "))"
  466.     SQLStatement = SQLStatement & _
  467.         pvtConcatenateOrderByClause( _
  468.             SQL:=SQLStatement, _
  469.             OrderByClause:=pvtOrderByClause)
  470.        
  471.     pvtBuildSQLStatementFromParent = SQLStatement
  472. End Function
  473.  
  474. Private Function pvtCheckCollection() As Long
  475. Attribute pvtCheckCollection.VB_Description = "(Private) internal function"
  476. ' Verify that the pvtCollection has been
  477. '   instantiated
  478.  
  479.     If pvtCollection Is Nothing Then
  480.         pvtErrorMessage pvtName & " cannot provide meaningfuly functionality because the collection has not been built."
  481.         pvtCheckCollection = False
  482.         Exit Function
  483.     End If
  484.  
  485.     pvtCheckCollection = True
  486. End Function
  487.  
  488. Private Function pvtCheckRecordSet() As Long
  489. Attribute pvtCheckRecordSet.VB_Description = "(Private) internal function"
  490. ' Verify that the RecordSet has been initialized
  491.  
  492.     If pvtRecordSet Is Nothing Then
  493.         pvtErrorMessage pvtName & " cannot insert data into the database because the collection was never built."
  494.         pvtCheckRecordSet = False
  495.         Exit Function
  496.     End If
  497.  
  498.     pvtCheckRecordSet = True
  499. End Function
  500.  
  501. Private Function pvtCheckSQLAccessibility() As Long
  502. Attribute pvtCheckSQLAccessibility.VB_Description = "(Private) internal function"
  503. ' Determine whether or not the desired table data
  504. '   can be derived, given the information provided
  505.     
  506.     If (pvtParentObject Is Nothing _
  507.     And pvtWhereClause = "" _
  508.     And pvtSQLStatement = "" _
  509.     ) Then
  510.         pvtErrorMessage pvtName & " cannot perform object instantiations without having been provided with either an SQL:=, a WhereClause:= or a Parent:= ."
  511.         pvtCheckSQLAccessibility = False
  512.         Exit Function
  513.     End If
  514.  
  515.     pvtCheckSQLAccessibility = True
  516. End Function
  517.  
  518.  
  519. Private Function pvtConcatenateOrderByClause(Optional ByVal SQL As Variant, Optional ByVal OrderByClause As Variant) As String
  520. ' Return eith er null string or an OrderBy clause
  521. '   including the leading "Order By"
  522.  
  523.     If OrderByClause <> "" Then
  524.         pvtConcatenateOrderByClause = _
  525.             " ORDER BY " & _
  526.             OrderByClause
  527.     Else
  528.         pvtConcatenateOrderByClause = ""
  529.     End If
  530.  
  531. End Function
  532.  
  533.  
  534. Private Function pvtCreateSQLStatement() As String
  535. Attribute pvtCreateSQLStatement.VB_Description = "(Private) internal function"
  536. ' Evaluate the available information and create
  537. '   an SQL Statement to access the desired rows
  538.  
  539. ' decide how to acquire an SQL Statement:
  540. '   first try the SQL Statement
  541.     If pvtSQLStatement = "" Then
  542.     
  543. '   next, build the SQL Statement from the
  544. '       Where Clause
  545.         If pvtWhereClause <> "" Then
  546.             pvtSQLStatement = _
  547.                 pvtBuildSQLStatementFromWhereClause( _
  548.                     WhereClause:=pvtWhereClause)
  549.  
  550. '   otherwise, use the Parent Object
  551.         Else
  552.             pvtSQLStatement = _
  553.                 pvtBuildSQLStatementFromParent( _
  554.                     Parent:=pvtParentObject)
  555.         End If
  556.     End If
  557.  
  558.     pvtCreateSQLStatement = pvtSQLStatement
  559. End Function
  560.  
  561. Private Function pvtDBInsert(Optional ByVal Item As Variant) As Long
  562. Attribute pvtDBInsert.VB_Description = "(Private) inserts the item from the associated table"
  563. ' Insert Item into the table, then return
  564. '   its ObjectID value
  565.  
  566.     Dim tempObjectErr As Long
  567.     Dim tempBookMark As String
  568.  
  569.     On Local Error Resume Next
  570.     
  571.     If Not pvtCheckRecordSet() Then
  572.         pvtDBInsert = False
  573.         Exit Function
  574.     End If
  575.     
  576. ' prepare a new record area
  577.     pvtRecordSet.AddNew
  578.  
  579. ' have the Item populate the RecordSet.
  580. '   check for errors on that end
  581.     Err = 0
  582.     tempObjectErr = Item.InitializeRecordSet(pvtRecordSet)
  583.     If tempObjectErr <> 0 _
  584.     Or Err <> 0 Then
  585.         If Err = pvtReceiverDoesNotSupportThisMethod Or tempObjectErr = pvtReceiverDoesNotSupportThisMethod Then
  586.             pvtErrorMessage "Object does not support the method 'InitializeRecordSet'." & vbCrLf & "Object cannot be supported by " & pvtName & " without this method."
  587.         End If
  588.         pvtDBInsert = 0
  589.         Exit Function
  590.     End If
  591.  
  592. ' execute the update
  593.     pvtRecordSet.Update
  594.  
  595. ' return the ObjectID
  596. '    pvtRecordSet.Requery
  597.     If Err = 0 Then
  598.         tempBookMark = pvtRecordSet.LastModified
  599.         pvtRecordSet.Bookmark = tempBookMark
  600.     End If
  601.     pvtDBInsert = pvtRecordSet("ObjectID")
  602. End Function
  603.  
  604.  
  605. Private Function pvtErrorMessage(Optional ByVal ErrorMessage As Variant) As Long
  606. Attribute pvtErrorMessage.VB_Description = "(Private) internal function"
  607.  
  608.     Dim RC As Long
  609.  
  610.     RC = MsgBox( _
  611.         ErrorMessage & vbCrLf & "Err=" & Err & ", Msg=" & Error(Err), _
  612.         vbOK + vbExclamation, _
  613.         pvtName & " Run-Time Error")
  614.     Err = 0
  615.     pvtErrorMessage = RC
  616. End Function
  617. Private Function pvtDBSelect(Optional ByVal SQL As Variant) As RecordSet
  618. Attribute pvtDBSelect.VB_Description = "(Private) selects the contained items from the associated table"
  619. ' Process the SQL Select statement and return
  620. '   a RecordSet
  621.  
  622. ' open a RecordSet containing the desired rows
  623.     Set pvtDBSelect = pvtDatabase. _
  624.                         OpenRecordset( _
  625.                             SQL, _
  626.                             dbOpenDynaset)
  627.     
  628.     pvtDBHasBeenReferenced = True
  629. End Function
  630.  
  631. Private Function pvtDBUpdate(Optional ByVal Item As Variant) As DBAwareCollection
  632. Attribute pvtDBUpdate.VB_Description = "(Private) updates the item from the associated table"
  633. ' Update the Item in the table
  634.  
  635.     On Local Error Resume Next
  636.     
  637.     If pvtRecordSet Is Nothing Then
  638.         pvtErrorMessage pvtName & " cannot update data in the database because the collection was never built."
  639.         Set pvtDBUpdate = Nothing
  640.         Exit Function
  641.     End If
  642.     
  643. ' prepare a new record area
  644.     pvtRecordSet.Edit
  645.  
  646. ' have the Item populate the RecordSet
  647.     Item.InitializeRecordSet (pvtRecordSet)
  648.     If Err = pvtReceiverDoesNotSupportThisMethod Then
  649.         pvtErrorMessage "Object does not support the method 'InitializeRecordSet'." & vbCrLf & "Object cannot be supported by " & pvtName & " without this method."
  650.     End If
  651.     
  652. ' execute the update
  653.     pvtRecordSet.Update
  654.  
  655. ' return the colection
  656.     Set pvtDBUpdate = Me
  657. End Function
  658.  
  659. Private Function pvtDBDelete() As Long
  660. Attribute pvtDBDelete.VB_Description = "(Private) deletes the item from the associated table"
  661. ' Update the Item in the table
  662.  
  663.     On Local Error Resume Next
  664.     
  665. ' bullet-proofing
  666.     If pvtRecordSet Is Nothing Then
  667.         pvtErrorMessage pvtName & " cannot delete data in the database because the collection was never built."
  668.         Set pvtDBUpdate = Nothing
  669.         Exit Function
  670.     End If
  671.         
  672. ' delete the record
  673.     Err = 0
  674.     pvtRecordSet.Delete
  675.  
  676.     If Err = 0 Then
  677.         pvtDBDelete = True
  678.     Else
  679.         pvtDBDelete = False
  680.     End If
  681. End Function
  682.  
  683. Private Function pvtInstantiateObjectsFromRecordSet(Optional ByVal RecordSet As Variant, Optional ByVal Collection) As Collection
  684. ' Return a Collection of objects which have been
  685. '   instantiated from data found in RecordSet
  686.     
  687.     Dim tempRow As Object
  688.     Dim newChildObject As Object
  689.     Dim tempIndex As Long
  690.     Dim tempCollection As New Collection
  691.  
  692.     On Local Error Resume Next
  693.     
  694. ' process the RecordSet
  695.     While Not RecordSet.EOF
  696.     
  697. ' determine whether or not the retrieved row
  698. '   has an instantiated object already in the
  699. '   DBAwareCollection
  700.         tempIndex = CollectionIndex( _
  701.             Item:=CStr(RecordSet("ObjectID")))
  702.         If tempIndex > 0 Then
  703.             Set newChildObject = _
  704.                 pvtCollection(tempIndex)
  705.         
  706. ' else, must instantiate a new object of the class
  707.         Else
  708.         
  709. ' have the Sample Object return an instantiated
  710. '   copy of itself
  711.             Set newChildObject = _
  712.                 pvtSampleObject.NewInstanceOfMyClass
  713.             If Err = pvtReceiverDoesNotSupportThisMethod Then
  714.                 pvtErrorMessage "Object does not support the method 'NewInstanceOfMyClass'." & vbCrLf & "Object cannot be supported by " & pvtName & " without this method."
  715.                 GoTo pvtInstantiateObjectsFromRecordSet_Error
  716.             End If
  717.         End If
  718.  
  719. ' have the new instantiated object copy populate
  720. '   itself from this RecordSet row
  721.         newChildObject _
  722.             .InitializeFromRecordSet (RecordSet)
  723.         If Err = pvtReceiverDoesNotSupportThisMethod Then
  724.             pvtErrorMessage "Object does not support the method 'InitializeFromRecordSet'." & vbCrLf & "Object cannot be supported by " & pvtName & " without this method."
  725.             GoTo pvtInstantiateObjectsFromRecordSet_Exit
  726.         End If
  727.         
  728. ' add the object to the collection
  729.         tempCollection.Add _
  730.             Item:=newChildObject, _
  731.             Key:=CStr(newChildObject.ObjectID)
  732.         
  733.         RecordSet.MoveNext
  734.     Wend
  735.         
  736.     GoTo pvtInstantiateObjectsFromRecordSet_Exit
  737.     
  738. pvtInstantiateObjectsFromRecordSet_Error:
  739.     
  740. pvtInstantiateObjectsFromRecordSet_Exit:
  741.     Set pvtInstantiateObjectsFromRecordSet = _
  742.         tempCollection
  743. End Function
  744.  
  745. Private Function pvtName() As String
  746. Attribute pvtName.VB_Description = "(Private) internal function"
  747.     pvtName = "DBAwareCollection"
  748. End Function
  749.  
  750. Private Function pvtCheckDatabase() As Integer
  751. Attribute pvtCheckDatabase.VB_Description = "(Private) internal function"
  752. ' Determine whether or not the database has been
  753. '   specified
  754.  
  755.     If pvtDatabase Is Nothing Then
  756.         pvtErrorMessage pvtName & " cannot function without having been provided the name of the database.  Use the 'Database:=' parameter of the InstantiateFromDatabase method to specify the database."
  757.         pvtCheckDatabase = False
  758.         Exit Function
  759.     End If
  760.  
  761.     pvtCheckDatabase = True
  762. End Function
  763.  
  764. Private Sub pvtReceiveGeneralParameters(Optional ByVal Database As Variant, Optional ByVal SampleObject As Variant, Optional ByVal Parent As Variant, Optional ByVal WhereClause As Variant, Optional ByVal SQL As Variant, Optional ByVal OrderByClause As Variant, Optional CollectionEmulationMode As Variant)
  765. Attribute pvtReceiveGeneralParameters.VB_Description = "(Private) internal function"
  766. ' Receive user-defined parameters
  767.  
  768.     If Not IsMissing(Database) Then
  769.         Set pvtDatabase = Database
  770.         pvtCollectionEmulationMode = False
  771.     End If
  772.     
  773.     If Not IsMissing(SampleObject) Then
  774.         Set pvtSampleObject = SampleObject
  775.     End If
  776.     
  777.     If Not IsMissing(Parent) Then
  778.         Set pvtParentObject = Parent
  779.     End If
  780.     
  781.     If Not IsMissing(WhereClause) Then
  782.         pvtWhereClause = WhereClause
  783.         pvtCollectionEmulationMode = False
  784.     End If
  785.     
  786.     If Not IsMissing(SQL) Then
  787.         pvtSQLStatement = SQL
  788.         pvtCollectionEmulationMode = False
  789.     End If
  790.     
  791.     If Not IsMissing(OrderByClause) Then
  792.         pvtOrderByClause = OrderByClause
  793.         pvtCollectionEmulationMode = False
  794.     End If
  795.  
  796.     If Not IsMissing(CollectionEmulationMode) Then
  797.         pvtCollectionEmulationMode = CollectionEmulationMode
  798.     End If
  799.  
  800. ' pass-along signigicant values to DBAwareObjectLink
  801.     pvtDBAwareObjectLink. _
  802.         SetDatabaseParameters _
  803.             Database:=Database, _
  804.             DBAwareCollection:=Me, _
  805.             CollectionEmulationMode:=pvtCollectionEmulationMode
  806.  
  807.     If Err = pvtReceiverDoesNotSupportThisMethod Then
  808.         pvtErrorMessage "The DBAwareObjectLink Object is invalid (is missing method 'SetDatabaseParameters'.)" & vbCrLf & "Object cannot be supported by " & pvtName & " without this method."
  809.     End If
  810.  
  811. End Sub
  812.  
  813.  
  814. Public Function RefreshRecordSet() As RecordSet
  815. Attribute RefreshRecordSet.VB_Description = "Refreshes the internally managed RecordSet which equates to the rows of the table which were used to instantiate the contained items.  Returns the RecordSet"
  816. ' Return the refreshed RecordSet after having refreshed its
  817. '   contents by again using the same SQL-oriented
  818. '   information used previously to generate the current
  819. '   DBAwareCollection state.
  820. ' Note: users of the method "InstantiateFromRecordSet"
  821. '   should not use this method
  822.  
  823.     If pvtRecordSetProvidedByUser Then
  824.         pvtErrorMessage pvtName & " cannot execute the .RefreshRecordSet method because the current RecordSet was user-provided.  Only those RecordSets created by " & pvtName & " can be supported by the .RefreshRecordSet method."
  825.         Set Refresh = Me
  826.     End If
  827.  
  828.     Refresh
  829.     
  830.     Set RefreshRecordSet = pvtRecordSet
  831. End Function
  832.  
  833. Public Sub SetDatabaseParameters(Optional ByVal Database As Variant, Optional ByVal SampleObject As Variant, Optional ByVal Parent As Variant, Optional ByVal WhereClause As Variant, Optional ByVal SQL As Variant, Optional ByVal OrderByClause As Variant, Optional ByVal CollectionEmulationMode As Variant)
  834. Attribute SetDatabaseParameters.VB_Description = "Allows the user to set all of the database-related parameters in a single statement"
  835. ' Receive any database parameters the application
  836. '   program wishes to set en masse
  837.  
  838.     pvtReceiveGeneralParameters _
  839.         Database:=Database, _
  840.         SampleObject:=SampleObject, _
  841.         Parent:=Parent, _
  842.         WhereClause:=WhereClause, _
  843.         OrderByClause:=OrderByClause, _
  844.         SQL:=SQL, _
  845.         CollectionEmulationMode:=CollectionEmulationMode
  846.  
  847. End Sub
  848.  
  849. Public Function InstantiateFromRecordSet(Optional ByVal RecordSet As Variant, Optional ByVal Database As Variant, Optional ByVal SampleObject As Variant, Optional ByVal Parent As Variant, Optional ByVal WhereClause As Variant, Optional ByVal SQL As Variant, Optional ByVal OrderByClause As Variant) As DBAwareCollection
  850. Attribute InstantiateFromRecordSet.VB_Description = "Sets the internally managed RecordSet"
  851. ' Sets a DBAwareCollection object which has been
  852. '   instantiated as a collection of objects
  853. '   represented by the contents of RecordSet
  854. ' Note: use of this method requires that the
  855. '   caller maintain all of the necessary object
  856. '   containment information, since DBAwareCollection
  857. '   is unaware of the techniques used to derive the
  858. '   contents of RecordSet
  859.     
  860.     On Local Error Resume Next
  861.  
  862.     pvtRecordSetProvidedByUser = True
  863.  
  864. ' test SampleObject for Database-readiness
  865.     If Not IsMissing(SampleObject) Then
  866.         If (SampleObject.TableName = "" Or Err = 438) Then
  867.             pvtCollectionEmulationMode = True
  868.         End If
  869.     End If
  870.  
  871.     pvtReceiveGeneralParameters _
  872.         Database:=Database, _
  873.         SampleObject:=SampleObject, _
  874.         Parent:=Parent, _
  875.         WhereClause:=WhereClause, _
  876.         OrderByClause:=OrderByClause, _
  877.         SQL:=SQL
  878.  
  879. ' reference the RecordSet containing the desired rows
  880.     Set pvtRecordSet = RecordSet
  881.  
  882. ' create the objects from the contents of the RecordSet
  883.     Set pvtCollection = _
  884.         pvtInstantiateObjectsFromRecordSet( _
  885.             RecordSet:=pvtRecordSet, _
  886.             Collection:=pvtCollection)
  887.     
  888.     Set InstantiateFromRecordSet = Me
  889. End Function
  890.  
  891. Public Function RecordSet() As RecordSet
  892. ' Returns a DataControl-ready RecordSet object
  893. '   which pertains to the collection of objects
  894. '   instantiated and contained within this
  895. '   DBAwareCollection
  896.     
  897.     If pvtCollectionEmulationMode Then
  898.         Set RecordSet = Nothing
  899.         Exit Function
  900.     End If
  901.     
  902.     Set RecordSet = pvtRecordSet
  903. End Function
  904. Public Function Refresh() As DBAwareCollection
  905. Attribute Refresh.VB_Description = "Refreshes the internally managed RecordSet which equates to the rows of the table which were used to instantiate the contained items.  Returns the DBAwareCollection"
  906. ' Return a refreshed DBAwareCollection, using again
  907. '   the same SQL-oriented information used previously
  908. '   to generate the current DBAwareCollection state.
  909. ' Note: users of the method "InstantiateFromRecordSet"
  910. '   should not use this method
  911.  
  912.     If pvtRecordSetProvidedByUser Then
  913. '        pvtErrorMessage pvtName & " cannot execute the .Refresh method because the current RecordSet was user-provided.  Only those RecordSets created by " & pvtName & " can be supported by the .Refresh method."
  914.         Set Refresh = Me
  915.     End If
  916.  
  917.     If pvtCollectionEmulationMode Then
  918.         Set Refresh = Me
  919.     Else
  920.         Set Refresh = InstantiateFromDatabase()
  921.     End If
  922. End Function
  923.  
  924. Public Function Remove(Optional ByVal Item As Variant, Optional ByVal Key As Variant, Optional ByVal NoDelete As Variant) As DBAwareCollection
  925. Attribute Remove.VB_Description = "Removes the item from the DBAwareCollection and (if there are no more parents referencing the item) the associated table"
  926. ' Remove the Item from the DBAwareCollection and
  927. '   return the DBAwareCollection
  928.  
  929.     Dim tempCountOfParentObjectLinksToItem As Long
  930.     Dim tempSuppressDelete As Boolean
  931.  
  932.     On Local Error Resume Next
  933.  
  934. ' bullet-proofing
  935.     If IsMissing(Item) Then
  936.         Remove = Me
  937.         Exit Function
  938.     End If
  939.     tempSuppressDelete = False
  940.     If Not IsMissing(NoDelete) Then
  941.         tempSuppressDelete = NoDelete
  942.     End If
  943.  
  944. ' sever the link from pvtParentObject to Item
  945.     If Not pvtCollectionEmulationMode Then
  946.         pvtDBAwareObjectLink. _
  947.             DeleteParentObjectLinksToItem _
  948.                 Child:=Item, _
  949.                 Parent:=pvtParentObject
  950.         If Err = pvtReceiverDoesNotSupportThisMethod Then
  951.             pvtErrorMessage "The DBAwareObjectLink Object is invalid (is missing method 'DeleteParentObjectLinksToItem'.)" & vbCrLf & "Object cannot be supported by " & pvtName & " without this method."
  952.         End If
  953.     End If
  954.     
  955. ' if not operating in Collection-emulation mode
  956.     If tempSuppressDelete = False And Not pvtCollectionEmulationMode Then
  957.  
  958. ' count the number of Parent objects which currently
  959. '   reference Item
  960.         tempCountOfParentObjectLinksToItem = _
  961.             pvtDBAwareObjectLink. _
  962.                 CountOfParentObjectLinksToItem( _
  963.                    Child:=Item, _
  964.                    Parent:=pvtParentObject)
  965.  
  966. ' if none, then it's OK to actually remove Item
  967. '   from the database
  968.         If tempCountOfParentObjectLinksToItem = 0 Then
  969.  
  970. ' verify that Item actually appears in the RecordSet
  971.             If pvtFindItemInRecordSet(Item:=Item) Then
  972.         
  973. ' delete Item from the database
  974. '   and free the Item
  975.                 pvtDBDelete
  976.             End If
  977.             
  978. ' else, just Refresh the current RecordSet to
  979. '   reflect the detached Item
  980.         ElseIf Not pvtRecordSetProvidedByUser Then
  981.             Refresh
  982.         End If
  983.     End If
  984.     
  985. ' remove Item from the Collection
  986.     pvtCollection.Remove _
  987.         CollectionIndex(Item)
  988.  
  989. ' fixed by Cary O. (01/16/1996)
  990.     Set Item = Nothing
  991.     
  992.     Set Remove = Me
  993. End Function
  994.  
  995.  
  996. Public Function Replace(Optional ByVal Item As Variant, Optional ByVal ReplaceWith As Variant) As DBAwareCollection
  997. Attribute Replace.VB_Description = "Replaces the item with the specified ReplaceWith item in the collection and in the associated table"
  998. ' Replace the specified Item with the ReplaceWith
  999. '   Item, then return the DBAwareCollection
  1000.     
  1001.     Dim ItemIndex As Long
  1002.     
  1003.     On Local Error Resume Next
  1004.  
  1005. ' bullet-proofing
  1006.     If IsMissing(Item) Or IsMissing(ReplaceWith) Then
  1007.         Set Replace = Me
  1008.         Exit Function
  1009.     End If
  1010.     
  1011. ' there are two ways to handle a Replace:
  1012. '   1) replace the object in-place (non Collection-emulation mode, only),
  1013. '   2) replace the object with another
  1014. '
  1015. ' process the replacement in-place:
  1016.     If Item.ObjectID = ReplaceWith.ObjectID And Not pvtCollectionEmulationMode Then
  1017.         
  1018. ' position to the record to be updated (fix by Cary O., 01/16/1996)
  1019. '   or exit, if not found
  1020.         If Not pvtFindItemInRecordSet(Item:=Item) Then
  1021.             Set Replace = Me
  1022.             Exit Function
  1023.         End If
  1024.         
  1025. ' initiate the RecordSet.Edit
  1026.         pvtRecordSet.Edit
  1027.         
  1028. ' have Item initialize the RecordSet (fix by Cary O., 01/16/1996)
  1029.         ReplaceWith.InitializeRecordSet pvtRecordSet
  1030.         If Err = pvtReceiverDoesNotSupportThisMethod Then
  1031.             pvtErrorMessage "Object does not support the method 'InitializeRecordSet'." & vbCrLf & "Object cannot be supported by " & pvtName & " without this method."
  1032.         End If
  1033.         
  1034. ' post the updates to the database
  1035.         pvtRecordSet.Update
  1036.         
  1037. ' execute Me.Refresh
  1038.         Refresh
  1039.         
  1040.         Set Replace = Me
  1041.         Exit Function
  1042.     End If
  1043.     
  1044. ' else, Item must be removed and replaced with ReplaceWith.
  1045. ' save the position of Item in the Collection
  1046.     ItemIndex = CollectionIndex(Item)
  1047.  
  1048. ' remove Item from the RecordSet and the Collection
  1049.     Remove _
  1050.         Item:=Item, _
  1051.         Key:=CStr(Item.ObjectID), _
  1052.         NoDelete:=True
  1053.  
  1054. ' free Item
  1055.     Set Item = Nothing
  1056.  
  1057. ' add the ReplaceWith item
  1058.     If ItemIndex > 0 Then
  1059.         Add _
  1060.             Item:=ReplaceWith, _
  1061.             Parent:=pvtParentObject, _
  1062.             After:=(ItemIndex - 1)
  1063.     Else
  1064.         Add _
  1065.             Item:=ReplaceWith, _
  1066.             Parent:=pvtParentObject
  1067.     End If
  1068.     
  1069.     Set Replace = Me
  1070. End Function
  1071.  
  1072. Private Function pvtFindItemInRecordSet(Optional ByVal Item As Variant) As Long
  1073. Attribute pvtFindItemInRecordSet.VB_Description = "(Private) internal function"
  1074.  
  1075.     Dim EachRecord As Variant
  1076.     Dim I As Long
  1077.     
  1078.     On Local Error Resume Next
  1079.     
  1080.     Err = 0
  1081.     
  1082. ' check the current record first
  1083.     If Not pvtRecordSet.BOF And Not pvtRecordSet.EOF And pvtRecordSet.RecordCount > 0 Then
  1084.         If pvtRecordSet("ObjectID") = CStr(Item.ObjectID) Then
  1085.             pvtFindItemInRecordSet = True
  1086.             Exit Function
  1087.         End If
  1088.     End If
  1089.     
  1090. ' else, .FindFirst
  1091.     pvtRecordSet.MoveFirst
  1092.     pvtRecordSet.FindNext "ObjectID = " & CStr(Item.ObjectID)
  1093.     
  1094.     If Err = 0 Then
  1095.         pvtFindItemInRecordSet = True
  1096.     Else
  1097.         pvtFindItemInRecordSet = False
  1098.     End If
  1099. End Function
  1100.  
  1101. Private Sub Class_Initialize()
  1102.     Set pvtCollection = New Collection
  1103.     Set pvtSampleObject = Nothing
  1104.     Set pvtParentObject = Nothing
  1105.     Set pvtDatabase = Nothing
  1106.     Set pvtRecordSet = Nothing
  1107.     
  1108.     pvtSQLStatement = ""
  1109.     pvtWhereClause = ""
  1110.     pvtDBHasBeenReferenced = False
  1111.     pvtCollectionEmulationMode = True
  1112.     pvtRecordSetProvidedByUser = False
  1113.     pvtHighestObjectID = 0
  1114. End Sub
  1115.  
  1116.  
  1117.  
  1118. Public Property Get WhereClause() As String
  1119. ' Returns the current WhereClause value
  1120.     
  1121.     WhereClause = pvtWhereClause
  1122. End Property
  1123.  
  1124. Public Property Let WhereClause(WhereClause As String)
  1125. ' Set the WhereClause to be used in future SQL Select
  1126. '   statements
  1127. ' Note: this step is not necessarily required of the user
  1128.  
  1129.     pvtReceiveGeneralParameters _
  1130.         WhereClause:=WhereClause
  1131.         
  1132.     pvtCollectionEmulationMode = False
  1133. End Property
  1134.  
  1135. Public Property Get OrderByClause() As String
  1136. ' Returns the current OrderByClause
  1137.     
  1138.     OrderByClause = pvtOrderByClause
  1139. End Property
  1140.  
  1141. Public Property Let OrderByClause(OrderByClause As String)
  1142. ' Set the OrderByClause to be used in future SQL Select
  1143. '   statements
  1144. ' Note: this step is not necessarily required of the user
  1145.  
  1146.     pvtReceiveGeneralParameters _
  1147.         OrderByClause:=OrderByClause
  1148.             
  1149.     pvtCollectionEmulationMode = False
  1150. End Property
  1151.