1 ###############################################################################
3 # Package: NaturalDocs::ClassHierarchy
5 ###############################################################################
7 # A package that handles all the gory details of managing the class hierarchy. It handles the hierarchy itself, which files define
8 # them, rebuilding the files that are affected by changes, and loading and saving them to a file.
10 # Usage and Dependencies:
12 # - <NaturalDocs::Settings> and <NaturalDocs::Project> must be initialized before use.
14 # - <NaturalDocs::SymbolTable> must be initialized before <Load()> is called. It must reflect the state as of the last time
15 # Natural Docs was run.
17 # - <Load()> must be called to initialize the package. At this point, the <Information Functions> will return the state as
18 # of the last time Natural Docs was run. You are free to resolve <NaturalDocs::SymbolTable()> afterwards.
20 # - <Purge()> must be called, and then <NaturalDocs::Parser->ParseForInformation()> must be called on all files that
21 # have changed so it can fully resolve the hierarchy via the <Modification Functions()>. Afterwards the
22 # <Information Functions> will reflect the current state of the code.
24 # - <Save()> must be called to commit any changes to the symbol table back to disk.
26 ###############################################################################
28 # This file is part of Natural Docs, which is Copyright © 2003-2010 Greg Valure
29 # Natural Docs is licensed under version 3 of the GNU Affero General Public License (AGPL)
30 # Refer to License.txt for the complete details
36 use NaturalDocs::ClassHierarchy::Class;
37 use NaturalDocs::ClassHierarchy::File;
39 package NaturalDocs::ClassHierarchy;
41 use Encode qw(encode_utf8 decode_utf8);
44 ###############################################################################
48 # handle: CLASS_HIERARCHY_FILEHANDLE
49 # The file handle used with <ClassHierarchy.nd>.
55 # A hash of all the classes. The keys are the class <SymbolStrings> and the values are <NaturalDocs::ClassHierarchy::Classes>.
62 # A hash of the hierarchy information referenced by file. The keys are the <FileNames>, and the values are
63 # <NaturalDocs::ClassHierarchy::File>s.
68 # hash: parentReferences
70 # A hash of all the parent reference strings and what they resolve to. The keys are the <ReferenceStrings> and the values are
71 # the class <SymbolStrings> that they resolve to.
78 # A <NaturalDocs::ClassHierarchy::File> object of the file being watched for changes. This is compared to the version in <files>
79 # to see if anything was changed since the last parse.
84 # string: watchedFileName
86 # The <FileName> of the watched file, if any. If there is no watched file, this will be undef.
91 # bool: dontRebuildFiles
93 # A bool to set if you don't want changes in the hierarchy to cause files to be rebuilt.
99 ###############################################################################
104 # File: ClassHierarchy.nd
106 # Stores the class hierarchy on disk.
111 # > [VersionInt: app version]
113 # The standard <BINARY_FORMAT> and <VersionInt> header.
115 # > [SymbolString: class or undef to end]
117 # Next we begin a class segment with its <SymbolString>. These continue until the end of the file. Only defined classes are
120 # > [UInt32: number of files]
121 # > [UString16: file] [UString16: file] ...
123 # Next there is the number of files that define that class. It's a UInt32, which seems like overkill, but I could imagine every
124 # file in a huge C++ project being under the same namespace, and thus contributing its own definition. It's theoretically
127 # Following the number is that many file names. You must remember the index of each file, as they will be important later.
128 # Indexes start at one because zero has a special meaning.
130 # > [UInt8: number of parents]
131 # > ( [ReferenceString (no type): parent]
132 # > [UInt32: file index] [UInt32: file index] ... [UInt32: 0] ) ...
134 # Next there is the number of parents defined for this class. For each one, we define a parent segment, which consists of
135 # its <ReferenceString>, and then a zero-terminated string of indexes of the files that define that parent as part of that class.
136 # The indexes start at one, and are into the list of files we saw previously.
138 # Note that we do store class segments for classes without parents, but not for undefined classes.
140 # This concludes a class segment. These segments continue until an undef <SymbolString>.
144 # <File Format Conventions>
150 # - Changed AString16s to UString16s.
154 # - Classes and parents switched from AString16s to <SymbolStrings> and <ReferenceStrings>.
155 # - A ending undef <SymbolString> was added to the end. Previously it stopped when the file ran out.
159 # - This file was introduced in 1.2.
163 ###############################################################################
164 # Group: File Functions
170 # Loads the class hierarchy from disk.
176 $dontRebuildFiles = 1;
179 my $fileName = NaturalDocs::Project->DataFile('ClassHierarchy.nd');
181 if (!NaturalDocs::Settings->RebuildData() && open(CLASS_HIERARCHY_FILEHANDLE, '<' . $fileName))
183 # See if it's binary.
184 binmode(CLASS_HIERARCHY_FILEHANDLE);
187 read(CLASS_HIERARCHY_FILEHANDLE, $firstChar, 1);
189 if ($firstChar != ::BINARY_FORMAT())
191 close(CLASS_HIERARCHY_FILEHANDLE);
195 my $version = NaturalDocs::Version->FromBinaryFile(\*CLASS_HIERARCHY_FILEHANDLE);
197 # Last file format change was 1.52
199 if (NaturalDocs::Version->CheckFileFormat( $version, NaturalDocs::Version->FromString('1.52') ))
202 { close(CLASS_HIERARCHY_FILEHANDLE); };
209 NaturalDocs::Project->ReparseEverything();
217 # [SymbolString: class or undef to end]
219 my $class = NaturalDocs::SymbolString->FromBinaryFile(\*CLASS_HIERARCHY_FILEHANDLE);
224 # [UInt32: number of files]
226 read(CLASS_HIERARCHY_FILEHANDLE, $raw, 4);
227 my $numberOfFiles = unpack('N', $raw);
231 while ($numberOfFiles)
235 read(CLASS_HIERARCHY_FILEHANDLE, $raw, 2);
236 my $fileLength = unpack('n', $raw);
239 read(CLASS_HIERARCHY_FILEHANDLE, $file, $fileLength);
240 $file = decode_utf8($file);
243 $self->AddClass($file, $class, NaturalDocs::Languages->LanguageOf($file)->Name());
248 # [UInt8: number of parents]
250 read(CLASS_HIERARCHY_FILEHANDLE, $raw, 1);
251 my $numberOfParents = unpack('C', $raw);
253 while ($numberOfParents)
255 # [ReferenceString (no type): parent]
257 my $parent = NaturalDocs::ReferenceString->FromBinaryFile(\*CLASS_HIERARCHY_FILEHANDLE,
258 ::BINARYREF_NOTYPE(),
259 ::REFERENCE_CH_PARENT());
263 # [UInt32: file index or 0]
265 read(CLASS_HIERARCHY_FILEHANDLE, $raw, 4);
266 my $fileIndex = unpack('N', $raw);
271 $self->AddParentReference( $files[$fileIndex - 1], $class, $parent );
278 close(CLASS_HIERARCHY_FILEHANDLE);
281 $dontRebuildFiles = undef;
288 # Saves the class hierarchy to disk.
294 open (CLASS_HIERARCHY_FILEHANDLE, '>' . NaturalDocs::Project->DataFile('ClassHierarchy.nd'))
295 or die "Couldn't save " . NaturalDocs::Project->DataFile('ClassHierarchy.nd') . ".\n";
297 binmode(CLASS_HIERARCHY_FILEHANDLE);
299 print CLASS_HIERARCHY_FILEHANDLE '' . ::BINARY_FORMAT();
300 NaturalDocs::Version->ToBinaryFile(\*CLASS_HIERARCHY_FILEHANDLE, NaturalDocs::Settings->AppVersion());
302 while (my ($class, $classObject) = each %classes)
304 if ($classObject->IsDefined())
306 # [SymbolString: class or undef to end]
308 NaturalDocs::SymbolString->ToBinaryFile(\*CLASS_HIERARCHY_FILEHANDLE, $class);
310 # [UInt32: number of files]
312 my @definitions = $classObject->Definitions();
313 my %definitionIndexes;
315 print CLASS_HIERARCHY_FILEHANDLE pack('N', scalar @definitions);
317 for (my $i = 0; $i < scalar @definitions; $i++)
320 my $uDefinition = encode_utf8($definitions[$i]);
321 print CLASS_HIERARCHY_FILEHANDLE pack('na*', length($uDefinition), $uDefinition);
322 $definitionIndexes{$definitions[$i]} = $i + 1;
325 # [UInt8: number of parents]
327 my @parents = $classObject->ParentReferences();
328 print CLASS_HIERARCHY_FILEHANDLE pack('C', scalar @parents);
330 foreach my $parent (@parents)
332 # [ReferenceString (no type): parent]
334 NaturalDocs::ReferenceString->ToBinaryFile(\*CLASS_HIERARCHY_FILEHANDLE, $parent, ::BINARYREF_NOTYPE());
336 # [UInt32: file index]
338 my @parentDefinitions = $classObject->ParentReferenceDefinitions($parent);
340 foreach my $parentDefinition (@parentDefinitions)
342 print CLASS_HIERARCHY_FILEHANDLE pack('N', $definitionIndexes{$parentDefinition});
346 print CLASS_HIERARCHY_FILEHANDLE pack('N', 0);
351 # [SymbolString: class or undef to end]
353 NaturalDocs::SymbolString->ToBinaryFile(\*CLASS_HIERARCHY_FILEHANDLE, undef);
355 close(CLASS_HIERARCHY_FILEHANDLE);
362 # Purges the hierarchy of files that no longer have Natural Docs content.
368 my $filesToPurge = NaturalDocs::Project->FilesToPurge();
370 foreach my $file (keys %$filesToPurge)
372 $self->DeleteFile($file);
378 ###############################################################################
379 # Group: Interface Functions
383 # Function: OnInterpretationChange
385 # Called by <NaturalDocs::SymbolTable> whenever a class hierarchy reference's intepretation changes, meaning it switched
386 # from one symbol to another.
388 # reference - The <ReferenceString> whose current interpretation changed.
390 sub OnInterpretationChange #(reference)
392 my ($self, $reference) = @_;
394 if (NaturalDocs::ReferenceString->TypeOf($reference) == ::REFERENCE_CH_PARENT())
396 # The approach here is simply to completely delete the reference and readd it. This is less than optimal efficiency, since it's
397 # being removed and added from %files too, even though that isn't required. However, the simpler code is worth it
398 # considering this will only happen when a parent reference becomes defined or undefined, or on the rare languages (like C#)
399 # that allow relative parent references.
401 my $oldTargetSymbol = $parentReferences{$reference};
402 my $oldTargetObject = $classes{$oldTargetSymbol};
404 my @classesWithReferenceParent = $oldTargetObject->Children();
406 # Each entry is an arrayref of file names. Indexes are the same as classesWithReferenceParent's.
407 my @filesDefiningReferenceParent;
409 foreach my $classWithReferenceParent (@classesWithReferenceParent)
411 my $fileList = [ $classes{$classWithReferenceParent}->ParentReferenceDefinitions($reference) ];
412 push @filesDefiningReferenceParent, $fileList;
414 foreach my $fileDefiningReferenceParent (@$fileList)
416 $self->DeleteParentReference($fileDefiningReferenceParent, $classWithReferenceParent, $reference);
421 # This will force the reference to be reinterpreted on the next add.
423 delete $parentReferences{$reference};
426 # Now we can just readd it.
428 for (my $i = 0; $i < scalar @classesWithReferenceParent; $i++)
430 foreach my $file (@{$filesDefiningReferenceParent[$i]})
432 $self->AddParentReference($file, $classesWithReferenceParent[$i], $reference);
437 # The only way for a REFERENCE_CH_CLASS reference to change is if the symbol is deleted. That will be handled by
438 # <AnalyzeChanges()>, so we don't need to do anything here.
443 # Function: OnTargetSymbolChange
445 # Called by <NaturalDocs::SymbolTable> whenever a class hierarchy reference's target symbol changes, but the reference
446 # still resolves to the same symbol.
450 # reference - The <ReferenceString> that was affected by the change.
452 sub OnTargetSymbolChange #(reference)
454 my ($self, $reference) = @_;
456 my $type = NaturalDocs::ReferenceString->TypeOf($reference);
459 if ($type == ::REFERENCE_CH_PARENT())
460 { $class = $parentReferences{$reference}; }
461 else # ($type == ::REFERENCE_CH_CLASS())
463 # Class references are global absolute, so we can just yank the symbol.
464 (undef, $class, undef, undef, undef, undef) = NaturalDocs::ReferenceString->InformationOf($reference);
467 $self->RebuildFilesFor($class, 1, 0, 1);
472 ###############################################################################
473 # Group: Modification Functions
479 # Adds a class to the hierarchy.
483 # file - The <FileName> the class was defined in.
484 # class - The class <SymbolString>.
485 # languageName - The name of the language this applies to.
489 # The file parameter must be defined when using this function externally. It may be undef for internal use only.
491 sub AddClass #(file, class, languageName)
493 my ($self, $file, $class, $languageName) = @_;
495 if (!exists $classes{$class})
497 $classes{$class} = NaturalDocs::ClassHierarchy::Class->New();
498 NaturalDocs::SymbolTable->AddReference($self->ClassReferenceOf($class, $languageName), $file)
503 # If this was the first definition for this class...
504 if ($classes{$class}->AddDefinition($file))
505 { $self->RebuildFilesFor($class, 1, 1, 1); };
507 if (!exists $files{$file})
508 { $files{$file} = NaturalDocs::ClassHierarchy::File->New(); };
510 $files{$file}->AddClass($class);
512 if (defined $watchedFileName)
513 { $watchedFile->AddClass($class); };
519 # Function: AddParentReference
521 # Adds a class-parent relationship to the hierarchy. The classes will be created if they don't already exist.
525 # file - The <FileName> the reference was defined in.
526 # class - The class <SymbolString>.
527 # symbol - The parent class <SymbolString>.
528 # scope - The package <SymbolString> that the reference appeared in.
529 # using - An arrayref of package <SymbolStrings> that the reference has access to via "using" statements.
530 # resolvingFlags - Any <Resolving Flags> to be used when resolving the reference.
532 # Alternate Parameters:
534 # file - The <FileName> the reference was defined in.
535 # class - The class <SymbolString>.
536 # reference - The parent <ReferenceString>.
538 sub AddParentReference #(file, class, symbol, scope, using, resolvingFlags) or (file, class, reference)
540 my ($self, $file, $class, $symbol, $parentReference);
544 my ($scope, $using, $resolvingFlags);
545 ($self, $file, $class, $symbol, $scope, $using, $resolvingFlags) = @_;
547 $parentReference = NaturalDocs::ReferenceString->MakeFrom(::REFERENCE_CH_PARENT(), $symbol,
548 NaturalDocs::Languages->LanguageOf($file)->Name(),
549 $scope, $using, $resolvingFlags);
553 ($self, $file, $class, $parentReference) = @_;
554 $symbol = (NaturalDocs::ReferenceString->InformationOf($parentReference))[1];
558 # In case it doesn't already exist.
559 $self->AddClass($file, $class);
562 if (exists $parentReferences{$parentReference})
564 $parent = $parentReferences{$parentReference};
568 NaturalDocs::SymbolTable->AddReference($parentReference, $file);
569 my $parentTarget = NaturalDocs::SymbolTable->References($parentReference);
571 if (defined $parentTarget)
572 { $parent = $parentTarget->Symbol(); }
574 { $parent = $symbol; };
576 # In case it doesn't already exist.
577 $self->AddClass(undef, $parent);
579 $parentReferences{$parentReference} = $parent;
583 # If this defined a new parent...
584 if ($classes{$class}->AddParentReference($parentReference, $file, \%parentReferences))
586 $classes{$parent}->AddChild($class);
588 $self->RebuildFilesFor($class, 0, 1, 0);
589 $self->RebuildFilesFor($parent, 0, 1, 0);
592 $files{$file}->AddParentReference($class, $parentReference);
594 if (defined $watchedFileName)
595 { $watchedFile->AddParentReference($class, $parentReference); };
600 # Function: WatchFileForChanges
602 # Watches a file for changes, which can then be applied by <AnalyzeChanges()>. Definitions are not deleted via a DeleteClass()
603 # function. Instead, a file is watched for changes, reparsed, and then a comparison is made to look for definitions that
604 # disappeared and any other relevant changes.
608 # file - The <FileName> to watch.
610 sub WatchFileForChanges #(file)
612 my ($self, $file) = @_;
614 $watchedFile = NaturalDocs::ClassHierarchy::File->New();
615 $watchedFileName = $file;
620 # Function: AnalyzeChanges
622 # Checks the watched file for any changes that occured since the last time is was parsed, and updates the hierarchy as
623 # necessary. Also sends any files that are affected to <NaturalDocs::Project->RebuildFile()>.
629 # If the file didn't have any classes before, and it still doesn't, it wont be in %files.
630 if (exists $files{$watchedFileName})
632 my @originalClasses = $files{$watchedFileName}->Classes();
634 foreach my $originalClass (@originalClasses)
636 # If the class isn't there the second time around...
637 if (!$watchedFile->HasClass($originalClass))
638 { $self->DeleteClass($watchedFileName, $originalClass); }
642 my @originalParents = $files{$watchedFileName}->ParentReferencesOf($originalClass);
644 foreach my $originalParent (@originalParents)
646 # If the parent reference wasn't there the second time around...
647 if (!$watchedFile->HasParentReference($originalClass, $originalParent))
648 { $self->DeleteParentReference($watchedFileName, $originalClass, $originalParent); };
655 $watchedFile = undef;
656 $watchedFileName = undef;
661 ###############################################################################
662 # Group: Information Functions
666 # Function: ParentsOf
667 # Returns a <SymbolString> array of the passed class' parents, or an empty array if none. Note that not all of them may be
670 sub ParentsOf #(class)
672 my ($self, $class) = @_;
674 if (exists $classes{$class})
675 { return $classes{$class}->Parents(); }
681 # Function: ChildrenOf
682 # Returns a <SymbolString> array of the passed class' children, or an empty array if none. Note that not all of them may be
685 sub ChildrenOf #(class)
687 my ($self, $class) = @_;
689 if (exists $classes{$class})
690 { return $classes{$class}->Children(); }
697 ###############################################################################
698 # Group: Support Functions
702 # Function: DeleteFile
704 # Deletes a file and everything defined in it.
708 # file - The <FileName>.
710 sub DeleteFile #(file)
712 my ($self, $file) = @_;
714 if (!exists $files{$file})
717 my @classes = $files{$file}->Classes();
718 foreach my $class (@classes)
720 $self->DeleteClass($file, $class);
723 delete $files{$file};
727 # Function: DeleteClass
729 # Deletes a class definition from a file. Will also delete any parent references from this class and file. Will rebuild any file
730 # affected unless <dontRebuildFiles> is set.
734 # file - The <FileName> that defines the class.
735 # class - The class <SymbolString>.
737 sub DeleteClass #(file, class)
739 my ($self, $file, $class) = @_;
741 my @parents = $files{$file}->ParentReferencesOf($class);
742 foreach my $parent (@parents)
744 $self->DeleteParentReference($file, $class, $parent);
747 $files{$file}->DeleteClass($class);
749 # If we're deleting the last definition of this class.
750 if ($classes{$class}->DeleteDefinition($file))
752 if (!$classes{$class}->HasChildren())
754 delete $classes{$class};
756 if (!$dontRebuildFiles)
757 { NaturalDocs::Project->RebuildFile($file); };
760 { $self->RebuildFilesFor($class, 0, 1, 1); };
767 # Function: DeleteParentReference
769 # Deletes a class' parent reference and returns whether it resulted in the loss of a parent class. Will rebuild any file affected
770 # unless <dontRebuildFiles> is set.
774 # file - The <FileName> that defines the reference.
775 # class - The class <SymbolString>.
776 # reference - The parent <ReferenceString>.
780 # If the class lost a parent as a result of this, it will return its <SymbolString>. It will return undef otherwise.
782 sub DeleteParentReference #(file, class, reference)
784 my ($self, $file, $class, $reference) = @_;
786 if (!exists $classes{$class})
789 $files{$file}->DeleteParentReference($class, $reference);
791 my $deletedParent = $classes{$class}->DeleteParentReference($reference, $file, \%parentReferences);
793 if (defined $deletedParent)
795 my $deletedParentObject = $classes{$deletedParent};
797 $deletedParentObject->DeleteChild($class);
799 $self->RebuildFilesFor($deletedParent, 0, 1, 0);
800 $self->RebuildFilesFor($class, 0, 1, 0);
802 if (!$deletedParentObject->HasChildren() && !$deletedParentObject->IsDefined())
804 delete $classes{$deletedParent};
805 NaturalDocs::SymbolTable->DeleteReference(
806 $self->ClassReferenceOf($class, NaturalDocs::Languages->LanguageOf($file)->Name()) );
809 return $deletedParent;
817 # Function: ClassReferenceOf
819 # Returns the <REFERENCE_CH_CLASS> <ReferenceString> of the passed class <SymbolString>.
821 sub ClassReferenceOf #(class, languageName)
823 my ($self, $class, $languageName) = @_;
825 return NaturalDocs::ReferenceString->MakeFrom(::REFERENCE_CH_CLASS(), $class, $languageName, undef, undef,
826 ::RESOLVE_ABSOLUTE() | ::RESOLVE_NOPLURAL());
831 # Function: RebuildFilesFor
833 # Calls <NaturalDocs::Project->RebuildFile()> for every file defining the passed class, its parents, and/or its children.
834 # Returns without doing anything if <dontRebuildFiles> is set.
838 # class - The class <SymbolString>.
839 # rebuildParents - Whether to rebuild the class' parents.
840 # rebuildSelf - Whether to rebuild the class.
841 # rebuildChildren - Whether to rebuild the class' children.
843 sub RebuildFilesFor #(class, rebuildParents, rebuildSelf, rebuildChildren)
845 my ($self, $class, $rebuildParents, $rebuildSelf, $rebuildChildren) = @_;
847 if ($dontRebuildFiles)
853 { @classesToBuild = $classes{$class}->Parents(); };
855 { push @classesToBuild, $class; };
856 if ($rebuildChildren)
857 { push @classesToBuild, $classes{$class}->Children(); };
859 foreach my $classToBuild (@classesToBuild)
861 my @definitions = $classes{$classToBuild}->Definitions();
863 foreach my $definition (@definitions)
864 { NaturalDocs::Project->RebuildFile($definition); };