diff --git a/CMakeLists.txt b/CMakeLists.txt index f8ceae02..e8e017c3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -CMAKE_MINIMUM_REQUIRED(VERSION 3.5) +CMAKE_MINIMUM_REQUIRED(VERSION 3.15.0) # Default compiler gfortran, otherwise cmake Fortran package will set it to f95 # You can override the compiler with cmake -DFTOBJ_COMPILER= #set(FTOBJ_AR "/usr/bin/gcc-ar" CACHE STRING "Static archive command") diff --git a/Docs/FTObjectLibrary.tex b/Docs/FTObjectLibrary.tex deleted file mode 100644 index 12e373a4..00000000 --- a/Docs/FTObjectLibrary.tex +++ /dev/null @@ -1,1432 +0,0 @@ -\documentclass[9pt]{article} -\usepackage{geometry} % See geometry.pdf to learn the layout options. There are lots. -%\usepackage{url} -\geometry{letterpaper} % ... or a4paper or a5paper or ... -%\geometry{landscape} % Activate for for rotated page geometry -%\usepackage[parfill]{parskip} % Activate to begin paragraphs with an empty line rather than an indent -\usepackage{graphicx} -\usepackage{amssymb} -\usepackage{epstopdf} -\usepackage{listings} -\usepackage[colorlinks]{hyperref} -\usepackage{color} -%\hypersetup{citecolor=DeepPink4} -%\hypersetup{linkcolor=DarkRed} -%\hypersetup{urlcolor=blueColor} -%\usepackage{cleveref} - - -\textwidth = 6.5 in -\textheight = 9 in -\oddsidemargin = 0.0 in -\evensidemargin = 0.0 in -\topmargin = 0.0 in -\headheight = 0.0 in -\headsep = 0.0 in -\parskip = 0.2in -\parindent = 0.0in -\renewcommand{\familydefault}{\sfdefault} - -\DeclareGraphicsRule{.tif}{png}{.png}{`convert #1 `dirname #1`/`basename #1 .tif`.png} - -\title{FTObjectLibrary} -\author{David A. Kopriva} -\date{} % Activate to display a given date or no date - -\begin{document} -\maketitle -%\lstset{language=Fortran} % Set your language (you can change the language for each code-block optionally) -\tableofcontents{} -\section{Introduction} - FTObjectLibrary provides a collection of reference counted Fortran 2003 classes to - facilitate writing generic object oriented Fortran programs. Reference counting - is implemented to assist with memory management so that the lifespans of objects - are properly maintained and are so that objects are deleted only when no other references are made to them. - - - FTObjectLibrary tries, as much as the maturity of Fortran compilers allow, to - use the new F2003/2008 features to make generic programming possible. The LCD - for the library is gfortran, and as modern features get implemented in the - compiler, FTObjectLibrary will be updated to include those features. In the meantime, there - are a few workarounds that exist in the code. - -% The library is inspired, for the large part, by -% \href{https://developer.apple.com/library/mac/documentation/General/Conceptual/CocoaEncyclopedia/Introduction/Introduction.html#//apple_ref/doc/uid/TP40010810-CH1-SW1}{Apple's Cocoa Foundation Framework}. -% Reading about the philosophy behind that -% would go a long way towards understanding the why's and wherefore's of the FTObjectLibrary. - - The library includes - three categories of classes: - \begin{itemize} -\item Value classes -\item Container classes -\item Error reporting and testing classes -\end{itemize} -% \subsection{Value Classes} - - Value classes include the base class, FTObject and at the current time, a subclass, FTValue. - \begin{itemize} - \item {\bf FTObject} - - FTObject is the base class that implements - the reference counting mechanism and other functions that should be overridden in subclasses. - It the base class for all classes in the FTObjectLibrary library. You will usually not allocate objects - of this class. Instead you will create your own subclasses of it that have data and procedures as needed. - \item {\bf FTValue} - - FTValue is a wrapper class that - allows storage of real, integer, character and logical - values, which can then be stored in containers. - -% \item {\bf FTData} -% -% Is a class that allows one to store any kind of data structure. - \end{itemize} - - You can create your own value - classes by extending FTObject and store instances of those classes in the containers. - -% \subsection{Container Classes} - Container classes let you store any subclass of the base class FTObject in them. This makes it easy to - store, for instance, a linked list of linked lists, or an array of dictionaries. - Included in the library are the following standard container classes: - \begin{itemize} -\item {\bf FTLinkedList} - -FTLinkedList is an implementation of a doubly (and, optionally, circular) linked list. - -\item {\bf FTStack} - -FTStack is a subclass of FTLinkedList that adds the usual push, pop, and peek routines. - -\item {\bf FTSparseMatrix} - -FTSparseMatrix associates a double index (i,j) to an FTObject. Basically this is a two dimensional sparse matrix of pointers to FTObjects. -\item {\bf FTMultiIndexTable} - -FTMultiIndexTable associates an integer array keys(:) to an FTObject. Basically this is an m--dimensional sparse matrix of pointers to FTObjects. - -\item {\bf FTDictionary} - -FTDictionary is an ``associative container'', that associates a string to another FTObject. - -\item {\bf FTValueDictionary} - -FTValueDictionary is a subclass of FTDictionary that has additional methods to store and retrieve -values. - -\item {\bf FTMutableObjectArray} - -A mutable one-dimensional array class that can store any FTObject. - -\end{itemize} - -% \subsection {Error and Testing Classes} - The library also contains classes for testing (FTAssertions, TestSuiteManagerClass) and for reporting - errors through the FTException class. - -% \subsection {Example Usage} -To give an idea about how to use the library, we show how to use a dictionary to store a real value and a string that describes the value. -In the snippet of code below, we prepare the dictionary for objects to be added to it by the init() method. We then create two values that are -added to the dictionary. - -{\color{blue}\begin{verbatim} - SUBROUTINE constructDictionary(dict) - CLASS(FTDictionary), POINTER :: dict - CLASS(FTObject) , POINTER :: obj - CLASS(FTValue) , POINTER :: v - - ALLOCATE(dict) - CALL dict % initWithSize(64) - - ALLOCATE(v) - CALL v % initWithValue(3.14159) - obj => v - CALL dict % addObjectForKey(obj,``Pi'') - CALL releaseFTValue(v) - - ALLOCATE(v) - CALL v % initWithValue(``Ratio of circumference to diameter'') - obj => v - CALL dict % addObjectForKey(obj,"definition") - CALL releaseFTValue(v) - END SUBROUTINE constructDictionary -\end{verbatim}} - - Notice that in the subroutine we have allocated memory for the dictionary ``dict'' and two FTValue objects. The dictionary - is returned to the calling procedure and can be accessed from that point through the actual argument. The two value objects would normally - be expected to be deallocated when leaving the subroutine, since otherwise they would not be accessible outside - of the procedure. It would be dangerous if we deallocated the two objects that we created and then try to use them later through -the dictionary. This is where a systematic approach to memory management comes in. When we allocate and initialize an object, we assume ownership of it. -When we add it to the dictionary, it assumes partial ownership. So instead of deallocating the two value objects, we relinquish ownership -by way of the releaseFTValue() procedure, leaving only the dictionary to be responsible for deallocating them when it does not need them any more. - - We use the dictionary as shown in the next snippet of code: - -{\color{blue}\begin{verbatim} - CLASS(FTDICTIONARY), POINTER :: dict - CLASS(FTValue) , POINTER :: v - REAL :: pi - CHARACTER :: s(FTDICT_KWD_STRING_LENGTH) - CALL constructDictionary(dict) - - v => valueFromObject(dict % objectForKey("Pi")) - pi = v % realValue() - - v => valueFromObject(dict % objectForKey(``definition'')) - s = v % stringValue(FTDICT_KWD_STRING_LENGTH) - PRINT *, "The num pi = ", pi," is defined as", TRIM(s) - CALL releaseFTDictionary(dict) -\end{verbatim}} - - In this snippet, the values for the two keys ``Pi'' and ``definition'' are retrieved and then used. - (Notice the fixed length string workaround.) - The function valueFromObject() converts the generic object to the specific FTValue type. (If we try to convert something that is not an FTValue, the method returns an ASSOCIATED(v) == .FALSE. pointer.) Finally, the dictionary is - released, which will release all of its objects and then deallocate all of those objects that it is - sole owner of, which in this example is both the value and definition of $\pi$, and then DEALLOCATE it - if it is no longer owned (referenced) by another object, which is the case here. - - In this way, any object that inherits from FTObject can be stored in a container, including other containers, making these - containers quite generic, up to the limits of the Fortran language. - - \section {Memory Management} - FTObjectLibrary uses a manual retain-release memory management model known as reference counting. A description of - this model can be found at - \url{https:developer.apple.com/library/mac/#documentation/cocoa/Conceptual/MemoryMgmt/Articles/mmRules.html#apple_ref/doc/uid/20000994-BAJHFBGH}. - The basic idea is that a pointer object exists as long as someone ``owns'' it. When no one owns it any more, the object is automatically deallocated upon release. This approach makes it easy to not have to keep track of when to deallocate - pointers, and ensures that a pointer that is in use is not prematurely deallocated. In long, complex, programs, this mechanism automatically keeps track of which - objects (pointers) that have been allocated need to be deallocated, and when. - - Ownership rules are as follows: - \begin{itemize} -\item If you allocate and initialize an object, you own it. -\item You own an object if you call the retain() subroutine on that object. -\item You may inherit the ownership of an object by calling a method that creates (allocates and initializes) it. -\item When you no longer need an object (or are going out of scope) you must release it using the releaseXXX() subroutine, where XXX refers to the specific name of the extended type. -\item You must neither relinquish ownership, nor deallocate a pointer object that you do not own. -\item You generally do not own objects returned as pointers by functions or subroutines. - \end{itemize} - -\emph{Fundamentally, if you call an init() method on an object, you should call a releaseXXX() in the same scope.} - - In the example below, the main program creates a linked list to store points in, a ``point'' object (e.g. that stores x,y,z values), and adds - the point object to the linked list. - -{\color{blue}\begin{verbatim} - PROGRAM main - CLASS(FTLinkedList), POINTER :: list ! Subclass of FTObject - CLASS(Point) , POINTER :: pnt ! Subclass of FTObject - CLASS(FTObject) , POINTER :: obj - - ALLOCATE(list) - CALL list % init() ! main now owns the list - - ALLOCATE(pnt) - CALL pnt % initWithXYZ(0.0,0.0,0.0) ! main now owns pnt - - obj => pnt - CALL list % add(obj) !list also owns pnt - CALL releasePoint(pnt) ! main gives up ownership to pnt - . - . - . - ! we're done with the list, it will deallocate pnt since the list is the last owner. - ! It will also deallocate itself since main is the last owner. - CALL releaseFTLinkedList(list) - - END PROGRAM main -\end{verbatim}} - -\section{Class Descriptions} - -\subsection{FTObject} - - FTObject defines the basic methods that are essential for reference counted objects. -FTObject is generally not going to be instantiated by itself, but rather it will - be subclassed and you will work with instances of the subclasses. FTValue, for instance, - is a subclass of FTObject. - Otherwise, pointers of type FTObject that point to instances of subclasses - will be stored in the container classes. - - - \subsubsection{Tasks} -\begin{itemize} -\item {\bf init()} - - Initializes an object and any memory that it needs to allocate, etc. - Should be implemented in subclasses.The base class implementation does nothing but - increase the reference count of the object. - -%\item {\bf destruct()} -% -% Destructor of the object, which releases and deallocates owned objects and memory. -% Should be implemented in subclasses. The base class implementation does nothing but -% decrease the reference count of the object. \emph{One does not call the destruct -% subroutine on pointers except to call the superclass destruct in a subclasses destructor. For -% non-pointer (stack variables) variables, you call the destruct rather than the release procedure.} - -\item {\bf printDescription(iUnit)} - - Prints a description of the object to a specified file unit. The base class implementation - does nothing but print ``FTObject'' - -\item{\bf copy()} - - Creates a copy (pointer) to the object of CLASS(FTObject) sourced with the object. - -\item {\bf retain()} - - Increases the reference count of the object. Any procedure or object that retain()'s - an object gains an ownership stake in that object. \emph{This procedure is not overridable.} - -\item {\bf releaseFTObject()} - - Decreases the reference count of a base class object pointer. To be called only by objects or procedures - that have ownership in an object pointer, i.e., for which init() or retain() have been called. - -\item {\bf isUnreferenced()} - - Test to see if there are no more owners of an object. Usually this is of interest only for debugging purposes. \emph{This procedure is not overridable.} - -\item {\bf refCount()} - - Returns the number of owners of an object. Usually this is of interest only for debugging purposes. - \emph{This procedure is not overridable.} -\end{itemize} - - \subsubsection{Subclassing FTObject} - - In general, subclasses of FTObject implement -\begin{itemize} -\item {\bf init()} -\item {\bf The destructor (FINAL)} -\item {\bf printDescription()} -\item {\bf release(self)} -\end{itemize} - -% A subclass should also provide function to convert from the base class to a subclass. -% The procedure can look something like -% -%{\color{blue}\begin{verbatim} -% FUNCTION subclassFromObject(obj) RESULT(cast) -% IMPLICIT NONE -% CLASS(FTObject), POINTER :: obj -% CLASS(SubClass), POINTER :: cast -% -% cast => NULL() -% SELECT TYPE (e => obj) -% TYPE is (SubClass) -% cast => e -% CLASS DEFAULT -% -% END SELECT -% -% END FUNCTION subclassFromObject -%\end{verbatim}} - - -{\bf Implementing init() } - - The init() procedure performs subclass specific operations to initialize an object. - - Subclasses that override init() \emph{must} include - a call to the super class method. For example, if ``Subclass'' EXTENDS(FTObject), overriding init() looks like - -{\color{blue}\begin{verbatim} - SUBROUTINE initSubclass(self) - IMPLICIT NONE - CLASS(Subclass) :: self - - CALL self % FTObject % init() - Allocate and initialize all member objects - ... Other Subclass specific code - END SUBROUTINE initSubclass -\end{verbatim}} - -You can have multiple initializers for an object, so it is also worthwhile understanding the concept of the ``designated initializer''. Generally speaking this is the -initializer that has the most arguments. It is also the only one that includes the call to the super class init procedure. For example, the designated initializer for a ``point'' class would be the one that takes the (x,y,z) values. - -{\color{blue}\begin{verbatim} - SUBROUTINE initPointWithXYZ(self,x,y,z) - IMPLICIT NONE - CLASS(Subclass) :: self - - CALL self % FTObject % init() - self % x = x - self % y = y - self % z = z - END SUBROUTINE initPointWithXYZ -\end{verbatim}} - -Other initializers might be the default, and the initializer that takes an array of length three. They will -do nothing but call the designated initializer. The default initializer sets the location to the origin, or some other -reasonable value. - -{\color{blue}\begin{verbatim} - SUBROUTINE initPoint(self) - IMPLICIT NONE - CLASS(Subclass) :: self - - call self % initPointWithXYZ(0.0,0.0,0.0) - END SUBROUTINE initPoint -\end{verbatim}} - -The array initializer is -{\color{blue}\begin{verbatim} - SUBROUTINE initPointWithArray(self,w) - IMPLICIT NONE - CLASS(Subclass) :: self - REAL :: w(3) - - CALL self % initPointWithXYZ(w(1),w(2),w(3)) - END SUBROUTINE initPointWithArray -\end{verbatim}} - -{\bf Implementing the destructor} - - The destructor reverses the operations done in the init() procedure. It releases and - deallocates any pointers that it owns. For example, if ``Subclass'' EXTENDS(FTObject) then overriding destruct looks like - -{\color{blue}\begin{verbatim} - SUBROUTINE destructSubclass(self) - IMPLICIT NONE - TYPE(Subclass) :: self - . - Release and deallocate (if necessary) all member objects - . - END SUBROUTINE destructSubclass -\end{verbatim}} - -The destructor is to be declared as a FINAL procedure for the type, and hence is not called directly. It is called automatically by Fortran -when an object is deallocated or goes out of scope. - -{\bf Implementing printDescription(iUnit)} - - printDescription is a method whose existence is to support debugging. Call printDescription(iUnit) - on any objects owned by self for a cascading of what is stored in the object. - -{\bf Implementing releaseXXX(self)} - -The release subroutine will call the base class releaseFTObject which will, in turn, release all objects that it -owns. If the object itself is no longer referenced, it will deallocate itself. If the subclass is going to be subclassed again, use the CLASS specifier, otherwise, we TYPE to work only on that specific subclass. - -{\color{blue}\begin{verbatim} - SUBROUTINE releaseSubclass(self) - IMPLICIT NONE - TYPE(Subclass) , POINTER :: self - CLASS(FTObject), POINTER :: obj - obj => self - CALL releaseFTObject(self = obj) - IF ( .NOT. ASSOCIATED(obj) ) THEN - self => NULL() - END IF - END SUBROUTINE releaseSubclass -\end{verbatim}} - -It is best to name the release procedures consistently. For instance, all the FTObjectLibrary classes declare their -release procedure by using the name of the extended type, for example releaseFTDictionary or releaseFTValueDictionary. - -{\bf Converting an object from the base to a subclass} - - Container classes and the copy function return pointers to a CLASS(FTObject). To use - any subclass features one must ``cast'' or convert a pointer to a pointer to the subclass. We like to have a specific - cast routine to do this as painlessly as possible. Each subclass should include a - function like this: - -{\color{blue}\begin{verbatim} - FUNCTION subclassFromSuperclass(obj) RESULT(cast) - IMPLICIT NONE - CLASS(FTObject), POINTER :: obj - CLASS(Subclass), POINTER :: cast - cast => NULL() - SELECT TYPE (e => obj) - TYPE is (Subclass) - cast => e - CLASS DEFAULT - END SELECT - END FUNCTION subclassFromSuperclass -\end{verbatim}} - -You saw an example above in the ``valueFromObject(obj)'' function. -\subsection{FTValue} - FTValue is a not completely F2003/2008 version of an immutable class - to store primitive values: integer, real, double precision, logical, - character. (To Add: complex) - - This version does not use CLASS(*) or deferred length strings - so that it can be used with gfortran 4.7/4.8. That's a shame, because both are really useful features to have. - - \subsubsection{Usage} -\begin{itemize} -\item Initialization - -{\color{blue}\begin{verbatim} - TYPE(FTValue) :: r, i, s, l, d - - CALL r % initWithValue(3.14) - CALL i % initWithValue(6) - CALL d % initWithValue(3.14d0) - CALL l % initWithValue(.true.) - CALL s % initWithValue("A string") -\end{verbatim}} - -\item Destruction - -{\color{blue}\begin{verbatim} - CALL releaseFTValue(r) !For Pointers -\end{verbatim}} - -\item Accessors - -{\color{blue}\begin{verbatim} - real = r % realValue() - int = i % integerValue() - doub = d % doublePrecisionValue() - logc = l % logicalValue() - str = s % stringValue(nChars) -\end{verbatim}} - -\item Description - -{\color{blue}\begin{verbatim} - str = v % description() - call v % printDescription(unit) -\end{verbatim}} - -\item Converting a base FTObject to an FTValue - -{\color{blue}\begin{verbatim} - CLASS(FTValue) , POINTER :: v - CLASS(FTObject), POINTER :: obj - v => valueFromObject(obj) -\end{verbatim}} -\end{itemize} - The class will attempt to convert between the different types: - -{\color{blue}\begin{verbatim} - CALL r % initWithReal(3.14) - print *, r % stringValue(8) - - Logical variables rules: - - real, doublePrecision, integer values - logicalValue = .FALSE. if input = 0 - logicalValue = .TRUE. if input /= 0 -\end{verbatim}} - - String values can be converted to numeric types. If the string is - not a numeric, $Huge(x)$ will be returned, where $x$ is of the - requested type. - -\subsection{Linked Lists} - -A linked list is a dynamic container that links the objects added to it in a chain. The chain can be of -any length and objects can be added or removed at any time and at any location in the chain. Linked lists -are useful when you don't know how many objects will be stored. They are fast to add or delete -objects from, but they are slow to access any given object. To access objects, you start at the beginning -of the chain and then follow the chain it until you reach the desired entry. You use a linked list when sequential -access is more typical than random access. Special classes of linked lists are singly linked lists, which -can be followed in only one direction from the start, double linked lists that can be followed in either direction, -and circular lists where the ends are connected. - -\subsubsection{FTLinkedList} - FTLinkedList is a container class that stores objects in a doubly linked list. Optionally, the list - can be made circular. As usual, FTLinkedList inherits from FTObjectClass. - - - {\bf Definition (Subclass of FTObject):} - {\color{blue}\begin{verbatim} - TYPE(FTLinkedList) :: list - \end{verbatim}} - - { \bf Usage:} - -\begin{itemize} - \item Initialization - - {\color{blue}\begin{verbatim} - CLASS(FTLinkedList), POINTER :: list - ALLOCATE(list) - CALL list % init() - \end{verbatim} -} - \item Adding an object - - {\color{blue}\begin{verbatim} - CLASS(FTLinkedList), POINTER :: list - CLASS(FTObject) , POINTER :: obj - - obj => r ! r is subclass of FTObject - CALL list % Add(obj) ! Pointer is retained by list - CALL release(r) ! If control is no longer wanted in this scope. - \end{verbatim}} - - \item Combining lists - - {\color{blue}\begin{verbatim} - CLASS(FTLinkedList), POINTER :: list, listToAdd - CLASS(FTObject) , POINTER :: obj - . - . - . - CALL list % addObjectsFromList(listToAdd) - \end{verbatim}} - - \item Inserting objects - - {\color{blue}\begin{verbatim} - CLASS(FTLinkedList) , POINTER :: list - CLASS(FTObject) , POINTER :: obj - CLASS(FTLinkedListRecord), POINTER :: record - - obj => r ! r is subclass of FTObject - CALL list % insertObjectAfterRecord(obj,record) ! Pointer is retained by list - CALL release(r) ! If caller wants to reliquish ownership - \end{verbatim}} - OR - {\color{blue}\begin{verbatim} - CLASS(FTLinkedList) , POINTER :: list - CLASS(FTObject) , POINTER :: obj, otherObject - CLASS(FTLinkedListRecord), POINTER :: record - - obj => r ! r is subclass of FTObject - CALL list % insertObjectAfterObject(obj,otherObject) ! Pointer is retained by list - CALL release(r) ! If caller wants to reliquish ownership - \end{verbatim}} - - - \item Removing objects - {\color{blue}\begin{verbatim} - CLASS(FTLinkedList), POINTER :: list - CLASS(FTObject) , POINTER :: obj - obj => r ! r is subclass of FTObject - CALL list % remove(obj) - \end{verbatim}} - - \item Getting an array of all of the objects - - {\color{blue}\begin{verbatim} - CLASS(FTLinkedList) , POINTER :: list - CLASS(FTMutableObjectArray) , POINTER :: array - array => list % allObjects() ! Array has refCount = 1 - \end{verbatim}} - - \item Making a linked list circular or not. The default is not circular. - {\color{blue}\begin{verbatim} - LOGICAL :: c = .true. - CALL list % makeCircular( c ) - \end{verbatim}} - - \item Checking to see if a linked list circular or not - {\color{blue}\begin{verbatim} - LOGICAL :: c - c = list % isCircular() - \end{verbatim}} - - \item Counting the number of objects in the list - - {\color{blue}\begin{verbatim} - n = list % count() - \end{verbatim}} - - \item Reversing the order of the list - - {\color{blue}\begin{verbatim} - CALL list % reverse() - \end{verbatim}} - - \item Destruction - - {\color{blue}\begin{verbatim} - CALL releaseFTLinkedList(list) ! If list is a pointer - \end{verbatim}} -\end{itemize} - -\subsubsection{FTLinkedListIterator} -Linked lists must be navigated in order along the chain. This is usually accomplished with the help of -an \emph{iterator} to navigate linked lists. FTObjectLibrary includes a class called FTLinkedListIterator for stepping through (iterating) a linked -list to access its entries. - - - {\bf Definition (Subclass of FTObject):} - {\color{blue}\begin{verbatim} - TYPE(FTLinkedListIterator) :: iterator - \end{verbatim}} - - { \bf Usage:} -\begin{itemize} - \item Initialization - -{\color{blue}\begin{verbatim} - CLASS(FTLinkedListIterator), POINTER :: iterator - CLASS(FTLinkedList) , POINTER :: list - ALLOCATE(iterator) - CALL iterator % init() - . - . - . -\end{verbatim}} -Or -{\color{blue}\begin{verbatim} - CLASS(FTLinkedList) , POINTER :: list - CLASS(FTLinkedListIterator), POINTER :: iterator - ALLOCATE(iterator) - CALL iterator % initWithFTLinkedList(list) ! Increases refCount of list - . - . - . -\end{verbatim}} - - \item Setting the iterator to the beginning of the list - -{\color{blue}\begin{verbatim} - CALL iterator % setToStart() -\end{verbatim}} - -\item Testing if the iterator is at the end of the list - -{\color{blue}\begin{verbatim} - IF( iterator % isAtEnd() ) -\end{verbatim}} - -\item Iterating through the list and accessing contained objects -{\color{blue}\begin{verbatim} - CLASS(FTObject), POINTER :: obj - CALL iterator % setToStart() - DO WHILE (.NOT.iterator % isAtEnd()) - obj => iterator % object() ! if the object is wanted - recordPtr => iterator % currentRecord() ! if the record is wanted - - !Do something with object or record - - CALL iterator % moveToNext() ! FORGET THIS CALL AND YOU GET AN INFINITE LOOP! - END DO -\end{verbatim}} - - \item Destruction - -{\color{blue}\begin{verbatim} - CALL releaseFTLinkedListIterator(iterator) ! If a pointer -\end{verbatim}} -\end{itemize} - -\subsection{Stacks} -A stack is a data structure that enforces last-in/first-out access to the objects stored in it. One puts -a new object onto the top of the stack with a \emph{push} operation, and pulls off the object at the -top of the stack with a \emph{pop} operation. Usually one has the option to just look at the top of -the stack with a \emph{peek} operation that doesn't remove the top object. You would implement a -Reverse Polish calculator with a stack, for instance. - - {\bf Definition (Subclass of FTLinkedListClass):} - {\color{blue}\begin{verbatim} - TYPE(FTStack) :: stack - \end{verbatim}} - {\bf Usage:} - -\begin{itemize} - \item Initialization - {\color{blue}\begin{verbatim} - ALLOCATE(stack) ! If stack is a pointer - CALL stack % init() - \end{verbatim}} - \item Destruction - {\color{blue}\begin{verbatim} - CALL releaseFTStack(stack) ! If stack is a pointer - \end{verbatim}} - \item Pushing an object onto the stack - - {\color{blue}\begin{verbatim} - TYPE(FTObject) :: obj - obj => r1 - CALL stack % push(obj) - \end{verbatim}} - \item Peeking at the top of the stack - {\color{blue} - \begin{verbatim} - obj => stack % peek() ! No change of ownership - SELECT TYPE(obj) - TYPE is (*SubclassType*) - É Do something with obj as subclass - CLASS DEFAULT - É Problem with casting - END SELECT - \end{verbatim} - } - \item Popping the top of the stack - {\color{blue}\begin{verbatim} - obj => stack % pop() ! Ownership transferred to caller. - ! Call releaseFTObject(obj) when done with it - \end{verbatim}} -\end{itemize} -\subsection{Object Arrays} -Fortran has pointers to arrays, but not arrays of pointers. To do the latter, one creates -a wrapper derived type and creates an array of that wrapper type. Fortran arrays are great, but -they are of fixed length, and they don't easily implement reference counting to keep track of -memory. For that, we have the FTMutableObjectArray. Performance reasons dictate that you -will use regular arrays for numeric types and the like, but for generic objects we would use -an Object Array. - -You initialize a FTMutableObjectArray with the number of objects that you expect it to hold. -However, it can re-size itself if necessary. To be efficient, it adds more than one entry at a time -given by the ``chunkSize'', which you can choose for yourself. (The default is 10.) - - {\bf Definition (Subclass of FTObject):} - {\color{blue}\begin{verbatim} - TYPE(FTMutableObjectArray) :: array - \end{verbatim}} - {\bf Usage:} - -\begin{itemize} - \item Initialization - {\color{blue}\begin{verbatim} - CLASS(FTMutableObjectArray) :: array - INTEGER :: N = 11 - CALL array % initWithSize(N) - \end{verbatim}} - - \item Destruction - {\color{blue}\begin{verbatim} - CALL releaseFTMutableObjectArray(array) !If array is a pointer - \end{verbatim}} - - \item Adding an object - - {\color{blue}\begin{verbatim} - TYPE(FTObject) :: obj - obj => r1 - CALL array % addObject(obj) - \end{verbatim}} - - \item Removing an object - {\color{blue}\begin{verbatim} - TYPE(FTObject) :: obj - CALL array % removeObjectAtIndex(i) - \end{verbatim}} - - \item Accessing an object - {\color{blue}\begin{verbatim} - TYPE(FTObject) :: obj - obj => array % objectAtIndex(i) - \end{verbatim}} - - \item Replacing an object - {\color{blue}\begin{verbatim} - TYPE(FTObject) :: obj - obj => r1 - CALL array % replaceObjectAtIndexWithObject(i,obj) - \end{verbatim}} - - \item Setting the chunk size - {\color{blue}\begin{verbatim} - CALL array % setChunkSize(size) - \end{verbatim}} - - \item Getting the chunk size - {\color{blue}\begin{verbatim} - i = array % chunkSize(size) - \end{verbatim}} - - \item Finding the number of items in the array - {\color{blue}\begin{verbatim} - n = array % count() - \end{verbatim}} - - \item Finding the actual allocated size of the array - {\color{blue}\begin{verbatim} - n = array % allocatedSize() - \end{verbatim}} - - \item Converting a base class pointer to an object array - {\color{blue}\begin{verbatim} - Array => objectArrayFromObject(obj) - \end{verbatim}} - -\end{itemize} - -\subsection{Sparse Matrices} -Hash tables are data structures designed to enable storage and fast -retrieval of key-value pairs. An example of a key-value pair is -a variable name (``gamma'') and its associated value (``1.4''). -The table itself is typically an array. -The location of the value in a hash table associated with -a key, $k$, is specified by way of a \emph{hash function}, $H(k)$. -In the case of a variable name and value, the hash function -would convert the name into an integer that tells where to -find the associated value in the table. - -A very simple example of a -hash table is, in fact, a singly dimensioned array. The key is -the array index and the value is what is stored at that index. -Multiple keys can be used to identify data; a two dimensional -array provides an example of where two keys are used to access memory -and retrieve the value at that location. -If we view a singly dimensioned array as a special case of a hash table, -its hash function is just the array index, $H(j)=j$. A doubly dimensioned array -could be (and often is) stored columnwise as a singly dimensioned array by creating a hash -function that maps the two indices to a single location in the array, e.g., -$H(i,j) = i + j*N$, where $N$ is the range of the first index, $i$. - -Two classes are included in FTObjectLibrary. The first, FTSparseMatrix, works with an ordered pair, (i,j), as the -keys. The second, FTMultiIndexTable, uses an array of integers as the keys. - -Both classes include enquiry functions to see of an object exists for the given keys. Otherwise, -the function that returns an object for a given key will return an UNASSOCIATED pointer if there -is no object for the key. Be sure to retain any object returned by the objectForKeys methods if -you want to keep it beyond the lifespan of the matrix or table. For example, - - {\color{blue}\begin{verbatim} - TYPE(FTObject) :: obj - obj => matrix % objectForKeys(i,j) - IF ( ASSOCIATED(OBJ) ) THEN - CALL obj % retain() - ! Cast obj to something useful - ELSE - ! Perform some kind of error recovery - END IF - \end{verbatim}} - - - -\subsubsection{FTSparseMatrix} -The sparse matrix included in the FTObjectLibrary is very simple in that it has a predefined -hash function with two keys, $(i,j)$. You will initialize the matrix -with the number of rows. - - {\bf Definition (Subclass of FTObject):} - {\color{blue}\begin{verbatim} - TYPE(FTSparseMatrix) :: matrix - \end{verbatim}} - {\bf Usage:} - -\begin{itemize} - \item Initialization - {\color{blue}\begin{verbatim} - CLASS(FTSparseMatrix) :: matrix - INTEGER :: N = 11 ! Number of rows - CALL matrix % initWithSize(N) - \end{verbatim}} - - \item Destruction - {\color{blue}\begin{verbatim} - CALL releaseFTSparseMatrix(matrix) !If matrix is a pointer - \end{verbatim}} - - \item Adding an object - - {\color{blue}\begin{verbatim} - TYPE(FTObject) :: obj - matrix % addObjectForKeys(obj,i,j) - \end{verbatim}} - - \item Accessing an object - - {\color{blue}\begin{verbatim} - TYPE(FTObject) :: obj - obj => matrix % objectForKeys(i,j) - \end{verbatim}} - - \item Checking if an entry exists - {\color{blue}\begin{verbatim} - LOGICAL :: exists - exists = matrix % containsKeys(i,j) - \end{verbatim}} - -\end{itemize} -\subsubsection{FTMultiIndexTable} -An extension (not in the subclass sense) of the sparse matrix class is the MultiIndexTable. It uses an integer array -of keys instead of just an (i,j) pair. A multiIndexTable can be used, for instance, to determine if the four node ids of -a face of an element match those of another face of another element. - - {\bf Definition (Subclass of FTObject):} - {\color{blue}\begin{verbatim} - TYPE(FTMultiIndexTable) :: table - \end{verbatim}} - {\bf Usage:} - -\begin{itemize} - \item Initialization - {\color{blue}\begin{verbatim} - CLASS(FTMultiIndexTable) :: table - INTEGER :: N = 11 ! maximum over all keys - CALL table % initWithSize(N) - \end{verbatim}} - - \item Destruction - {\color{blue}\begin{verbatim} - CALL releaseFTMultiIndexTable(table) !If table is a pointer - \end{verbatim}} - - \item Adding an object - - {\color{blue}\begin{verbatim} - TYPE(FTObject) :: obj - INTEGER :: keys(m) ! m = # keys - matrix % addObjectForKeys(obj,keys) - \end{verbatim}} - - \item Accessing an object - - {\color{blue}\begin{verbatim} - TYPE(FTObject) :: obj - INTEGER :: keys(m) ! m = # keys - obj => matrix % objectForKeys(keys) - \end{verbatim}} - - \item Checking if an entry exists - {\color{blue}\begin{verbatim} - LOGICAL :: exists - INTEGER :: keys(m) ! m = # keys - exists = matrix % containsKeys(keys) - \end{verbatim}} - -\end{itemize} - -\subsection{Dictionaries} -A dictionary is a special case of a hash table that stores \emph{key-value pairs}. It is an -example of what is called an ``associative container''. In the implementation of FTObjectLibrary, -the value can be any subclass of FTObject and the key is a character variable. The library -includes the base dictionary that can store and retrieve any subclass of FTObject. It also includes a -subclass that is designed to store and retrieve FTValue objects. - -\subsubsection{FTDictionary} - {\bf Definition (Subclass of FTObject):} - {\color{blue}\begin{verbatim} - TYPE(FTDictionary) :: dict - \end{verbatim}} - {\bf Usage:} - -\begin{itemize} - \item Initialization - {\color{blue}\begin{verbatim} - CLASS(FTDictionary) :: dict - INTEGER :: N = 16 ! Should be a power of two. - CALL dict % initWithSize(N) - \end{verbatim}} - - \item Destruction - {\color{blue}\begin{verbatim} - CALL releaseFTDictionary(dict) !If dict is a pointer - \end{verbatim}} - - \item Adding a key-object pair - - {\color{blue}\begin{verbatim} - CLASS(FTDictionary), POINTER :: dict - CLASS(FTObject) , POINTER :: obj - CHARACTER(LEN=M) :: key - obj => r ! r is subclass of FTObject - CALL dict % addObjectForKey(obj,key) - \end{verbatim}} - - \item Accessing an object - - {\color{blue}\begin{verbatim} - TYPE(FTObject) :: obj - obj => dict % objectForKey(key) - \end{verbatim}} - - \item Converting a base class pointer to a dictionary - {\color{blue}\begin{verbatim} - dict => dictionaryFromObject(obj) - \end{verbatim}} - - \item Getting all of the keys (The target of the pointer must be deallocated by the caller) - {\color{blue}\begin{verbatim} - CHARACTER(LEN=FTDICT_KWD_STRING_LENGTH), POINTER :: keys(:) - keys => dict % allKeys() - \end{verbatim}} - - \item Getting all of the objects - {\color{blue}\begin{verbatim} - CLASS(FTMutableObjectArray), POINTER :: objectArray - objectArray => dict % allObjects() ! The array is owned by the caller. - \end{verbatim}} - -\end{itemize} -\subsubsection{FTValueDictionary} -FTValueDictionary adds methods to store and retrieve FTValue objects. - - {\bf Definition (Subclass of FTDictionary):} - {\color{blue}\begin{verbatim} - TYPE(FTValueDictionary) :: dict - \end{verbatim}} - {\bf Usage:} -\begin{itemize} -\item Adding a value - {\color{blue}\begin{verbatim} - CALL dict % addValueForKey(1,"integer") - CALL dict % addValueForKey(3.14,"real") - CALL dict % addValueForKey(98.6d0,"double") - CALL dict % addValueForKey(.true.,"logical") - CALL dict % addValueForKey("Hello World","string") - \end{verbatim}} - -\item Accessing a value - {\color{blue}\begin{verbatim} - i = dict % integerValueForKey("integer") - r = dict % realValueForKey("real") - d = dict % doublePrecisionValueForKey("double") - l = dict % logicalValueForKey("logical") - s = dict % stringValueForKey("string",15) - \end{verbatim}} - - Note that the FTValue class will do type conversion, so you can also access something like - {\color{blue}\begin{verbatim} - i = dict % integerValueForKey("real") - s = dict % stringValueForKey(``real'',15) - \end{verbatim}} -\item Converting an FTDictionary to an FTValueDictionary - {\color{blue}\begin{verbatim} - valueDict => valueDictionaryFromDictionary(dict) - \end{verbatim}} -\item Converting an FTObject to an FTValueDictionary - {\color{blue}\begin{verbatim} - valueDict => valueDictionaryFromObject(obj) - \end{verbatim}} - -\end{itemize} -\section{String Sets} -A StringSet implements set operations on strings. An FTStringSet is an unordered group of unique (case dependent) strings. It is -primarily used to find whether or not a given string is a member of a set of strings. The class also includes usual -set operations of intersection, union and difference. To enumerate over the items in a set, use the strings() procedure -to return a pointer to an array of the strings in the set, and then loop over that array. - -An empty set can be initialized, to which strings can be added, or a set can be initialized with an array of strings. The -string length must be FTDICT\_KWD\_STRING\_LENGTH or less. - - {\bf Definition (Subclass of FTObject):} - {\color{blue}\begin{verbatim} - TYPE(FTStringSet) :: set - \end{verbatim}} - {\bf Usage:} - -\begin{itemize} - \item Initialization - {\color{blue}\begin{verbatim} - CLASS(FTStringSet) :: set - INTEGER :: N = 16 ! Should be a power of two. - CALL dict % initFTStringSet(FTStringSetSize = N) - \end{verbatim}} - - {\color{blue}\begin{verbatim} - CLASS(FTStringSet) :: set - CHARACTER(LEN=*) :: strings(:) - CALL dict % initWithStrings(strings) - \end{verbatim}} - - \item Destruction - {\color{blue}\begin{verbatim} - CALL releaseFTStringSet(set) !If set is a pointer - \end{verbatim}} - - \item Adding a string - - {\color{blue}\begin{verbatim} - CHARACTER(LEN=M) :: str - CALL set % addString(str) - \end{verbatim}} - - \item Testing for inclusion - - {\color{blue}\begin{verbatim} - LOGICAL :: test - CHARACTER(LEN=M) :: str - test = set % containsString(str) - \end{verbatim}} - - \item Finding the union of two sets (Release when through) - {\color{blue}\begin{verbatim} - unionSet => set1 % unionWithSet(set2) - ... - call ReleaseFTStringSet(unionSet) - \end{verbatim}} - - \item Finding the intersection of two sets (Release when through) - {\color{blue}\begin{verbatim} - intersectionSet => set1 % intersectionWithSet(set2) - ... - call ReleaseFTStringSet(intersectionSet) - \end{verbatim}} - - \item Finding the difference of two sets (Release when through) - {\color{blue}\begin{verbatim} - differenceSet => set1 % setFromDifference(set2) - ... - call ReleaseFTStringSet(differenceSet) - \end{verbatim}} - - \item Converting a base class pointer to a set - {\color{blue}\begin{verbatim} - set => FTStringSetFromObject(obj) - \end{verbatim}} - - \item Getting all of the strings (The target of the pointer must be deallocated by the caller) - {\color{blue}\begin{verbatim} - CHARACTER(LEN=FTDICT_KWD_STRING_LENGTH), POINTER :: strings(:) - strings => set % strings() - ... - deallocate(strings) - \end{verbatim}} - -\end{itemize} - - - -\section{Advanced Classes: Testing and Error Reporting} - -The library includes classes for testing and reporting errors. Errors are reported through -instances of the FTException class. Testing is made semi-automatic through the -TestSuiteManager class and procedures defined in the FTAssertions module. -\subsection{Assertions} -An assertion is a true-false statement that you expect to be true. Assertions are used -to test for exceptional situations (AKA ``Failures'') in a code. For example, knowing that density -always must be positive, you might assert that fact before using it, and if the result is false -generate an error. With FTObjectLibrary that would be - {\color{blue}\begin{verbatim} - CALL assert(rho > 0,``Density must be positive'') - \end{verbatim}} - - -Fortran does not have an assertion mechanism, and so pretty much everyone writes their own. -There are a couple of open source projects available, but one never knows how actively they will be -maintained. In the grand Fortran tradition of writing one's own, FTObjectLibrary has -an (incomplete) assertion module. - -To use assertions, you will USE the FTAssertions module and initialize the assertions system by calling - {\color{blue}\begin{verbatim} - CALL initializeSharedAssertionsManager - \end{verbatim}} -During the course of your program, the sharedAssertionsManager will keep track of the -success or failure of the assertions that you make. You can enquire at any time how many assertions -have failed and how many assertions have been made with the two enquiry functions - {\color{blue}\begin{verbatim} - INTEGER FUNCTION numberOfAssertionFailures() - INTEGER FUNCTION numberOfAssertions() - \end{verbatim}} -You can get a summary of the assertions by calling the subroutine - {\color{blue}\begin{verbatim} - SUBROUTINE SummarizeFTAssertions(title,iUnit) - IMPLICIT NONE - CHARACTER(LEN=*) :: title - INTEGER :: iUnit - \end{verbatim}} -When you are done, you finalize the sharedAssertionsManager with - {\color{blue}\begin{verbatim} - CALL finalizeSharedAssertionsManager - \end{verbatim}} - -So how do you make assertions? FTObjectLibrary supplies two subroutines that post -failures to the sharedAssertionsManager. The first takes a LOGICAL variable - {\color{blue}\begin{verbatim} - SUBROUTINE assert(test,msg) - IMPLICIT NONE - CHARACTER(LEN=*), OPTIONAL :: msg - LOGICAL :: test - \end{verbatim}} -The second tests equality through the overloaded subroutine assertEqual, which allows a variety -of argument type listed below: - {\color{blue}\begin{verbatim} - INTERFACE assertEqual - MODULE PROCEDURE assertEqualTwoIntegers - MODULE PROCEDURE assertEqualTwoIntegerArrays1D - MODULE PROCEDURE assertEqualTwoIntegerArrays2D - MODULE PROCEDURE assertWithinToleranceTwoReal - MODULE PROCEDURE assertWithinToleranceTwoRealArrays1D - MODULE PROCEDURE assertWithinToleranceTwoRealArrays2D - MODULE PROCEDURE assertWithinToleranceTwoDouble - MODULE PROCEDURE assertWithinToleranceTwoDoubleArrays1D - MODULE PROCEDURE assertWithinToleranceTwoDoubleArrays2D - MODULE PROCEDURE assertEqualTwoLogicals - MODULE PROCEDURE assertEqualString - END INTERFACE assertEqual - \end{verbatim}} -The individual calls have the signatures - {\color{blue}\begin{verbatim} - SUBROUTINE assertEqualTwoIntegers(expectedValue,actualValue,msg) - IMPLICIT NONE - INTEGER, INTENT(in) :: expectedValue,actualValue - CHARACTER(LEN=*), OPTIONAL :: msg - - SUBROUTINE assertEqualTwoIntegerArrays1D(expectedValue,actualValue) - IMPLICIT NONE - INTEGER, INTENT(in) , DIMENSION(:) :: expectedValue,actualValue - - SUBROUTINE assertEqualTwoIntegerArrays2D(expectedValue,actualValue) - IMPLICIT NONE - INTEGER, INTENT(in) , DIMENSION(:,:) :: expectedValue,actualValue - SUBROUTINE assertWithinToleranceTwoReal(x,y,tol,msg) - IMPLICIT NONE - REAL, INTENT(in) :: x,y,tol - CHARACTER(LEN=*), OPTIONAL :: msg - - SUBROUTINE assertWithinToleranceTwoRealArrays1D(expectedValue,actualValue,tol,msg) - IMPLICIT NONE - REAL, INTENT(IN), DIMENSION(:) :: expectedValue,actualValue - REAL, INTENT(IN) :: tol - CHARACTER(LEN=*), OPTIONAL :: msg - - SUBROUTINE assertWithinToleranceTwoRealArrays2D(expectedValue,actualValue,tol) - IMPLICIT NONE - REAL, INTENT(IN), DIMENSION(:,:) :: expectedValue,actualValue - REAL, INTENT(IN) :: tol - - SUBROUTINE assertWithinToleranceTwoDouble(expectedValue,actualValue,tol,msg) - IMPLICIT NONE - DOUBLE PRECISION, INTENT(in) :: expectedValue,actualValue,tol - CHARACTER(LEN=*), OPTIONAL :: msg - - SUBROUTINE assertWithinToleranceTwoDoubleArrays1D(expectedValue,actualValue,tol,msg) - IMPLICIT NONE - DOUBLE PRECISION, INTENT(IN), DIMENSION(:) :: expectedValue,actualValue - DOUBLE PRECISION, INTENT(IN) :: tol - CHARACTER(LEN=*), OPTIONAL :: msg - - SUBROUTINE assertWithinToleranceTwoDoubleArrays2D(expectedValue,actualValue,tol) - IMPLICIT NONE - DOUBLE PRECISION, INTENT(IN), DIMENSION(:,:) :: expectedValue,actualValue - DOUBLE PRECISION, INTENT(IN) :: tol - - SUBROUTINE assertEqualString(expectedValue,actualValue,msg) - IMPLICIT NONE - CHARACTER(LEN=*) :: expectedValue,actualValue - CHARACTER(LEN=*), OPTIONAL :: msg - - SUBROUTINE assertEqualTwoLogicals(expectedValue,actualValue,msg) - IMPLICIT NONE - LOGICAL, INTENT(in) :: expectedValue,actualValue - CHARACTER(LEN=*), OPTIONAL :: msg - \end{verbatim}} - Notice that you can only check the equality of two floating point numbers to within some tolerance. -\subsection{Testing} -FTObjectLibrary also includes a testing suite with which you can create a suite of tests -to make sure your codes are working and stay working. The tests are managed by an instance of the -{\color{blue}{TestSuiteManager}} class. It is designed to be used with minimal fuss. You -\begin{enumerate} -\item Initialize the test suite -\item Add test subroutines -\item Have the testSuiteManager perform the tests -\item Finalize the test suite manager -\end{enumerate} - - -An example of running a suite of tests is the following: - {\color{blue}\begin{verbatim} - TYPE(TestSuiteManager) :: testSuite - - EXTERNAL :: FTDictionaryClassTests - EXTERNAL :: FTExceptionClassTests - EXTERNAL :: FTValueClassTests - EXTERNAL :: FTValueDictionaryClassTests - EXTERNAL :: FTLinkedListClassTests - EXTERNAL :: StackClassTests - EXTERNAL :: MutableArrayClassTests - EXTERNAL :: HashTableTests - - CALL testSuite % init() - - CALL testSuite % addTestSubroutineWithName(FTValueClassTests,"FTValueClass Tests") - CALL testSuite % addTestSubroutineWithName(FTDictionaryClassTests,"FTDictionaryClass Tests") - CALL testSuite % addTestSubroutineWithName(FTValueDictionaryClassTests,"FTValueDictionaryClass Tests") - CALL testSuite % addTestSubroutineWithName(FTLinkedListClassTests,"FTLinkedListClass Tests") - CALL testSuite % addTestSubroutineWithName(StackClassTests,"StackClass Tests") - CALL testSuite % addTestSubroutineWithName(MutableArrayClassTests,"Mutable Array Tests") - CALL testSuite % addTestSubroutineWithName(HashTableTests,"Hash Table Tests") - CALL testSuite % addTestSubroutineWithName(FTExceptionClassTests,"FTExceptionClass Tests") - - CALL testSuite % performTests() - - \end{verbatim}} -The test subroutines have no arguments. The interface is -{\color{blue}\begin{verbatim} - ABSTRACT INTERFACE - SUBROUTINE testSuiteFunction() - END SUBROUTINE testSuiteFunction - END INTERFACE -\end{verbatim}} - -The test functions should USE the FTAssertions module as in the previous section. You don't have to do any reporting code in your tests, however. Reporting is managed by the testSuiteManager at the end of performTests. - - {\bf Definition:} - {\color{blue}\begin{verbatim} - TYPE(TestSuiteManager) :: tester - \end{verbatim}} - {\bf Usage:} -\begin{itemize} -\item Initialization - {\color{blue}\begin{verbatim} - CALL tester % init() - \end{verbatim}} - -\item Creating a test - Create subroutines with the interface -{\color{blue}\begin{verbatim} - ABSTRACT INTERFACE - SUBROUTINE testSuiteFunction() - END SUBROUTINE testSuiteFunction - END INTERFACE -\end{verbatim}} -that (typically) includes unit test calls. -\item Adding a test case - {\color{blue}\begin{verbatim} - CALL tester % addTestSubroutineWithName(SubroutineName, description) - \end{verbatim}} -where -\begin{itemize} -\item SubroutineName = a subroutine with the interface as above, and -\item description = a CHARACTER(LEN=128) character string that names the test -\end{itemize} - -\item Setting the output location - {\color{blue}\begin{verbatim} - CALL tester % setOutputUnit(iUnit) - \end{verbatim}} -\item Running tests - {\color{blue}\begin{verbatim} - CALL tester % performTests() - \end{verbatim}} -\end{itemize} - -\subsection{Exceptions} - An FTException object provides a way to pass generic - information about an exceptional situation. Methods for - dealing with exceptions are defined in the SharedExceptionManagerModule - module. - - An FTException object wraps: - \begin{itemize} - \item A severity indicator - \item A name for the exception - \item An optional dictionary that contains whatever information is deemed necessary. - - It is expected that classes will define exceptions that use instances - of the FTException Class. - \end{itemize} - - Defined constants: -{\color{blue}\begin{verbatim} - FT_ERROR_NONE = 0 - FT_ERROR_WARNING = 1 - FT_ERROR_FATAL = 2 -\end{verbatim}} - - {\bf Usage:} -\begin{itemize} -\item Initialization - {\color{blue}\begin{verbatim} - e % initFTException(severity,exceptionName,infoDictionary) - - Plus the convenience initializers, which automatically - create a FTValueDictionary with a single key called "message": - - e % initWarningException(msg = "message") - e % initFatalException(msg = "message") - \end{verbatim}} - -\item Setting components - Create subroutines with the interface -{\color{blue}\begin{verbatim} - e % setInfoDictionary(infoDictionary) -\end{verbatim}} - -\end{itemize} -\end{document} \ No newline at end of file diff --git a/Docs/FTObjectLibraryProject.md b/Docs/FTObjectLibraryProject.md index 61ed66aa..47c976ea 100644 --- a/Docs/FTObjectLibraryProject.md +++ b/Docs/FTObjectLibraryProject.md @@ -17,13 +17,6 @@ predocmark:> facilitate writing generic object oriented Fortran programs. Reference counting is implemented to assist with memory management so that the lifespans of objects are properly maintained and are so that objects are deleted only when no other references are made to them. - - - FTObjectLibrary tries, as much as the maturity of Fortran compilers allow, to - use the new F2003/2008 features to make generic programming possible. The LCD - for the library is gfortran, and as modern features get implemented in the - compiler, FTObjectLibrary will be updated to include those features. In the meantime, there - are a few workarounds that exist in the code. The library includes three categories of classes: @@ -53,11 +46,11 @@ values. # Documentation -Documentation can be found in the linked pages, and in the user's guide *FTObjectLibrary.pdf* found in the Docs directory. +Documentation can be found in the [user's guide](UsersGuide.md). # Examples -Examples can be found in the Examples directory and in the Testing directory. The examples include a simple reverse Polish calculator using a stack, and another showing the use of a linked list. The testing directory includes tests that can be run on the library, which themselves serve as examples of the use of all of the classes. +Examples can be found in the [Examples](../Examples) directory and in the [Testing](../Testing) directory. The examples include a simple reverse Polish calculator using a stack, and another showing the use of a linked list. The testing directory includes tests that can be run on the library, which themselves serve as examples of the use of all of the classes. # Building the Library diff --git a/Docs/News.md b/Docs/News.md new file mode 100644 index 00000000..e515d4c1 --- /dev/null +++ b/Docs/News.md @@ -0,0 +1,5 @@ +# News + +May 11, 2025 + +The stringValue() and stringValueForKey() functions now use allocated strings, so the requestedLength argument is no longer necessary. Existing code can continue to use the older versions, but those versions are deprecated and undocumented. \ No newline at end of file diff --git a/Docs/UsersGuide.md b/Docs/UsersGuide.md new file mode 100644 index 00000000..1c1f13c1 --- /dev/null +++ b/Docs/UsersGuide.md @@ -0,0 +1,1316 @@ +# Introduction + +FTObjectLibrary provides a collection of reference counted Fortran 2003 +classes to facilitate writing generic object oriented Fortran programs. +Reference counting is implemented to assist with memory management so +that the lifespans of objects are properly maintained and are so that +objects are deleted only when no other references are made to them. + +The library includes three categories of classes: + +- Value classes + +- Container classes + +- Error reporting and testing classes + +### Value Classes + +Value classes include the base class, FTObject and at the current time, +a subclass, FTValue. + +- **FTObject** + + FTObject is the base class that implements the reference counting + mechanism and other functions that should be overridden in subclasses. + It the base class for all classes in the FTObjectLibrary library. You + will usually not allocate objects of this class. Instead you will + create your own subclasses of it that have data and procedures as + needed. + +- **FTValue** + + FTValue is a wrapper class that allows storage of real, integer, + character and logical values, which can then be stored in containers. + +You can create your own value classes by extending FTObject and store +instances of those classes in the containers. + +### Container Classes + +Container classes let you store any subclass of the base class FTObject +in them. This makes it easy to store, for instance, a linked list of +linked lists, or an array of dictionaries. Included in the library are +the following container classes: + +- **FTLinkedList** + + FTLinkedList is an implementation of a doubly (and, optionally, + circular) linked list. + +- **FTStack** + + FTStack is a subclass of FTLinkedList that adds the usual push, pop, + and peek routines. + +- **FTSparseMatrix** + + FTSparseMatrix associates a double index (i,j) to an FTObject. + Basically this is a two dimensional sparse matrix of pointers to + FTObjects. + +- **FTMultiIndexTable** + + FTMultiIndexTable associates an integer array keys(:) to an FTObject. + Basically this is an m--dimensional sparse matrix of pointers to + FTObjects. + +- **FTDictionary** + + FTDictionary is an "associative container", that associates a string + to another FTObject. + +- **FTValueDictionary** + + FTValueDictionary is a subclass of FTDictionary that has additional + methods to store and retrieve values. + +- **FTMutableObjectArray** + + A mutable one-dimensional array class that can store any FTObject. + +### Error Reporting and Testing Classes + +The library also contains classes for testing (FTAssertions, +TestSuiteManagerClass) and for reporting errors through the FTException +class. + +# Examples + +To give an idea about how to use the library, we show how to use a +dictionary to store a real value and a string that describes the value. +In the snippet of code below, we prepare the dictionary for objects to +be added to it by the init() method. We then create two values that are +added to the dictionary. + + SUBROUTINE constructDictionary(dict) + CLASS(FTDictionary), POINTER :: dict + CLASS(FTObject) , POINTER :: obj + CLASS(FTValue) , POINTER :: v + + ALLOCATE(dict) + CALL dict % initWithSize(64) + + ALLOCATE(v) + CALL v % initWithValue(3.14159) + obj => v + CALL dict % addObjectForKey(obj,``Pi'') + CALL releaseFTValue(v) + + ALLOCATE(v) + CALL v % initWithValue(``Ratio of circumference to diameter'') + obj => v + CALL dict % addObjectForKey(obj,"definition") + CALL releaseFTValue(v) + END SUBROUTINE constructDictionary + +Notice that in the subroutine we have allocated memory for the +dictionary "dict" and two FTValue objects. The dictionary is returned to +the calling procedure and can be accessed from that point through the +actual argument. The two value objects would normally be expected to be +deallocated when leaving the subroutine, since otherwise they would not +be accessible outside of the procedure. It would be dangerous if we +deallocated the two objects that we created and then try to use them +later through the dictionary. This is where a systematic approach to +memory management comes in. When we allocate and initialize an object, +we assume ownership of it. When we add it to the dictionary, it assumes +partial ownership. So instead of deallocating the two value objects, we +relinquish ownership by way of the releaseFTValue() procedure, leaving +only the dictionary to be responsible for deallocating them when it does +not need them any more. + +We use the dictionary as shown in the next snippet of code: + + CLASS(FTDictionary), POINTER :: dict + CLASS(FTValue) , POINTER :: v + REAL :: pi + CALL constructDictionary(dict) + + v => valueFromObject(dict % objectForKey("Pi")) + pi = v % realValue() + + v => valueFromObject(dict % objectForKey(``definition'')) + PRINT *, "The num pi = ", pi," is defined as", TRIM(v % stringValue()) + CALL releaseFTDictionary(dict) + +In this snippet, the values for the two keys "Pi" and "definition" are +retrieved and then used. +The function valueFromObject() converts the generic object to the +specific FTValue type. (If we try to convert something that is not an +FTValue, the method returns an ASSOCIATED(v) == .FALSE. pointer.) +Finally, the dictionary is released, which will release all of its +objects and then deallocate all of those objects that it is sole owner +of, which in this example is both the value and definition of $\pi$, and +then DEALLOCATE it if it is no longer owned (referenced) by another +object, which is the case here. + +In this way, any object that inherits from FTObject can be stored in a +container, including other containers, making these containers quite +generic. + +Other examples can be found in the [Examples](../Examples) directory, which has examples for using a linked list, and for a simple RPN calculator. Finally, the test routines in the [Testing](../Testing) directory provide examples of all of the features of the library. + +# Memory Management + +FTObjectLibrary uses a manual retain-release memory management model +known as reference counting. A description of this model can be found at +[Apple's site](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmRules.html#apple_ref/doc/uid/20000994-BAJHFBGH). +The basic idea is that a pointer object exists as long as someone "owns" +it. When no one owns it any more, the object is automatically +deallocated upon release. This approach makes it easy to not have to +keep track of when to deallocate pointers, and ensures that a pointer +that is in use is not prematurely deallocated. In long, complex, +programs, this mechanism automatically keeps track of which objects +(pointers) that have been allocated need to be deallocated, and when. + +Ownership rules are as follows: + +- If you allocate and initialize an object, you own it. + +- You own an object if you call the retain() subroutine on that object. + +- You may inherit the ownership of an object by calling a method that + creates (allocates and initializes) it. + +- When you no longer need an object (or are going out of scope) you must + release it using the releaseXXX() subroutine, where XXX refers to the + specific name of the extended type. + +- You must neither relinquish ownership, nor deallocate a pointer object + that you do not own. + +- You generally do not own objects returned as pointers by functions or + subroutines. + +*Fundamentally, if you call an init() method on an object, you should +call a releaseXXX() in the same scope.* + +In the example below, the main program creates a linked list to store +points in, a "point" object (e.g. that stores x,y,z values), and adds +the point object to the linked list. + + PROGRAM main + CLASS(FTLinkedList), POINTER :: list ! Subclass of FTObject + CLASS(Point) , POINTER :: pnt ! Subclass of FTObject + CLASS(FTObject) , POINTER :: obj + + ALLOCATE(list) + CALL list % init() ! main now owns the list + + ALLOCATE(pnt) + CALL pnt % initWithXYZ(0.0,0.0,0.0) ! main now owns pnt + + obj => pnt + CALL list % add(obj) !list also owns pnt + CALL releasePoint(pnt) ! main gives up ownership to pnt + . + . + . + ! we're done with the list, it will deallocate pnt since the list is the last owner. + ! It will also deallocate itself since main is the last owner. + CALL releaseFTLinkedList(list) + + END PROGRAM main + +# Class Descriptions + +## FTObject + +FTObject defines the basic methods that are essential for reference +counted objects. FTObject is generally not going to be instantiated by +itself, but rather it will be subclassed and you will work with +instances of the subclasses. FTValue, for instance, is a subclass of +FTObject. Otherwise, pointers of type FTObject that point to instances +of subclasses will be stored in the container classes. + +### Tasks + +- **init()** + + Initializes an object and any memory that it needs to allocate, etc. + Should be implemented in subclasses.The base class implementation does + nothing but increase the reference count of the object. + +- **printDescription(iUnit)** + + Prints a description of the object to a specified file unit. The base + class implementation does nothing but print "FTObject" + +- **copy()** + + Creates a copy (pointer) to the object of CLASS(FTObject) sourced with + the object. + +- **retain()** + + Increases the reference count of the object. Any procedure or object + that retain()'s an object gains an ownership stake in that object. + *This procedure is not overridable.* + +- **releaseFTObject()** + + Decreases the reference count of a base class object pointer. To be + called only by objects or procedures that have ownership in an object + pointer, i.e., for which init() or retain() have been called. + +- **isUnreferenced()** + + Test to see if there are no more owners of an object. Usually this is + of interest only for debugging purposes. *This procedure is not + overridable.* + +- **refCount()** + + Returns the number of owners of an object. Usually this is of interest + only for debugging purposes. *This procedure is not overridable.* + +### Subclassing FTObject + +In general, subclasses of FTObject implement + +- **init()** + +- **The destructor (FINAL)** + +- **printDescription()** + +- **release(self)** + +**Implementing init()** + +The init() procedure performs subclass specific operations to initialize +an object. + +Subclasses that override init() *must* include a call to the super class +method. For example, if "Subclass" EXTENDS(FTObject), overriding init() +looks like + + SUBROUTINE initSubclass(self) + IMPLICIT NONE + CLASS(Subclass) :: self + + CALL self % FTObject % init() + Allocate and initialize all member objects + ... Other Subclass specific code + END SUBROUTINE initSubclass + +You can have multiple initializers for an object, so it is also +worthwhile understanding the concept of the "designated initializer". +Generally speaking this is the initializer that has the most arguments. +It is also the only one that includes the call to the super class init +procedure. For example, the designated initializer for a "point" class +would be the one that takes the (x,y,z) values. + + SUBROUTINE initPointWithXYZ(self,x,y,z) + IMPLICIT NONE + CLASS(Subclass) :: self + + CALL self % FTObject % init() + self % x = x + self % y = y + self % z = z + END SUBROUTINE initPointWithXYZ + +Other initializers might be the default, and the initializer that takes +an array of length three. They will do nothing but call the designated +initializer. The default initializer sets the location to the origin, or +some other reasonable value. + + SUBROUTINE initPoint(self) + IMPLICIT NONE + CLASS(Subclass) :: self + + call self % initPointWithXYZ(0.0,0.0,0.0) + END SUBROUTINE initPoint + +The array initializer is + + SUBROUTINE initPointWithArray(self,w) + IMPLICIT NONE + CLASS(Subclass) :: self + REAL :: w(3) + + CALL self % initPointWithXYZ(w(1),w(2),w(3)) + END SUBROUTINE initPointWithArray + +**Implementing the destructor** + +The destructor reverses the operations done in the init() procedure. It +releases and deallocates any pointers that it owns. For example, if +"Subclass" EXTENDS(FTObject) then overriding destruct looks like + + SUBROUTINE destructSubclass(self) + IMPLICIT NONE + TYPE(Subclass) :: self + . + Release and deallocate (if necessary) all member objects + . + END SUBROUTINE destructSubclass + +The destructor is to be declared as a FINAL procedure for the type, and +hence is not called directly. It is called automatically by Fortran when +an object is deallocated or goes out of scope. + +**Implementing printDescription(iUnit)** + +printDescription is a method whose existence is to support debugging. +Call printDescription(iUnit) on any objects owned by self for a +cascading of what is stored in the object. + +**Implementing releaseXXX(self)** + +The release subroutine will call the base class releaseFTObject which +will, in turn, release all objects that it owns. If the object itself is +no longer referenced, it will deallocate itself. If the subclass is +going to be subclassed again, use the CLASS specifier, otherwise, we +TYPE to work only on that specific subclass. + + SUBROUTINE releaseSubclass(self) + IMPLICIT NONE + TYPE(Subclass) , POINTER :: self + CLASS(FTObject), POINTER :: obj + obj => self + CALL releaseFTObject(self = obj) + IF ( .NOT. ASSOCIATED(obj) ) THEN + self => NULL() + END IF + END SUBROUTINE releaseSubclass + +It is best to name the release procedures consistently. For instance, +all the FTObjectLibrary classes declare their release procedure by using +the name of the extended type, for example releaseFTDictionary or +releaseFTValueDictionary. + +**Converting an object from the base to a subclass** + +Container classes and the copy function return pointers to a +CLASS(FTObject). To use any subclass features one must "cast" or convert +a pointer to a pointer to the subclass. We like to have a specific cast +routine to do this as painlessly as possible. Each subclass should +include a function like this: + + FUNCTION subclassFromSuperclass(obj) RESULT(cast) + IMPLICIT NONE + CLASS(FTObject), POINTER :: obj + CLASS(Subclass), POINTER :: cast + cast => NULL() + SELECT TYPE (e => obj) + TYPE is (Subclass) + cast => e + CLASS DEFAULT + END SELECT + END FUNCTION subclassFromSuperclass + +You saw an example above in the "valueFromObject(obj)" function. + +## FTValue + +FTValue is an immutable class to +store primitive values: integer, real, double precision, logical, +character. (To Add: complex) + +### Usage + +- Initialization + + TYPE(FTValue) :: r, i, s, l, d + + CALL r % initWithValue(3.14) + CALL i % initWithValue(6) + CALL d % initWithValue(3.14d0) + CALL l % initWithValue(.true.) + CALL s % initWithValue("A string") + +- Destruction + + CALL releaseFTValue(r) !For Pointers + +- Accessors + + real = r % realValue() + int = i % integerValue() + doub = d % doublePrecisionValue() + logc = l % logicalValue() + str = s % stringValue() + +- Description + + str = v % description() + call v % printDescription(unit) + +- Converting a base FTObject to an FTValue + + CLASS(FTValue) , POINTER :: v + CLASS(FTObject), POINTER :: obj + v => valueFromObject(obj) + +The class will attempt to convert between the different types: + + CALL r % initWithReal(3.14) + print *, r % stringValue() + + Logical variables rules: + + real, doublePrecision, integer values + logicalValue = .FALSE. if input = 0 + logicalValue = .TRUE. if input /= 0 + +String values can be converted to numeric types. If the string is not a +numeric, HUGE(x) will be returned, where x is of the requested type. + +## Linked Lists + +A linked list is a dynamic container that links the objects added to it +in a chain. The chain can be of any length and objects can be added or +removed at any time and at any location in the chain. Linked lists are +useful when you don't know how many objects will be stored. They are +fast to add or delete objects from, but they are slow to access any +given object. To access objects, you start at the beginning of the chain +and then follow the chain it until you reach the desired entry. You use +a linked list when sequential access is more typical than random access. +Special classes of linked lists are singly linked lists, which can be +followed in only one direction from the start, double linked lists that +can be followed in either direction, and circular lists where the ends +are connected. + +### FTLinkedList + +FTLinkedList is a container class that stores objects in a doubly linked +list. Optionally, the list can be made circular. As usual, FTLinkedList +inherits from FTObjectClass. + +**Definition (Subclass of FTObject):** + + TYPE(FTLinkedList) :: list + +**Usage:** + +- Initialization + + CLASS(FTLinkedList), POINTER :: list + ALLOCATE(list) + CALL list % init() + +- Adding an object + + CLASS(FTLinkedList), POINTER :: list + CLASS(FTObject) , POINTER :: obj + + obj => r ! r is subclass of FTObject + CALL list % Add(obj) ! Pointer is retained by list + CALL release(r) ! If control is no longer wanted in this scope. + +- Combining lists + + CLASS(FTLinkedList), POINTER :: list, listToAdd + CLASS(FTObject) , POINTER :: obj + . + . + . + CALL list % addObjectsFromList(listToAdd) + +- Inserting objects + + CLASS(FTLinkedList) , POINTER :: list + CLASS(FTObject) , POINTER :: obj + CLASS(FTLinkedListRecord), POINTER :: record + + obj => r ! r is subclass of FTObject + CALL list % insertObjectAfterRecord(obj,record) ! Pointer is retained by list + CALL release(r) ! If caller wants to reliquish ownership + + OR + + CLASS(FTLinkedList) , POINTER :: list + CLASS(FTObject) , POINTER :: obj, otherObject + CLASS(FTLinkedListRecord), POINTER :: record + + obj => r ! r is subclass of FTObject + CALL list % insertObjectAfterObject(obj,otherObject) ! Pointer is retained by list + CALL release(r) ! If caller wants to reliquish ownership + +- Removing objects + + CLASS(FTLinkedList), POINTER :: list + CLASS(FTObject) , POINTER :: obj + obj => r ! r is subclass of FTObject + CALL list % remove(obj) + +- Getting an array of all of the objects + + CLASS(FTLinkedList) , POINTER :: list + CLASS(FTMutableObjectArray) , POINTER :: array + array => list % allObjects() ! Array has refCount = 1 + +- Making a linked list circular or not. The default is not circular. + + LOGICAL :: c = .true. + CALL list % makeCircular( c ) + +- Checking to see if a linked list circular or not + + LOGICAL :: c + c = list % isCircular() + +- Counting the number of objects in the list + + n = list % count() + +- Reversing the order of the list + + CALL list % reverse() + +- Destruction + + CALL releaseFTLinkedList(list) ! If list is a pointer + +### FTLinkedListIterator + +Linked lists must be navigated in order along the chain. This is usually +accomplished with the help of an *iterator* to navigate linked lists. +FTObjectLibrary includes a class called FTLinkedListIterator for +stepping through (iterating) a linked list to access its entries. + +**Definition (Subclass of FTObject):** + + TYPE(FTLinkedListIterator) :: iterator + +**Usage:** + +- Initialization + + CLASS(FTLinkedListIterator), POINTER :: iterator + CLASS(FTLinkedList) , POINTER :: list + ALLOCATE(iterator) + CALL iterator % init() + . + . + . + + OR + + CLASS(FTLinkedList) , POINTER :: list + CLASS(FTLinkedListIterator), POINTER :: iterator + ALLOCATE(iterator) + CALL iterator % initWithFTLinkedList(list) ! Increases refCount of list + . + . + . + +- Setting the iterator to the beginning of the list + + CALL iterator % setToStart() + +- Testing if the iterator is at the end of the list + + IF( iterator % isAtEnd() ) + +- Iterating through the list and accessing contained objects + + CLASS(FTObject), POINTER :: obj + CALL iterator % setToStart() + DO WHILE (.NOT.iterator % isAtEnd()) + obj => iterator % object() ! if the object is wanted + recordPtr => iterator % currentRecord() ! if the record is wanted + + !Do something with object or record + + CALL iterator % moveToNext() ! FORGET THIS CALL AND YOU GET AN INFINITE LOOP! + END DO + +- Destruction + + CALL releaseFTLinkedListIterator(iterator) ! If a pointer + +## Stacks + +A stack is a data structure that enforces last-in/first-out access to +the objects stored in it. One puts a new object onto the top of the +stack with a *push* operation, and pulls off the object at the top of +the stack with a *pop* operation. Usually one has the option to just +look at the top of the stack with a *peek* operation that doesn't remove +the top object. You would implement a Reverse Polish calculator with a +stack, for instance. + +**Definition (Subclass of FTLinkedListClass):** + + TYPE(FTStack) :: stack + +**Usage:** + +- Initialization + + ALLOCATE(stack) ! If stack is a pointer + CALL stack % init() + +- Destruction + + CALL releaseFTStack(stack) ! If stack is a pointer + +- Pushing an object onto the stack + + TYPE(FTObject) :: obj + obj => r1 + CALL stack % push(obj) + +- Peeking at the top of the stack + + obj => stack % peek() ! No change of ownership + SELECT TYPE(obj) + TYPE is (*SubclassType*) + ! Do something with obj as subclass + CLASS DEFAULT + ! Problem with casting + END SELECT + +- Popping the top of the stack + + obj => stack % pop() ! Ownership transferred to caller. + Call releaseFTObject(obj) ! when done with it + +## Object Arrays + +Fortran has pointers to arrays, but not arrays of pointers. To do the +latter, one creates a wrapper derived type and creates an array of that +wrapper type. Fortran arrays are great, but they are of fixed length, +and they don't easily implement reference counting to keep track of +memory. For that, we have the FTMutableObjectArray. Performance reasons +dictate that you will use regular arrays for numeric types and the like, +but for generic objects we would use an Object Array. + +You initialize a FTMutableObjectArray with the number of objects that +you expect it to hold. However, it can re-size itself if necessary. To +be efficient, it adds more than one entry at a time given by the +"chunkSize", which you can choose for yourself. (The default is 10.) + +**Definition (Subclass of FTObject):** + + TYPE(FTMutableObjectArray) :: array + +**Usage:** + +- Initialization + + CLASS(FTMutableObjectArray) :: array + INTEGER :: N = 11 + CALL array % initWithSize(N) + +- Destruction + + CALL releaseFTMutableObjectArray(array) !If array is a pointer + +- Adding an object + + TYPE(FTObject) :: obj + obj => r1 + CALL array % addObject(obj) + +- Removing an object + + TYPE(FTObject) :: obj + CALL array % removeObjectAtIndex(i) + +- Accessing an object + + TYPE(FTObject) :: obj + obj => array % objectAtIndex(i) + +- Replacing an object + + TYPE(FTObject) :: obj + obj => r1 + CALL array % replaceObjectAtIndexWithObject(i,obj) + +- Setting the chunk size + + CALL array % setChunkSize(size) + +- Getting the chunk size + + i = array % chunkSize(size) + +- Finding the number of items in the array + + n = array % count() + +- Finding the actual allocated size of the array + + n = array % allocatedSize() + +- Converting a base class pointer to an object array + + Array => objectArrayFromObject(obj) + +## Sparse Matrices + +Hash tables are data structures designed to enable storage and fast +retrieval of key-value pairs. An example of a key-value pair is a +variable name ("gamma") and its associated value ("1.4"). The table +itself is typically an array. The location of the value in a hash table +associated with a key, $k$, is specified by way of a *hash function*, +$H(k)$. In the case of a variable name and value, the hash function +would convert the name into an integer that tells where to find the +associated value in the table. + +A very simple example of a hash table is, in fact, a singly dimensioned +array. The key is the array index and the value is what is stored at +that index. Multiple keys can be used to identify data; a two +dimensional array provides an example of where two keys are used to +access memory and retrieve the value at that location. If we view a +singly dimensioned array as a special case of a hash table, its hash +function is just the array index, $H(j)=j$. A doubly dimensioned array +could be (and often is) stored columnwise as a singly dimensioned array +by creating a hash function that maps the two indices to a single +location in the array, e.g., $H(i,j) = i + jN$, where $N$ is the range +of the first index, $i$. + +Two classes are included in FTObjectLibrary. The first, FTSparseMatrix, +works with an ordered pair, (i,j), as the keys. The second, +FTMultiIndexTable, uses an array of integers as the keys. + +Both classes include enquiry functions to see of an object exists for +the given keys. Otherwise, the function that returns an object for a +given key will return an UNASSOCIATED pointer if there is no object for +the key. Be sure to retain any object returned by the objectForKeys +methods if you want to keep it beyond the lifespan of the matrix or +table. For example, + + TYPE(FTObject) :: obj + obj => matrix % objectForKeys(i,j) + IF ( ASSOCIATED(OBJ) ) THEN + CALL obj % retain() + ! Cast obj to something useful + ELSE + ! Perform some kind of error recovery + END IF + +### FTSparseMatrix + +The sparse matrix included in the FTObjectLibrary is very simple in that +it has a predefined hash function with two keys, $(i,j)$. You will +initialize the matrix with the number of rows. + +**Definition (Subclass of FTObject):** + + TYPE(FTSparseMatrix) :: matrix + +**Usage:** + +- Initialization + + CLASS(FTSparseMatrix) :: matrix + INTEGER :: N = 11 ! Number of rows + CALL matrix % initWithSize(N) + +- Destruction + + CALL releaseFTSparseMatrix(matrix) !If matrix is a pointer + +- Adding an object + + TYPE(FTObject) :: obj + matrix % addObjectForKeys(obj,i,j) + +- Accessing an object + + TYPE(FTObject) :: obj + obj => matrix % objectForKeys(i,j) + +- Checking if an entry exists + + LOGICAL :: exists + exists = matrix % containsKeys(i,j) + +### FTMultiIndexTable + +An extension (not in the subclass sense) of the sparse matrix class is +the MultiIndexTable. It uses an integer array of keys instead of just an +(i,j) pair. A multiIndexTable can be used, for instance, to determine if +the four node ids of a face of an element match those of another face of +another element. + +**Definition (Subclass of FTObject):** + + TYPE(FTMultiIndexTable) :: table + +**Usage:** + +- Initialization + + CLASS(FTMultiIndexTable) :: table + INTEGER :: N = 11 ! maximum over all keys + CALL table % initWithSize(N) + +- Destruction + + CALL releaseFTMultiIndexTable(table) !If table is a pointer + +- Adding an object + + TYPE(FTObject) :: obj + INTEGER :: keys(m) ! m = # keys + table % addObjectForKeys(obj,keys) + +- Accessing an object + + TYPE(FTObject) :: obj + INTEGER :: keys(m) ! m = # keys + obj => table % objectForKeys(keys) + +- Checking if an entry exists + + LOGICAL :: exists + INTEGER :: keys(m) ! m = # keys + exists = table % containsKeys(keys) + +## Dictionaries + +A dictionary is a special case of a hash table that stores *key-value +pairs*. It is an example of what is called an "associative container". +In the implementation of FTObjectLibrary, the value can be any subclass +of FTObject and the key is a character variable. The library includes +the base dictionary that can store and retrieve any subclass of +FTObject. It also includes a subclass that is designed to store and +retrieve FTValue objects. + +### FTDictionary + +**Definition (Subclass of FTObject):** + + TYPE(FTDictionary) :: dict + +**Usage:** + +- Initialization + + CLASS(FTDictionary) :: dict + INTEGER :: N = 16 ! Should be a power of two. + CALL dict % initWithSize(N) + +- Destruction + + CALL releaseFTDictionary(dict) !If dict is a pointer + +- Adding a key-object pair + + CLASS(FTDictionary), POINTER :: dict + CLASS(FTObject) , POINTER :: obj + CHARACTER(LEN=M) :: key + obj => r ! r is subclass of FTObject + CALL dict % addObjectForKey(obj,key) + +- Accessing an object + + TYPE(FTObject) :: obj + obj => dict % objectForKey(key) + +- Converting a base class pointer to a dictionary + + dict => dictionaryFromObject(obj) + +- Getting all of the keys (The target of the pointer must be deallocated + by the caller) + + CHARACTER(LEN=FTDICT_KWD_STRING_LENGTH), POINTER :: keys(:) + keys => dict % allKeys() + +- Getting all of the objects + + CLASS(FTMutableObjectArray), POINTER :: objectArray + objectArray => dict % allObjects() ! The array is owned by the caller. + +### FTValueDictionary + +FTValueDictionary adds methods to store and retrieve FTValue objects. + +**Definition (Subclass of FTDictionary):** + + TYPE(FTValueDictionary) :: dict + +**Usage:** + +- Adding a value + + CALL dict % addValueForKey(1,"integer") + CALL dict % addValueForKey(3.14,"real") + CALL dict % addValueForKey(98.6d0,"double") + CALL dict % addValueForKey(.true.,"logical") + CALL dict % addValueForKey("Hello World","string") + +- Accessing a value + + i = dict % integerValueForKey("integer") + r = dict % realValueForKey("real") + d = dict % doublePrecisionValueForKey("double") + l = dict % logicalValueForKey("logical") + s = dict % stringValueForKey("string") + + Note that the FTValue class will do type conversion, so you can also + access something like + + i = dict % integerValueForKey("real") + s = dict % stringValueForKey("real") + +- Converting an FTDictionary to an FTValueDictionary + + valueDict => valueDictionaryFromDictionary(dict) + +- Converting an FTObject to an FTValueDictionary + + valueDict => valueDictionaryFromObject(obj) + +# String Sets + +A StringSet implements set operations on strings. An FTStringSet is an +unordered group of unique (case dependent) strings. It is primarily used +to find whether or not a given string is a member of a set of strings. +The class also includes usual set operations of intersection, union and +difference. To enumerate over the items in a set, use the strings() +procedure to return a pointer to an array of the strings in the set, and +then loop over that array. + +An empty set can be initialized, to which strings can be added, or a set +can be initialized with an array of strings. The string length must be +FTDICT\_KWD\_STRING\_LENGTH or less. + +**Definition (Subclass of FTObject):** + + TYPE(FTStringSet) :: set + +**Usage:** + +- Initialization + + CLASS(FTStringSet) :: set + INTEGER :: N = 16 ! Should be a power of two. + CALL dict % initFTStringSet(FTStringSetSize = N) + + CLASS(FTStringSet) :: set + CHARACTER(LEN=*) :: strings(:) + CALL set % initWithStrings(strings) + +- Destruction + + CALL releaseFTStringSet(set) !If set is a pointer + +- Adding a string + + CHARACTER(LEN=M) :: str + CALL set % addString(str) + +- Testing for inclusion + + LOGICAL :: test + CHARACTER(LEN=M) :: str + test = set % containsString(str) + +- Finding the union of two sets (Release when through) + + unionSet => set1 % unionWithSet(set2) + ... + call ReleaseFTStringSet(unionSet) + +- Finding the intersection of two sets (Release when through) + + intersectionSet => set1 % intersectionWithSet(set2) + ... + call ReleaseFTStringSet(intersectionSet) + +- Finding the difference of two sets (Release when through) + + differenceSet => set1 % setFromDifference(set2) + ... + call ReleaseFTStringSet(differenceSet) + +- Converting a base class pointer to a set + + set => FTStringSetFromObject(obj) + +- Getting all of the strings (The target of the pointer must be + deallocated by the caller) + + CHARACTER(LEN=FTDICT_KWD_STRING_LENGTH), POINTER :: strings(:) + strings => set % strings() + ... + deallocate(strings) + +# Advanced Classes: Testing and Error Reporting + +The library includes classes for testing and reporting errors. Errors +are reported through instances of the FTException class. Testing is made +semi-automatic through the TestSuiteManager class and procedures defined +in the FTAssertions module. + +## Assertions + +An assertion is a true-false statement that you expect to be true. +Assertions are used to test for exceptional situations (AKA "Failures") +in a code. For example, knowing that density always must be positive, +you might assert that fact before using it, and if the result is false +generate an error. With FTObjectLibrary that would be + + CALL assert(rho > 0,``Density must be positive'') + +Fortran does not have an assertion mechanism, and so pretty much +everyone writes their own. There are a couple of open source projects +available, but one never knows how actively they will be maintained. In +the grand Fortran tradition of writing one's own, FTObjectLibrary has an +(incomplete) assertion module. + +To use assertions, you will USE the FTAssertions module and initialize +the assertions system by calling + + CALL initializeSharedAssertionsManager + +During the course of your program, the sharedAssertionsManager will keep +track of the success or failure of the assertions that you make. You can +enquire at any time how many assertions have failed and how many +assertions have been made with the two enquiry functions + + INTEGER FUNCTION numberOfAssertionFailures() + INTEGER FUNCTION numberOfAssertions() + +You can get a summary of the assertions by calling the subroutine + + SUBROUTINE SummarizeFTAssertions(title,iUnit) + IMPLICIT NONE + CHARACTER(LEN=*) :: title + INTEGER :: iUnit + +When you are done, you finalize the sharedAssertionsManager with + + CALL finalizeSharedAssertionsManager + +So how do you make assertions? FTObjectLibrary supplies two subroutines +that post failures to the sharedAssertionsManager. The first takes a +LOGICAL variable + + SUBROUTINE assert(test,msg) + IMPLICIT NONE + CHARACTER(LEN=*), OPTIONAL :: msg + LOGICAL :: test + +The second tests equality through the overloaded subroutine assertEqual, +which allows a variety of argument type listed below: + + INTERFACE assertEqual + MODULE PROCEDURE assertEqualTwoIntegers + MODULE PROCEDURE assertEqualTwoIntegerArrays1D + MODULE PROCEDURE assertEqualTwoIntegerArrays2D + MODULE PROCEDURE assertWithinToleranceTwoReal + MODULE PROCEDURE assertWithinToleranceTwoRealArrays1D + MODULE PROCEDURE assertWithinToleranceTwoRealArrays2D + MODULE PROCEDURE assertWithinToleranceTwoDouble + MODULE PROCEDURE assertWithinToleranceTwoDoubleArrays1D + MODULE PROCEDURE assertWithinToleranceTwoDoubleArrays2D + MODULE PROCEDURE assertEqualTwoLogicals + MODULE PROCEDURE assertEqualString + END INTERFACE assertEqual + +The individual calls have the signatures + + SUBROUTINE assertEqualTwoIntegers(expectedValue,actualValue,msg) + IMPLICIT NONE + INTEGER, INTENT(in) :: expectedValue,actualValue + CHARACTER(LEN=*), OPTIONAL :: msg + + SUBROUTINE assertEqualTwoIntegerArrays1D(expectedValue,actualValue) + IMPLICIT NONE + INTEGER, INTENT(in) , DIMENSION(:) :: expectedValue,actualValue + + SUBROUTINE assertEqualTwoIntegerArrays2D(expectedValue,actualValue) + IMPLICIT NONE + INTEGER, INTENT(in) , DIMENSION(:,:) :: expectedValue,actualValue + + SUBROUTINE assertWithinToleranceTwoReal(x,y,tol,absTol,msg) + IMPLICIT NONE + REAL, INTENT(in) :: x,y,tol + REAL, INTENT(IN), OPTIONAL :: absTol + CHARACTER(LEN=*), OPTIONAL :: msg + + SUBROUTINE assertWithinToleranceTwoRealArrays1D(expectedValue,actualValue,tol,absTol,msg) + IMPLICIT NONE + REAL, INTENT(IN), DIMENSION(:) :: expectedValue,actualValue + REAL, INTENT(IN) :: tol + REAL, INTENT(IN), OPTIONAL :: absTol + CHARACTER(LEN=*), OPTIONAL :: msg + + SUBROUTINE assertWithinToleranceTwoRealArrays2D(expectedValue,actualValue,tol) + IMPLICIT NONE + REAL, INTENT(IN), DIMENSION(:,:) :: expectedValue,actualValue + REAL, INTENT(IN) :: tol + + SUBROUTINE assertWithinToleranceTwoDouble(expectedValue,actualValue,tol,absTol,msg) + IMPLICIT NONE + DOUBLE PRECISION, INTENT(in) :: expectedValue,actualValue,tol + REAL, INTENT(IN), OPTIONAL :: absTol + CHARACTER(LEN=*), OPTIONAL :: msg + + SUBROUTINE assertWithinToleranceTwoDoubleArrays1D(expectedValue,actualValue,tol,absTol,msg) + IMPLICIT NONE + DOUBLE PRECISION, INTENT(IN), DIMENSION(:) :: expectedValue,actualValue + DOUBLE PRECISION, INTENT(IN) :: tol + REAL, INTENT(IN), OPTIONAL :: absTol + CHARACTER(LEN=*), OPTIONAL :: msg + + SUBROUTINE assertWithinToleranceTwoDoubleArrays2D(expectedValue,actualValue,tol,abstol) + IMPLICIT NONE + DOUBLE PRECISION, INTENT(IN), DIMENSION(:,:) :: expectedValue,actualValue + DOUBLE PRECISION, INTENT(IN) :: tol + REAL, INTENT(IN), OPTIONAL :: absTol + + SUBROUTINE assertEqualString(expectedValue,actualValue,msg) + IMPLICIT NONE + CHARACTER(LEN=*) :: expectedValue,actualValue + CHARACTER(LEN=*), OPTIONAL :: msg + + SUBROUTINE assertEqualTwoLogicals(expectedValue,actualValue,msg) + IMPLICIT NONE + LOGICAL, INTENT(in) :: expectedValue,actualValue + CHARACTER(LEN=*), OPTIONAL :: msg + +Notice that you can only check the equality of two floating point +numbers to within some tolerance. One can either specify just a relative error tolerance, tol, or a relative and optional absolute tolerance, tol and absTol. + +## Testing + +FTObjectLibrary also includes a testing suite with which you can create +a suite of tests to make sure your codes are working and stay working. +The tests are managed by an instance of the class. It is designed to be +used with minimal fuss. You + +1. Initialize the test suite + +2. Add test subroutines + +3. Have the testSuiteManager perform the tests + +4. Finalize the test suite manager + +An example of running a suite of tests is the following: + + TYPE(TestSuiteManager) :: testSuite + + EXTERNAL :: FTDictionaryClassTests + EXTERNAL :: FTExceptionClassTests + EXTERNAL :: FTValueClassTests + EXTERNAL :: FTValueDictionaryClassTests + EXTERNAL :: FTLinkedListClassTests + EXTERNAL :: StackClassTests + EXTERNAL :: MutableArrayClassTests + EXTERNAL :: HashTableTests + + CALL testSuite % init() + + CALL testSuite % addTestSubroutineWithName(FTValueClassTests,"FTValueClass Tests") + CALL testSuite % addTestSubroutineWithName(FTDictionaryClassTests,"FTDictionaryClass Tests") + CALL testSuite % addTestSubroutineWithName(FTValueDictionaryClassTests,"FTValueDictionaryClass Tests") + CALL testSuite % addTestSubroutineWithName(FTLinkedListClassTests,"FTLinkedListClass Tests") + CALL testSuite % addTestSubroutineWithName(StackClassTests,"StackClass Tests") + CALL testSuite % addTestSubroutineWithName(MutableArrayClassTests,"Mutable Array Tests") + CALL testSuite % addTestSubroutineWithName(HashTableTests,"Hash Table Tests") + CALL testSuite % addTestSubroutineWithName(FTExceptionClassTests,"FTExceptionClass Tests") + + CALL testSuite % performTests() + +The test subroutines have no arguments or include optional data. The interface is + + ABSTRACT INTERFACE + SUBROUTINE testSuiteFunction(optData) + CHARACTER(LEN=1), POINTER, OPTIONAL :: optData(:) + END SUBROUTINE testSuiteFunction + END INTERFACE + +The test functions should USE the FTAssertions module as in the previous +section. You don't have to do any reporting code in your tests, however. +Reporting is managed by the testSuiteManager at the end of performTests. Look at the [Testing](../Testing) directory in the repository for examples on how to set up testing. + +**Definition:** + + TYPE(TestSuiteManager) :: tester + +**Usage:** + +- Initialization + + CALL tester % init() + +- Creating a test Create subroutines with the interface + + ABSTRACT INTERFACE + SUBROUTINE testSuiteFunction(optData) + CHARACTER(LEN=1), POINTER, OPTIONAL :: optData(:) + END SUBROUTINE testSuiteFunction + END INTERFACE + + that (typically) includes unit test calls. The optData is optional data that can be included as part + of the test, if necessary. The Fortran TRANSFER function can be used to encode and decode the data, so pretty much any data can be transferred generically. + +- Adding a test case + + CALL tester % addTestSubroutineWithName(SubroutineName, description) + + where + + - SubroutineName = a subroutine with the interface as above, and + + - description = a CHARACTER(LEN=128) character string that names the + test + +- Setting the output location + + CALL tester % setOutputUnit(iUnit) + +- Running tests + + CALL tester % performTests() + +## Exceptions + +An FTException object provides a way to pass generic information about +an exceptional situation. Methods for dealing with exceptions are +defined in the SharedExceptionManagerModule module. + +An FTException object wraps: + +- A severity indicator + +- A name for the exception + +- An optional dictionary that contains whatever information is deemed + necessary. + + It is expected that classes will define exceptions that use instances + of the FTException Class. + +Defined constants: + + FT_ERROR_NONE = 0 + FT_ERROR_WARNING = 1 + FT_ERROR_FATAL = 2 + +**Usage:** + +- Initialization + + e % initFTException(severity,exceptionName,infoDictionary) + + Plus the convenience initializers, which automatically + create a FTValueDictionary with a single key called "message": + + e % initWarningException(msg = "message") + e % initFatalException(msg = "message") + +- Setting components Create subroutines with the interface + + e % setInfoDictionary(infoDictionary) diff --git a/README.md b/README.md index c8043826..cb68ef9f 100644 --- a/README.md +++ b/README.md @@ -10,16 +10,6 @@ to facilitate writing generic object oriented Fortran programs. Reference counting is implemented to assist with memory management so that the lifespans of objects are properly maintained and are so that objects are deleted only when no other references are made to them. - -**NOTE: This repository is in an experimental stage and may undergo breaking -changes at any time.** - - -FTObjectLibrary tries, as much as the maturity of Fortran compilers allow, to -use the new F2003/2008 features to make generic programming possible. The LCD -for the library is gfortran, and as modern features get implemented in the -compiler, FTObjectLibrary will be updated to include those features. In the meantime, there -are a few workarounds that exist in the code. The library includes three categories of classes: @@ -47,13 +37,17 @@ values. The library also contains classes for testing (FTAssertions, TestSuiteManagerClass) and for reporting errors through the FTException class. +## News + +New about changes can be found [here](Docs/News.md). + ## Documentation -Documentation can be found in the linked pages, and in the user's guide *FTObjectLibrary.pdf* found in the Docs directory. +Documentation can be found in the [user's guide](Docs/UsersGuide.md). ## Examples -Examples can be found in the Examples directory and in the Testing directory. The examples include a simple reverse Polish calculator using a stack, and another showing the use of a linked list. The testing directory includes tests that can be run on the library, which themselves serve as examples of the use of all of the classes. +Examples can be found in the [Examples](Examples) directory and in the [Testing](Testing) directory. The examples include a simple reverse Polish calculator using a stack, and another showing the use of a linked list. The testing directory includes tests that can be run on the library, which themselves serve as examples of the use of all of the classes. ## Building the Library diff --git a/Source/FTObjects/FTExceptionClass.f90 b/Source/FTObjects/FTExceptionClass.f90 index 93e334bd..b7c35392 100644 --- a/Source/FTObjects/FTExceptionClass.f90 +++ b/Source/FTObjects/FTExceptionClass.f90 @@ -518,7 +518,7 @@ Module SharedExceptionManagerModule !> e => errorObject() !> d => e % infoDictionary() !> userDictionary => valueDictionaryFromDictionary(dict = d) -!> msg = userDictionary % stringValueForKey("message",FTDICT_KWD_STRING_LENGTH) +!> msg = userDictionary % stringValueForKey("message") !> END IF !>###Printing all exceptions !> call printAllExceptions diff --git a/Source/FTObjects/FTValueClass.f90 b/Source/FTObjects/FTValueClass.f90 index 6f45f3f5..11f0922a 100644 --- a/Source/FTObjects/FTValueClass.f90 +++ b/Source/FTObjects/FTValueClass.f90 @@ -61,7 +61,7 @@ !> int = i % integerValue() !> doub = d % doublePrecisionValue() !> logc = l % logicalValue() -!> str = s % stringValue(nChars) +!> str = s % stringValue() !> !> - Description !> @@ -77,7 +77,7 @@ !> The class will attempt to convert between the different types: !> !> CALL r % initWithReal(3.14) -!> print *, r % stringValue(8) +!> print *, r % stringValue() !> !> Logical variables rules: !> @@ -168,7 +168,10 @@ Module FTValueClass #ifdef _has_Quad PROCEDURE :: quadValue #endif - PROCEDURE :: stringValue + + PROCEDURE :: stringvalueR + PROCEDURE :: stringValueA + GENERIC :: stringValue => stringvalueR, stringValueA PROCEDURE :: logicalValue PROCEDURE :: integerValue ! @@ -619,14 +622,64 @@ LOGICAL FUNCTION logicalValue(self) END FUNCTION logicalValue ! !--------------------------------------------------------------------------- -!> Get the string value of length requestedLength stored in the object, or -!> convert the value +!> Get the string value stored in the object, or convert the value !> in the object to a string of that length if it is of a different type. +!> It is overloaded as stringValue(). +!--------------------------------------------------------------------------- +! +!//////////////////////////////////////////////////////////////////////// +! + FUNCTION stringValueA(self) RESULT(s) + IMPLICIT NONE + CLASS(FTValue) :: self + CHARACTER(LEN=:), ALLOCATABLE :: s + + CHARACTER(LEN= FTVALUE_STRING_LENGTH) :: tmpString + + REAL :: r + INTEGER :: i + DOUBLE PRECISION :: d + LOGICAL :: l + + SELECT CASE (self % valueType) + CASE (FTVALUECLASS_INTEGER) + i = TRANSFER(self % valueStorage, i) + WRITE(tmpString,*) i + s = TRIM(ADJUSTL(tmpString)) + CASE (FTVALUECLASS_DOUBLE) + d = TRANSFER( self % valueStorage, d) + WRITE(tmpString,*) d + s = TRIM(ADJUSTL(tmpString)) + CASE (FTVALUECLASS_REAL) + r = TRANSFER(self % valueStorage, r) + WRITE(tmpString,*) r + s = TRIM(ADJUSTL(tmpString)) + CASE (FTVALUECLASS_STRING) + tmpString = TRANSFER(self % valueStorage, tmpString) + s = tmpString(1:SIZE(self % valueStorage)) + CASE (FTVALUECLASS_LOGICAL) + l = TRANSFER(self % valueStorage, l) + IF ( l ) THEN + s = ".true." + ELSE + s = ".false." + END IF + END SELECT + + END FUNCTION stringValueA +! +!--------------------------------------------------------------------------- +!> Get the string value of length requestedLength stored in the object, or +!> convert the value in the object to a string of that length if it is of a +!> different type. This version is deprecated in favor of the stringValueA +!> function that uses allocated strings. This version is included only so +!> that other code that uses this library doesn't break. It is overloaded +!> as stringValue(requestedLength). !--------------------------------------------------------------------------- ! !//////////////////////////////////////////////////////////////////////// ! - FUNCTION stringValue(self,requestedLength) RESULT(s) + FUNCTION stringValueR(self,requestedLength) RESULT(s) IMPLICIT NONE CLASS(FTValue) :: self INTEGER :: requestedLength @@ -664,7 +717,7 @@ FUNCTION stringValue(self,requestedLength) RESULT(s) END IF END SELECT - END FUNCTION stringValue + END FUNCTION stringValueR !@mark - ! !--------------------------------------------------------------------------- @@ -679,7 +732,7 @@ FUNCTION FTValueDescription(self) CLASS(FTValue) :: self CHARACTER(LEN=DESCRIPTION_CHARACTER_LENGTH) :: FTValueDescription - FTValueDescription = self % stringValue(DESCRIPTION_CHARACTER_LENGTH) + FTValueDescription = self % stringValue() END FUNCTION FTValueDescription ! diff --git a/Source/FTObjects/FTValueDictionaryClass.f90 b/Source/FTObjects/FTValueDictionaryClass.f90 index ad63ec5b..610df2d7 100644 --- a/Source/FTObjects/FTValueDictionaryClass.f90 +++ b/Source/FTObjects/FTValueDictionaryClass.f90 @@ -53,7 +53,7 @@ !> r = dict % realValueForKey("real") !> d = dict % doublePrecisionValueForKey("double") !> l = dict % logicalValueForKey("logical") -!> s = dict % stringValueForKey("string",15) +!> s = dict % stringValueForKey("string") !>#Converting an FTDictionary to an FTValueDictionary !> valueDict => valueDictionaryFromDictionary(dict) !>#Converting an FTObject to an FTValueDictionary @@ -104,8 +104,10 @@ Module FTValueDictionaryClass PROCEDURE :: quadValueForKey #endif PROCEDURE :: integerValueForKey - PROCEDURE :: stringValueForKey PROCEDURE :: logicalValueForKey + PROCEDURE :: stringValueForKeyA + PROCEDURE :: stringValueForKeyR + GENERIC :: stringValueForKey => stringValueForKeyA, stringValueForKeyR ! ! ------------- ! Introspection @@ -350,12 +352,11 @@ END FUNCTION logicalValueForKey ! !//////////////////////////////////////////////////////////////////////// ! - FUNCTION stringValueForKey(self,key,requestedLength) + FUNCTION stringValueForKeyA(self,key) IMPLICIT NONE CLASS(FTValueDictionary) :: self CHARACTER(LEN=*) :: key - INTEGER :: requestedLength - CHARACTER(LEN=requestedLength) :: stringValueForKey + CHARACTER(LEN=:), ALLOCATABLE :: stringValueForKeyA CLASS(FTValue) , POINTER :: v => NULL() CLASS(FTObject), POINTER :: obj => NULL() @@ -363,12 +364,40 @@ FUNCTION stringValueForKey(self,key,requestedLength) obj => self % objectForKey(key) IF ( ASSOCIATED(obj) ) THEN v => valueFromObject(obj) - stringValueForKey = v % stringValue(requestedLength) + stringValueForKeyA = v % stringValue() ELSE - stringValueForKey = "" + stringValueForKeyA = "" END IF - END FUNCTION stringValueForKey + END FUNCTION stringValueForKeyA +! +!//////////////////////////////////////////////////////////////////////// +! + FUNCTION stringValueForKeyR(self,key,requestedLength) +! +! ----------------------------------------------------------------- +! Legacy function from before gfortran had allocatable string. Kept +! to not break code that already uses it. +! ----------------------------------------------------------------- +! + IMPLICIT NONE + CLASS(FTValueDictionary) :: self + CHARACTER(LEN=*) :: key + INTEGER :: requestedLength + CHARACTER(LEN=requestedLength) :: stringValueForKeyR + + CLASS(FTValue) , POINTER :: v => NULL() + CLASS(FTObject), POINTER :: obj => NULL() + + obj => self % objectForKey(key) + IF ( ASSOCIATED(obj) ) THEN + v => valueFromObject(obj) + stringValueForKeyR = v % stringValue(requestedLength) + ELSE + stringValueForKeyR = "" + END IF + + END FUNCTION stringValueForKeyR !@mark - ! !//////////////////////////////////////////////////////////////////////// diff --git a/Source/FTTesting/Assert.f90 b/Source/FTTesting/Assert.f90 index e3da5dea..13f29779 100644 --- a/Source/FTTesting/Assert.f90 +++ b/Source/FTTesting/Assert.f90 @@ -133,8 +133,8 @@ Module FTAssertions ! ------- ! TYPE FTAssertionFailureRecord - CHARACTER(LEN=FT_ASSERTION_STRING_LENGTH) :: msg, expected, actual - CHARACTER(LEN=FT_ASSERTION_STRING_LENGTH) :: assertionType + CHARACTER(LEN=:), ALLOCATABLE :: msg, expected, actual + CHARACTER(LEN=:), ALLOCATABLE :: assertionType TYPE(FTAssertionFailureRecord), POINTER :: next END TYPE FTAssertionFailureRecord ! diff --git a/Testing/Tests/AssertionFailureTests.f90 b/Testing/Tests/AssertionFailureTests.f90 index ba975e1e..f935ec16 100644 --- a/Testing/Tests/AssertionFailureTests.f90 +++ b/Testing/Tests/AssertionFailureTests.f90 @@ -21,7 +21,7 @@ SUBROUTINE AssertionFailureTests TYPE(FTAssertionsManager), POINTER :: assertionsManager INTEGER :: numberUnexpectedFailures - CHARACTER(LEN=FT_ASSERTION_STRING_LENGTH) :: expected, actual + CHARACTER(LEN=64) :: expected, actual ! ! ----- ! Setup diff --git a/Testing/Tests/DictionaryTests.f90 b/Testing/Tests/DictionaryTests.f90 index 3b264e69..83013ab3 100644 --- a/Testing/Tests/DictionaryTests.f90 +++ b/Testing/Tests/DictionaryTests.f90 @@ -126,7 +126,7 @@ SUBROUTINE FTDictionaryClassTests obj => dict % objectForKey(keys(i)) v => valueFromObject(obj) IF ( ASSOCIATED(v) ) THEN - s = v % stringValue(FTDICT_KWD_STRING_LENGTH) + s = v % stringValue() CALL FTAssertEqual(values(i),s,"Value for key in dictionary class") ELSE msg = "Value for key "//TRIM(values(i))// " not of correct type" @@ -145,11 +145,11 @@ SUBROUTINE FTDictionaryClassTests storedKey = storedKeys(i) obj => dict % objectForKey(storedKey) v => valueFromObject(obj) - sExpected = v % stringValue(FTDICT_KWD_STRING_LENGTH) + sExpected = v % stringValue() obj => storedObjects % objectAtIndex(indx = i) v => valueFromObject(obj) - sActual = v % stringValue(FTDICT_KWD_STRING_LENGTH) + sActual = v % stringValue() CALL FTAssertEqual(sExpected, sActual,"String for stored key") END DO diff --git a/Testing/Tests/ExceptionTests.f90 b/Testing/Tests/ExceptionTests.f90 index 0c839594..5500d6a7 100644 --- a/Testing/Tests/ExceptionTests.f90 +++ b/Testing/Tests/ExceptionTests.f90 @@ -144,7 +144,7 @@ SUBROUTINE FTExceptionClassTests msg = "Conversion of dictionary to valueDictionary") CALL FTAssertEqual(expectedValue = "TAF:Designed to fail!", & - actualValue = userDictionary % stringValueForKey(key = "message",requestedLength = 21),& + actualValue = userDictionary % stringValueForKey("message"),& msg = "Message set for assertion failure") CALL FTAssertEqual(expectedValue = 777, & @@ -192,7 +192,7 @@ SUBROUTINE FTExceptionClassTests actualValue = e % className(), & msg = "Class name for FTException") - msg = userDictionary % stringValueForKey("message",FTDICT_KWD_STRING_LENGTH) + msg = userDictionary % stringValueForKey("message") CALL FTAssertEqual("An error has occurred",msg,"String for key: message") r = userDictionary % realValueForKey("value") diff --git a/Testing/Tests/LinkedListTests.f90 b/Testing/Tests/LinkedListTests.f90 index 2ce98fc3..293e736e 100644 --- a/Testing/Tests/LinkedListTests.f90 +++ b/Testing/Tests/LinkedListTests.f90 @@ -225,7 +225,7 @@ SUBROUTINE basicTests CASE(1) CALL FTAssertEqual(1,v % integerValue(),"First item is integer value") CASE(2) - CALL FTAssertEqual("r2 is a string",v % stringValue(14),"Second item is string value") + CALL FTAssertEqual("r2 is a string",v % stringValue(),"Second item is string value") CASE(3) CALL FTAssertEqual(3.14,v % realValue(),singleTol,msg="Third item in list is real value") END SELECT diff --git a/Testing/Tests/MultiIndexTableTests.f90 b/Testing/Tests/MultiIndexTableTests.f90 index 749b7fda..c44663e4 100644 --- a/Testing/Tests/MultiIndexTableTests.f90 +++ b/Testing/Tests/MultiIndexTableTests.f90 @@ -131,7 +131,7 @@ SUBROUTINE MultiIndexTableTests ! DO j = 1, 4 v => valueFromObject(table % objectForKeys(keys(:,j))) - str = v % stringValue(5) + str = v % stringValue() CALL FTAssertEqual(expectedValue = strs(j),actualValue = str,msg = "object retrieval") END DO ! diff --git a/Testing/Tests/ValueClassTests.f90 b/Testing/Tests/ValueClassTests.f90 index 2decb454..c26f8385 100644 --- a/Testing/Tests/ValueClassTests.f90 +++ b/Testing/Tests/ValueClassTests.f90 @@ -114,8 +114,10 @@ SUBROUTINE FTValueClassTests ! Also test the string value ! -------------------------- ! + s = v % stringValue() + CALL FTAssertEqual("3.140000",s(1:8),"Compare string value for real value using allocated string") s = v % stringValue(8) - CALL FTAssertEqual("3.140000",s(1:8),"Compare string value for real value") + CALL FTAssertEqual("3.140000",s(1:8),"Compare string value for real value using requestedLength string") ! ! -------------------------------------------------------------------------- @@ -143,6 +145,8 @@ SUBROUTINE FTValueClassTests CALL FTAssertEqual(3.14,v % realValue(),singleTol,msg="Real storage to real") CALL FTAssertEqual(3,v % integerValue(),"Integer return for real object") CALL FTAssertEqual(DBLE(3.14),v % doublePrecisionValue(),doubleTol,msg="Double return for real object") + s = v % stringValue() + CALL FTAssertEqual("3.140000",s(1:8),"String return for real object") s = v % stringValue(8) CALL FTAssertEqual("3.140000",s(1:8),"String return for real object") CALL FTAssertEqual(.true.,v % logicalValue(),"Logical return for real object") @@ -165,6 +169,7 @@ SUBROUTINE FTValueClassTests CALL FTAssertEqual(666.0,v % realValue(),singleTol,msg="Integer storage to real") CALL FTAssertEqual(666,v % integerValue(),"Integer storage to integer") CALL FTAssertEqual(DBLE(666.0),v % doublePrecisionValue(),doubleTol,msg="Integer storage to double") + CALL FTAssertEqual("666",v % stringValue(),"Integer storage to string") CALL FTAssertEqual("666",v % stringValue(3),"Integer storage to string") CALL FTAssertEqual(.true.,v % logicalValue(),"Integer storage to logical") ! @@ -186,6 +191,8 @@ SUBROUTINE FTValueClassTests CALL FTAssertEqual(REAL(d),v % realValue(),singleTol,msg="Double storage to real") CALL FTAssertEqual(0,v % integerValue(),"Double storage to integer") CALL FTAssertEqual(d,v % doublePrecisionValue(),doubleTol,msg="Double storage to double") + s = v % stringValue() + CALL FTAssertEqual("0.33333333333333",s(1:16),"Double storage to string") s = v % stringValue(16) CALL FTAssertEqual("0.33333333333333",s(1:16),"Double storage to string") CALL FTAssertEqual(.true.,v % logicalValue(),"Double storage to logical") @@ -238,7 +245,10 @@ SUBROUTINE FTValueClassTests actualValue = v % integerValue(), & msg = "Integer value from logical:"// logicalToStr(j)) CALL FTAssertEqual(expectedValue = logicalToStr(j), & - actualValue = v % stringValue(requestedLength = 7), & + actualValue = v % stringValue(), & + msg = "String value from logical:"// logicalToStr(j)) + CALL FTAssertEqual(expectedValue = logicalToStr(j), & + actualValue = v % stringValue(7), & msg = "String value from logical:"// logicalToStr(j)) CALL FTAssertEqual(expectedValue = logicalToReal(j), & actualValue = v % realValue(), & @@ -277,7 +287,10 @@ SUBROUTINE FTValueClassTests CALL castToValue(obj, vFromObj) CALL FTAssert(ASSOCIATED(vFromObj),msg = "Cast value from object as subroutine") CALL FTAssertEqual(expectedValue = "stringValue", & - actualValue = vFromObj % stringValue(requestedLength = 11), & + actualValue = vFromObj % stringValue(), & + msg = "Check that cast is correct") + CALL FTAssertEqual(expectedValue = "stringValue", & + actualValue = vFromObj % stringValue(11), & msg = "Check that cast is correct") ! ! --------------- diff --git a/Testing/Tests/ValueDictionaryTests.f90 b/Testing/Tests/ValueDictionaryTests.f90 index d0ab257f..bee8c0fc 100644 --- a/Testing/Tests/ValueDictionaryTests.f90 +++ b/Testing/Tests/ValueDictionaryTests.f90 @@ -59,7 +59,8 @@ SUBROUTINE FTValueDictionaryClassTests REAL :: x,r DOUBLE PRECISION :: xd LOGICAL :: lgcal - CHARACTER(LEN=FTDICT_KWD_STRING_LENGTH) :: sValue + CHARACTER(LEN=:), ALLOCATABLE :: sValue + CHARACTER(LEN=6) :: sValue6 ! ! ------------------------------------------------------- ! Initialize the dictionary. We set it up with @@ -101,9 +102,13 @@ SUBROUTINE FTValueDictionaryClassTests ! ----------------------- ! DO i = 1,4 - sValue = dict % stringValueForKey(keys(i),8) + sValue = dict % stringValueForKey(keys(i)) CALL FTAssertEqual(sValue,stringValues(i),"Value for key as string ") END DO + DO i = 1,4 + sValue6 = dict % stringValueForKey(keys(i),6) + CALL FTAssertEqual(sValue6,stringValues(i),"Value for key as string ") + END DO ! ! ------------------------ ! Get them out as logicals @@ -182,7 +187,10 @@ SUBROUTINE FTValueDictionaryClassTests actualValue = i) lgcal = dict2 % logicalValueForKey("bologna") CALL FTAssert(.NOT. lgcal,msg = "Retrieve nonexistent logical") - sValue = dict2 % stringValueForKey("bologna",8) + sValue = dict2 % stringValueForKey("bologna") + CALL FTAssertEqual(expectedValue = "", & + actualValue = sValue) + sValue6 = dict2 % stringValueForKey("bologna",6) CALL FTAssertEqual(expectedValue = "", & actualValue = sValue) !