NetworkedPlanet | TMCore Engine API Guide |
APIExample2 is a simple application which shows the basics of
using the programmatic indexes provided by TMCore. The program creates a
topic map from a directory (specified on the command line) and all its
subdirectories. For each directory and file in the hierarchy a topic is
created and assigned either the type "File" or "Directory" (these are
defined by separate topics created when the program first initialises).
For each file and for each subdirectory of the starting directory an
association is created to the containing directory. That association is
typed as "Parent-Child" with the containing directory playing a role of
type "Parent" and the contained file or directory topic playing the role
"Child". In addition, every file with a file name extension (eg.
MyFile.txt ) is also typed by a topic that describes the extension (e.g.
".TXT"). The first time that a file with a particular extension is
encountered, the typing topic is created. For each subsequent file with
the same extension, that same topic is used. To assign the right
extension type topic to a file topic, the
org.tmapi.index.core.TopicsIndex
is used to look
up the extension type topic.
This example can be found in the file
examples/CS/APIExample2
for C# code and
examples/VB/APIExample2
for VB code.
Example 3.8. The Main Program Flow
public void Run(string startDirectory) { // Initialise a new ITopicMapSystem and create the topic map m_tmSystem = TopicMapSystemFactory.GetTopicMapSystem(); m_tm = SafeCreateTM(m_tmSystem, "http://www.example.com/topicmaps/MyFileSystem"); // Add the basic topics required for creating our file-system topic map CreateOntology(); ProcessDirectory(new DirectoryInfo(startDirectory), null); ListTopicsAndAssociations(); ListTopicTypes(); ListDLLs(); }
When the program is run, the name of the directory to process is retrieved from the command-line (not shown in the code above). This is passed into the main program loop as the parameter startDirectory. In the main program loop, the following is done:
A new TopicMapSystem is created and a new TopicMap is created in that system. This code is exactly the same as already discussed for APIExample1.
The CreateOntology() method is invoked to "bootstrap" the topic map with the topics that define the basic topic types of "File", "Directory" and the basic association type "Parent-Child" and role types "Parent" and "Child".
The ProcessDirectory() method is invoked to recurse through the directory specified by the startDirectory parameter, creating File and Directory topics and linking them together using Parent-Child associations.
All of the topics and associations created are listed to the console by the method ListTopicsAndAssociations(). This method makes use of code very similar to that already seen in APIExample1 and will not be covered in detail here.
All of the topics used to type other topics are listed to the console by the method ListTopicTypes().
All of the topics of a specific type (the type representing files with the .DLL extension) are listed to the console by the method ListDLLs();
Example 3.9. Creating the Topic Map Ontology
/// <summary> /// Creates the topics for the basic types used by this topic map. /// </summary> private void CreateOntology() { // Topic to type all topics that represent files m_tFile = CreateTopic("File", "http://www.networkedplanet.com/psi/examples/#File") // Topic to type all topics that represent directories m_tDirectory = CreateTopic("Directory", "http://www.networkedplanet.com/psi/examples/#Directory"); // Topics for creating the Parent/Child associations m_tParent = CreateTopic("Parent", "http://www.networkedplanet.com/psi/examples/#Parent"); m_tChild = CreateTopic("Child", "http://www.networkedplanet.com/psi/examples/#Child"); m_tParentChild = CreateTopic("Parent-Child", "http://www.networkedplanet.com/psi/examples/#ParentChild"); } /// <summary> /// Utility method to create a topic with a single name and a /// single subject identifier. /// </summary> /// <param name="name">The name to add to the new topic</param> /// <param name="subjectIdentifier">The subject identifier URL for the new topic</param> /// <returns>The newly created topic</returns> private ITopic CreateTopic(string name, string subjectIdentifier) { ITopic ret = m_tm.CreateTopic(); ret.CreateTopicName(name); if (subjectIdentifier != null) { ret.AddSubjectIdentifier(m_tm.CreateLocator(subjectIdentifier)); } ret.Save(); return ret; }
The above code shows one simple pattern for populating a new topic map with the basic typing topics for an application (often called an 'ontology'). The method CreateTopic() is used to create a single topic with a given name and subject identifier. This method is then invoked from the CreateOntology() method for each topic to be created. Of course, if your application will make use of the same topic map each time, you should check that a particular topic does not exist before creating a new one with the same subject identifier - we will see how to use indexes to achieve this in the next section. In this code, the topics are then stored as member variables of the main program class - in more complex applications you may want to create a specific "Ontology" class to manage these topics.
Example 3.10. Using Indexes
[1] /// <summary> [2] /// Adds a topic representing a file into the topic map. [3] /// </summary> [4] /// <param name="fileInfo">The file to be processed.</param> [5] /// <param name="parentDir">The topic representing the directory that contains this file.</param> [6] private void ProcessFile(FileInfo fileInfo, ITopic parentDir) [7] { [8] // Create a topic representing the file [9] ITopic fileTopic = m_tm.CreateTopic(); [10] fileTopic.AddType(m_tFile); [11] fileTopic.CreateTopicName(fileInfo.FullName); [12] fileTopic.CreateTopicName(fileInfo.Name, new ITopic[] {parentDir}); [14] if (fileInfo.Extension != null) [15] { [16] // Lookup the topic that represents this file's file extension [17] string fileTypeIdentifier = [18] "http://www.networkedplanet.com/psi/examples/extensions/#" + [19] fileInfo.Extension.Substring(1).ToUpper(); [21] ITopic extensionType = m_tm.TopicsIndex.GetTopicBySubjectIdentifier(fileTypeIdentifier); [22] if (extensionType == null) [23] { [24] // Create a topic to represent this files's file extension [25] extensionType = m_tm.CreateTopic(); [26] extensionType.CreateTopicName("File Type: " + fileInfo.Extension.ToUpper()); [27] extensionType.AddSubjectIdentifier(fileTypeIdentifier); [28] extensionType.Save(); [29] } [30] fileTopic.AddType(extensionType); [31] } [33] fileTopic.Save(); [34] ... [35] }
The code snippet above shows part of the process of creating a
topic to represent a file. From line [14] to line [31] is code for
assigning a type to the file topic based on the extension of the file
name. So for example all files representing .DLL files will be typed by
a topic "DLL", all .TXT files by a topic "TXT" and so on. We want to be
sure that we only create the "DLL" or "TXT" topic on the first time we
encounter a file with that extension and not on subsequent files with
the same extension. The best way to identify topics is by assigning a
subject identifier to them. A subject identifier is simply a unique URI
identifier for the topic. In our example we create a subject identifier
with the URI
"http://www.networkedplanet.com/psi/examples/extensions/#{extension}".
Note that we force the file name extension to upper case to ensure that
"X.DLL" and "y.dll" both receive the same typing topic as subject
identifiers are case-sensitive. Having created a locator string with the
appropriate URL (lines [17]-[19]), we then look up the topic with that
subject identifier using the
TopicsIndex
. Every ITopicMap
provides
access to a set of indexes of the different objects in the topic map.
All of these indexes are accessed through read-only properties of the
ITopicMap
interface - they are easily
identified as each property name ends with "Index". Each of the indexes
provide a different set of look-up methods. The index we are using here,
the TopicsIndex provides methods to look up a topic
by its subject identifier, its subject locator, or its type(s). On line
21, the method
ITopicsIndex.GetTopicBySubjectIdentifier()
is
invoked. Because a topic map can only ever have one topic with a given
subject identifier, this method will either return a single
ITopic
instance or null. If the method
returns null, a new topic is created on lines 24-28. Finally the topic
(either the one returned by
GetTopicBySubjectIdentifier()
or the newly
created topic) is added to the types for the file topic using the method
ITopic.AddType()
, and the file topic is saved
to commit all the changes.
Example 3.11. Listing All Topic Types
[1] /// <summary> [2] /// Displays the topics which are used to type other topics in the topic map. [3] /// </summary> [4] private void ListTopicTypes() [5] { [6] System.Console.WriteLine("All Topic Types:"); [7] IList topicTypes = m_tm.TopicsIndex.GetTopicTypes(); [8] foreach(ITopic t in topicTypes) [9] { [10] PrintTopicNames(t); [11] } [12] System.Console.WriteLine(); [13] }
The example above shows another use of the indexes. In addition
to allowing you to look up objects by certain properties, most of the
indexes also allow you to do a reverse look up e.g. to quickly find
all topics which are used to type other topics. These reverse look up
methods can be extremely useful in constructing views and indexes for
the user. In this example the reverse look up is to find all topics
which are used to type other topics in the topic map and is achieved
on line [7] with a call to
ITopicsIndex.GetTopicTypes()
, which returns
an IList
of
ITopic
instances.