Hi Rich,
The short answer is "you certainly can". The longer answer is a bit more complicated due to the fact that I did a custom Protoclass based on the latest PROTOCLASS MVB code. I don't know at which version this came out in. I've been using 2012.1 field test which unfortunately expired this morning. You can accomplish the same goal in either version by finding the section in your version that sets parameters for individual properties. A good string to search for in your code would be "MVBATTRIBUTE" since that gets set in either version. You can add some code to look in %dictionary to find the MV attribute you want and look at the DICT properties underneath that to grab the length and add a MAXLENGTH parameter to your property.
The quickest thing I could advise is to run PROTOCLASS on a scratch file that has dictionary items (you can always delete the file and class right after), then at the command line type
USER> [ zw %dictionary
Then you can see the structure of %dictionary. This array gets built by a parseDict call in the code and persists after you run PROTOCLASS. It's not quite as complicated as it seems.
I'm enclosing my custom class and the routine that runs it. The stock PROTOCLASS says "modify the code", so I did exactly that :)
Best regards,
Bill
The library ClassMethod:
ClassMethod Protoclass(Filename As %String = "", PackageName As %String = "MVFile", DeleteExistingClass As %Boolean = 0, GenerateXData As %Boolean = 1, MaxRealAttribute As %Integer = 8999)
{
If $Get( %MVBCommon ) = "" Then %MVBCommon = "MVBooster.Common"->%New()
CompleteClassName = PackageName : "." : Filename
DefaultQueryFields = %MVBCommon->nil
************************************
* PROTOCLASS.mvb
*
* Creates the verb PROTOCLASS which the shell will also find
* if it is called as PROTOCLASS.
*
* This MVBASIC program shows how to parse the dictionary of an MV
* file and use some or all of the DICT elements to create a Cache' class
* definition. The class is then compiled and is available in the namespace/class
* whence the program was run. It also serves as a useful example of how to utilize
* standard Cache' classes, as this is what it uses to build the class definition.
*
* A class is first defined and saved, which produces a class that can then be edited
* using Cache' studio, then it is compiled, which is then the basis for all other
* projections such as SQL tables.
*
* Because no one set of files is defined in the same way as any other, you are free
* to make copies of this program and modify it to suit your own application requirements.
*
* +--------------------------------------------------------+
* | Copyright 2007-2009 by InterSystems Corporation, |
* | Cambridge, Massachusetts, U.S.A. |
* | All rights reserved. |
* | |
* | Confidential, unpublished property of InterSystems. |
* | |
* | This media contains an authorized copy or copies |
* | of material copyrighted by InterSystems and is the |
* | confidential, unpublished property of InterSystems. |
* | This copyright notice and any other copyright notices |
* | included in machine readable copies must be reproduced |
* | on all authorized copies. |
* +--------------------------------------------------------+
*
*
* Declare some global (cross program) variables that are
* multivalued in the Cache' sense, not just the standard MV style.
* To do this we DIM the variables, but do not supply any dimensions
* which tells the compiler that it is a standard COS style variable
* that needs no dimension sizing and just works.
*
* jwwf - Obviously this is a highly-modified version that works with MVBooster.
* I left the original InterSystems copyright intact.
*
DIM %dictionary()
Dim %MVBFileInfo() ; * utilize the MVBooster file information structure
* Now some variables that are only local to this program
DIM ClassCrossReference()
UseAsciiHex = 0
*
* Parse the command line. We have access to MV TCL II processing
* through the %SYSTEM.MV class, and so we do not need to program
* all of that in BASIC. Note, that you can produce the list of
* DICTionary elements to process in any way you choose, then just call
* the parseDict() method as below to add it to the %dictionary() multilevel
* array.
"MVBooster.Library"->FileInfo( Filename )
If Status() <> %MVBCommon->successful Then
%MVBTrace->WriteError()
Return
End
FileDataLevel = %MVBFileInfo( Filename, %MVBCommon->MVBFileInfoFilePropertiesSubscript )
FileDataLevel->OpenDict()
FileDictLevel = FileDataLevel->DictHandle
* We must not run protoclass against system globals
If FileDataLevel->FileHandle->%Global[1,1]= "%" Then ABORT 446,fileName
* We must not run protoclass from a different account
If FileDataLevel->FileHandle->%TargetAccount <> FileDataLevel->FileHandle->%Account Then
%MVBTrace->WriteEntry( "Protoclass for this file must be run in the ":FileDataLevel->%TargetAccount:" account." )
Stop
End
If FileDataLevel->FileHandle->%StorageType <> 0 Then
%MVBTrace->WriteEntry( "Protoclass can only be used against files stored in globals in inode format." )
Stop
End
*
* Make sure our dictionary tracking structure is clean
*
$KILL %dictionary
%DictNumber = 0
*
* Initialize the list of items to add to XDATA
*
XDATAList = %MVBCommon->nil
*
* Now we can loop on each item ID from the DICTionary and parse
* it into the %dictionary variable. This will make it much easier
* to traverse it multiple times to see what special processing we
* may need to define the class.
*
@ID = %MVBCommon->nil
Loop
@ID = $Order( %MVBFileInfo( Filename, %MVBCommon->MVBFileInfoDictionaryStructureSubscript, @ID ) )
Until @ID = %MVBCommon->nil Do ; *"%SYSTEM.MV"->TCL2Next() = 1 DO
* One more DICT element was found, read this in from the
* DICT
FileDataLevel->DictRead( @ID )
If FileDataLevel->DictRecord = %MVBCommon->nil Then
%MVBTrace->WriteEntry( "Could not find " : @ID : " in DICT " : Filename )
Continue
End
* Is this a type of DICT entry that this method can handle? Sometimes
* people purposefully or accidentally store things in the DICT that
* should not really be there. These must be filtered out in favor
* of DICT types that can be processed.
Record = FileDataLevel->DictRecord
type = UPCase( Record< 1 >[ 1, 1 ] )
If INDEX("ADSIV", type, 1) <> 0 Then
*
* Test for any specific names not to generate properties for
*
If @ID <> "@ID" Then
* Now parse the entry. There is another %SYSTEM.MV class method
* to call that will do this.
*
"%SYSTEM.MV"->parseDict(FileDictLevel, Record, @ID)
*
* Examine the string in attribute 8
*
pattr8 = $Get( %dictionary( %DictNumber, "attr8" ) )
If pattr8 <> %MVBCommon->nil Then
statusString = "%SYSTEM.MV"->ConvAnalyze(pattr8)
errorTypeCode = FIELD(statusString,",",1)
If errorTypeCode < 0 Then
%MVBTrace->WriteEntry( "DICT entry '":@ID:"' ignored due to errors in dict item." )
%dictionary(%DictNumber,"processed") = 1
Continue
End
* save the bit pattern that indicates the codes found
%dictionary(%DictNumber, "attr8bits") = FIELD(statusString,",",2)
* Save the list of referenced attributes
%dictionary(%DictNumber, "attr8refs") = FIELD(statusString,",",3,999)
End Else
* this would be a reasonable place to setup sqlname
* %dictionary(%DictNumber,"sqlname")=....
End
End
Continue
End
* This block is run when an item can not be processed into an property. If GenerateXData
* is set to @true then add it to the XDATA block, else throw it away.
If GenerateXData = @true Then
XDATAList<-1> = @ID
End Else
%MVBTrace->WriteEntry( "DICT entry '" : @ID : "' ignored." )
End
Repeat
* All manageable DICT items have been parsed. Begin the first phase
* of processing them into class properties.
*
* A Cache' class can only define one class property as being stored
* in the dynamic array. This means that if the dictionary contains
* multiple references to the same attribute, we must create
* a raw entry for the attribute and 'calculate' the value of each
* of the duplicate properties from this value.
*
* In practice, it is not usually a good idea to have duplicate references
* in the class because once the property is calculated, we cannot
* easily determine if the calculation is reversible. Therefore, we
* make such properties read only.
*
* The nature of a SQL table of Cache' Class means that duplicate definitions
* of the same attribute are generally redundant (as formatting and
* so on is performed outside the table/class.) However it is sometimes valid.
* Where there are no duplicates we can refer to the attribute
* directly. The property will then only be calculated if it is subject
* to an Itype or Attribute 8 (referred to as MVTOLOGICAL) transformation.
* (See comments later on these types of transformations...)
*
* Now loop through the parsed dictionary elements and find
* out which are duplicates. We will ignore references to attribute
* 0 as being the Item ID or a calculated field. Furthermore, we will
* assume that fields marked as attribute MaxRealAttribute or higher are also calculated.
* (This is fairly common practice, though highly inefficient
* for various reasons.)
*
* You may need to change the code here to adapt it to the practices
* at your own site.
** Note: (JWWF) That's exactly what I did with the entire routine.
*
* See if we have a class for this file already
*
ClassDefinition = "%SYSTEM.MV"->getPrimaryMVClass( FileDataLevel->FileHandle, 0, %MVBCommon->nil )
*
* If desired we can delete the existing class and start from scratch. The DeleteExistingClass
* argument in the formal spec controls this. (It defaults to @false.)
*
If DeleteExistingClass = @true Then
If ClassDefinition <> %MVBCommon->nil Then ClassToDelete = ClassDefinition->Name Else ClassToDelete = CompleteClassName
"%Dictionary.ClassDefinition"->%DeleteId( ClassToDelete ) ; * delete the class object
"%ExtentMgr.Util"->DeleteExtentDefinition( ClassToDelete ) ; * also delete the definition else the rest of this method may go awry.
ClassDefinition = %MVBCommon->nil
End
If ClassDefinition= %MVBCommon->nil Then
* Create the primary MV class for the file. The name comes from a concatenation of
* the package name (supplied in the formal spec), a period, and the class name (also
* in the formal spec).
ClassDefinition = "%SYSTEM.MV"->getPrimaryMVClass( FileDataLevel->FileHandle, 1, CompleteClassName )
End
* Extract the unqualified classname: the last '.' delimited substring
className = FIELD( ClassDefinition->Name, ".", DCOUNT(ClassDefinition->Name, "." ) )
properties = ClassDefinition->Properties
* Now Build a cross refrence array of any existing properties that will make updating
* the class a lot easier. This will be empty for a new class and is updated every
* time we add or modify a property.
*
* The layout is: ClassCrossReference(sub1,sub2) = value
* where the subscripts are:
* sub1 sub2 value
* 0 property name property object
* 1 Dict Name " "
* 2 Attr No " " This is the primary definition for that attr
* 3 property name index object
* 4 Dict Name %MVBCommon->nil %MVBCommon->nil
* 5 storage.attrNo proPropertyName in storage
* 6 storage.proPropertyName value object in storage
* 7 Values Object in storage
* 8 attrNo,proPropertyName property object - other definitions for this attribute
* 9 upcased name true case name
* 10 1 1 if class locked against MV verb updates
"%SYSTEM.MV"->buildMVClassXref( ClassDefinition, ClassCrossReference )
*
* Check to see if this class has been locked to prevent unintentional
* modification
*
If $Get( ClassCrossReference( 10, 1 ) ) = 1 Then
"MVBooster.Library"->SetCondition( 104, CompleteClassName : " has been locked against automatic updates.", "MVBooster.Library->Protoclass" )
Return
End
*
* First Pass: Compare the dict items we have with the current class
*
CacheDictionaryLocation = %MVBCommon->nil
Loop
CacheDictionaryLocation = $Order( %dictionary( CacheDictionaryLocation ) )
While CacheDictionaryLocation <> %MVBCommon->nil Do
If $Get( %dictionary( CacheDictionaryLocation, "processed" ) ) <> %MVBCommon->nil Then Continue
*
* See if we already have a property defined for this DICT item
*
MVDictDefinition = $Get( %dictionary( CacheDictionaryLocation, "name" ) )
PropertyReference = $Get( ClassCrossReference( 1, MVDictDefinition ) )
If PropertyReference = %MVBCommon->nil Then
Continue
End
* We already have a property for this DICT item
* Look for any major changes
If $Get( %dictionary(CacheDictionaryLocation, "attrno" ) ) <> PropertyReference->Parameters->GetAt( "MVATTRIBUTE" ) Then Continue
If $Get( %dictionary(CacheDictionaryLocation, "attr8" ) ) <> PropertyReference->Parameters->GetAt( "MVTOLOGICAL" ) Then Continue
If $Get( %dictionary(CacheDictionaryLocation, "itypeSource" ) ) <> PropertyReference->Parameters->GetAt( "MVITYPE" ) Then Continue
If $Get( %dictionary(CacheDictionaryLocation, "sqlname" ) ) <> PropertyReference->SqlFieldName Then Continue
If $Get( %dictionary(CacheDictionaryLocation, "dataType" ) ) <> PropertyReference->Type Then Continue
If $Get( %dictionary(CacheDictionaryLocation, "mv" ) ) = "M" And PropertyReference->Collection <> "list" Then Continue
If $Get( %dictionary(CacheDictionaryLocation, "mv" ) ) <> "M" And PropertyReference->Collection = "list" Then Continue
If $Get( %dictionary(CacheDictionaryLocation, "assoc" ) ) <> PropertyReference->Parameters->GetAt( "MVASSOCIATION" ) Then Continue
If $Get( %dictionary(CacheDictionaryLocation, "svassoc" ) ) <> PropertyReference->Parameters->GetAt( "MVSVASSOCIATION" ) Then Continue
If $Get( %dictionary(CacheDictionaryLocation, "justification" ) ) = "R" And $Get( %dictionary( CacheDictionaryLocation, "dataType" ) ) = "%String" Then
If PropertyReference->Parameters->GetAt( "COLLATION" ) <> "MVR" Then Continue
End Else
If PropertyReference->Parameters->GetAt( "COLLATION" ) = "MVR" Then Continue
End
*
* Handle any minor changes here
*
If $Get( %dictionary(CacheDictionaryLocation, "conv" ) ) <> PropertyReference->Parameters->GetAt( "MVTODISPLAY" ) Then
PropertyReference->Parameters->SetAt( $Get( %dictionary( CacheDictionaryLocation,"conv" ) ), "MVTODISPLAY" )
End
If $Get( %dictionary(CacheDictionaryLocation, "type" ) ) <> PropertyReference->Parameters->GetAt( "MVTYPE" ) Then
PropertyReference->Parameters->SetAt( $Get( %dictionary( CacheDictionaryLocation, "type" ) ), "MVTYPE" )
End
If $Get( %dictionary( CacheDictionaryLocation, "heading" ) ) <> PropertyReference->Parameters->GetAt( "MVHEADING" ) Then
PropertyReference->Parameters->SetAt( $Get( %dictionary( CacheDictionaryLocation, "heading" ) ), "MVHEADING" )
End
If $Get( %dictionary( CacheDictionaryLocation, "width" ) ) <> PropertyReference->Parameters->GetAt( "MVWIDTH" ) Then
PropertyReference->Parameters->SetAt( $Get( %dictionary( CacheDictionaryLocation,"width" ) ), "MVWIDTH" )
End
If $Get( %dictionary( CacheDictionaryLocation, "format" ) ) <> PropertyReference->Parameters->GetAt("MVFORMAT") Then
PropertyReference->Parameters->SetAt( $Get( %dictionary( CacheDictionaryLocation, "format" ) ) ,"MVFORMAT" )
End
*
* Consider this one processed
*
%dictionary( CacheDictionaryLocation, "processed" ) = 1
*
* Mark that Protoclass would have generated this if it hadn't already existed
*
MVAuto = PropertyReference->Parameters->GetAt( "MVAUTO" )
If Count( MVAuto, "P") = 0 Then PropertyReference->Parameters->SetAt( MVAuto : "P", "MVAUTO" )
Repeat
*
* Second Pass - delete those properties that we can replace
*
CacheDictionaryLocation = %MVBCommon->nil
Loop
CacheDictionaryLocation = $Order( %dictionary( CacheDictionaryLocation ) )
While CacheDictionaryLocation <> %MVBCommon->nil DO
If $Get( %dictionary( CacheDictionaryLocation, "processed" ) ) <> %MVBCommon->nil Then Continue
MVDictDefinition = $Get( %dictionary( CacheDictionaryLocation, "name" ) )
PropertyReference = $Get( ClassCrossReference( 1, MVDictDefinition ) )
If PropertyReference = %MVBCommon->nil Then Continue
*
* See if we are permitted to modify this property
*
MVAuto = PropertyReference->Parameters->GetAt( "MVAUTO" )
PropertyName = PropertyReference->Name
Begin Case
Case MVAuto = "P"
* this property was generated by protoclass
%MVBTrace->WriteEntry( "Deleting property " : PropertyName : " for replacement" )
"%SYSTEM.MV"->deleteMVProperty( PropertyName, ClassDefinition, ClassCrossReference )
* MVBooster makes class properties with custom get/set methods. If those exist, remove them along
* with the property to which they're attached.
Gosub RemoveGetSetMethods
Continue
Case COUNT (MVAuto, "I ") > 0
%MVBTrace->WriteEntry( "Property " : PropertyName : " cannot be modified until all indexes based on it are deleted" )
Case COUNT( MVAuto, "R" ) > 0
%MVBTrace->WriteEntry( "Property " : PropertyName : " cannot be modified because other properies are based on it" )
Case 0
%MVBTrace->WriteEntry( "Property " : PropertyName : " cannot be modified because it is not tagged as Protoclass generated" )
End Case
*
* indicate that we have processed this dict item
*
%dictionary( CacheDictionaryLocation, "processed" ) = 1
Repeat
* Third pass - process the simple attributes
* then later we will process the calculated attributes
* This allows the createProperty routine to base the complex properties on
* the simple properties.
%MVBTrace->WriteEntry( "Processing simple attribute definitions" )
CacheDictionaryLocation = %MVBCommon->nil
Loop
CacheDictionaryLocation = $Order( %dictionary( CacheDictionaryLocation ) )
While CacheDictionaryLocation <> %MVBCommon->nil Do
If $Get( %dictionary( CacheDictionaryLocation, "processed" ) ) <> %MVBCommon->nil Then Continue
*
* See if this is a 'simple' attribute definition
*
If ( $Get( %dictionary( CacheDictionaryLocation, "attr8" ) ) <> %MVBCommon->nil ) Or ( $Get( %dictionary(CacheDictionaryLocation, "itypeSource" ) ) <> %MVBCommon->nil ) Then Continue
AttributeNumberFromdictionary = $Get( %dictionary( CacheDictionaryLocation, "dataType" ) )
If ( AttributeNumberFromdictionary = %MVBCommon->nil ) Or ( AttributeNumberFromdictionary = 0 ) Or ( AttributeNumberFromdictionary > MaxRealAttribute ) Then Continue
MVDictDefinition = $Get( %dictionary( CacheDictionaryLocation, "name" ) )
PropertyReference = $Get( ClassCrossReference( 1, MVDictDefinition ) )
If PropertyReference <> %MVBCommon->nil Then Continue
*
* Let's construct a valid property name for this DICT entry
*
PropertyName = "MVBooster.Library"->CamelCase( MVDictDefinition ) ; * use custom CamelCase, not ISC's
*
* Override the collation if we need to.
* Properties with a type of %string will default to 'space' collation
* but if we intend to index this field, it is safer to use a length limitted collation
* like SqlString(150) to avoid errors with invalid data. You may find that defaulting
* to SqlString(150) then adjusting those properties that might possibly exceed
* 150 characters works best for you.
*
If $Get( %dictionary( CacheDictionaryLocation, "dataType" ) ) = "%String" And $Get(%dictionary(CacheDictionaryLocation,"justification"))<>"R" Then
* %dictionary(CacheDictionaryLocation,"collation")="SqlString(150)"
End
*
* Create the property
* This function takes care of the creation of the property
* You can make any modifications you need after it is created
*
%MVBTrace->WriteEntry( "Creating property called " : PropertyName : " from " : MVDictDefinition )
"%SYSTEM.MV"->createMVProperty( PropertyName, CacheDictionaryLocation, ClassDefinition, ClassCrossReference )
* MVBooster sets up a default "ALL" query so the class can be leveraged by SQL immediately.
Locate PropertyName In DefaultQueryFields By "AL" Setting Pos Else
Ins PropertyName Before DefaultQueryFields< Pos >
End
* MVBooster sets file class properties up to take advantage of the MVFileAbstraction superclass.
AttributeNumber = %MVBFileInfo( Filename, %MVBCommon->MVBFileInfoDictionaryStructureSubscript, MVDictDefinition, "attrno" )
Type = %MVBFileInfo( Filename, %MVBCommon->MVBFileInfoDictionaryStructureSubscript, MVDictDefinition, "dataType" )
DictType = %MVBFileInfo( Filename, %MVBCommon->MVBFileInfoDictionaryStructureSubscript, MVDictDefinition, "type" )
Gosub RemoveGetSetMethods
Gosub AddGetSetMethods
*
* Indicate that this property was generated by ProtoClass. (MVBooster obeys the same convention.)
*
PropertyReference = $Get( ClassCrossReference( 0, PropertyName ) )
PropertyReference->Parameters->SetAt( "P", "MVAUTO" )
*
* indicate to the second pass that we have processed this dict item
*
%dictionary( CacheDictionaryLocation, "processed" ) = 1
Repeat
*
* Fourth pass for computed properties
*
%MVBTrace->WriteEntry( "Processing computed attribute definitions" )
CacheDictionaryLocation = %MVBCommon->nil
Loop
CacheDictionaryLocation = $Order( %dictionary( CacheDictionaryLocation ) )
While CacheDictionaryLocation <> %MVBCommon->nil DO
If $Get( %dictionary( CacheDictionaryLocation, "processed" ) ) <> %MVBCommon->nil Then Continue
*
* See if this is a 'simple' attribute definition
*
AttributeNumberFromdictionary = $Get( %dictionary( CacheDictionaryLocation, "attrno" ) )
MVDictDefinition = $Get(%dictionary( CacheDictionaryLocation, "name" ) )
If AttributeNumberFromdictionary > MaxRealAttribute Then
* We don't support attributes that high but if there is a correlative that
* accesses other attributes then this could well be a dummy attribute number.
sc = $Get( %dictionary( CacheDictionaryLocation, "attr8bits" ) )
If sc <> 0 Then
* This may well be a dummy attribute number
%dictionary( CacheDictionaryLocation, "attrno" ) = 0
End Else
%MVBTrace->WriteEntry( "Skipping " : MVDictDefinition : " since attribute " : AttributeNumberFromdictionary : " is out of range." )
Continue
End
End
*
* See if we already have a property defined for this DICT item
*
PropertyReference = $Get( ClassCrossReference( 1, MVDictDefinition ) )
If PropertyReference <> %MVBCommon->nil Then
Continue ;* We already have a property for this DICT item
End
*
* Cconstruct a valid property name for this DICT entry
*
PropertyName = "MVBooster.Library"->CamelCase( MVDictDefinition ) ; * use MVBooster's version of camel-case
If $Data( ClassCrossReference( 9, Upcase( PropertyName ) ) ) Or ( UseAsciiHex = @true ) Then
* There is already a property of this name for a different DICT item
* default to the old style ascii hex name
PropertyName = "%SYSTEM.MV"->validCacheName( MVDictDefinition )
If $Data( ClassCrossReference( 9, Upcase( PropertyName ) ) ) Then
%MVBTrace->WriteEntry( "Skipping " : MVDictDefinition : " since name " : PropertyName : " is already in use." )
Continue
End
End
%MVBTrace->WriteEntry( "Creating property called " : PropertyName : " from " : MVDictDefinition )
*Locate PropertyName In DefaultQueryFields By "AL" Setting Pos Else
* Ins PropertyName Before DefaultQueryFields< Pos >
*End
"%SYSTEM.MV"->createMVProperty( PropertyName, CacheDictionaryLocation, ClassDefinition, ClassCrossReference )
AttributeNumber = %MVBFileInfo( Filename, %MVBCommon->MVBFileInfoDictionaryStructureSubscript, MVDictDefinition, "attrno" )
Type = %MVBFileInfo( Filename, %MVBCommon->MVBFileInfoDictionaryStructureSubscript, MVDictDefinition, "dataType" )
DictType = %MVBFileInfo( Filename, %MVBCommon->MVBFileInfoDictionaryStructureSubscript, MVDictDefinition, "type" )
Gosub RemoveGetSetMethods
Gosub AddGetSetMethods
*
* Indicate that this property was generated by ProtoClass
*
PropertyReference = $Get( ClassCrossReference( 0, PropertyName ) )
PropertyReference->Parameters->SetAt( PropertyReference->Parameters->GetAt( "MVAUTO" ) : "P", "MVAUTO" )
If $Get( %MVBFileInfo( Filename, "class", PropertyName, "parameters", "MVTYPE" ) ) = "I" Then
* Override I-type calculations; MVBooster handles this
PropertyReference->Calculated = @true
PropertyReference->SqlComputed = @false
*PropertyReference->SqlComputeCode = "$this." : PropertyName : "Get()"
ClassCrossReference( 0, PropertyName ) = PropertyReference
End
Repeat
*
* Build an XDATA block containing any DICT Items
* that do not represent valid Class Properties but need to be in the DICT
* when a user has decided to use the Class as the master document.
*
If ( GenerateXData = @true ) And ( XDATAList <> %MVBCommon->nil ) Then
xdef = "%Dictionary.XDataDefinition"->%New(className:":MVAdditionalDictItems")
xstream=xdef->Data
xdataCount=1
xstream->WriteLine( '<DictItems>')
Loop
@ID = XDATAList<xdataCount>
xdataCount+=1
While @ID <>%MVBCommon->nil DO
FileDataLevel->DictRead( @ID )
If FileDataLevel->DictRecord = %MVBCommon->nil Then
%MVBTrace->WriteEntry( "DICT entry '" : @ID : "' was not found in " : FileDataLevel->FileName )
Continue
End
xstream->WriteLine( '<DictItem Name="':@ID:'">')
For AttrNo = 1 TO DCOUNT( FileDataLevel->DictRecord, @AM )
%attrData=record<AttrNo>
$XECUTE 'Set %attrData = $ZCVT(%attrData,"O","XML")'
xstream->WriteLine( "<Attr>":%attrData:"</Attr>")
NEXT AttrNo
xstream->WriteLine('</DictItem>')
Repeat
xstream->WriteLine('</DictItems>')
ClassDefinition->XDatas->Insert(xdef)
End
*
* call the method to add methods to implement any Itypes
*
*"%SYSTEM.MV"->generateItypeMethods(ClassDefinition)
*
* If all of the fields are virtual we have a problem in that no storage will be defined
* So add "dummyAttribute" if there aren't any real attributes
*
dummyDef = $Get( ClassCrossReference( 0, "dummyAttribute" ) )
If dummyDef = %MVBCommon->nil Then
If $Order( ClassCrossReference( 2, %MVBCommon->nil ) ) = %MVBCommon->nil Then
PropertyName = ClassDefinition->Name : ":dummyAttribute"
rdef = "%Dictionary.PropertyDefinition"->%New( PropertyName )
rdef->Parameters->SetAt( "0", "MVPROJECTED" )
rdef->Parameters->SetAt( "1", "MVATTRIBUTE")
rdef->Type = "%String"
ClassDefinition->Properties->Insert( rdef )
Locate PropertyName In DefaultQueryFields By "AL" Setting Pos Else
Ins PropertyName Before DefaultQueryFields< Pos >
End
Gosub RemoveGetSetMethods
Gosub AddGetSetMethods
End
End Else
If $Order( ClassCrossReference( 2, %MVBCommon->nil ) ) <> %MVBCommon->nil Then
* We now have real attributes defined so
* now delete the Dummy attribute that was already there
ClassDefinition->Properties->Remove( dummyDef )
End
End
*
* Make sure settings are right for MVBooster
*
ClassDefinition->Parameters->SetAt( 1, "MVREPOPULATE" ) ; * update the DICT from now on
ClassDefinition->Parameters->SetAt( 0, "MVCLEARDICT" ); * leave current DICT items in place
ClassDefinition->Parameters->SetAt( 0, "MVAUTOLOCK" ); * let MVBooster manage locks
ClassDefinition->Super = "%Persistent, %MV.Adaptor, MVBooster.MVFileAbstraction" ; * leverage MVBooster
ClassDefinition->Language = "mvbasic"
MethodNumber = %MVBCommon->nil
Loop
MethodNumber = ClassDefinition->Methods->Next( MethodNumber )
Until MethodNumber = %MVBCommon->nil Do
M = ClassDefinition->Methods->GetAt( MethodNumber )
If %MVBDebugging = @true Then %MVBTrace->WriteEntry( "Found method" : MethodNumber : " " : M->Name )
If M->Name = "%OnNew" Then
ClassDefinition->Methods->RemoveAt( MethodNumber )
End
Repeat
* Build the %OnNew method for this property that will use MVBooster.MVFileAbstraction methods inherited by the new class
NewMethod = "%Dictionary.MethodDefinition"->%New(CompleteClassName : ":%OnNew" )
NewMethod->ClassMethod = @false ; * this is an instance method
NewMethod->Language = "mvbasic"
NewMethod->ReturnType = "%Status"
NewMethod->FormalSpec = "WorkSilently:%Boolean = 0,ReadOnly:%Boolean = 0"
StringBuilder = "%GlobalCharacterStream"->%New() ; * build the body of the method in here
StringBuilder->WriteLine( \Return @ME->New( @ME->%GetParameter("MVFILENAME" ), WorkSilently, ReadOnly )\ )
NewMethod->Implementation = StringBuilder
ClassDefinition->Methods->Insert( NewMethod )
Gosub AddDefaultQuery
$Kill StringBuilder ; * destroy the method definition code builder so the variable can be reused
$Kill NewMethod ; * destroy the method definition so it will be clean on the next loop through here
*
* Now we can save the class
*
If %MVBDebugging Then
%MVBTrace->WriteEntry( "Saving the generated class..." )
End
sc = ClassDefinition->%Save()
If sc[1,1] = 0 Then
"MVBooster.Library"->SetCondition( 101, "Class save ended with errors.", "MVBooster.Protoclass" )
%MVBTrace->WriteError()
"%SYSTEM.MV"->decomposeStatus(sc)
Return
End Else
* Save was good, so now to compile it.
%MVBTrace->WriteEntry( "Compiling the generated class..." )
sc = "%SYSTEM.OBJ"->Compile(ClassDefinition->Name, "cfvko3")
If sc[1,1] = 0 Then
"MVBooster.Library"->SetCondition( 102, "Class compilation ended with errors.", "MVBooster.Protoclass" )
%MVBTrace->WriteError()
*"%SYSTEM.MV"->decomposeStatus(sc)
Return
End
End
%MVBTrace->WriteEntry( "Class generation and compilation was successful!" )
"MVBooster.Library"->ClearConditions()
Return
RemoveGetSetMethods:
* Okay, there was a property with a matching name. There is likely a Get() and Set() method as well.
NextMethod = %MVBCommon->nil
Loop
NextMethod = ClassDefinition->Methods->Next( NextMethod )
Until NextMethod = %MVBCommon->nil Do
ExistingMethod = ClassDefinition->Methods->GetAt( NextMethod )
If ( ExistingMethod->Name = PropertyName : "Set" ) Or ( ExistingMethod->Name = PropertyName : "Get" ) Then
ClassDefinition->Methods->RemoveAt( NextMethod )
End
Repeat
Return
AddGetSetMethods:
* The property is set up. MVBooster has its own mechanism for handling file update and
* retrieval. You can go back and customize each Set() method to enforce data integrity
* and each Get() method for data shaping.
* Build the Get() method for this property that will use MVBooster.MVFileAbstraction methods inherited by the new class
GetMethod = "%Dictionary.MethodDefinition"->%New(CompleteClassName : ":" : PropertyName : "Get" )
GetMethod->ClassMethod = @false ; * this is an instance method
GetMethod->Language = "mvbasic"
GetMethod->ReturnType = Type ; * same as the property type
StringBuilder = "%GlobalCharacterStream"->%New() ; * build the body of the method in here
StringBuilder->WriteLine( "theRecord = @ME->Record" )
If AttributeNumber Matches "1N0N" Then
StringBuilder->WriteLine( "return theRecord<" : AttributeNumber : ">" )
End Else ; * it's an I-type formula
Formula = EReplace( AttributeNumber, "@ID", "@ME->Key" )
Formula = EReplace( AttributeNumber, "@RECORD", "@ME->Record" )
StringBuilder->WriteLine( "Result = " : Formula )
StringBuilder->WriteLine( "return Result" )
End
GetMethod->Implementation = StringBuilder
ClassDefinition->Methods->Insert( GetMethod )
$Kill StringBuilder ; * destroy the method definition code builder so the variable can be reused
$Kill GetMethod ; * destroy the method definition so it will be clean on the next loop through here
* Build the Set() method for this property. It will also use methods and properties inherited from the MVBooster.MVFileAbstraction class
SetMethod = "%Dictionary.MethodDefinition"->%New(CompleteClassName : ":" : PropertyName : "Set" )
SetMethod->ClassMethod = @false ; * this is also an instance method
SetMethod->Language = "mvbasic"
SetMethod->ReturnType = "%Status"
SetMethod->FormalSpec = "Value:" : Type
StringBuilder = "%GlobalCharacterStream"->%New() ; * build the body of the method in here
If DictType <> "I" Then
* You'll need to go back into the class and add your own data integrity enforcement code if it's needed
StringBuilder->WriteLine( "@ME->SetAttribute( " : AttributeNumber : ", Value )" )
StringBuilder->WriteLine( \Return "%SYSTEM.Status"->OK()\ )
End Else
StringBuilder->WriteLine( \Return "%SYSTEM.Status"->Error( 0, "Cannot set a virtual type." )\ )
SetMethod->Private = 1
End
SetMethod->Implementation = StringBuilder
ClassDefinition->Methods->Insert( SetMethod )
$Kill StringBuilder
$Kill SetMethod
"MVBooster.Library"->ClearConditions()
Return
AddDefaultQuery:
* Add a default "ALL" query. Remove any existing before replacing.
NextQuery = %MVBCommon->nil
Loop
NextQuery = ClassDefinition->Queries->Next( NextQuery )
Until NextQuery = %MVBCommon->nil Do
ExistingQuery = ClassDefinition->Queries->GetAt( NextQuery )
If %MVBDebugging = @true Then %MVBTrace->WriteEntry( "Found query " : ExistingQuery->Name : "; removing." )
If Upcase( ExistingQuery->Name ) = "ALL" Then
ClassDefinition->Queries->RemoveAt( NextQuery )
*Exit ; * there'll only be one
End
Repeat
NewQuery = "%Dictionary.QueryDefinition"->%New()
NewQuery->Name = "All"
NewQuery->SqlQuery = "SELECT " : Convert( @fm, ',', DefaultQueryFields ) : " FROM " : Filename : " ORDER BY %ID "
NewQuery->Type = "%SQLQuery"
NewQuery->Parameters->SetAt( 1, "CONTAINID" )
ClassDefinition->Queries->Insert( NewQuery )
Return
}
The routine that calls the ClassMethod
MVBProtoclass
0001 Program MVBProtoclass
0002 #Pragma RoutineName = MVBProtoclass
0003
0004 * Create or update an MVBooster file class from the DICT of a file in the VOC.
0005 * Switches:
0006 * -File filespec Designates the file in the VOC whose DICT will be used
0007 * -DELete Delete any existing class for this file
0008 * -P packagespec designates the package name into which this file class
0009 * should be categorized. If this switch is missing, then
0010 * the package "MVFile" is used. If the designated package
0011 * name does not yet exist, it will be automatically created.
0012 * This simplifies mapping and exposing files by application
0013 * type or suite.
0014 * -MAXrealattribute The highest A/AMC of a "real" (not calculated) A or S type.
0015 * Defaults to 8999 (Pick used 9000 and above.)
0016 * -Xdata Create an XData section for MVRepopulate
0017 *
0018 * The MVBooster debugging switches may also be used.
0019
0020 *
0021 * Make sure the catalog pointer was modified to make this a TCL2 verb
0022 *
0023 *FINDSTR "2D" IN $Get(%parseControl) SETTING LINT Else ABORT 445, "PROTOCLASS":@VM:"D"
0024
0025 MaxDefault = 8999 ; * Default maximum "real" attribute. Any DICT item with a greater A/AMC will be considered a ca
lculation.
0026
0027 %MVBCommon = "MVBooster.Common"->%New()
0028 %MVBCommon->Setup()
0029 %MVBCommon->SetSwitchesFromCommandLine()
0030
0031 * Was the file switch "-F" specified?
0032 FindStr "-F" In %MVBTokenizedSentence Setting Pos Else
0033 %MVBTrace->WriteEntry( "Use the -F filespec argument to specify which file wants a class built." )
0034 Stop
0035 End
0036
0037 Filename = %MVBTokenizedSentence< Pos + 1 >
0038
0039 Dim %MVBFileInfo() ; * access the Cache' array for MV Booster file info
0040 "MVBooster.Library"->FileInfo( Filename )
0041
0042 If $Get( %MVBFileInfo( Filename, %MVBCommon->MVBFileInfoFilePropertiesSubscript, %MVBCommon->MVBFileHandle ) ) = %MVBCommon
->nil Then
0043 %MVBTrace->WriteEntry( "No file named " : DQuote( Filename ) : " can be opened from the VOC." )
0044 Stop
0045 End
0046
0047 FileDataLevel = %MVBFileInfo( Filename, %MVBCommon->MVBFileInfoFilePropertiesSubscript )
0048 FileDataLevel->OpenDict()
0049 FileDictLevel = %MVBFileInfo( Filename, %MVBCommon->MVBFileInfoDictPropertiesSubscript )
0050
0051 If %MVBDebugging Then
0052 $Xecute " zw %MVBFileInfo(" : DQuote( Filename ) : ")"
0053 End
0054
0055 FindStr "-DEL" In %MVBTokenizedSentence Setting Pos Then
0056 DeleteExistingClass = @true
0057 End Else
0058 DeleteExistingClass = @false
0059 End
0060
0061 FindStr "-P" In %MVBTokenizedSentence Setting Pos Then
0062 PackageName = %MVBTokenizedSentence< Pos + 1 >
0063 End Else
0064 PackageName = %MVBCommon->nil
0065 End
0066
0067 FindStr "-MAX" In %MVBTokenizedSentence Setting Pos Then
0068 MaxRealAttribute = %MVBTokenizedSentence< Pos + 1 >
0069 If Not( MaxRealAttribute Matches "1N0N" ) Then MaxRealAttribute = MaxDefault
0070 End Else
0071 MaxRealAttribute = MaxDefault
0072 End
0073
0074 FindStr "-X" In %MVBTokenizedSentence Setting Pos Then
0075 GenerateXDATA = @true
0076 End Else
0077 GenerateXDATA = @false
0078 End
0079
0080 CompleteClassName = PackageName : "." : Filename
0081 DefaultQueryFields = %MVBCommon->nil
0082
0083 "MVBooster.Library"->Protoclass( Filename, PackageName, DeleteExistingClass, GenerateXData, MaxRealAttribute )
0084
0085 Return
0086
0087 ************************************