Encore

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

Author: Oliver Langer

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:

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:

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:

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:

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:

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.