Mercurial > pub > Impl
diff Lib/Schema.pm @ 0:03e58a454b20
Создан репозитарий
author | Sergey |
---|---|
date | Tue, 14 Jul 2009 12:54:37 +0400 |
parents | |
children | 16ada169ca75 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Lib/Schema.pm Tue Jul 14 12:54:37 2009 +0400 @@ -0,0 +1,748 @@ +package Schema; +package Schema::TypeName; +package Schema::Type; +package Schema::Template; +package Schema::TemplateSpec; +package Schema::Member; +package Schema::Property; + +package Schema::TypeName; +use Common; + +#our @ISA = qw(Object); + +# ìîæíî îïòèìèçèðîâàòü ïðîèçâîäèòåëüíîñòü, ñîçäàâàÿ îáúåêò ñêàëàÿð äëÿ ïðîñòûõ +# èìåí è îáõåêò õåø äëÿ ñïåöèàëèçàöèé +# ñäåëàíî + +sub new { + my $class = shift; + my $this; + + my $name = shift; + my @list = map { ref $_ eq 'Schema::TypeName' ? $_ : new Schema::TypeName($_) } @_; + + die new Exception('TypeName soud be a simple identifier',$name) if not $name =~ /^\w+$/; + + if (@list) { + $this = bless {}, $class; + $this->{Name} = $name; + $this->{TemplateList} = \@list if @list; + } else { + $this = bless \$name, $class; + } + + return $this; +} + +sub Name { + my $this = shift; + return (UNIVERSAL::isa($this,'HASH') ? $this->{Name} : $$this); +} + +sub Simple { + return $_[0]->Name; +} + +# ñïèñîê ïàðàìåòðîâ òèïà +sub TemplateList { + my $this = shift; + return (UNIVERSAL::isa($this,'HASH') ? (wantarray ? @{$this->{TemplateList}} : $this->{TemplateList} ) : (wantarray ? return () : undef)); +} + +# èìÿ òèïà ÿâëÿåòñÿ èìåíåì øàáëîíà +sub isTemplateSpec { + my $this = shift; + return( UNIVERSAL::isa($this,'HASH') ? 1 : 0 ); +} + +sub CanonicalName { + my $this = shift; + + if (UNIVERSAL::isa($this,'HASH')) { + if (my $result = $this->{SavedCanonicalName}) { + $result; + } else { + $result = $this->{Name}; + $result .= '@'. join('#',map {ref $_ eq __PACKAGE__ ? $_->CanonicalName : $_} @{$this->{TemplateList}}) . '@@'; + $this->{SavedCanonicalName} = $result; + } + } else { + $$this; + } +} + +sub Canonical { + return $_[0]->CanonicalName; +} + +# Íå ðåãèñòðèðóåò âíîâü ñîçäàííûõ òèïîâ â òàáëèöå +# Ýòî èç-çà ñëó÷àÿ, êîãäà: +# MyClass { Hash<int> my_map; }, òîåñòü ïîëó÷åííûé òèï Hahs<int> óæå ñïåöèàëèçèðîâàí è îí áóäåò ñðàçó èíñòàíòèíîðîâàí +# DoNotCreate äëÿ ñïåöèàëèçàöèè øàáëîíà òîëüêî ñóùåñòâóþùèìè òèïàìè +sub Resolve { + my ($this,$TypeTable,$DoNotCreate) = @_; + + if (my $type = $TypeTable->ResolveType($this,$DoNotCreate)) { + # ïðåäïîëàãàåòñÿ, ÷òî ñõåìà àâòîìàòè÷åñêè ñîçäàåò ññûëêè âïåðåä íà íåîïðåäåëåííûå ïðîñòûå òèïû + return $type; + } else { + if ($this->isTemplateSpec) { + return new Schema::TemplateSpec($this->Name,map {ref $_ eq __PACKAGE__ ? $_->Resolve($TypeTable,$DoNotCreate) : Schema::TypeName->new($_)->Resolve($TypeTable,$DoNotCreate)} @{$this->{TemplateList}} ); + } else { + die new Exception("Simple type not found", $this->Name); + } + } +} + +package Schema::TypeTable; +use Common; +our @ISA = qw(Object); + +BEGIN { + DeclareProperty(Table => ACCESS_NONE); + DeclareProperty(NextTable => ACCESS_NONE); +} + +sub CTOR { + my ($this,$nextTable) = @_; + $this->{$NextTable} = $nextTable; +} + +sub ResolveType { + my ($this,$TypeName,@args) = @_; + + if (my $Type = $this->{$Table}->{$TypeName->CanonicalName}) { + return $Type; + } elsif($this->{$NextTable}) { + return $this->{$NextTable}->ResolveType($TypeName,@args); + } else { + return undef; + } +} + +sub RegisterType { + my ($this,$Type) = @_; + + if (not $this->{$Table}->{$Type->Name->CanonicalName}) { + $this->{$Table}->{$Type->Name->CanonicalName} = $Type; + } else { + die new Exception("A type already registered",$Type->Name->CanonicalName); + } +} + +sub _ListTypes { + my $this = shift; + return values %{$this->{$Table}}; +} + +sub Dispose { + my $this = shift; + + $_->Dispose foreach values %{$this->{$Table} ? $this->{$Table} : {} }; + + delete $this->{$Table}; + + $this->SUPER::Dispose; +} + +# Ñïåöèàëèçàöèÿ øàáëîíà - ýòî èìÿ ñïåöèàëèçèðóåìîãî øàáëîíà è ïàðàìåòðû, êîòîðûå áóäóò åìó ïåðåäàíû (âàæåí ïîðÿäîê ïàðàìåòðîâ) +# Ñïåöèàëèçàöèÿ øàáëîíà ïàðàìåòðàìè ïîðàæäàåò ÷àñòè÷íî ñïåöèàëèçèðîâàííûé øàáëîí, êîòîðûé ïî ñóòè òàêæå ÿâëÿåòñÿ øàáëîíîì +# Åñëè ñïåöèàëèçàöèÿ ïîëíàÿ, òî ìîæíî ñîçäàòü ýêçåìïëÿð øàáëîíà, òîåñòü ïîëíîöåííûé òèï +package Schema::TemplateSpec; +use Common; +our @ISA = qw(Object); + +BEGIN { + DeclareProperty(Name => ACCESS_READ); + DeclareProperty(Parameters => ACCESS_READ); + DeclareProperty(TemplateList => ACCESS_READ); +} + +sub CTOR { + my ($this,$templateName,@typeList) = @_; + + my %Params; + + $this->{$TemplateList} = \@typeList; + + # âû÷èñëÿåì ïàðàìåòðû äàííîé ñïåöèàëèçàöèè + my @nameList; + foreach $typeItem (@typeList) { + map { $Params{$_->Name} = $_ } @{$typeItem->Parameters} if $typeItem->isTemplate; + push @nameList, $typeItem->Name; + } + + $this->{$Parameters} = [ values %Params ]; + $this->{$Name} = new Schema::TypeName($templateName,@nameList); +} + +sub isTemplate { + 1; +} + +sub canInstantinate { + my ($this) = @_; + if (@{$this->{$Parameters}}) { + 0; + } else { + 1; + } +} + +sub Specialize { + my ($this,$refParams,$TypeTable) = @_; + + my @specializedList = map {$_->isTemplate && !$_->canInstantinate ? $_->Specialize($refParams,$TypeTable) : $_ } @{$this->{$TemplateList}}; + + if ($TypeTable) { + + my $TypeName = new Schema::TypeName($this->Name->Name,map {$_->Name} @specializedList); + my $templateSpec = $TypeTable->ResolveType($TypeName); + if (not $templateSpec) { + $templateSpec = new Schema::TemplateSpec($this->Name->Name,@specializedList); + $TypeTable->RegisterType($templateSpec); + } + return $templateSpec; + } else { + return new Schema::TemplateSpec($this->Name->Name,@specializedList); + } +} + +# Ïàðàìåòð øàáëîíà +# Ïî ñóòè ÿâëÿåòñÿ øàáëîíîì òèïà Param_Name<T> -> T; +package Schema::Parameter; + +sub new { + my $TypeName = new Schema::TypeName($_[1]); + bless \$TypeName,$_[0]; +} + +sub Name { + ${shift()}; +} + +sub Specialize { + my ($this,$refArgs) = @_; + return $refArgs->{$$this->Name}; +} + +sub isTemplate { + 1; +} + +sub canInstantinate { + 0; +} + +sub Parameters { + if (wantarray) { + shift; + } else { + [shift]; + } +} + + +# ×ëåí êëàññà +package Schema::Member; +use Common; +our @ISA = qw(Object); +our $Abstract = 1; + +BEGIN { + DeclareProperty(Name => ACCESS_READ); +} +sub CTOR { + my($this,$name) = @_; + + $this->{$Name} = $name; +} + +# ×ëåí êëàññà - ñâîéñòâî. +# Ñâîéñòâî ìîæåò áûòü øàáëîíîì, åñëè øàáëîíîì ÿâëÿåòñÿ åãî òèï +package Schema::Property; +use Common; +our @ISA = qw(Schema::Member); + +BEGIN { + DeclareProperty(Type => ACCESS_READ); +} + +sub CTOR { + my ($this,$name,$type) = @_; + $this->SUPER::CTOR($name); + + $this->{$Type} = $type or die new Exception("A type for the property must be specified",$name); +} + +sub isTemplate { + my $this = shift; + return $this->{$Type}->isTemplate; +} + +sub canInstantinate { + my $this = shift; + return $this->{$Type}->canInstantinate; +} + +sub Instantinate { + my ($this,$Schema) = @_; + return new Schema::Property($this->Name,$Schema->Instantinate($this->{$Type})); +} + +sub Specialize { + my ($this,$refParams,$TypeTable) = @_; + return new Schema::Property($this->Name,$this->{$Type}->Specialize($refParams,$TypeTable)); +} + +# Òèï, îïèñûâàåò òèï îáúåêòà +package Schema::Type; +use Common; +our @ISA = qw(Object); + +BEGIN { + DeclareProperty(Name => ACCESS_READ); + DeclareProperty(Schema => ACCESS_READ); + DeclareProperty(Members => ACCESS_READ); + DeclareProperty(BaseList => ACCESS_READ); + DeclareProperty(Attributes => ACCESS_READ); #hash of attributes +} + +sub CTOR { + my ($this,$argSchema,$name) = @_; + + $this->{$Name} = ref $name eq 'Schema::TypeName' ? $name : new Schema::TypeName($name); + $this->{$Schema} = $argSchema; +} + +sub isTemplate { + 0; +} + +sub Equals { + my ($this,$other) = @_; + if (UNIVERSAL::isa($other,'Schema::Type')) { + return ($this->Name->CanonicalName eq $other->Name->CanonicalName); + } else { + return 1; + } +} + +sub CreateProperty { + my ($this,$PropName,$TypeName) = @_; + + $PropType = $this->_ResolveType($TypeName); + + return new Schema::Property($PropName,$PropType); +} + +sub AddBase { + my ($this,$type) = @_; + + $type = $this->_ResolveType($type); + + not $type->isType($this) or die new Exception('Cant derive from the class which is derived from self', $this->Name->CanonicalName, $type->Name->CanonicalName); + + push @{$this->{$BaseList}},$type; +} + +sub isType { + my ($this,$type,$maxlevel) = @_; + + return 0 if defined $maxlevel and $maxlevel < 0; + my $typeName = UNIVERSAL::isa($type,'Schema::Type') ? $type->Name : $type ; + + return ( + $this->{$Name}->CanonicalName eq $typeName->CanonicalName ? + 1 + : + scalar (grep {$_->isType($typeName,defined $maxlevel ? $maxlevel - 1 : undef)} $this->BaseList) + ); +} + +sub ValidateType { + my ($this,$type) = @_; + + die new Exception('Can\'t use an unspecialized template',$type->Name->CanonicalName) if ($type->isa('Schema::TypeTemplate')); + + if ($type->isTemplate and not $type->canInstantinate) { + die new Exception('Cant use a not fully specialized template in a simple type',$type->Name->CanonicalName, $this->Name->Name) if not $this->isTemplate; + + my %Params = map {$_->Name->Name() , 1} @{$this->Parameters}; + my @Unresolved = grep {not $Params{$_->Name->Name}} @{$type->Parameters()}; + + die new Exception('Not all parameters can be rsolved',map {$_->Name->Name} @Unresolved) if @Unresolved; + } +} + +sub InsertProperty { + my ($this,$PropName,$PropType) = @_; + + $PropType = $this->_ResolveType($PropType); + + my $prop = new Schema::Property($PropName,$PropType); + + push @{$this->{$Members}}, $prop; + + return $prop; +} + +sub AddMember { + my ($this,$member) = @_; + + push @{$this->{$Members}},$member; +} + +sub GetTypeTable { + my $this = shift; + return $this->{$Schema}; +} + +sub _ResolveType { + my ($this,$type) = @_; + if ($type->isa('Schema::TypeName')) { + $type = $type->Resolve($this->GetTypeTable()); + } elsif ($type->isa('Schema::Type') or $type->isa('Schema::TemplateSpec')) { + $this->ValidateType($type); + } else { + die new Exception('Invalid type',$type); + } + + $type = $this->{$Schema}->Instantinate($type) if ($type->isTemplate and $type->canInstantinate and not $this->isTemplate); + return $type; +} + +sub ListMembers { + my ($this,%options) = @_; + + my @members; + + if ($options{'foreign'}) { + push @members, $_->isa('Schema::Type') ? $_->ListMembers(%options) : () foreach @{$this->{$BaseList} ? $this->{$BaseList} : []}; + } + push @members, @{$this->{$Members} ? $this->{$Members} : []}; + + return @members; +} + +sub FindMembers { + my ($this,$memberName,%options) = @_; + + my @members = grep { $_->Name eq $memberName} @{$this->{$Members} ? $this->{$Members} : []}; + + if ($options{'deep'}) { + push @members,$_->ListMembers(%options) foreach @{$this->{$BaseList} ? $this->{$BaseList} : []}; + } + + if(wantarray) { + return @members; + } else { + return shift @members; + } +} + +sub SetAttributes { + my ($this,%attributes) = @_; + + while (my ($key,$value) = each %attributes) { + $this->{$Attributes}{$key} = $value; + } +} + +sub GetAttribute { + my ($this,$name) = @_; + + return $this->{$Attributes}{$name}; +} + +sub _dump { + my ($this) = @_; + return $this->Name->CanonicalName; +} + +sub Dispose { + my ($this) = @_; + + undef %{$this}; + $this->SUPER::Dispose; +} + +# Øàáëîí - ïðàìåòðèçîâàííûé òèï +package Schema::Template; +use Common; +our @ISA = qw(Schema::Type); + +BEGIN { + DeclareProperty(Parameters => ACCESS_READ); + DeclareProperty(LocalTypes => ACCESS_NONE); + +} + +sub CTOR { + my ($this,$Schema,$name,@args) = @_; + # ïàðàìåòðû íå ÿâëÿþòñÿ ÷à÷òüþ èìåíè + $this->SUPER::CTOR($Schema,$name); + + $this->{$Parameters} = [ map {new Schema::Parameter($_) } @args ]; + my $TypeTable = new Schema::TypeTable($Schema); + $TypeTable->RegisterType($_) foreach @{$this->{$Parameters} }; + $this->{$LocalTypes} = $TypeTable; +} + +sub GetTypeTable { + my ($this) = @_; + return $this->{$LocalTypes}; +} + +sub isTemplate { + 1; +} + +sub Specialize { + my ($this,$refArgs,$TypeTable) = @_; + + my @specializedList = map {$_->Specialize($refArgs)} @{$this->{$Parameters}}; + + # ñîçäàåì ñïåöèàëèçàöèþ øàáëîíà + my $specializedType; + + if ($TypeTable) { + my $TypeName = new Schema::TypeName($this->Name->Name,map {$_->Name} @specializedList); + + if(my $specializedType = $TypeTable->ResolveType($TypeName)) { + return $specializedType; + } else { + $specializedType = new Schema::TemplateSpec($this->Name->Name, @specializedList ); + $TypeTable->RegisterType($specializedType); + return $specializedType; + } + } else { + return new Schema::TemplateSpec($this->Name->Name, @specializedList ); + } +} + +sub canInstantinate { + 0; +} + +# ñîçäàíèå ýêçåìïëÿðà øàáëîíà. +# Ñîçäàòü øàáëîí = ïîëíîñòüþ åãî ñïåöèàëèçèðîâàòü +# Ïðèíèìàåò íàáîð ïàðàìåòðîâ øàáëîíà è ñîçäàåò íîâûé òèï èëè âîçâðàùàåò èç ñõåìû +sub Instantinate { + my ($this,$refArgs,$instance) = @_; + + my %ParamInstances; + my @TemplateListNames; + + foreach my $param (@{$this->{$Parameters}}) { + my $type = $refArgs->{$param->Name->Name}; + die new Exception("Parameter not specified",$param->Name->Name) if not $type; + if ($type->isTemplate) { + if ($type->canInstantinate) { + $type = $this->Schema->Instantinate($type); + } else { + die new Exception("Parameter must be a fully speciazlied type",$param->Name->Name); + } + } + + $ParamInstances{$param->Name->Name} = $type; + push @TemplateListNames, $type->Name; + } + + # ïàðàìåòðû ïðåäñòàâëÿþò ñîáîé ðåàëüíûå òèïû, ïåðåõîäèì ê ñîçäàíèþ òèïà + # äàííàÿ ôóíêöèÿ áåóñëîâíî ñîçäàåò íîâûé òèï, ýòó ôóíêöèþ èñïîëüçóåò ñõåì + + $instance = $this->Schema->CreateType( new Schema::TypeName($this->Name->Name,@TemplateListNames) ) if not $instance; + + $instance->SetAttributes(%{$this->Attributes}) if $this->Attributes; + $instance->SetAttributes( + TemplateInstance => { + Template => $this, + Parameters => \%ParamInstances + } + ); + + foreach my $Ancestor ($this->BaseList) { + $instance->AddBase( + $Ancestor->isTemplate ? + ( $Ancestor->canInstantinate ? + $this->Schema->Instantinate($Ancestor) + : + $this->Schema->Instantinate($Ancestor->Specialize(\%ParamInstances,$this->GetTypeTable)) + ) + : + $Ancestor + ); + } + + foreach my $Member ($this->Members) { + $instance->AddMember( + $Member->isTemplate ? + ($Member->canInstantinate ? + $Member->Instantinate($this->Schema) + : + $Member->Specialize(\%ParamInstances,$this->GetTypeTable)->Instantinate($this->Schema) + ) + : + $Member + ); + } + + return $instance; +} + +sub _ResolveType { + my ($this,$type) = @_; + if ($type->isa('Schema::TypeName')) { + $type = $type->Resolve($this->GetTypeTable()); + if (not $this->{$LocalTypes}->ResolveType($type->Name)) { + $this->{$LocalTypes}->RegisterType($type); + } + } elsif ($type->isa('Schema::Type') or $type->isa('Schema::TemplateSpec')) { + $this->ValidateType($type); + } else { + die new Exception('Invalid type',$type); + } + + return $type; +} + + +package Schema; +use strict; +use Common; +our @ISA = qw(Schema::TypeTable); + +BEGIN { + DeclareProperty(PendingInstances => ACCESS_NONE); + DeclareProperty(UnresolvedTypes => ACCESS_NONE); +} + +sub CTOR { + +} + +# Ñõåìà àâòîìàòè÷åñêè ñîçäàåò ññûëêè âïåðåä íà íåñóùåñòâóþùèå ïðîñòûå òèïû +sub ResolveType { + my ($this,$TypeName,$DoNotCreate) = @_; + + if (my $type = $this->SUPER::ResolveType($TypeName)) { + return $type; + } else { + if (not $TypeName->isTemplateSpec and not $DoNotCreate) { + $type = new Schema::Type($this,$TypeName); + $this->RegisterType($type); + $this->{$UnresolvedTypes}->{$TypeName->CanonicalName} = $TypeName; + return $type; + } else { + return undef; + } + } +} + +sub CreateType { + my ($this,$TypeName) = @_; + + $TypeName = new Schema::TypeName($TypeName) if ref $TypeName ne 'Schema::TypeName'; + + if (my $type = $this->SUPER::ResolveType($TypeName)) { + if ($this->{$UnresolvedTypes}->{$TypeName->CanonicalName}) { + delete $this->{$UnresolvedTypes}->{$TypeName->CanonicalName}; + return $type; + } else { + die new Exception("Type already exists",$TypeName->CanonicalName); + } + } else { + $type = new Schema::Type($this,$TypeName); + $this->SUPER::RegisterType($type); + return $type; + } +} + +sub CreateTemplate { + my ($this,$TemplateName,@ParamNames) = @_; + + die new Exception("Parameters required for the template") if not @ParamNames; + + if (ref $TemplateName eq 'Schema::TypeName') { + die new Exception('Template specialization is not valid name for a new template',$TemplateName->CanonicalName) if $TemplateName->isTemplateSpec; + } else { + $TemplateName = new Schema::TypeName($TemplateName); + } + + if (my $type = $this->SUPER::ResolveType($TemplateName)) { + die new Exception('Type already exists'); + } else { + $type = new Schema::Template($this,$TemplateName,@ParamNames); + $this->SUPER::RegisterType($type); + return $type; + } +} + +# ñîçäàíèå ýêçåìïëÿðà øàáëîíà +# ñîçäàåòñÿ íîâûé ïóñòîé òèï, äîáàâëÿåòñÿ â PendingInstances +sub Instantinate { + my ($this,$TemplateSpec) = @_; + + # ïðè ñïåöèàëèçàöèè íàïðìåð ýòîãî: T m_var; ïîëó÷èì äëÿ èíñòàíòèíèöèè real_type m_var; è íå ïðîâåðÿÿ îòäàäèì åãî íà ñïåöèàëèçàöèþ, + # âîò è îáðàáîòêà + return $TemplateSpec if not $TemplateSpec->isTemplate; + + die new Exception('Only a template specialization can be instantinated') if ref $TemplateSpec ne 'Schema::TemplateSpec'; + die new Exception('Only fully specialized template can be instantinated') if not $TemplateSpec->canInstantinate; + + my $TypeName = $TemplateSpec->Name; + + if (my $type = $this->SUPER::ResolveType($TypeName)) { + return $type; + } else { + $type = new Schema::Type($this,$TypeName); + $this->SUPER::RegisterType($type); + push @{$this->{$PendingInstances}},[$TemplateSpec,$type]; + return $type; + } +} + +sub Close { + my ($this) = @_; + + if (keys %{$this->{$UnresolvedTypes}}) { + die new Exception('Some type definitions are absent',keys %{$this->{$UnresolvedTypes}}); + } + + if ($this->{$PendingInstances}) { + while( my $ref = shift @{$this->{$PendingInstances}} ) { + my ($spec,$instance) = @$ref; + if (my $typeTemplate = $this->SUPER::ResolveType( new Schema::TypeName($spec->Name->Name) )) { + die new Exception('Can\'t instantinate a specialization of the simple type',$instance->Name->CanonicalName) if not $typeTemplate->isTemplate; + if (scalar(@{$typeTemplate->Parameters}) == scalar(@{$spec->TemplateList})) { + my @Params = @{$typeTemplate->Parameters}; + $typeTemplate->Instantinate({map { (shift @Params)->Name->Name, $_ } @{$spec->TemplateList}},$instance); + } else { + die new Exception('A template parameters doesn\'t match to the specialization list',$instance->Name->CanonicalName); + } + } else { + die new Exception('Can\'t instantinate a specialization, the specified template isn\'t found', $instance->Name->CanonicalName); + } + } + + delete $this->{$PendingInstances}; + } +} + +sub EnumTypes { + my ($this,%options) = @_; + + return grep { ($_->isTemplate and not $options{'skip_templates'}) or (not $_->isTemplate and not $options{'skip_classes'}) } $this->_ListTypes; +} + +sub Dispose { + my ($this) = @_; + + delete $this->{$UnresolvedTypes}; + + $this->SUPER::Dispose; +} + +1;