Encore

Based on Encore version 0.2. 06/02/21.

Author: Oliver Langer

News

2006/05/26 – Encore Distribution Policy Change

TODO

About

Encore is meant as a basis library for projects. In particular it serves as such for FT.

At present Encore supports the following functionalities:

- XML-based Inversion of Control

- Logging

- A Testframework

- Iterators

- Parameterized Strings

- StringUtils

These funtionalities are explained in the following sections.

XML-based Inversion of Control

With an XML-based reflection a set of objects is created based on the XML element types and their relationships. Take the following XML extract into account e.g.:

    <Container name=“rootContainer“>
      <Node name=“nodeA“ id=“_nodeA“/>
      <Node name=“nodeB“ id=“_nodeB“/>
      <Edge name=“edge“ srcNode-ref=“_nodeA“ destNode-ref=“_nodeB“/>
    </Container>

This XML fragment may result in the creation:

- A container object C with name = „rootContainer“

- A node object N1 with name = „nodeA“ and id = “_nodeA“, which is added to C by calling [C addNode: N1 ]

- A node object N2 with name = “nodeB“ and id = “_nodeB“, which is added to C by calling [C addNode: N2]

- An edge object E with name = „edge“. Its field srcNode is set via [E setSrcNode: N1], so that it refers to N1. Analogeously field destNode is set so that it refers to N2. Afterwards E is added to C through [C addEdge: E]

This is exactly how the XML-based inversion of control works under Encore at present. The implementation behind is rule-based. That is a rule „fires“ if the underlying condition matches the currently parsed Xml construct. The following rules are currently supported and thus enable the mentioned creation of objects:

- Element type handling: if an xml element type is to be found, a class with the same name is tried to be allocated. Regarding the example this means that for the xml element type <Node name=“nodeA“ id=“_nodeA“/> a class with name „Node“ is being search and if found, it is being allocated. When this xml element type is being closed then `set<ElementTypeName>` or `add<ElementTypeName>` is being called of the „top“ object. Wrt. the example, [C addNode: N1] is being called.
  Special handling is being done if an XML element type contains an attribute named „class“. In this case the value of „class“ is being interpreted as the name of the class to be instantiated. E.g.: <Node class=“MyNodeC“ name=“nodeC“ id=“_nodeC“/> looks up for a class named „MyNodeC“ instead of „Node“.

- Attribute handling: if an attribute a of an xml element type is being visited then a corresponding setter method set<a> or add<a> is being searched and called, if existent. The first capital of <a> must be in upper case. With respect to the previously given example this means, that [N1 setName: „nodeA“] is being called in order to hand off the value of the attribute <a>=“name“ to the currently parsed instance.

- Reference handling: the attribute „id“ is handled specially: Internally its value is being associated with the object allocated with the underlying XML element type. Refering to the example this means, that the identifier „_nodeA“ is being associated with the instance which is being allocated with „<Node ..“. On the contrary, all attributes ending with „-idref“ also have a special meaning: they refer to a previously being associated „id“ resulting in a corresponding set<property>- or add<property>-call, where <property> does not contain „idref“. Wrt. the example, [E setSrcNode: N1] and [E setDestNode: N2] is being called.

As a result, the given example would be suitable enough to set up the following objects with related data:

    @interface Container : ECObject {
      NSString *name;
      NSMutableArray *nodes;
      NSMutableArray *edges;
    }

    - addNode: (Node *) aNode;
    - addEdge: (Edge *) anEdge;
    - setName: (NSString *) aName;
    @end

    @interface Node : ECObject {
      NSString *name;
    }

    - setName: (NSString *) aName;
    @end

    @interface Edge : ECObject {
      Node *srcNode;
      Node *targetNode;
      NSString *name;
    }

    - setName: (NSString *) aName;
    - setSrcNode: (Node *) aNode;
    - setDestNode: (Node *) aNode;
    @end

With the xml fragment stated before the control flow is as follows:

1. An instance of `Container` is being allocated and put on the stack.

2. `setName` is being called and the value of the related attribute is passed to it („`rootContainer`“).

3. An instance of `Node` („nodeA“) is being allocated and put on the stack.

4. `setName` is appropriately being called.

5. The instance is being associated with the identifier `_nodeA`.

6. After the XML element type `<Node>` is closed the following steps are taken:

   1. The top element of the stack is being removed and

   2. hand-off to the (new) top element of the stack by looking for a related `set`- or `add`-method. In this case the new top element is the container thus `addNode` of this container is being called.

7. An instance of `Node` („nodeB“) is being allocated and put on the stack.

8. `setName` is appropriately being called.

9. The instance is being associated with the identifier `_nodeB`.

10. After the XML element type `<Node>` is closed the following steps are taken:

    1. The top element of the stack is being removed and

    2. hand-off to the (new) top element of the stack by looking for a related `set`- or `add`-method. In this case the new top element is the container thus `addNode` of this container is being called.

11. An instance of Edge is being allocated and put on the stack.

12. `setName` is called

13. For all attributes ending with the suffix „-`ref`“ the object, which is associated with the value of the attribute, is looked up and passed to the instance by calling a corresponding `set`- or `add`-method. In this case the previously allocated nodes are found and passed to the edge instance by calling `setSrcNode` and `setDestNode` appropriately.

14. After the XML element type `<Edge>` is closed the underlying edge instance is being removed from the stack and added to the new top element, which is the instance of `Container`.

15. The last steps consists of closing the XML element type `<Container>`. Since this element is the overall root element, the parsing ends with it and the related Objective-C instance is marked as the root user object.

The underlying framework is minimal at present and supports only the features mentioned so far. But the framework allows to easily add new features. These features are implemented as rules which are fired if the related XML context matches. This XML context is implicitly defined by the current parsing state and the current event fired by `NSXMLParser`. To see how it works refer to `ECXMLControlSetAttributeRule` e.g.

The following example shows the steps necessary to initialize and use this framework:

    ECXMLControl *xmlControl;
    NSURL *fileURL;
    Container *container;

    fileURL = [NSURL fileURLWithPath: @"./sample.xml"];
    xmlControl = [[[ECXMLControl alloc] initWithContentsOfURL: fileURL]autorelease];

    [xmlControl parseXML];
    container = (Container *)[xmlControl rootUserObject];

The last line fetches the root user object. Normally this is the instance related to the first element of an XML file.

Logging

Encore offers a logging mechanism, which offers functionalities similar to log4j; that is:

- Logging levels: Logging entries can be written using one of the logging levels `TRACE, DEBUG, INFO, WARNING, ERROR; CRITICAL, FATAL`.

- Logging writer: A protocol is defined which allows the usage of different logging modalities. At present, a file-based writer and a writer which logs entries via NSLog are supported.

- Logging formatter: Logging formatters allow the configuration of the logging output. At present a generic logging formatter class is supported.

- Logging context: A logging context bundles a logging level, a writer and a formatter and is explicitly being chosen when implementations want to write logging entries. In other words if an implementation wants to write a logging entry it selects a contexts, level and hands-over the message to write a logging entry. Through this context the frameworks knows which levels might by written, which writer and which format has to be used. In program languages different from Objective-C there is often the namespaces being used as context specifier.

- XML-based configuration: The configuration of ECLogging may be done via the API or via a flexible XML-based configuration. The latter allows the configuration of the logging without modification of the code. It is based on ECXMLControl.

Using the API the initialization of the logging may look as follows:

    id  loggingWriter;
    ECLoggingConfiguration *loggingConfiguration, *rootLoggingConfiguration
    id loggingFormatter;

    loggingFormatter = [[[ECDefaultLoggingFormatter alloc] init]autorelease];
    loggingWriter = [[[ECNSLogLoggingWriter alloc] init]autorelease];
    // logging configuration for context „sample.context“
    loggingConfiguration = [[[ECLoggingConfiguration alloc] init] autorelease];
    [loggingConfiguration setLoggingLevel: @“DEBUG“
    [loggingConfiguration setLoggingWriter: loggingWriter];
    [loggingConfiguration setLoggingFormatter: loggingFormatter];

    [[ECLogging instance]
      addLoggingConfiguration: loggingConfiguration
      forContext: „sample.context“ ];
    // root logging configuration
    rootLoggingConfiguration = [[[ECLoggingConfiguration alloc] init] autorelease];
    [rootLoggingConfiguration setLoggingLevel: @“WARN“
    [rootLoggingConfiguration setLoggingWriter: loggingWriter];
    [rootLoggingConfiguration setLoggingFormatter: loggingFormatter];

    [[ECLogging instance]
      addRootLoggingConfiguration: rootLoggingConfiguration];

This example sets up two logging configurations:

- `loggingConfiguration`: This configuration is used in conjunction with the context „sample.context“. Its level is set to `DEBUG` and it uses the default logging formatter as well as the `ECNSLoggingWriter`. The latter is used to print out the logging messages via `NSLog`. Now all instances logging entries specifying the context „sample.context“ will implicitly use this configuration. To be precise: All contexts starting with the same prefix will use this configuration – these are e.g. „sample.conext“, „sample.context.subcontext1“, „sample.context.bla“. Whereas a context like e.g. „sample.myOwnContext“ won't use this configuration of course. It is possible to use multiple logging configurations.

- `rootLoggingConfiguration`: This configuration also uses the default formatter and `NSLog` to print out the messages. In contrast to `loggingConfiguration` it only handles messages categorized as `WARN` or „higher“. The purpose of the root logging configuration is that it is implicitly chosen when a context is specified for which now configuration exists. For applications this means that such a root configuration may allways be configured at least.

Writing logging entries is straight forward:

    [[ECLogger loggerForContext: @“sample.context“]
      debug: @“A simple logging entry“];
    [[ECLogger loggerForContext: @“sample.context.subcontext1“]
      error: @“Should also log since we are implicitly using the context sample.context“];
    [[ECLogger loggerForContext: @“an.unknown.context“]
      debug: @“You should NOT see this logging entry:“\
        „we should implicitly use the root logger, but this logger only logs entries at „\
        „level WARN or higher!“];
    [[ECLogger loggerForContext: @“another.unknown“]
      warn: @“Let's warn here, so that the root logger should print it out“];

In order to reduce overhead when trying to write using a particular logging level which might not be enabled with the given configuration, a corresponding check method is supported for the levels `TRACE`, `DEBUG`, `INFO`. So logging using one of these levels may look as follows:

      if( [[ECLogger loggerForContext: @“sample.context“] isDebugEnabled] ) {
        [[ECLogger loggerForContext: @“sample.context“]
          debug: @“This is a debug entry“];
      }

It is also possible to hand-off string formats and related parameters to a logging call – e.g:

    [[ECLogger loggerForContext: @“sample.context“]
      error: @“Got exception while trying to read from file: %@“, exception];

To write into a file instead of using NSLog you may use ECFileLoggingWriter. This class allows the usage of rotating logging files, thus allowing you to set up the (approx.) maximum size of a file. Also it is possible to specify the format of the name of a logging file. This example shows a possible configuration of this class:

    ECFileLoggingWriter *loggingWriter
      = [[[ECFileLoggingWriter alloc] init] autorelease];
    [loggingWriter setBaseFilename: filename];
    [loggingWriter setMaxFilesize: 1048576];
    // ...install as expected:
    [loggingConfiguration setLoggingWriter: loggingWriter];

So far the configuration based on the API (program code) has been introduced. Another, yet mor flexible way relies in the usage of XML. The implementation supports the configuration by a small set of additional classes. As a result, a configuration may look as follows:

    <ECLoggingXMLConfiguration>
      <!--define some writer -->
      <ECLoggingWriter id="filewriter" class="ECFileLoggingWriter"
        baseFilename="sample.log"/>
      <ECLoggingWriter id="nslogwriter" class="ECNSLogLoggingWriter"/>
      <!-- define the default formatter -->
      <ECLoggingFormatter class="ECDefaultLoggingFormatter" id="defaultWriter"/>

      <!-- map some contexts to configurations -->
      <ECLoggingConfiguration id="debugIntoFile"
        loggingLevel="DEBUG"
        loggingWriter-idref="filewriter"
        loggingFormatter-idref="defaultWriter"/>

      <ECLoggingConfiguration id="infoIntoFile"
        loggingLevel="INFO"
        loggingWriter-idref="filewriter"
        loggingFormatter-idref="defaultWriter"/>

      <ECLoggingConfiguration id="errorOnNSLog"
        loggingLevel="ERROR"
        loggingWriter-idref="nslogwriter"
        loggingFormatter-idref="defaultWriter"/>

      <!-- define the logging context -->
      <ECLoggingContext  context="sample.A" loggingConfiguration-idref="debugIntoFile"/>
      <ECLoggingContext  context="sample.B" loggingConfiguration-idref="infoIntoFile"/>
      <ECLoggingContext  rootContext="true" loggingConfiguration-idref="errorOnNSLog"/>
    </ECLoggingXMLConfiguration>

After execution of this piece of code the configuration can be used.

    NSURL *fileURL;

    fileURL = [NSURL fileURLWithPath: @"/logging-config.xml"];
    xmlControl = [[[ECXMLControl alloc] initWithContentsOfURL: fileURL] autorelease];

    [xmlControl parseXML];

Regarding the provided classes and their configuration possibilities please refer to the API description, since their exists no additional documentation at the moment. For fully functional examples please refer to the subdirectory „tests“ of the Encore distribution.

A Testframework

So far, please refer to the test cases to be found in the folder „./tests“.

Iterators

So far, please refer to the API description.

Parameterized Strings

So far, please refer to the test cases to be found in the folder „./tests“.

StringUtils

So far, please refer to the API description.