Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
Tuscany SCA For C++ - scagen C++ generator tool
===============================================
See cpp/build.txt in the parent directory or the cpp/sca/INSTALL file
for build instructions.
Building the tools
------------------
NOTE: this is built and installed by the build step above.
Currently, there is only one tool: "scagen". It can be built using the
ant build script at \tuscany\cpp\sca\tools\scagen\build.xml.
The default target "all" will build the java jars, documentation,
scripts and a zip file of the whole thing. This is all the ant
build tasks apart from "test" which runs all the junit tests.
The ant build script can be altered to add the junit tests to the
default target. Replace the line
The "test" task was not included in "all" as it requires a
junit jar file to run. This jar is available here:
http://www.junit.org/index.htm testing has been done with
Junit version 3.8.1.
Running the scagen tool
-----------------------
The scagen tool user interface is quite basic in this initial release.
It can be run from the scagen.jar file using "java -jar scagen.jar"
or from small scripts - scagen.bat for Windows and scagen.sh for Unix.
The parameters are:
-dir
-output
e.g.
scagen -dir c:\mycomposites\composite1 -output c:\mycomposites\bld\composite1
What scagen does
----------------
The input directory passed to the scagen tools as
the -dir parameter method is taken to be the SCA
composite root directory. All the sca.composite and .fragment
files in that directory are inspected to resolve all
the elements within them.
Each element found is inspected
to see if it has a element within it.
Each element should have a
header attribute that represents a C++ header file
that contains function prototypes for the C++
implementation of the service. An optional class
attribute can be used to select one class if more than
one that is present in the header file. The default
class is the one with the same name as the header file.
The tool will verify that the implementation header
contains an appropriate class prototype.
The directory that contains the implementation header
should also contain a matching .componentType file for
the equivalent SCA component. So for example, a
MyServiceImpl.h file would have a corresponding
MyServiceImpl.componentType file in the same directory.
Each componentType file is inspected for
and elements. For each element
that is found that contains a element
within it,
the header attribute of the is taken
as the filename of the C++ interface header for the
SCA service. This C++ header file is opened and used
as a means for specifying the SCA service resulting
in an appropriate wrapper and proxy being generated
for this service interface. Both method bodies and h
eaders are generated in the given output directory.
The processing of a element is the same
except that only a proxy header and implementation
re generated.
Getting started with the code
-----------------------------
The following is a list of tasks that are performed by the scagen tool
for each task we will describe technically how it is accomplished and
the location of the code that can be inspected/changed to alter the
behaviour.
Here are the tasks listed, below is a paragraph for each one:
o (Overall structure of the code)
o Walking the input directory
o Scanning the .composite and .fragment files
o finding the C++ implementation headers
o finding/checking the classname in the C++ implementation headers
o find the matching .componentTemplate files
o going into the componentTemplate files to extract the interface header filenames
o going into the interface header files and parsing them to extract the method signatures
into a network of objects we can inspect.
o taking all the meta data stored as objects and building a DOM for XSLT processing
o using XSLT to produce a proxy header
o using XSLT to produce a proxy implementation
o using XSLT to produce a wrapper header
o using XSLT to produce a wrapper implementation
Overall structure of the code
-----------------------------
There are two packages org.apache.tuscany.sca.cpp.tools.common and
org.apache.tuscany.sca.cpp.tools.services. The ...common package is
taken from some existing code that was also contributed to axis that
was used to parse C++ code and do various tasks like insert trace.
This code was repackaged and shipped as a tuscany package but there
has been a desire not to change it significantly from the equivalent
org.apache.axis.tools.common package to leave the door open for
future convergence.
Where the ...common package has been amended (for example to cope with
namespaces better or the provision of an Options.reset method to reset a static
variable and enable the tuscany junit tests to run independently) these
have been flagged with a "Tuscany" comment. The ...common package basically
provides two functions - 1) the ability to go into a directory (see DirectoryTree.java)
and process files that fit a particular filter (e.g. "*.hpp") by passing them to
implementer of the FileActor Interface (see the classes "Headers" for the
actor that processes C++ headers and "XMLFileActor" for the file actor that
processes the .componentType and sca.composite/fragment files.)
The ...services package contains the majority of code written afresh for the
scagen tool including the subclasses of XMLFileActor (see ComponentTypeFileHandler.java
and CompositeOrFragmentFileHandler.java) that are the classes that tie this
package to the ...common package and which are called by the
DirectoryTree walker.
Walking the composite root input directory
---------------------------------------
The main method of the scagen class creates an instance of
"DirectoryScanner" and registers with it a file handler of
type "CompositeOrFragmentFileHandler" for all files that end
in ".composite" or ".fragment". On calling the "walkTree" method
on the scanner it will in turn call the actOnFile method on the
CompositeOrFragmentFileHandler for appropriate files.
Scanning the .composite and .fragment files
----------------------------------------
The scanning of these files by the respective "CompositeOrFragmentFileHandler"
and "ComponentTypeFileHandler" is mostly handled by the superclass
"XMLFileActor". This class will recursively goes through the whole
XML file and considers the name of the XML element it finds.
"XMLFileActor" contains a map of element names to element handlers
that will "flatten out" the structure of the XML file "above" the
level of node we are interested in.
So for example the ComponentTypeFile handler sets up the handlers
map as follows:
GenericDomNodeHandler gdnh = new GenericDomNodeHandler();
handlers.put("componentType", gdnh);
handlers.put("interface.cpp", gdnh);
ServiceDomNodeHandler sdnh = new ServiceDomNodeHandler();
handlers.put("service", sdnh);
ReferenceDomNodeHandler rdnh = new ReferenceDomNodeHandler();
handlers.put("reference", rdnh);
The majority of processing done by these DomNOdeHandlers is to
place the attributes and values discovered into another map that
maps an (static version of) the XPath of a value to the value itself.
So for example "/componentType/service/interface.cpp/@header" might contain
the current ("root to here") value of the header attribute of the current
interface.
Particular handlers for the "leaves" of this tree
such as ServiceDomNodeHandler and ReferenceDomNodeHandler
can then consume these values from the map without having
to be concerned with the actual names of things,
like the service name, appearing in the key. It should be
understood though that there are multiple values placed in the map
for one "key" as the processing works its way through the
XML tree. For example the processing of a second component will
overlay its data over the same keys as the first component.
(After "wiping" the appropriate subtree.)
Finding the C++ implementation headers
--------------------------------------
The "/composite/component/implementation.cpp/@header" and
is used to key into the name of the implementation header
and this is opened directly and passed to the
actOnFileMethod of a Headers object from the ...common package
bypassing the DirectoryScanner code. The path is relative to
the given (-dir option) composite root directory.
Finding/checking the classname in the C++ implementation headers
-----------------------------------------------------------------
This implementation header is not used to define the
methods of the SCA service but rather is opened to check
any given implementation.cpp/@class attribute
(or find out the name of the implementation class
in the header if this is not specified in the XML. This
is done using the same method that later parses the interface
C++ headers into java objects - we just them inspect the
class attribute of the "Signature" objects that represent the methods
we find in the header.
Find the matching .componentType files
------------------------------------------
By SCA convention we go to the same directory as the implementation
files and look for the XXX.componentType files with the same name.
A instance of the ComponentDOMNodeHandler handles the data in the
Component Element and pre-creates a ComponentTypeFileHandler that
will eventually be called to process the .componentType file. This
object receives a number of "setParameter" calls to poke into it
matadata that is available prior/outside the the actual .componentType
file it will read.
Go into the componentType files to extract the interface header filenames
-----------------------------------------------------------------------------
We open up the .componentTemplateFiles with exactly the same
mechanism as we read the sca.composite/fragment file (by creating
a DOM and descending through it this time using a ComponentTypeFileHandler that it
has had various data values ( e.g. the implementation class and namespace used later)
poked into it. The ComponentTypeFileHandler itself has individual
handlers for the service and reference XML/DOM element/nodes
that is comes across (ServiceDomNodeHandler and ReferenceDomNodeHandler
respectively). Each these handlers will pull out the name of
a C++ interface header and use it to resolve the interface of the
SCA Service.
Parsing the interface header files for signatures
-------------------------------------------------
The Service/Reference DOM Node hander both call the
ServicesGenerator.handleInterfaceHeader(parameters, true);
method, the second parameter is used to differentiate
the call source as we don't need wrapper files for
SCA references (just proxies).
The ServicesGenerator uses the Headers file actor from
the ...common package to create a List of Signature
objects that describe the interface methods in the C++
header.
Take all the meta data stored as objects and build a DOM
--------------------------------------------------------
We now have a List of Signature objects and a map that
represents the flattened information that we have pulled
from the XML files in the ServiceGenerator class.
We call a "createDOMofMethods" method
to consolidate all this information into one DOM
(this task should be split into more than one method as the
signature/parameter list of the method is too large).
Use XSLT to produce the output files (Proxy/Wrapper headers and Implementations)
--------------------------------------------------------------------------------
The ServicesGenerator.handleInterfaceHeader(parameters, forReference);
method closes of with the code:
createProxyCPPFromDom(outputDir, dom);
createProxyHeaderFromDom(outputDir, dom);
if (!forReference) {
createWrapperCPPFromDom(outputDir, dom);
createWrapperHeaderFromDom(outputDir, dom);
}
Each of the create methods sets up the output
file name and a different XSLT transform and calls
"createOutputFromDom" to transform/filter the data in the
"model" that is held in our DOM of the data to a particular
"view" as expressed in the C++ output file.
The four XSLT style sheets are in rough order of the output
file and this corresponds very roughly to a depth first descent
of the DOM tree so, for example, we could have in a stylesheet:
...
void*
::newImplementation()
{
return new (target);
}
which would be output as:
void* MyClassImpl_MyClass_Proxy::newImplementation()
{
return new MyClassImpl(target)
}
given appropriate valies for $class and "../@implClass" and
$class might be defined to be:
xsl:variable name="clazz">
_
_Proxy
giving "MyClassImpl_MyClass_Proxy"
The stylesheets can be found in the xsl subdirectory of the
org.apache.tuscany.sca.cpp.tools.services package.
Unit Testing Scagen Code Changes
--------------------------------
The junit unit test
/tuscany/cpp/sca/tools/scagen/
junit/org/apache/tuscany/sca/cpp/tools/junit/TestAllCompositesTest.java
will dynamically look for all the subdirectores of the directory
path given by TuscanyTestCase.junit_composites and run the scagen
tool on them as if they were composites roots.
By convention an "expected_output" directory is located
(see the CVS tree or the test program) and the actual
and expected results compared. This testcase is thus a
good first/basic regression test for any changes.
New test cases can thus be added without having to write
any new junit java code by by creating new SCA composites and
the associated expected Scagen output - perhaps by using the tool
initially and checking the output is satisfactory before copying
it to the expected output directory at:
/tuscany/cpp/sca/tools/scagen/junit/testoutput//expected_output
where input data is taken from
/tuscany/cpp/sca/tools/scagen/junit/testinput/composites/