Split libraries from application framework applications
Another but different attempt at #43 (closed) and a bit of #307 .
In #43 (closed) the direction is to keep it compatible with a framework application. I am not sure that is the right direction. For example, if some code uses an option to steer behavior, do we really want to inject an option value into the option before we can call the library function we want? It seems weird. Also you don't get control for output processing. As we stack applications on top of each other, output of the inner-most applications may not make much sense for a user any more.
In #307 the aim is to reduce the number of classes/packages needed by the generated scanners/parsers. Important, but not relevant here, as Junit runs in Eclipse, so we have everything already.
Usecase: So I had the idea to write small CIF specifications as test specifications in Junit. The idea worked until the parser(?) wanted to output something, probably a warning or error. Then it found it was not a framework application and crashed.
I looked at AppEnvData
and concluded we store
- The application.
- Output stream state.
- Option values.
- System properties.
for a framework application.
So here, I try to aim for creating a library. It should be as much as possible independent of the framework, and free of external support code that is not core of the library functionality. On the other hand, connecting to a framework application should be easy if so desired.
At first sight, of the stored AppEnvData
information, only output stream state and the option values seem truly relevant.
- For output stream state, I think we want to keep the current
out/iout/dout/doout
idea, except they cannot be static methods. For example, in my Junit tests, I may want to ignore output, or just verify I have no errors or check I have certain errors or output. - Option values are a nuisance from a library point of view. You typically want to provide a path as a string to the library if it needs reading or writing a file. You want to supply a numeric value directly. Using an option for that is a silly work-around, it may also interfere if the library is used in a larger application that uses the same options for other purposes.
Thus, I was thinking a library should have a configuration object for interfacing with the world. Probably a good idea is to keep it read-only for the library, but that can be decided locally. It should contain at least
- Settings to configure the library behavior, for as far as these are not supplied through a function call to it.
- Output stream state.
The settings are just fields in the configuration object. We may want to make a static convenience method that constructs such an object from option values for example, but it should be possible to create it in other ways too.
About the streams, I am thinking a class with the current out/iout/dout/doout
idea for all streams, but no back-end. That back-end should be a new object connected to the former class. In this way you can have different ways of handling output. You could have a back-end that ignores output, or stores it, or checks nothing is written, or passes it on to a framework entry point. Sort of a pipeline of output stream processing to configure them.
I am also wondering if this should be done separately for each stream. Currently, operations on standard output, debug output, and error/warning output all seems the same. Splitting in 3-4 objects reduces code duplication. You could also write a streams merger that takes 2 or more streams and combines them. Note that if you use more than one library, the output streams at some point must be merged (or processed at least). Merge back-ends could be used there too.
Obviously, output streams can be stored inside the config object.
The configuration object is likely needed at many places in the library code. Not sure that would be easily fixable, I assume not.
As the library has no static entry or exit points, this object must be made available by passing it into the relevant classes. I would say the best spot is in the constructor, making it a read-only field available to all (depending on the design of the library code of course).
I don't think you can avoid it. I am not even sure you should want to avoid it. Our applications are mostly one input, one output. They are all connected with the world by AppEnvData
. Many applications use the same option values (at the very least CIF file input and output). All applications use the same output stream.
If you write a larger applications with multiple libraries, at some point you will get conflicting needs for the same option values or output streams.
So while the config object adds some clutter to instantiation, I think it's an acceptable cost to making the code independent. In a sense it may not even be that bad, it basically makes the current implicit connections explicit. We already have all kinds of connections between pieces of code, but we don't see them. For example, in plcgen
I imported and call some cif2plc
code. There is no list of externally used input available.
No doubt I have forgotten a few important parts in the configuration. Termination requests is one, perhaps GUI is another. For 'simple' functionality like parsing/type-checking this is mostly not needed. They look asif it should be simple to add them to the configuration object, but likely we want to postpone that until we have more confidence in the approach?
Thoughts?
More ideas:
- Maybe make the object configuration an interface? Would be more flexible in combining libraries perhaps?
Decided naming conventions:
- Settings/input class:
- If only settings:
*Settings
. - If settings and input data:
*Input
. - If only input data:
*Input
.
- If only settings: