Core

Gregor.Core

Sunday, May 04, 2003

Gregor.Core namespace

Utility Modules

Primarily, this namespace offers a number of modules with hopefully useful functions. You can spot the modules by the presence of a ModuleAttribute.

For those who are in the habit of eating sandwiches using a pair of pliers, a Module is what C# calls a "static class", and what Java calls a "final class with a private constructor and only static fields and methods".

The Dev module deals with logging and handling runtime errors (allowing for user notification, calling back the application for general error notifications as well as customized exception reporting, and collecting exceptions). The Timing module makes for simple time measurements.

There are some common string constants in the Consts and Chars modules, conversion functions (Conv module), and some bit shifting and bit testing helpers (Bytes module). For validating method input, have a look at the Check module. The Flow module fixes awkward flow control issues, such as testing against a Nothing reference in VB.NET or checking for a non-empty string in C#.

VB.NET's bad habits on logical operators and boolean conversion made me write logical functions (such as Flow.BoolAnd), although I don't actually use them (politics is bad advice for designing both a language and an application framework).

There are some helpers that improve working with XML nodes and attributes, and a few other gimmicks, such as a function that tells you who listens to a given event on a given control (see the Reflect module), attempts at solving the line break mess that exists in Windows (see the Parse module), collection transformers for use with For-Each (see the Walk module), and some support for resource-critical objects (see the Resource and ResourcePool modules).

Utility Classes

Among the utilities are the NStringIndents class, a dynamic lookup table for indentation strings, the CPathSearcher class, the CNewLine enumeration class, and more.

Much of the types in this namespace used to live in the Development, Documentation, Language, and Utilities sub namespaces. Back then, I had this to say:

The problem with "util" namespaces/packages/modules/"classes" is that the functionality eventually outgrows the foundation by causing a hopeless namespace overload. So I might reorganize the stuff in this namespace at some time; it's too bad I repeated the mistake made in the Java API. When you think "util", think again.

I still think that certain functionality, such as collections or file I/O, ought to go into their own namespaces. But with the sheer variety of utilities available, creating dozens of namespaces isn't too friendly for the user, either. So I decided to pull things up into the Gregor.Core namespace, making Imports easier.

Custom Attribute Classes

Gregor.Core is also the place where I put custom attributes for documentary purposes. I haven't used it that much so far, but it's changing. The main class is the DescriptionAttribute class, which has a special standing with the assembly browser offered by the AppCore project.

Gregor.Core.Collections namespace

Base Classes

Contains the NCollectionBase class, which is a base for creating strongly typed string/integer indexable collections (a cross of list and dictionary, with an iterator that walks over the values). The class implements the IObserveableCollection interface as well.

Furthermore, this namespace contains base classes for creating other type-safe collections: NStackBase, NQueueBase, and NMruBase (an auto-reorder stack that ensures uniqueness of entries), as well as a few typed specializations of the former classes.

Special Containers

The CCaseDictionary is a special string/object dictionary that allows to choose at runtime whether or not to ignore the case of its string keys when looking up entries.

History Classes

The .NET versions of the infamous history classes traditionally used by nearly every of my applications live here. There are two major classes that allow for historizing information: There is the CTotalHistory class (ordering entries by most recent access, not allowing duplicate entries; it's similiar to the NMruBase class), and the CBrowseHistory class (back-and-forward browsing context, with overwriting and duplicates, including an optional minimum visit time requirement), both inheriting from CHistoryBase.

The time when history entries where mere strings has long passed. Now, all entries are based on the CHistoryEntry class, allowing special implementations of determining entry equality. History entries now know their date/time of last access as well. Have a look at WebEdit.NET to see what can be done with a subclass of CHistoryEntry.

Table and Tree Structures

The namespace also provides self-describing table structures (CTable and friends), which can be persisted to XML. Via an interface that any collection may implement (ITableSource), the gap to strongly typed collections is bridged. Some setting classes in Gregor.AppCore make use of this.

The hierarchical counterpart to the tables are the tree classes. Using CTree, CTreeNode, and CNodeAttribute, you can quickly create a flexible tree structure. The trees, too, can be initialized from and persisted to XML. Again, there is an interface (ITreeSource), that a more strongly typed class can implement for moving the data. The tree classes also implement general tree interfaces (ITree, ITreeNode), which means they support what's discussed in the following paragraphs.

Walking Trees

The CTreeWalker class is a general-purpose depth-first iterator, which can traverse any tree with the help and grace of a support class implementing ITreeWalkHelper. The Gregor.NET framework offers several such helpers:

Here's an example of using the tree walker with a TreeView control:

Dim helper As New CTreeViewTreeWalkHelper(m_TreeView)
helper.ExcludeHiddenNodes = True
Dim walker As New CTreeWalker(helper)
Dim indents As New NStringIndents("  ")
Do While walker.MoveNext()
    Console.WriteLine(indents(walker.Level) & walker.Current.ToString())
Loop

Note that the tree walker exposes the Level property, which helps with the proper indentation. If you don't need the walker's extras, you can iterate with foreach as well. If the tree class you're using is not enumerable, or does not return a CTreeWalker as the enumerator, you can often use this code:

foreach(ITreeNode node in Walk.Tree(root)){
    Console.WriteLine(node);
}

Grouping Collections

Working with the tree classes, there is the CGrouperBase class. Think of grouping as an extension to sorting. The CGrouperBase class implements the IComparer interface, because sorting is seen as a prerequesite to grouping. When a grouper is instantiated, it is told about the properties to group on (group keys). It's possible sort by more keys than are used for grouping. Derived classes can implement property access efficiently, and restrict the grouper to strongly typed collections. A default implementation is the CReflectingGrouper class, which can group any collection on publicly accessible properties, which are (rather inefficiently) evaluated with reflection. Here's how the reflecting grouper can be used:

DirectoryInfo di = new DirectoryInfo(@"C:\");
FileInfo[] files = di.GetFiles();

// sort by extension and name, but group by extension only
CReflectingGrouper grouper = new CReflectingGrouper(1, "Extension", "Name");
CGroupNode root = grouper.Group(files);

// walk through tree of group nodes (note that the root node
// returned by the grouper never has any values)
NStringIndents indents = new NStringIndents("  ");
CTreeWalker walker = new CTreeWalker(new CDefaultTreeWalkHelper(root));
while(walker.MoveNext()){

    CGroupNode node = (CGroupNode) walker.Current;
    Console.WriteLine(indents[walker.Level] + node);

    // group nodes at any level below root may have values from the collection
    foreach(FileInfo fi in node.Values){
        Console.WriteLine(indents[walker.Level + 1] + fi.Name);
    }
}

Also check out the CXmlNodeGrouper, which sorts/groups an XmlNodeList according to XPath expressions applied against each node.

Grouping behaviour can be further refined by means of so-called group rules (deriving from CGroupRule). Group rules may be set on a per-property basis, and are evaluated before property values are compared. Basicly, a group rule transformes the value of a property into another object, thus affecting the comparison. This enables several things, like grouping on properties of non-comparable types, using ranges (see the CInitialLetterGroupRule class), or defining an arbitrary ordering (see the CRankingGroupRule class). Group rules aren't tied to any particular grouper type, so they can be readily reused.

For grouper implementation examples, see the CFileNameGrouper test class in the Gregor.Core source archive, the CTreeListGrouper (Gregor.UICore), or the CShellListGrouper (Gregor.AppCore) classes.

Gregor.Core.Configuration namespace

This namespace is intended for anything that helps you out with configuring an application. For now, there is support for parsing command lines. The CCommandLine class is a wrapper over command line arguments (which may come as a string or a string array). It provides easy access to operands and options with convenient methods.

A command line may also be validated against rules provided in an XML file. That's the task of CConfigurationInfo and related classes.

[more to be supplied]

Gregor.Core.FileSystem namespace

Wrapping File Paths

Offers classes that represent file system paths. The idea is to have some type safety: a method can accept both absolute and relative path, or only absolute paths, for example. There is the notion of a drive-quailified path (a Windows path), as well as Unix paths. Of course, properly combining absolute and relative paths, or calculating relative paths is the main point.

Gregor.Core.WinApi namespace

P/Invoke Central

This is designated to centralize Windows API calls (which are still scattered everwhere - shame!). For now, it offers some OS version info.