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:
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:
An
instance of Container
is being allocated and put on the stack.
setName
is being called and the value of the related attribute is passed to
it („rootContainer
“).
An
instance of Node
(„nodeA“) is being allocated and put on the stack.
setName
is appropriately being called.
The
instance is being associated with the identifier _nodeA
.
After the
XML element type <Node>
is closed the following steps are taken:
The top element of the stack is being removed and
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.
An
instance of Node
(„nodeB“) is being allocated and put on the stack.
setName
is appropriately being called.
The
instance is being associated with the identifier _nodeB
.
After the
XML element type <Node>
is closed the following steps are taken:
The top element of the stack is being removed and
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.
An instance of Edge is being allocated and put on the stack.
setName
is called
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.
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
.
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.
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.
So far, please refer to the test cases to be found in the folder „./tests“.
So far, please refer to the API description.
So far, please refer to the test cases to be found in the folder „./tests“.
So far, please refer to the API description.