Document not found! Please try again

Generic Components to Foster Reuse - CiteSeerX

3 downloads 164772 Views 77KB Size Report
new application scenarios allow to overcome these limitations and help to ... software components have to be made sufficiently abstract to be reusable, while at the ... means for delivering solutions that — despite relying on the reuse of software — are customized to ... In this way, the benefits of reusing pre-fabricated and.
Generic Components to Foster Reuse L. Baum, M. Becker System Software Research Group University of Kaiserslautern D-67653 Kaiserslautern, Germany {lbaum, mbecker}@informatik.uni-kl.de Abstract Software reuse and especially the paradigm of software components are promising approaches to increase the efficiency of software development. One of the basic problems of software reuse, however, is the trade-off between the abstraction from project-specific aspects on the one side, and the actual contribution of a reusable component during the realization of a new system on the other side. Conventional components with fixed properties are trapped within the inherent tension between overly general and less efficient solutions satisfying a large number of niche requirements, and specifically optimized but less reusable designs for each point in the requirements space. Generic components which are designed and implemented to be mechanically adaptable to new application scenarios allow to overcome these limitations and help to drastically increase the efficiency of software development. This paper introduces the fundamental concepts of generic components and particularly discusses viable techniques to implement generic components. It furthermore shows how generic components may be instantiated automatically by tools and reports on our first experiences in using generic components for the construction of embedded operating systems.

1. Introduction The highly competitive and dynamic field of software development implies the need to continuously increase the efficiency of software development processes. Software reuse and especially the component paradigm are commonly recognized as the most promising approaches to achieve this goal. However, there are still inherent problems with both techniques. On the product side, software components have to be made sufficiently abstract to be reusable, while at the same time need to be sufficiently concrete to be of any use at all. On the process side, there are the problems of finding best-fitting components in the reuse pool, and of integrating selected components into one system while considering component interdependencies and architectural constraints. We have developed an approach to component-based and reuse-oriented software development in the domain of embedded operating systems that addresses these typical problems. The combination of stringent nonfunctional requirements and specialized hardware platforms offering only limited computing resources creates a particularly strong demand for efficient development processes for the implementation of embedded operating systems, since such operating systems have to be highly customized to the settings of individual applications and hence have to be built to order. These demands for individually tailored solutions on the other hand make conventional reuse approaches especially problematic in this context. We address this problem by deploying generic components which provide enough flexibility to be easily adapted to individual requirements. The necessary modifications to these components in order to instantiate them with desired properties are mainly automated by tools, so-called generators. This paper basically discusses the underlying concepts and the software technology required to implement generic components. The concepts have been applied in the realm of embedded operating systems, but should as well be

applicable to any other domain — provided that a known and manageable set of component variants is likely to cover the majority of possible requirements. The rather process-related questions of how to describe requirements and component properties, as well as the mapping of requirements to appropriate component selections and configurations is covered by a companion paper [2]. The following section introduces the concept of generic components, while Section 3 extends this introduction by taking a look at generic parameters and possible types of parameters. Section 4 focusses on diverse implementation techniques to realize generic components, before Section 5 concludes with the presentation of some first experiences concerning the improved reusability and the increase in development time.

2. Generic components One of the basic product-related problems of software reuse is the trade-off between the abstraction from project-specific aspects on the one side, and the actual contribution of a reusable component during the realization of a new system on the other side. Our answer to this problem is the deployment of generic components [3], [16]. Generic components are software components for which specific properties have been left variable during the component’s implementation. These properties may relate to data elements or functions provided by the component, or to component-wide nonfunctional aspects which cannot be attributed to individual data elements or functions at the component’s interface. The properties are chosen at the component’s deployment time, i.e., succeeding to the initial implementation but before run time, by assigning values to the component’s so-called generic parameters. Tools, in general called generators, then allow to automatically instantiate generic components to conventional components with the desired properties. Generic components are thus a means for delivering solutions that — despite relying on the reuse of software — are customized to the settings of a specific application project. In this way, the benefits of reusing pre-fabricated and tested software artefacts are combined with the goal of producing efficient and therefore specialized software. In detail, a generic component can be defined as a 5-tuple gc with

gc := ( n, Pf, GP, Pp, Pv ) where

n Pf

GP Pp Pv

: unique name of the component : set of fixed component properties in terms of provided data elements Df, functions Ff, and component-wide nonfunctional properties Nf: Pf = Df ∪ Ff ∪ Nf : set of generic parameters : set of variable component properties, i.e., the entirety of all component properties that result from possible settings of the component’s generic parameters : function mapping valid settings for the component’s generic parameters to a set of resulting component properties: Pv : VS → ℘(Pp)

VS = { S=(s1, ..., s|GP|) | si is a valid value for parameter i and S is a valid combination of parameter values } As a consequence, Pv(S) denotes the set of variable component properties that result from a valid setting S ∈ VS of the component’s generic parameters. Similarly to Pf, Pv can be viewed as a combination of functions Dv, Fv, and Nv : Pv(S) = Dv(S) ∪ Fv(S) ∪ Nv(S) with Dv(S) being the set of provided data elements, Fv(S) being the set of provided functions, and Nv(S) being the set of component-wide nonfunctional properties. and Df ∪ Ff ∪ Dv(S) ∪ Fv(S) ≠ ∅, Pp ≠ ∅ ⇔ GP ≠ ∅ The sets Df, Ff, Dv(S), and Fv(S) refer to the externally visible and accessible features a generic component provides for its environment in terms of functions and data elements. While the special case of GP = Pp = ∅ is equivalent to a conventional software component, variability is introduced by Pp and the function Pv, where Dv(S) and Fv(S) obviously determine functional properties. As each function and each data element is expected to be associated with a description of its individual functional and nonfunctional properties, generic parameters may as well concern nonfunctional component properties. However, there might be cases where component-wide nonfunctional properties cannot sensibly be attributed to individual data elements or functions at the component’s interface. To this end, the sets Nf and Nv(S) are explicitly modeled. Another implication to be stressed is the fact that aside from the special case of GP = ∅, generic components cannot be used directly, but essentially require to be instantiated before building an executable system. Considering an example of a data container for integers which also provides a function for sorting its contents, two different sorting algorithms called quicksort and bubblesort are assumed to be available. Such a container may be realized as a generic component SampleContainer with a generic parameter maxsize (GP = {maxsize}) denoting the maximum number of integers the container is expected to hold (cf. Figure 1). Depending on the actual value of maxsize, the function sort would be instantiated with the quicksort algorithm (sortquicksort) — let’s say for maxsize > 10 — or with the bubblesort algorithm (sortbubblesort) for maxsize ≤ 10. Consequently, standard container functions like add and remove make up the elements of Ff and the sorting function belongs to Pp, where Fv(S) determines the appropriate instantiation of the sorting function for S ∈ { (i), i ∈ N }. Although both possible instantiations provide exactly the same functionality, their nonfunctional properties differ in terms of static and dynamic memory requirements as well as the computational complexity. These variable properties can clearly be attributed to the sorting function and thus are determined by Fv(S) rather than by Nv(S). In case the parameter maxsize would furthermore be used to decide about the internal data representation and hence the nonfunctional properties of the data access functions, add and remove would be elements of Fv(S) as well. The fact that a certain amount of memory space is needed to hold the container elements makes up a nonfunctional attribute of the component as a whole — a description of the memory complexity of the internal data structure would thus have to be listed in Nf or Nv(S) respectively.

SampleContainer add(int i) externally visible functions

symbol for generic parameter

remove(int i) hidden internals

sort()

Fig. 1. Graphical representation of a generic component, illustrated at a generic container for integer values.

The above definition of a generic component is meant to give an intuitive understanding of what constitutes a generic component rather than defining how individual elements are to be described or how a generic component has to be implemented. While the concrete representation of the elements of GP, Pf, and Pv(S) has thus been left undefined intentionally, in practice, a combination of different description techniques such as IDLs [17], [21], ADLs [13], [14], or Extended Design Spaces [5], [11] may be used to describe individual facets of generic components. The definition as well does not make any statements whatsoever about the actual implementation of a generic component. In fact, generic components may be of any size, may be implemented using arbitrary programming languages, and may be available in source code or pre-compiled object code respectively. For most practical examples, after all, it is reasonable to expect a generic component to be realized as a single C++ class or a small set of classes. Availability of the component’s source code is as well a reasonable assumption, since the code generation or the modification of reused code during the instantiation of a generic component is typically much easier to perform at the level of source code. However, this should not mislead to the impression, that the component’s source code must be visible to the programmer who initiates the instantiation. Providing generators to transparently instantiate generic components without disclosing the component’s source code is a very appealing option for software vendors who want to protect their investment in the implementation of their components. Even the manipulation of components at the level of object code is possible.

3. Generic parameters Generic parameters are the handles for customizing generic components. From a high-level point of view, generic parameters can be compared to conventional function parameters as they both allow to tailor the component behavior by simply assigning appropriate values. Conventional parameters differ from generic parameters in the way that all necessary component code to cope with different parameter settings is present at run time, allowing for dynamic changes of parameter values. Generic parameters, in turn, are used at the system’s build time to tailor the component code for a particular parameter setting — they disappear when the component is instantiated. Since generic parameters actually influence the component’s implementation, the effects of parameter settings can be significantly greater than in the case of conventional parameters. In general, generic parameters are kind of placeholders for specific properties that have been left variable during component implementation. In the individual case, a generic parameter can then take the shape of, for instance, a simple integer value denoting the size of a data structure as intimated in the example above, a placeholder for a com-

ponent interface or for a specific algorithm that is to be chosen from a fixed set of alternatives, or — in the most complex case — a placeholder for arbitrary code. As generic parameters are intended to allow for a tool-supported instantiation of generic components into tailor-made conventional components, it is important to take a look at how the customized target code can actually be derived during component instantiation. From the conceptual point of view, a generic component can be viewed as an incomplete implementation exhibiting gaps which relate to properties that have been left variable, bundled with additional information about how the code can be complemented for specific parameter settings. During component instantiation, three approaches are conceivable to fill these gaps (see Figure 2): to provide a set of pre-build variants from which a generic parameter may choose one, to provide tools which appropriately modify existing code or actually generate new code, or to have someone else — typically the application programmer or the operating systems expert — provide the appropriate implementation. According to these options, three major types of generic parameters can be identified: selection parameters, generative parameters, and code parameters. part of the implementation left variable pre-built alternatives

specification

generator

(

pre-built source material

)

user-defined code

Fig. 2. Alternatives for instantiating generic components.

Selection parameters are the simplest type of generic parameter. They are used to select discrete entities from a finite set of alternatives. Entities in this sense are pre-fabricated variants of parts of a component implementation, e.g. data structures, algorithms, or other code fragments. The parameter maxsize in the SampleContainer example mentioned in the previous section, for instance, is at first not a selection parameter since it can hold any integer value, but could be re-modeled as a selection parameter as it ultimately only selects between two different sorting algorithms. If a generic component encompasses variants that require completely different implementations, selectable entities can in a broader sense even relate to complete component implementations. Generative parameters do not directly choose pre-fabricated solutions but are inputs to tools that generate appropriate solutions. In the most common scenario, a pre-fabricated solution is nevertheless available as the reusable raw material which just has to be customized accordingly, e.g. by redefining a specific constant, changing the name of a function, or eliminating unused code fragments. Most parameters that may hold arbitrary integer values or character strings are good examples of generative

parameters. More sophisticated generators might also take complex specifications as inputs to actually produce new implementations. Where the first two types of parameters cannot supply enough flexibility, code parameters allow for user-defined code — within certain limits prescribed by the component’s implementor — to be inserted into pre-defined gaps of reusable component implementations. In such a way, code parameters pick up the idea of application-specific system extensions as offered by Spin [6] and ExoKernel [10], for instance. A simple example might be a user-defined data structure, encapsulated in an object that implements a pre-defined interface of access methods. In any case, the basic challenge is to define constraints for the code to be inserted and to allow for reasoning about properties of the inserted code. In their full extent, parameters of this type are obviously very powerful, but on the other hand are complex to handle, hard to support by tools, and can easily undermine system integrity. In principle, these types of generic parameters can be combined hierarchically (cf. Figure 3). For example, the choice of a particular variant in the context of a selection parameter might introduce new parameters, and the code generated in the context of a generative parameter may represent the interface to a new code parameter. A recursive introduction of new parameters while choosing parameter settings, after all, adds a considerable amount of complexity to the configuration process — and the development of generic components likewise — as it is impossible to catch an initial overview of all parameters that are to be set. At least at the implementation level, this kind of parameter nesting is thus typically avoided. Exceptions to this rule are selection parameters, where it appears to be a natural assumption that the variants to be inserted may introduce additional parameters — especially if these variants are of non-trivial size.

user-defined code

set of alternatives

generator

specification

Fig. 3. Examples for parameter settings that introduce new parameters.

4. Implementation techniques Looking for techniques to support the implementation of generic parameters, a wealth of possibilities addressing different facets like code organization and modification, customization of structure and behavior, extensibility of existing implementations, or code generation are available [3]. The first fundamental distinction to be made is that between techniques supporting the organization and modi-

fication of existing source code, and techniques supporting the extension of existing code by completely new implementations or even supporting the transformation of formal specifications to new source code. A second dimension of classification is added by the question of language dependence: while some techniques can be deployed independently from the programming language used to implement the respective components, others are integrated into a particular programming language (cf. Table 1). The following examples of language-specific techniques will be oriented towards C++ as it is one of the most popular modern languages and offers a set of powerful mechanisms for generic programming. Language-independent

Language-specific (C++)

Support for the organization and modification of existing code

• File management • Universal pre-processors

• C++ pre-processor • Templates

Support for the extension of existing and the generation of new code

• Code generators

• Templates • Inheritance and polymorphism

Table 1. Classification of techniques to support the implementation of generic parameters.

File management A sensible organization of a software system at build-time into a set of files that may be exchanged for different versions is an obvious and simple approach, so trivial in fact that it need not be listed here but for completeness. The file-based approach allows to choose among pre-implemented variants and may be applied to source code as well as to object code. The granularity of changes at the source code level is typically bound to the units of compilation and hence varies between a complete class and individual member functions. Configuration management systems like RCS [20] and CVS [7] can provide additional support concerning the efficient storage of file variants and the definition of consistent sets of different versions of files, thereby offering a rudimentary means to capture positive interdependencies between variants. Pre-processors A possibility to apply individual modifications to existing code with an arbitrary degree of granularity is the use of pre-processors. Pre-processors scan the source code for particular tokens — be it programming language constructs or specific pre-processor statements —, parse their semantic context, and perform pre-defined actions associated with the respective scenarios. In their full extent, pre-processors are problem-specific compilers similar to those for simple programming languages and can be built using scanner and parser generators like lex/yacc [12] and flex/bison [18], for instance. A much simpler example of source code pre-processing based on the standard UNIX stream editor sed [19] is given in Figure 4. The code excerpt shows the get function of a generic dispatcher which saves the processor context of the currently running thread and returns a handle to this thread. The dispatcher is intended to be used in multi-processor environments and therefore uses a parameter n to identify the respective processor. To allow for optimizing the source code for single-processor environments, references to the parameter n have been marked by comments of the form GPx_My (identifying Generic Parameter no. x, Mark no. y). An appropriate sed script can search for the respective lines of source code and apply whatever changes are necessary. In detail, line 1 of the script below

tells sed to look for Dispatcher.h and Dispatcher.cc files and open them for parsing. According to the sed command syntax, line 3 searches for lines with the mark GP2_M10, then takes the first occurrence of the string “size_t n“ and replaces it by an empty string. Line 5 of the script finally completely removes the line marked GP2_M12 from the source code. File Dispatcher.cc: 1 ... 2 TCBptr Dispatcher::get(size_t n) { // GP2_M10 3 TCBptr old; 4 old = running[n]; // GP2_M11 5 old->setLastCPU(n); // GP2_M12 6 ProcContext.All.Save(old->TCB.Register); 7 return old; 8 } 9 ... sed script: 1 2 3 4 5 6

s/\(Dispatcher\.[hc][c] -\)/\1 Single Proc -/ ... /GP2_M10/ s/size_t n// /GP2_M11/ s/\[n\]// s/\/\/GP2_M12// ...

Fig. 4. Example of source code pre-processing with sed scripts.

Pre-processors can effectively handle variability in both the structure of code and data, and the behavior of programs. Domain-specific and problem-specific pre-processors can apply virtually any kind of modifications to existing source code. Especially this versatility and the fine granularity at which modifications can be applied underpin the important role of pre-processors in the concert of viable techniques. On the downside, it has to be noted that the modifications typically have to be planned in advance thoroughly, since most pre-processors require the source code to be complemented by special mark-ups. The C++ pre-processor is the language-specific equivalent to the general pre-processing mechanism. Most commonly used are the #include statement for including other source files, the #define statement for declaring constants and defining simple macros, and the #ifdef, #else, and #endif constructs for conditionally including or excluding certain parts of the source code for or from compilation. C++ pre-processor directives are most useful to express so-called negative variability, i.e. exceptions to the rule. When a line of source code must be present most of the time but absent in a few cases that are known at compile time, the C++ pre-processor provides a viable solution. Of course, the C++ pre-processor can also be used for any kind of code manipulation as in the general pre-processing approach. However, even in small examples the extensive use of C++ pre-processor directives can quickly render the source code unreadable. One reason surely is the comparatively coarse-grained restriction to complete lines of code. Code generators From a certain point of view, pre-processors are simple generators: they take the original source code and a set of pre-processor commands as inputs and generate the appropriately modified source code. Pre-processors, however, mainly reuse code and neither change the representation between input and output nor create new structures. On the contrary, generators in the narrower sense use a set

of fine-grained rules to produce new solutions and new structures in a representation that is different to the generator’s inputs [8]. From the generic component’s point of view, the values of generic parameters are —or directly determine— formal specifications of appropriate solutions that are the input to the generator and then translated to component code automatically. As such, the use of code generators is the most powerful but typically also the most complex way to implement generic parameters. The use of generators is especially tempting wherever the modification of pre-built components does not appear to be sufficient to cope with heavily varying and unforeseen requirements. On the other hand, the use of generators for general system software functionality is questionable, although past decades have seen some remarkable results of so-called application generators [1], [15], [22]. Generators have to rely on domain-specific defaults for those aspects that are not explicitly specified by their input, and the lack of a standard architecture for system systems inhibits the generated output from being as efficient as manually optimized code. Notwithstanding these objections for the specific case of operating systems, generators can make sense for narrow sub-domains where they have shown to produce competitive results. The use of SDL code generators for communication subsystems is a clear example [9]. Templates C++ templates are a means to parameterize class definitions and function definitions, allowing them to be instantiated with user-defined arguments at compile time. In fact, templates can be regarded as an intelligent implementation of macros that supports the C++ scoping, naming, and typing rules. For each template argument the source code contains instantiations for, the compiler generates a concrete definition and compiles it as if it had been coded manually without using the template mechanism. Consequently, templates neither require core language mechanisms at run-time nor do they necessarily reduce code size. While from the technical point of view, templates represent a kind of code generation mechanism and hence support the modification of existing source code, templates can also be regarded as a way to code solutions that can easily be extended to work with new code. Templates are thus listed in both vertical classification categories in Table 1. Class templates define the commonalities of class variants that differ in individual types of data members, parameter types, or types of references. Common to all class variants is their structure, both in terms of code and data. Basically the same holds for function templates which define abstractions of algorithms with details such as type dependencies, constants, and defaults factored out into parameters of variation. The most popular field of application is the definition of generic container classes that can be instantiated for arbitrary data types, and the definition of functions performing general operations on data elements of arbitrary type. Template arguments usually are not used for structural changes. Inheritance Inheritance is a means to facilitate coding new solutions that are to a certain extent similar to already existing solutions. Most often, inheritance is considered as a mere code reuse mechanism, but more broadly, inheritance can express both subtyping and code commonality. The commonalities between a subclass and its superclass can thus concern behavior as well as structure, resulting in the distinction between inheritance of behavior and inheritance of structure. Inheritance facilitates the extension of existing code in a sense that instances of new classes that are derived from a well-known base class can be accessed as if they were instances of the base class

itself. In this way, existing pre-implemented components can seamlessly interoperate with newly introduced classes without having to know about these new implementations. Virtual functions in combination with polymorphism add a further level of support to this scenario: by using virtual functions for the base class definition, derived classes may overwrite these functions’ implementations and thus modify their behavior. Polymorphism then ensures that the correct implementation for a function call is determined at run time, independently from whether the calling client knows about the correct inheritance hierarchy or not. In this way, existing components may even profit from new functionality without having to be recompiled. This comfort, after all, comes at the price of some overhead in terms of both memory space and run time. Summary All the techniques presented above address certain aspects of the implementation of generic components. Table 2 summarizes our experiences by pointing out which scenarios can be addressed sensibly by the respective techniques. To this end, the first column lists the commonalities that are to be preserved between different variants of a generic component, and the second column identifies the differences that are to be handled by generic parameters. Note that for the characterization of scenarios, the term ’structure’ is used for both data structure and code structure of a component, and the term ’interface’ in this context relates to syntactical questions of component accessibility. The supported types of generic parameters are listed in column three, where S denotes selection parameters, G denotes generative parameters, and C stands for code parameters. It should also be noted that these techniques are typically used in combination to implement a number of parameters simultaneously. As a result, the settings of different generic parameters may affect overlapping parts of a generic component’s implementation. In particular, code transformations performed by pre-processors may depend on several parameters and on specific combinations of parameter settings, making it difficult to distinguish clearly between the aforementioned three types of parameters. For example, a parameter which conceptually just selects between existing alternatives — i.e., a selection parameter — might in fact be handled in combination with a generative parameter and thus directly influence the manipulation of code. Parts of this phenomenon also originate from the attempts to avoid implementing nested parameters as discussed at the end of the previous section (cf. Figure 3). Commonalities

Variabilities

Types of parameters

Implem. technique

General semantics, name

Any implementation aspects

S

File management

Gross structure

Fine details of algorithm or structure

Mainly S, also G

C++ pre-processor

Gross structure

Fine details of algorithm or structure

Mainly G, also S

Universal pre-processor

Structure + algorithm

Types

Mainly C, also S + G

C++ templates

Interface

Gross structure + algorithm

G

Code generators

Basic behavior

Additional structures

Mainly C, also S

Public inheritance

Internal structure

External behavior

Mainly C, also S

Private inheritance

Table 2. Applicability of implementation techniques for different scenarios.

5. Conclusions The presented concept of generic components aims at improving the reusability of pre-fabricated software components by explicitly planning for specific types of foreseeable adaptations and by providing mechanisms and tools to perform such adaptations automatically. Generic components are implemented to be easily modified within prescribed bounds and to expose generic parameters which provide the external handles for choosing and fine-tunig component properties. Tools like configuration management systems, pre-processors, or full-fledged code generators then allow to flexibly customize components for specific application environments with minimized manual intervention. The individual techniques used to implement and customize generic components are all well-known, but their explicit combination within the concept of generic components presents a new and promising way to pursue component reuse. With the ability to flexibly adapt the implementation of reused components, conventional approaches to improve component reusability which typically interfere with the overall system performance — strict uncoupling of components, indirect communication mechanisms, and large inheritance hierarchies being prominent examples — can be avoided. The concept of generic components approximates the advantages of manual customization and optimization, while on the other hand sidestepping the obstacles of time-consuming and error-prone coding by reusing pre-fabricated and tested implementations. We have applied the presented techniques and the notion of generic components to build a number of reusable building blocks for embedded operating systems. As software reuse is especially beneficial within one type of architecture [4], these building blocks are designed for specific reference architectures, respectively. For a small operating system kernel for embedded systems, for instance, a total of 12 generic components provides the flexibility to generate more than a hundred different kernel variants covering a wide spectrum of possible requirements. Thanks to the fine-grained modifications that are applied to the generic components during instantiation, the resulting system properties — especially concerning critical nonfunctional aspects such as the timing behavior and the memory consumption — are comparable to conventional manual implementations. On the downside, however, the implementation effort for generic components is significantly higher than for conventional components. According to our experience, making component implementations generic accounts for additional 50 to 100% of the development effort as compared to conventional implementations. And for most cases, this is just half the way: in order to further support the selection and configuration of components by tools as presented in [2], comprehensive component descriptions are required. Such descriptions roughly make up another 100% of the initial effort for conventional implementations, ultimately summing up to tripled development costs for generic components — an investment, after all, that is by far compensated by the increased probability of reuse. To further reduce this overhead, we are currently extending our research to the questions of how to specifically support the development of generic components. The possibilities range from ergonomic editors smartly handling the variant parts of the source code, to automated detection of component interdependencies and automatic deduction of properties of composed artefacts.

6. References [1]

R. W. Bailey, E. J. Paddock: The Trick Simulation Environment, LinCom Corp., Technical Report TM098-4750-147, 1998

[2]

L. Baum, M. Becker, L. Geyer, G. Molter: Mapping Requirements to Reusable Components Using Design Spaces, Proc. of the 2000 IEEE International Conference on Requirements Engineering (ICRE’00), Schaumburg, USA, June 2000

[3]

L. Baum: Towards Generating Customized Run-time Platforms from Generic Components, Proc. of the 11th International Conference on Advanced Information Systems Engineering (CAISE’99), 6th DC, Heidelberg, Germany, June 1999

[4]

L. Baum, M. Becker, L. Geyer, G. Molter, P. Sturm: Driving the Composition of Run-time Platforms by Architectural Knowledge, Proc. of the 8th ACM SIGOPS European Workshop, Sintra, Portugal, September 1998

[5]

L. Baum, L. Geyer, G. Molter, S. Rothkugel, P. Sturm: Architecture-centric Software Development Based on Extended Design Spaces, Proc. of the 2nd ARES Workshop, Las Palmas de Gran Canaria, February 1998

[6]

B. N. Bershad, et. al.: Extensibility, Safety and Performance in the Spin Operating System, Proc. of the 15th Symp. on Operating Systems Principles (SOSP), ACM Press, 1995

[7]

P. Cederqvist: Version Management with CVS, Signum Support AB, Technical Report, 1993

[8]

C. T. Cleaveland: Building Application Generators, IEEE Software, July 1998

[9]

R. O’Donnell, B. Waldt, J. Bergstrand: Automatic Code for Embedded Systems Based on Formal Methods, Telelogic Technical Paper, Telelogic Inc., 1998

[10]

D. R. Engler, M. F. Kaashoek, W. O’Toole: ExoKernel: An Operating System Architecture for Application-level Resource Management, Proc. of the 15th Symp. on Operating Systems Principles (SOSP), ACM Press, 1995

[11]

T. G. Lane: Studying Software Architecture Through Design Spaces and Rules, Technical Report CMU/ SEI 90-TR-18, Carnegie Mellon Univ., 1990

[12]

J. R. Levine, T. Mason, D. Brown: Lex & Yacc, 2nd Edition, O’Reilly & Associates, 1992

[13]

D. Luckham, et. al.: Specification and Analysis of System Software Using Rapide, IEEE Transactions on Software Engineering, Vol. 21, No. 4, 1995

[14]

J. Magee, N. Dulay, S. Eisenbach, J. Kramer: Specifying Distributed Software Architectures, Proc. of the 5th European Software Engineering Conference (ESEC’95), 1995

[15]

B. A. Myers: User-Interface Tools: Introduction and Survey, Software, Special Issue on User Interfaces, January 1989

[16]

J. Nehmer, P. Sturm, M. Baentsch, L. Baum, G. Molter, S. Rothkugel: Customization of System Software for Large-scale Embedded Applications, Computer Communications, Vol. 20, Elsevier, June 1997

[17]

Object Management Group: OMG IDL Syntax and Semantics, OMG TC Document 97-05-03, May 1997

[18]

V. Paxson: GNU Flex Manual, Version 2.5.3, Free Software Foundation, 1996

[19]

W. R. Stevens: Advanced Programming in the UNIX Environment, Addison-Wesley, 1992

[20]

W. F. Tichy: RCS — A System for Version Control, Software Practice and Experience, July 1985

[21]

B. R. Whittle, M. Ratcliffe: Software Component Interface Description for Reuse, IEE BCS Software Engineering Journal, Vol. 8, No. 6, 1993

[22]

T. Zhou, L. Liu, C. Pu: TAM: A System for Dynamic Transactional Activity Management, Proc. of the 1999 SIGMOD Conference, 1999

Suggest Documents