text:
The ATL Language | ||
Overview of the Atlas Transformation Language | The ATL Tools |
This section is dedicated to the description of the ATL language. As introduced in Overview of the Atlas Transformation Language, the language enables to define three kinds of ATL units: the ATL transformation modules, the ATL queries and the ATL libraries. According to their type, these different kinds of units may be composed of a combination of ATL helpers, attributes, matched and called rules. This section aims to detail the syntax of these different ATL elements. For this purpose, the ATL language is based on OMG OCL (Object Constraint Language) norm for both its data types and its declarative expressions. There exist a few differences between the OCL definition and the current ATL implementation. They will be specified in this section by specific remarks.
The ATL data type scheme is very close, but not similar, to the one defined by OCL. The following schema provides an overview of the data type's structure considered in ATL. The different data types presented in this schema represent the possible instances of the OclType class.
The root element of the OclType instances structure is the abstract OclAny type, from which all other considered types directly or indirectly inherit. ATL considers six main kinds of data types: the primitive data types, the collection data types, the tuple type, the map type, the enumeration type and the model element type. Note that the map data type is implemented by ATL as an additional facility, but does not appear in the OCL specification.
The class OclType can be considered as the definition of a type in the scope of the ATL language. The different elements appearing in the schema represent the type instances that are defined by OCL (except the map and the ATL module data types), and implemented within the ATL engine.
The OCL primitive data types correspond to the basic data types of the language (the string, boolean and numerical types). The set of collection types introduced by OCL provides ATL developers with different semantics for the handling of collections of elements. Additional data types include the enumerations, a tuple and a mapping data type and the model element data type. This last corresponds to the type of the entities that may be declared within the models handled by the ATL engine. Finally, the ATL module data type, which is specific to the ATL language, is associated with the running ATL units (either modules or queries).
Before going further in the description of these data types, it must be noted that each OCL expression, including the operations associated with each kind of data type (that are presented along with their respective data type), is defined in the context of an instance of a specific type. In ATL as in OCL, the reserved keyword self is used to refer to this contextual instance.
The class OclType corresponds to the definition of the type instances specified by OCL. It is associated with a specific OCL operation: allInstances(). This operation, which accepts no parameter, returns a set containing all the currently existing instances of the type self.
The ATL implementation provides an additional operation that enables to get all the instances of a given type that belong to a given metamodel. Thus, the allInstancesFrom(model : String) operation returns a set containing the instances of type self that are defined within the model namely identified by model.
This section describes a set of operations that are common to all existing data types. The syntax used to call an operation from a variable in ATL follows the classical dot notation:
ATL currently provides support for the following OCL-defined operations:
The operations oclIsNew() and oclAsType() defined by OCL are currently not supported by the ATL engine. ATL however implements a number of additional operations:
The ATL Module data type is specific to the ATL language. This internal data type aims to represent the ATL unit (either a module or a query) that is currently run by the ATL engine. There exists a single instance of this data type, and developers can refer to it (in their ATL code) using the variable thisModule. The thisModule variable makes it possible to access the helpers and the attributes that have been declared in the context of the ATL module.
The ATL Module data type also provides the resolveTemp operation. This specific operation makes it possible to point, from an ATL rule, to any of the target model elements (including non-default ones) that will be generated from a given source model element by an ATL matched rule.
The operation resolveTemp has the following declaration:
resolveTemp(var, target_pattern_name)
The parameter var corresponds to an ATL variable that contains the source model element from which the searched target model element is produced. The parameter target_pattern_name is a string value that encodes the name of the target pattern element that maps the provided source model element (contained by var) into the searched target model element.
Note that, as it is defined in the scope of the ATL module, this operation must be called from the variable thisModule. The resolveTemp operation must not be called before the completion of the matching phase. This means that the operation can be called from:
ATL developers may note that the operation call does not specify the matched rule from which the generated target model element comes from. However, as explained in the Rules section, a source model element should not be matched by more than one matched rule. As a consequence, the concerned matched rule can be derived from the specified source model element.
Here is an example of resolveTemp use:
rule AtoAnnotedB { from a : MMA!A to ann : MMB!Annotation (), b : MMB!B ( annotation <- ann ) }
In this first rule, an object of type A'' is transformed into two objects: a ''B (the b'' variable) instance annoted with an ''Annotation instance (the ann variable).
In the second rule, we want to refer to the newly created B'' elements from their ''A source. We assume that
rule ARefToBRef { from aRef : MMA!ARef to bRef : MMB!BRef ( ref <- thisModule.resolveTemp(aRef.ref, 'b') ) }
Notice that the variable name is passed as a String to the resolveTemp method.
OCL defines four basic primitive data types:
According to the considered data type (string, numerical values and boolean values), OCL defines a number of specific operations. They are detailed in the following sections along with some additional functions provided by the ATL engine.
The set of OCL operations defined for the boolean data type is the following:
returns true otherwise.
In this case:
if (exp1 and exp2) then ... else ... endif
exp2 will always be evaluated, regardless of the result of the first expression. ATL evaluates it like this:
if (exp1.and(exp2)) then ... else ... endif
So remember that in this case:
if (self.attributes->size() > 0 and self.attributes->first().attr)
Even though the first member is false, there may be a call of the "attr" property on an undefined element, which will cause an error.
OCL defines the following operations for the string data type:
Besides the OCL-defined operations, ATL implements a number of additional operations for the string data type:
As a last point, ATL currently defines two additional functions that make it possible to write strings to outputs. These functions are useful for redirecting the result of ATL queries, but they may also be used for debugging purposes:
Note that these two functions are provided as temporary solutions as the ATL toolkit does still not provide any integrated solution for the redirection of the result of ATL queries. They are likely to be removed from future releases of the ATL tool suite.
The following OCL operations are defined for both OCL numerical data types (integer and real):
Note that the - unary operator defined by OCL (that returns the negative value of self) is not implemented in current version of ATL. As a consequence, a -x negative numerical value has to be declared as the result of a call to the - binary operator: 0-x.
OCL also defines some operations that are specific to the integer and the real data types:
Besides the OCL-defined operations, ATL provides a set of additional functions. The toString() operation, available for both the integer and real data types returns a string representing the integer/real value of self. Both integer and real data types can be converted to a long data type by calling the longValue() operation. There also exist a set of ATL operations specific to the real data type:
In the following, some usage examples of OCL operations on primitive data types are illustrated:
or false
'test'.substring(2, 3)
Sequence{'t', 'e', 's', 't'}
'test'.replaceAll('t', 'o')
'aaabaftaap'.regexReplaceAll('a*', 'A')
23 div 2 or 23."div"(2)
OCL defines a number of collection data types that provide developers with different ways to handle collections of elements. The provided collection types are Set, OrderedSet, Bag and Sequence. Collection is the common abstract superclass of these different types of collections.
The existing collection classes have the following characteristics:
A collection can be seen as a template data type. This means that the declaration of a collection data type has to include the type of the elements that will be contained by the type instances. Whatever the type of the contained elements, the declaration of a collection data type has to conform to the following scheme:
The supported collection data types are Set, OrderedSet, Sequence and Bag. The element data type can be any supported oclType, including another collection type.
The definition of a collection variable is achieved as follows:
Please note that the brackets used in the type definition must here be replaced by curly brackets. Examples of collection type definitions and instantiations can be found here.
ATL provides a large number of operations in the context of the different supported collection types. Note that there exists a specific syntax for invoking an operation onto a collection type:
The different kinds of existing OCL collections share a number of common operations:
Note that the product() operation defined by OCL is unsupported by the current ATL implementation. However, ATL defines three additional operations in the context of a collection (OCL defines similar operations in the context of each collection type):
Note that, in the current ATL version, the casting operation asOrderedSet() defined by OCL is implemented for none of the collection types.
The sequence type supports all the collection operations. OCL defines a number of additional operations that are specific to sequences:
Set supports all collection operations and some specific ones:
Note that the flatten() operation defined by OCL is not implemented in the current version of ATL.
The sequence type supports all the collection operations. OCL defines a number of additional operations that are specific to ordered sets:
Besides this set of operations specified by OCL, ATL implements the following additional functions:
The OCL specification defines a number of iterative operations, also called iterative expressions, on the collection types. The main difference between a classical operation and an iterative expression on a collection is that the iterator accepts an expression as parameter, whereas operations only deal with data. The definition of an iterative expression includes:
The syntax used to call an iterative expression is the following:
source->operation_name(iterators | body)
ATL currently provides support for the following set of defined iterative expressions:
Note that the collect() operation provided by ATL implements the semantics of the collectNested() operation defined in the OCL specification. Getting the semantics of the collect() operation as defined by OCL can simply be achieved with ATL by calling the flatten() operation onto the result provided by the ATL collect() iterative expression, as follows:
source->collect(iterator | body)->flatten()
The ATL language introduces another constraint compared to the OCL specification. The specification indeed allows declaring multiple iterators in the scope of the exists() and the forAll() iterative expressions.
Besides these predefined iterative operations, OCL specifies a more generic collection iterator, named iterate(). This iterative expression has an iterator, an accumulator and a body. The accumulator corresponds to an initialized variable declaration. The body of an iterate() expression is an expression that should make use of both the declared iterator and accumulator. The value returned by an iterate() expression corresponds to the value of the accumulator variable once the last iteration has been performed. An iterative expression is defined with the following syntax:
source->iterate(iterator; variable_declaration = init_exp | body )
In the following, some operations on collections are illustrated:
Sequence{1, 2, 3}
Set{Sequence{'monday'}, Sequence{'march', 'april', 'may'}}
Bag{1, 2, 3}->isEmpty()
Sequence{1, 2, 3}->size()
Set{3, 3, 3}->size()
evaluates to 1 since the set data type eliminates duplicatesOrderedSet{1, 2, 3}->first()
Sequence{1, 2, 3}->union(Sequence{7, 3, 5})
Sequence{1, 2, 3, 7, 3, 5}
Set{1, 2, 3}->union(Set{7, 3, 5})
Set{1, 2, 3, 7}
Sequence{Sequence{1, 2}, Sequence{3, 5, 2}, Sequence{1}}->flatten()
Sequence{1, 2, 3, 5, 2, 1}
Sequence{Sequence{1, 2}, Sequence{3, 5, 2}, Sequence{1}}->subSequence(2, 3)
Sequence{ Sequence{3, 5, 2}, Sequence{1}}
Sequence{5, 15, 20}->insertAt(2, 10)
Sequence{5, 10, 15, 20}
Set{1, 2, 3}->intersection(Set{7, 3, 5})
Set{1, 2, 3}->symetricDifference(Set{7, 3, 5})
Set{1, 2, 7, 5}
Sequence{1, 2, 3, 4, 5, 6}->select(i | i <= 3)
Set{1, 2, 3}
MOF!Class.allInstances()->collect(e | e.name)
Sequence{12, 13, 12}->forAll(i | i > 2)
Sequence{12, 13, 12}->one(i | i > 2)
Sequence{12, 13, 12}->exists(i | i > 2)
Sequence{8, -1, 2, 2, -3}->iterate(e; res : Integer = 0 |
if e > 0
then res + e
else res
Sequence{8, -1, 2, 2, -3}->select(e | e > 0)->sum()
An enumeration is an OclType. It has a name just as any other data type. However, compared to the data presented up to now, the enumerations have to be defined within the source and target metamodels of a transformation.
With the OCL specification, referring to an enumeration literal (e.g. an enumeration defined value) is achieved by specifying the enumeration type (e.g. the name of the enumeration), followed by two double-points and the enumeration value. Consider, as an example, an enumeration named Gender that defines two possible values, male and female. Accessing to the female value of this enumeration type in OCL is achieved as follows: Gender::female.
The current ATL implementation differs from the OCL specification. Access to enumeration values is simply achieved by prefixing the enumeration by a sharp character (the enumeration type is no more required): #female. The enumeration data type is associated with no specific operation.
The tuple data type enables to compose several values into a single variable. A tuple consists into a number of named parts that may each have a distinct type. Note that a tuple type is not named. As a consequence, a declared tuple type has to be identified by its full declaration each time it is required.
Each part of a tuple type is associated with an OclType and is identified by a unique name. The declaration of a tuple data type must conform to the following syntax:
TupleType(var_name1 : var_type1, ..., var_nameN : var_typeN)
Note that the order in which the different parts are declared is not significant. As an example, it is possible to consider the declaration of a tuple type associating an Author model element from the MMAuthor metamodel with a couple of strings encoding the title of a book and the name of the editor of this book:
TupleType(a : MMAuthor!Author, title : String, editor : String)
The instantiation of a declared tuple variable has to respect the following syntax:
Tuple{var_name1 [: var_type1]? = init_exp1, ..., var_namen [: var_typen]? = init_expn}
When declaring a tuple instance, the types of the tuple parts can be omitted. As a consequence, the two following tuple instantiations corresponding to the tuple type defined above are equivalent:
Tuple{editor : String = 'ATL Eds.', title : String = 'ATL Manual', a : MMAuthor!Author = anAuthor} Tuple{title = 'ATL Manual', a = anAuthor, editor = 'ATL Eds.'}
As for the declaration of a tuple type, the instantiation of the different parts of a tuple variable may be performed in any order. The different parts of a tuple structure can be accessed using the same dot notation that is used for the invocation of operations or the access to model element attributes. Thus, the expression
Tuple{title = 'ATL Manual', a = anAuthor, editor = 'ATL Eds.'}.title
provides access to the title part of the tuple.
Besides the set of common operations, the current ATL implementation defines an additional casting operation in the context of the tuple dada type: the asMap() operation returns a map variable in which the name of the tuple parts are associated with their respective values.
Provided as an additional facility in the ATL implementation, the map data type does not belong to the OCL specification. This data type enables to manage a structure in which each value is associated with a unique key that enables to access it (see the Java Map interface for further details).
The declaration of a map type has to conform to the following syntax:
Map(key_type, value_type)
Note that, as a tuple type, a map type is not named, which again implies to specify the full type declaration when required. The following map declaration associates some Author model element values with integer keys:
Map(Integer, MMAuthor!Author)
Instantiating a map variable is achieved according to the following syntax:
Map{(key1, value1), ..., (keyn, valuen)}
As an example, the following expression instantiates a two entries map corresponding to the map type declared above:
Map{(0, anAuthor1), (1, anAuthor2)}
Besides the set of common operations, the ATL implementation provides the following operations on map data:
The last kind of data type introduced by the OCL specification corresponds to the model elements. These last are defined within the source and target metamodels of an ATL transformation. Metamodels usually define a number of different model elements (also called classes).
In ATL, model element variables are referred to by means of the notation metamodel!class in which metamodel identifies (through its name) one of the metamodels handled by the transformation, and class points to a given model element (e.g. class) of this metamodel. Note that, as opposed to the OCL notation, which does not specify the metamodel a given class comes from, the ATL notation makes it possible to handle several metamodel at once.
A model element has a number of features that can be either attributes or references. Both are accessed through the dot notation self.feature. Thus, in the context of the MMAuthor metamodel, the expression anAuthor.name enables to access to the attribute name of the instance anAuthor of the Author class.
In ATL, the model elements can only be generated by means of the ATL rules (either matched or called rules). Initializing a newly generated model element consists in initializing its different features. Such assignments are operated by means of the bindings of the rules target pattern elements.
Please note that the operation oclIsUndefined(), defined for the OclAny data type, tests whether the value of an expression is undefined. This operation is useful when applied on an attribute with a multiplicity zero to one (which is void or not). However, attributes with the multiplicity n are usually represented as collections that may be empty and not void.
It is also possible to include the full path using the following scheme:
Actually the ATL Parser doesn't deal well with "::" so we need to surround the path using ".
For instance, using the metamodel excerpt given above, we could write:
In some cases, full name reference is required to avoid ambiguity due to name collision. Let us consider the following metamodel:
package P1 {
class C1 {}
package P2 { class C1 {} } }
package P3 { class C1 {} }
Using MM!C1 is incorrect because it cannot reliably me mapped to a specific class. If you try to do this, a warning will be reported in the ATL console. In this case, it is mandatory to write:
Here is a sample of OCL expressions using features of model elements. They are defined in the context of the MOF metamodel:
MOF!Class.allInstances()->collect(e | e.name)
MOF!DataType.allInstances() ->select(e | e.oclIsTypeOf(MOF!PrimitiveType)) ->collect(e| e.name)
MOF!PrimitiveType.allInstances()->collect(e| e.name)
MOF!Class.allInstances() ->select(e | e.supertypes->size() > 1) ->collect(e | e.name)
In ATL, as in the OCL standard, comments start with two consecutive hyphens "--" and end at the end of the line. The ATL editor in Eclipse colours comments with dark green, if the standard configuration is used:
-- this is an example of a comment
Comments can be used to specify individual documentation for ATL helpers and rules, which appears in the content assist. To achieve this, simple use three consecutive hyphens "---" instead of two, just before the documented element.
--- Evaluates whether the context has the given stereotype or not. helper context UML!Element def: hasStereotype(stereotype : String) : Boolean = self.getAppliedStereotypes() -> collect(st | st.name) -> includes(stereotype);
Besides the declarative expressions that correspond to the instances of the supported data types, as well as the invocation of operations on these data types, OCL defines additional declarative expressions that aim to enable developers to structure OCL code. This section is dedicated to the description of these declarative expressions. There exist two kinds of advanced declarative expressions: the "if" and the "let" expressions. The "if" expression provides an alternative expression facility. The "let" expression, as for it, enables to define and initialize new OCL variables.
An OCL "if" expression is expressed with an if-then-else-endif structure. As an expression, an "if" expression should be evaluated (e.g. must have a value) in any cases. This means that the "else" clause of an "if" expression can not be omitted. All "if" expressions must conform to the following syntax:
if condition then exp1 else exp2 endif
The condition of the "if" expression is a boolean expression. According to the evaluation of this boolean expression, the "if" expression will return the value corresponding to either exp1 (in case condition is evaluated to true) or exp2 (in case condition is evaluated to false). This is illustrated by the following simple "if" expression:
if 3 > 2 then 'three is greater than two' else 'this case should never occur' endif
Note that the different parts of an "if" expression can, in turn, include another composed OCL expression, including operation invocations, "let" expressions or nested "if" expressions. As an example, it is possible to consider the following example:
if mySequence->notEmpty() then if mySequence->includes(myElement) then 'the element is at position ' + mySequence->indexOf(myElement).toString() else 'the sequence does not contain the element' endif else 'the sequence is empty' endif
The OCL "let" expression enables the definition of variables. A "let" expression has to conform to the following syntax:
let var_name : var_type = var_init_exp in exp
The identifier var_name corresponds to the name of the declared variable. var_type identifies the type of the declared variable. A variable declared by means of a "let" expression must be initialized with the var_init_exp. The initialization expression can be of any available OCL expression type, including nested "let" expressions. Finally, the in keyword introduces the expression in which the newly declared variable can be used. Again, this expression can be of any existing OCL expression type. This is illustrated by the following simple example:
let a : Integer = 1 in a + 1
Several "let" expressions can be enchained in order to declare several variables, as in the following example:
let x : Real = if aNumber > 0 then aNumber.sqrt() else aNumber.square() endif in let y : Real = 2 in x/y
An OCL variable is visible from is declaration to the end of the OCL expression it belongs to. Note that, although it is not advised, OCL allows developers to declare several variables of the same name within a single expression. In such a case, the lastly declared variable will hide the other variables having the same name.
The "let" expressions also prove to be very useful at the debugging stage. Indeed, the ATL development tools integrate debugging facilities that enable, among other things, to consult the value of the declared variables during the execution of an ATL program. In many cases, it proves to be useful to also be able to consult the value returned by a complex OCL expression. This could be achieved with few modification of the OCL code by declaring an OCL variable initialized with the complex expression to be checked. By this means, the value computed by the expression will be stored in an OCL variable, and thus be available for visualization during the debugging of the ATL program.
In order to illustrate this point, consider the following expression:
It is here assumed that the collection aSequence is a sequence of Real elements. In case this sequence is empty, the invocation of the operation first() will return the value OclUndefined. Invoked onto OclUndefined, the operation square() will raise an error at runtime. In such a case, it may be interesting to be able to check, at debug stage, whether the first element exists or is undefined by storing its value in a dedicated variable. This is the purpose of the following expression:
let firstElt : Real = aSequence->first() in firstElt.square()
Besides the "if" and "let" structural expressions, the OCL language enables to define different kinds of expressions whose syntax has been introduced in the Data Types section. These expressions include:
A number of errors, while designing OCL expressions in ATL, come from the evaluation mode of these OCL expressions. Indeed, in many languages, such as C++ and Java, there exists an optimiser that stops the evaluation of logical expressions when finding either a true value followed by the "or" logical operator or a false value followed by the "and" logical operator. No matter the rest of the expression may result into an error or an exception, the expression will be successfully evaluated.
As opposed to these common programming languages, the semantics of composed expressions, as defined by OCL, are such that each expression has to be fully evaluated. As a consequence, some expressions that usually appear to be correct will raise errors in ATL, as illustrated by the following example:
not person.oclIsUndefined() and person.name = 'Isabel'
This expression will therefore raise an error for an undefined person model element when evaluating the expression person.name. An error-free way to express an equivalent logical expression is:
if person.oclIsUndefined() then false else person.name = 'Isabel' endif
The same remark can be applied similarly to the logical expressions that use the logical "or" operator, such as:
person.oclIsUndefined() or person.name = 'Isabel'
The correct way to express this logical expression is:
if person.oclIsUndefined() then true else person.name = 'Isabel' endif
Note that the logical expressions that are likely to raise this kind of errors may be embedded in more complex OCL expressions:
collection->select(person | not person.oclIsUndefined() and person.name = 'Isabel')
Using the same rewriting rule, this expression can be transformed into the correct following expression:
collection->select(person | if person.oclIsUndefined() then false else person.name = 'Isabel' endif )
There may exist several ways to rewrite an incorrect expression. Thus, the following expression will compute the same result:
collection ->select(person | not person.oclIsUndefined()) ->select(person | person.name = 'Isabel')
Note that the first solution should here be preferred to this one for efficient reasons: the first solution iterates the collection only once.
ATL enables developers to define methods within the different kinds of ATL units. In the ATL context, these methods are called helpers. They make it possible to define factorized ATL code that can then be called from different points of an ATL program. There exist two different, although very similar from their syntax, kinds of helpers: the functional and the attribute helpers. Both kinds of helpers must be defined in the context of a given data type. However, compared to an attribute helper, which is commonly referred to as an attribute, a functional helper, referred to as a helper, can accept parameters. This difference implies some differences in the execution semantics of both helper kinds.
ATL helpers can be viewed as the ATL equivalent to methods. They make it possible to define factorized ATL code that can be called from different points of an ATL transformation. An ATL helper is defined according to the following scheme:
helper [context context_type]? def : helper_name(parameters) : return_type = exp;
Each helper is characterized by its context (context_type), its name (helper_name), its set of parameters (parameters) and its return type (return_type). The context of a helper is introduced by the keyword context. It defines the kind of elements the helper applies to, that is, the type of the elements from which it will be possible to invoke it. Note that the context may be omitted in a helper definition. In such a case, the helper is associated with the global context of the ATL module. This means that, in the scope of such a helper, the variable self refers to the run module/query itself.
The name of a helper is introduced by the keyword def. As its context, it is part of the signature of the helper (along with the parameters and the return_type). A helper accepts a set of parameters that is specified between brackets after the helper's name. A parameter definition includes both the parameter name and the parameter type, as specified by the following scheme:
parameter_name : parameter_type
Several parameters can be declared by separating them with a comma (","). The name of the parameter ( parameter_name) is a variable identifier within the helper. This means that, within a given helper definition, each parameter name must be unique. Note that the specified context type as well as the parameters' type and the return type may be of any of the data types supported by ATL. The body of a helper is specified as an OCL expression. This expression can be of any of the supported expression types. As an example, it is possible to consider the following helper:
helper def : averageLowerThan(s : Sequence(Integer), value : Real) : Boolean = let avg : Real = s->sum()/s->size() in avg < value;
This helper, named averageLowerThan, is defined in the context of the ATL module (since no context is explicitly specified). It aims to compute a boolean value stating whether the average of the values contained by an integer sequence (the s'' parameter) is strictly lower than a given real value (the ''value parameter). The body of the helper consists in a "let" expression which defines and initializes the avg variable. This variable is then compared to the reference value.
Note that several helpers may have the same name in a single transformation. However, helpers with a same name must have distinct signatures to be distinguishable by the ATL engine (see Limitations).
The super
keyword lets you call helpers with the same name defined on a super type of the current type.
Suppose you have the following metamodel:
class A {} class B extends A {}
Then you can write:
helper context A def: test() : Integer = 1; helper context B def: test() : Integer = super.test() + 1;
Besides helpers, the ATL language makes it possible to define attributes. Compared to a helper, an attribute can be viewed as a constant that is specified within a specific context. The major difference between a helper and an attribute definition is that the attribute accepts no parameter.
The syntax used to define an ATL attribute is very close to the definition of functional helpers. The only difference is that the attribute syntax does not enable to define any parameter:
helper [context context_type]? def : attribute_name : return_type = exp;
As for a helper, the context definition can be omitted in the declaration of an attribute. In this case, the attribute will be associated with the ATL module context. The following attribute, which is related to the MMPerson metamodel, can be considered as an example:
helper def : getYoungest : MMPerson!Person = let allPersons : Sequence(MMPerson!Person) = MMPerson!Person.allInstances()->asSequence() in allPersons->iterate(p; y : MMPerson!Person = allPersons->first() | if p.age < y.age then p else y endif );
This attribute, named getYoungest, is defined within the ATL module context. It applies to a source metamodel MMPerson that contains Person model elements. It aims to compute the youngest person of the source model (the return type is therefore MMPerson!Person). The attribute body consists in a "let" expression that defines the allPersons variable. This variable is a sequence of MMPerson!Person model elements that contains all the persons defined within the source model (note that the computed set has to be cast into a sequence). The computed sequence is then iterated by means of an iterate expression in which the iteration variable p'' represents the currently iterated person. The iterate expression results into a MMPerson!Person model element which will correspond to the youngest of the iterated persons. This result is contained by the variable ''y which is initialized to the first person of the allPersons sequence (in order to get this first person, it is required to define a sequence rather than a set). The body of this iterate expression consists in an "if" expression that simply compares the ages of the current youngest person to the one of the currently iterated person. According to the result of this comparison, the "if" expression will either return the previous youngest person or the iterated one.
Declaring a parameter-less helper and an attribute may appear to be equivalent. However, there exists a major difference between the helpers and the attributes execution semantics. The code of a helper is executed each time this helper is invoked. As opposed to a helper, an attribute accepts no parameter. This means that, for a given execution context (an input model element or the ATL module), an attribute will always return the same value. The ATL engine therefore computes the return value of an attribute only once, either when this attribute is invoked for the first time, or at the transformation/query initialization stage for those attributes that are declared in the context of the ATL module.
Current implementation suffers from three limitations in the domain of helpers/attributes. The first one deals with the definition of the signature of the helpers. Helpers are indeed identified through their signature which includes the helper name, its context and its parameters. However, current implementation only considers the subset composed of the helper name and the helper context of this signature: the helpers' parameters do not make it possible to discriminate helpers that have a same name and same context. This implies that all the helpers defined within a given context in an ATL program must have a distinct name. This restriction also concerns the helpers that are defined within a library which is imported in either a query or a module.
The second limitation concerns the definition of helpers in the context of a collection type. Such definitions are actually unsupported by the ATL engine. A simple solution to get round this problem is to move the collection element from context to parameters and to declare the helper in the context of the ATL module. Consider the definition of a helper that aims to select among a set of Person model elements those who are younger than a given age. This helper should be defined as:
helper context Set(MMPerson!Person) def : getYoungPersons(age : Integer) : Set(MMPerson!Person) = self->select(p | p.age < age);
Taking into account the current ATL limitation, this helper can be defined as follows:
helper def : getYoungPersons(s : Set(MMPerson!Person), age : Integer) : Set(MMPerson!Person) = s->select(p | p.age < age);
Note that this change has a very limited impact onto the body of the helper. The only difference is the self variable used in the previous version of the helper that has to be replaced by the name of the parameter that represents the collection ( s). Finally, last limitation concerning helpers is related to the library unit. Current implementation does not support the definition of attributes within an ATL library. The developer should therefore substitute a parameter-less helper to each of the attributes of the developed libraries. As an example, in the scope of a library, the following attribute:
helper context String def : getFirstChar : String = self.substring(1, 1);
must be replaced by its corresponding helper:
helper context String def : getFirstChar() : String = self.substring(1, 1);
In the scope of the ATL language, the generation of target model elements is achieved through the specification of transformation rules. ATL defines two different kinds of transformation rules: the matched and the called rules. A matched rule enables to match some of the model elements of a source model, and to generate from them a number of distinct target model elements.
As opposed to matched rules, a called rule has to be invoked from an ATL imperative block in order to be executed. ATL imperative code can be defined within either the action block of matched rules, or the body of the called rules
ATL enables developers to specify imperative code within dedicated blocks, either in matched or called rules. An imperative block is composed of sequence of imperative statements. As in the Java C or C++ languages, each statement must be ended with a semicolon character (";").
The current ATL implementation provides three kinds of statements: the assignment statements, the "if" statements and the "for" statements. Note that, as opposed to the OCL expressions, these statements do not return any value. As a consequence, they can not be used in the scope of some ATL declarative code. The three different imperative statements are detailed in the following subsections.
The ATL assignment statement enables to assign values to either attributes that are defined in the context of the ATL module, or to target model element features. The syntax of the assignment statement conforms to the following scheme:
target <- exp;
As specified, the target of the assignment is either a module attribute or an output model element feature. The assigned expression (exp) can be of any of the supported ATL expressions.
Consider, as a first example, the following attribute definition which defines an integer counter in the context of the ATL module:
helper def: counter : Integer = 0;
The value of this counter attribute can be incremented in the scope of an imperative block using an assignment operation:
thisModule.counter <- thisModule.counter + 1;
The assignment statement can be used in the same way to assign values to model element features in the way. For instance, considering a Person model element aPerson, it is possible to write:
aPerson.father.age <- aPerson.age + 25;
It is possible to initialize the references of a newly generated target model element. The following assignment illustrates this with the assignment of another locally generated (e.g. in the same rule) model element ( anotherPerson):
aPerson.father <- anotherPerson;
In the same way, it is also possible to assign to a reference a model element that is generated by a different matched rule. As described here, in such a case, the assigned element is the corresponding source element. If this last does not correspond to a rule default target pattern element, it is required to use the operation resolveTemp(). Note however that the operation resolveTemp shall be called only once the matching phase of the transformation has completed. This means that resolveTemp cannot be invoked neither from the entrypoint called rule, nor from another called rule invoked from this entrypoint.
The "if" statement enables to define alternative imperative treatments. "if" statements have to conform to the following syntax:
if(condition) { statements1 } [else { statements2 }]?
Each "if" statement defines a condition. This condition must be an OCL expression that returns a boolean value. An "if" statement must also include a "then" statements section. This section, specified between curved brackets, contains the sequence of statements ( statements1) that is executed when the conditional expression is evaluated to true. An "if" statement may also include an optional "else" statements section. When specified, this section has to follow the "then" statements section. It is introduced by the keyword else, and must also be defined between curved brackets. This section contains the optional sequence of statements ( statements2) that has to be executed when the conditional expression is evaluated to false.
The following example illustrates the use of an "if" statement limited to a simple "then" section:
if(aPerson.gender = #male) { thisModule.menNb <- thisModule.menNb + 1; thisModule.men->including(aPerson); }
Next example presents an "if" expression defining both a "then" and an "else" sections, with a nested "if" statement:
if(aPerson.gender = #male) { thisModule.fullName <- 'Mr. ' + aPerson.name + ' ' + aPerson.surname; } else { if(aPerson.isSingle) { thisModule.fullName <- 'Miss ' + aPerson.name; thisModule.surname <- aPerson.surname; } else { thisModule.fullName <- 'Mrs. ' + aPerson.name; thisModule.surname <- aPerson.marriedTo.surname; } thisModule.fullName <- thisModule.fullName + ' ' + thisModule.surname; }
Note that the curved brackets delimitating both the "then" and the "else" sections may be omitted when the corresponding sections contain a single statement, as in the following example:
if(aPerson.gender = #male) thisModule.men->including(aPerson); else thisModule.women->including(aPerson);
The "for" statement enables to define iterative imperative computations. A "for" statement has to conform to the following syntax:
for(iterator in collection) { statements }
The "for" statement defines an iteration variable (iterator) that will iterate over the different elements of the reference collection. For each of these elements, the sequence of statements contained by the "for" statement will be executed.
The following example, also related to the MMPerson metamodel illustrates the use of the "for" imperative statement:
for(p in MMPerson!Person.allInstances()) { if(p.gender = #male) thisModule.men->including(aPerson); else thisModule.women->including(aPerson); }
It is currently not possible to declare variables within ATL imperative blocks. The variables that can be used in the scope of these blocks are:
Note that the current implantation does not enable to modify the locally defined variables from an imperative assignment statement. This means that, beside the source and target model elements, the only variables that can be modified from an imperative block are the attributes that have been defined in the context of the ATL module. As a consequence, the modifiable variables that may be required in the scope of an imperative bock must, with the current implementation, be declared as ATL module attributes.
The ATL matched rule mechanism provides ATL developers with a convenient mean to specify the way target model elements must be generated from source model elements. For this purpose, a matched rule enables to specify 1) which source model element must be matched, 2) the number and the type of the generated target model elements, and 3) the way these target model elements must be initialized from the matched source elements. The specification of a matched rule has to conform to the following syntax:
rule rule_name { from in_var : in_type [in model_name]? [( condition )]? [using { var1 : var_type1 = init_exp1; ... varn : var_typen = init_expn; }]? to out_var1 : out_type1 [in model_name]? ( bindings1 ), out_var2 : distinct out_type2 foreach(e in collection)( bindings2 ), ... out_varn : out_typen [in model_name]? ( bindingsn ) [do { statements }]? }
Each matched rule is identified by its name ( rule_name). A matched rule name must be unique within an ATL transformation. An ATL matched rule is composed of two mandatory (the from and the to parts) and two optional (the using and the do parts) sections. Note that the different variables that may be declared in the scope of a rule (the source and target pattern elements and the local variables) must have a unique name. This restriction does not apply to the OCL expressions contained by this rule. The different sections of an ATL matched rule are detailed in the following subsections.
The from section corresponds to the rule source pattern. This pattern, composed of a single source pattern element contains the source variable declaration ( in_var). This declaration specifies the type of the source model elements that will be matched by the rule ( in_type). It can moreover contain, between brackets, an optional boolean expression ( condition) that enable to target a subset of the transformation source model elements that conform to the source type. If the source pattern element includes no explicit condition, all the source model elements of the transformation that conform to the specified source type will be matched by the rule. The following code excerpt illustrates the syntax of the from section:
from p : MMPerson!Person ( p.name = 'Smith' )
Note that the following excerpt
from p : MMPerson!Person ( true )
is equivalent to:
from p : MMPerson!Person
The declaration can specify the input model where to match elements from. When several input models conform to the same metamodel, the in keyword allow to get only elements from the specified model name. Here is a sample transformation header:
create ... from IN1 : MMPerson, IN2 : MMPerson;
Below is an input pattern which only considers IN2 elements:
from p : MMPerson!Person in IN2
The optional using section makes it possible to locally declare a number of local variables. The variables declared in this section can be used in the using section itself (provided that the variable is not invoked before its declaration), as well as in the to and the do sections. Each declared variable is identified by its name ( vari) and its type ( var_typei), and must be initialized using an OCL expression.
The following code excerpt illustrates the use of the using section:
from c : GeometricElement!Circle using { pi : Real = 3.14; area : Real = pi * c.radius.square(); }
The to section corresponds to the target pattern of the rule. It contains a number of target pattern elements. This section is mandatory and must contain at least one target pattern element. When several target pattern elements are specified, they must be separated by comas (","). Note that the first target pattern element corresponds to the default pattern element of the rule. This means that the target model element associated with this rule's default target pattern can be viewed as the default counterpart of the source model element matched by the rule.
In ATL, there exist two different kinds of target pattern elements: the simple and the iterative ones. Each target pattern element, whatever its type, corresponds to a variable declaration characterized by a name ( out_vari) and a type ( out_typei). A simple target pattern is specified as a set of bindings that define the way the features (either attributes or references) of the generated element must be initialized. Each binding has to conform to the following syntax:
feature_name <- exp
The name of the initialized feature ( feature_name) has to refer to a feature of the variable associated with the target pattern element, as defined in its metamodel. The specified expression ( exp) is an OCL expression. When a target pattern element contains more than one binding, the successive bindings have to be separated by comas. Note that it is not required to explicitly initialize all the features of a generated model element. The default value of the features that are not initialized by means of an explicit binding may change according to the model handler used to access the model element. As a consequence, ATL developers are strongly encouraged not to produce code that depends on these default values.
As an example, it is possible to consider the following ATL rule, which is defined in the context of the Biblio metamodel defined on the Biblio metamodel:
rule Journal2Book { from j : Biblio!Journal to b : Biblio!Book ( title <- j.title + '_(' + j.vol + '):' + j.num, authors <- j.articles ->collect(e | e.authors)->flatten()->asSet() chapters <- j.articles, pagesNb <- j.articles->collect(e | e.pagesNb)->sum() ) }
This rule aims to produce a Book model element from a Journal model element. It initializes the title, authors, chapters and pagesNb features of the generated Book:
This example has illustrated the initialization of the attributes of a generated target model element. As previously stated, the bindings also enable to initialize reference features. Three main cases therefore have to be considered:
The first case (assigning a model element produced by the same rule) is also the simplest one: the considered reference can be initialized with the name of the other target pattern element. Consider the following example in which the rule Case1 has two target pattern model elements ( o_1 and o_2), with o_1 having a reference to a Class2 model element defined ( linkToClass2):
rule Case1 { from i : MM_A!ClassA to o_1 : MM_B!Class1 ( linkToClass2 <- o_2 ), o_2 : MM_B!Class2 ( ... ) }
The reference feature is here simply initialized with the local target pattern element that corresponds to the target model element.
In the second case (assigning the default target element of another rule), the considered reference has to be initialized with the source model element which is matched by the remote rule for generating the target model element to be assigned. In the following example, the rule Case2_R1 aims to generate a target model element ( o_1) that has a reference to a target model element that corresponds to the default target pattern ( o_1) of the rule Case2_R2. Assuming that the source model element matched by Case2_R1 has a reference ( linkToClassB) to the relevant MM_A!ClassB source model element, this assignment is expressed as follows:
rule Case2_R1 { from i : MM_A!ClassA to o_1 : MM_B!Class1 ( linkToClass2 <- i.linkToClassB ) }
rule Case2_R2 { from i : MM_A!ClassB to o_1 : MM_B!Class2 ( ... ), ... }
The reference is here initialized with the source model element that is matched by rule Case2_R2 when generating the target model element MM_B!Class2.
It may also happen that a developer wants to initialize a reference with a non-default target pattern element of a remote rule. This last case requires the use of the resolveTemp() operation defined in the context of the ATL module. This operation makes it possible to access the target model elements that are associated with the non-default target pattern elements of a remote rule. It accepts two parameters: the source model element which is matched by the remote rule for generating the target model element to be assigned, and the name of the target pattern element it is associated with. This is illustrated with the following example, which is similar to the previous one, except that the target model element to be assigned is not generated by the default target pattern of rule Case3_R2.
rule Case3_R1 { from i : MM_A!ClassA to o_1 : MM_B!Class1 ( linkToClass2 <- thisModule.resolveTemp(i.linkToClassB, 'o_n') ) }
rule Case3_R2 { from in : MM_A!ClassB to o_1 : MM_B!Class3 ( ... ), ... o_n : MM_B!Class2 ( ... ), ... }
Compared to the previous case, the reference is here initialized by invoking the operation resolveTemp() with the source model element ( i.linkToClassB, the same that in the previous example) and the name of the target pattern (" o_n") as arguments.
Since ATL 3.1:
The declaration can specify the output model where to create elements. When several output models conform to the same metamodel, the in keyword allow to specify the output model name. Here is a sample transformation header:
create OUT1 : MM_B, OUT2 : MM_B from ... ;
Below is an output pattern which creates elements in the OUT2 model:
to o : MM_B!Class2 in OUT2
WARNING: iterative target patterns are deprecated since ATL 2.0 as they break internal traceability links. It is better to use unique lazy rules instead.
As opposed to the simple target pattern element, which allows generating a single target model element, the iterative target pattern element makes it possible to generate a set of target model elements conforming to a same type. An iterative target pattern element is introduced by the keyword distinct. It produces a target model element for each element belonging to a given reference ordered collection (either a Sequence or an OrderedSet). This collection, along with its associated iterator (e), is introduced by the keyword foreach. As for a simple target pattern element, an iterative target pattern element defines a number of bindings. These bindings specify the way the features of the target model elements generated by this target pattern element will be initialized.
The following example aims to generate a number of distinct Cell model elements equal to the size of a collection:
using { coll : Sequence(String) = Sequence{'a', 'b', 'c'}; } to cells : distinct Table!Cell foreach(e in coll)( content <- e, id <- coll->indexOf(e) )
Note that the collection operation indexOf can be used here to compute a unique column id because the reference collection ( coll) does not contain multiple instances of a same element in the collection. Otherwise, the id of the multiple instances of a same element would all have been initialized with the index of the first instance of this element.
Since the reference collection is defined, in this example, as a constant, both its size and its content are known. It is thus possible, instead of using a single iterative target pattern element, to define as many simple target pattern elements as the number of elements in the collection. However, the use of an iterative out pattern element will be required when working with a collection which is a priori unknown (for instance, a collection that comes from a source model).
Attention must be paid when assigning a collection to a target model element feature in the scope of an iterative target pattern element. Indeed, when executing an iterative target pattern element, the ATL engine iterates over the reference collection, but also, in the same time, over the collection expressions that are assigned to features within this pattern element. During the iteration over the reference collection, the current element of a collection expression is assigned to its targeted feature. This has two main consequences:
The following example illustrates the way to assign a collection to feature in the scope of an iterative out pattern element:
using { coll : Sequence(String) = Sequence{'a', 'b', 'c'}; } to lines : distinct Table!Line foreach(e in coll)( id <- coll->indexOf(e), cell_titles <- Sequence{ Set{'PlayerA_Score1', 'PlayerB_Score1'}, Set{'PlayerA_Score2', 'PlayerB_Score2'}, Set{'PlayerA_Total', 'PlayerB_ Total', 'Total'} } )
This example is quite similar to the previous one. Instead of generating some Cell model elements, it generates a Line model element for each element of a reference collection ( coll). Each line is associated with a unique id, which is computed in the same way it was in the previous example. The difference is here that each line is also characterized by a sequence of strings that encode the title of the different cells of the line.
In order to associate each generated Line model element with its own set of cell titles, the property cell_titles is initialized with a sequence containing as many elements as the reference collection. Each generated line will be associated with its corresponding element in this sequence (the one positioned at the same rank). Thus, the first generated line will be associated with the "PlayerA_Score1" and "PlayerB_Score1" cell titles whereas the third line will be associated with the "PlayerA_Total", "PlayerB_Total" and "Total" cell titles. Please note that:
Attention must also be paid when referring to the elements generated in the scope of an iterative target pattern. Thus, in the scope of a simple target pattern element, an iterative target pattern variable refers to the whole set of generated elements that are generated by the corresponding pattern element. It is also possible to invoke an iterative target pattern variable from another iterative target pattern element provided that: 1) both iterative target pattern elements belong to the same rule, and 2) both iterative target pattern elements iterate over the same ordered collection. In such a case, the variable refers to the target model element generated by the current iteration.
The following code excerpt illustrates the different ways to refer to elements produced by iterative target pattern elements:
using { coll : Sequence(String) = Sequence{'Score1', 'Score2', 'Total'}; } to tab : Table!Table ( lines <- t_lines ), t_lines : distinct Table!Line foreach(e in coll)( id <- coll->indexOf(e), caption <- line_captions ), line_captions : distinct Table!Caption foreach(e in coll)( content <- e )
This new example is inspired from the previous ones. The objective is here to create a Table model element, itself composed of Line model elements. Each Line has to be associated with its own Caption model element. In the scope of the simple target pattern element tab, the variable t_lines refers to the whole sequence of generated Line model elements.
Since both iterative target pattern elements iterate over the same reference collection, the variable line_captions used in the t_lines target pattern element refers to a single of the Caption model elements generated by the line_captions target pattern element. Since the used reference collection is ordered, the line_captions variable will refer to the Caption generated from the same element of the reference collection.
The last section of an ATL matched rule is the optional do section. This section enables to specify a sequence of ATL imperative statements that will be executed once the initialization of the target model elements generated by the rule has completed. This imperative block can in particular be used to initialize some model element features that have not been initialized using the declarative bindings, or to modify some already initialized features.
The imperative block provides a convenient way to simply assign a unique id to each of the generated model elements. The following example, related to the Biblio metamodel, illustrates this point:
helper def : id : Integer = 0; ... rule Journal2Book { from j : Biblio!Journal to b : Biblio!Book ( ... ) do { thisModule.id <- thisModule.id + 1; b.id <- thisModule.id; } }
In this example, a global id variable is defined in the context of the ATL module, and initialized to zero. In order to associate each generated model element with a unique id, the imperative block of the matched rule simply increments the value of the id global variable and assigned this new value to the id property of the generated model element.
How to call lazy rules:
Let a simple lazy rule:
lazy rule getCross { from i: ecore!EObject to rel: metamodel!Relationship ( ) }
We can call it from a matched rule as follows:
rule Example { from s : ecore!EObject to t : metamodel!Node ( name <- s.toString(), edges <- thisModule.getCross(s) ) }
If we want to call lazy rule multiple times:
rule Example { from s : ecore!EObject to t : metamodel!Node ( name <- s.toString(), edges <- ecore!EClass.allInstancesFrom('yourmodel')->collect(e | thisModule.getCross(e)) ) }
Declaring a lazy rule as unique, with the following syntax:
unique lazy rule Example{ ... }
give it the following behavior:
When a unique lazy rule is executed, it always returns the same target element for a given source element. The target element is retrieved by navigating the internal traceability links, in a way similar to standard rules.
Non-unique lazy rule do not navigate the traceability links, and create new target elements at each execution.
Besides matched rules, ATL defines an additional kind of rules enabling to explicitly generate target model elements from imperative code. Except for the entrypoint called rule, this kind of rules must be explicitly called from an ATL imperative block. The specification of a called rule has to conform to the following syntax:
[entrypoint]? rule rule_name''(''parameters''){ [using { var1 : var_type1 = init_exp1; ... varn : var_typen = init_expn; }]? [to out_var1 : out_type1 ( bindings1 ), out_var2 : distinct out_type2 foreach(e in collection)( bindings2 ), ... out_varn : out_typen ( bindingsn )]? [do { statements }]? }
A called rule is identified by its name ( rule_name). A called rule name has to be unique within an ATL transformation, and must not collide with a helper name. Moreover, a called rule cannot be called "main". A called rule can optionally be declared as the transformation entrypoint. An ATL transformation can include one entrypoint called rule. Compared to the other called rules, the entrypoint called rule does not need to be explicitly called: it is implicitly invoked at the beginning of the transformation execution, once the module initialization phase has completed.
A called rule can accept parameters. They have to be specified in the same way they are for helpers. It is composed of three optional sections: the using, the to and the do sections. Compared to a matched rule, a called rule has no from section, and its to section is optional. Note however that the semantics of the available sections are similar to those defined for matched rules:
The following code excerpt, provides a called rule example:
helper def: metamodel : KM3!Metamodel = OclUndefined; ... entrypoint rule Metamodel() { to t : KM3!Metamodel do { thisModule.metamodel <- t; } }
This called rule is defined as the transformation entry point. This means that it is executed between the initialization and the matching phases. It generates a Metamodel model element. The code specified within the imperative block makes a variable (metamodel) defined in the context of the ATL module pointing to this model element. By this mean, the generated Metamodel remains accessible for further computation during the transformation.
There is two keywords introduced by rules inheritance: abstract and extends. They can be used like this:
abstract rule A { from [fromA] using [usingA] to [toA] do [doA] } rule B extends A { from [fromB] using [usingB] to [toB] do [doB] } rule C extends B { from [fromC] using [usingC] to [toC] do [doC] }
When ATL compiles this transformation, it is the same as if you gave this as input:
rule B { from [fromB] using [usingB] to [toA.bindings union toB.bindings] do [doB] } rule C { from [fromC] using [usingC] to [toA.bindings union toB.bindings union toC.bindings] do [doC] }
However, there are some limitations and constraints. First, the compiler does not support multiple inheritances and it is not planned to be implemented. Constraints are the following:
Here is a complete example to illustrate. It is a KM3-copier, i.e. every model element from the source model is copied as-is to the target model:
module Copy; create OUT : MM from IN : MM;
rule CopyDataType extends CopyClassifier { from s : MM!DataType to t : MM!DataType }
rule CopyEnumeration extends CopyClassifier { from s : MM!Enumeration to t : MM!Enumeration ( literals <- s.literals ) }
rule CopyParameter extends CopyTypedElement { from s : MM!Parameter to t : MM!Parameter }
rule CopyReference extends CopyStructuralFeature { from s : MM!Reference to t : MM!Reference ( isContainer <- s.isContainer, opposite <- s.opposite ) }
rule CopyTypedElement extends CopyModelElement { from s : MM!TypedElement to t : MM!TypedElement ( lower <- s.lower, upper <- s.upper, isOrdered <- s.isOrdered, isUnique <- s.isUnique, type <- s.type ) }
rule CopyOperation extends CopyTypedElement { from s : MM!Operation to t : MM!Operation ( parameters <- s.parameters ) }
rule CopyAttribute extends CopyStructuralFeature { from s : MM!Attribute to t : MM!Attribute }
rule CopyEnumLiteral extends CopyModelElement { from s : MM!EnumLiteral to t : MM!EnumLiteral }
rule CopyPackage extends CopyModelElement { from s : MM!Package to t : MM!Package ( contents <- s.contents ) }
rule CopyClass extends CopyClassifier { from s : MM!Class to t : MM!Class ( isAbstract <- s.isAbstract, supertypes <- s.supertypes, structuralFeatures <- s.structuralFeatures, operations <- s.operations ) }
rule CopyClassifier extends CopyModelElement { from s : MM!Classifier to t : MM!Classifier }
abstract rule CopyModelElement extends CopyLocatedElement { from s : MM!ModelElement to t : MM!ModelElement ( name <- s.name ) }
rule CopyMetamodel extends CopyLocatedElement { from s : MM!Metamodel to t : MM!Metamodel ( contents <- s.contents ) }
abstract rule CopyLocatedElement { from s : MM!LocatedElement to t : MM!LocatedElement ( location <- s.location ) }
rule CopyStructuralFeature extends CopyTypedElement { from s : MM!StructuralFeature to t : MM!StructuralFeature ( subsetOf <- s.subsetOf, derivedFrom <- s.derivedFrom ) }
Here are three types of declarative rules:
The following table summarizes their differences with respect to the number of time they are applied.
Kind of rule | Number of references to source pattern | Number of times the target pattern gets created | Kind of traceability link created |
standard | 0 | 1 | default or not (using keyword nodefault) |
1 | 1 | ||
n > 1 | 1 | ||
lazy | 0 | 0 | Not default |
1 | 1 | ||
n > 1 | n | ||
unique lazy | 0 | 0 | Not default |
1 | 1 | ||
n > 1 | 1 |
In addition, imperative rules may also be used.
Here are a few guidelines about which rules and constructs to use. They may be summarized as: "Make your transformation as complex as necessary but as simple as possible".
The refining execution mode has been introduced to ease the programming of refining (or refactoring) transformations. With the refining mode, ATL developers can focus on the ATL code dedicated to the generation of modified target elements. Other model elements (e.g. those that remain unchanged between the source and the target model) are implicitly processed by the ATL engine.
This mode is selected in the ATL language by simply replacing the from keyword by the refining keyword in the transformation header. Obviously, refining mode may only be used for endogenous transformations, i.e. when source and target model share the same metamodel. Listing below shows how the header of a refactoring transformation on a ClassDiagram metamodel may look like.
module refiningSample; create OUT : ClassDiagram refining IN : ClassDiagram;
The ATL2010 compiler implements an in-place strategy, e.g., the changes are directly performed on the source model without making any copy of the elements. The rules in the transformation only need to specify the values that have changed whereas all the other elements just remain untouched. The transformations in this mode are performed in two steps. In the first step, the transformation engine executes the rules which, as a result, produce a set of changes that are temporally stored. In the second step, this set changes are applied directly on the source model.
This compiler enhances the refining capabilities provided by the 2006 compiler, that also implemented an in-place strategy. First of all, it addresses the lacking of deletion support by providing the user an explicit way to specify in the transformation that a matched element has to be removed.
To support the deletion feature, some modifications have been introduced in the concrete and abstract syntax of the language to provide a new keyword: DROP. The listing below shows and ATL 2010 refining transformation that reverses the prior introduced change in attributes visibility using the new drop keyword.
rule PrivateAttribute { from s : ClassDiagram!Attribute (s.isPrivate and s.owner.op->exists(o|o.name = 'get' + s.name.toUpperCase() and o.returnType = s.type) ) to t : ClassDiagram ! Attribute ( isPrivate <- false ) } rule DeleteOperation { from s : ClassDiagram!Operation (s.owner.attr ->exists(a|a.name = s.name.toUpperCase().substring(3, s.name->size()) and a.isPrivate)) to drop }
From the semantic point of view, we have to note that a deletion can easily generate a conflict with another modification. This happens because we chose to provide deletion with a cascade-delete semantics for composition associations (i.e. the deletion of a container triggers the deletion of the contained elements). For this reason, the second step in the transformation that is in charge of applying the changes is implemented by first applying all the creation changes, then all the modification changes, and finally all the deletion changes.
Some ATL standard mode advanced features are not supported in Refining Mode. Concretely, the following elements are not supported: Action blocks, Lazy rules, multiple input elements and Iterative output patterns. Covering these advanced features as well as providing new refining features (like cloning) are in the future plans.
Refining transformations are implemented differently depending on the ATL compiler used for the transformation. In the following, we explain the features of the previous compilers so that ATL users can choose the refinement implementation strategy that best fits their scenario.
The 2004 compiler implements a copy strategy. The ATL transformation performs as if there were implicit copy rules that would match every element unmatched by explicit (i.e., programmer-written) rules. Such a copy rule creates a target element of the same type as the source element. Then, it initializes all the properties of the new element by copying the values of the properties of the source element. However, not all the elements are copied. To be copied the elements must satisfy at least one of these conditions: 1) they are matched by an explicit rule, or 2) they are contained in a copied element, or 3) they are referenced (non-containment) by a copied element.
This conditions allows the deletion of parts of the model just by removing any reference to them.
The listing below shows a refining transformation in ATL 2004 compiler that change the visibility of attributes and adds the corresponding getters.
rule PublicAttribute { from s : ClassDiagram!Attribute ( not s.isPrivate and not s.owner.op->exists(o | o.name = 'get' + s.name.toUpperCase() and o.returnType = s.type) ) to t : ClassDiagram!Attribute ( name <- s.name, owner <- s.owner, isPrivate <- true, type <- s.type ), getter : ClassDiagram!Operation ( name <- 'get' + s.name.toUpperCase(), owner <- s.owner, isPrivate <- false, returnType <- s.type ) }
Partially implements an in-place strategy. This implementation works in the same way the ATL2010 does, but does not provide support for the deletion of elements. Using reverse binding for the first output pattern element is also not supported.
Compiler | Strategy | Deletion | Supported language subset in Refining Mode |
2004 | Copy | Implicitly supported | Full support (2004 features) |
2006 | In-place | Not supported | Partial support |
2010 | In-place | Explicitly supported | Partial support |
Remember that to select between the different compilers the first line of the transformation must consist of a comment with the @atlcompiler tag followed by the name of the compiler (by example, -- @atlcompiler atl2010)
Besides module units, ATL enables developers to define queries on model. A query unit accepts a number of source models and produces a single return value of any supported primitive data type. A query unit is composed a single query element along with a number of helpers and attributes that may be defined in the context of either the ATL module or any model element defined within the query source models. Note that an ATL query unit must start with the declaration of its query element. The specification of this query element has to conform to the following syntax:
query ''query_name'' = ''exp'';
There is no constraint on the naming of the query element. However, it is advised to give the query element the same name that the file in which it is defined.
The body of the query element ( exp) is an OCL expression of any of the supported primitive data types: string, boolean, integer or real. Helpers and attributes defined in the query file (as well as those that belong to imported ATL libraries) can be called in the scope of the body of the query element.
When using the ATL Integrated Development Environment (IDE), developers may be interesting in writing the result of an executed query into a file. This could be easily achieved by producing a string value (other primitive data types will have to be cast into strings) on which the operation ''writeTo()'' can be called. As an example, it is possible to consider the following query:
query PersonNb = MMPerson!Person.allInstances()->size().toString().writeTo('result.txt');
This query is executed on a MMPerson model containing a number of Person entities. The query first gets the set of all existing Person classes in the model and gets the size of the computed set. In order to write this value in a file, the computed integer value is cast into a string (operation toString()) before being written into the file "result.txt". Note that, although the result is written into a file, the query still returns the computed string.
This section provides the list of the ATL reserved keywords. These keywords cannot be used to name variables in any context of an ATL unit (either a module, a query or a library). It is possible to distinguish three kinds of keywords: the constant keywords, the language keywords and the type keywords:
Note that the use of the string "main", which does not belong to the set of language keywords, is restricted. "main" cannot be used to identify (e.g. to name) neither a called rule, nor a helper or an attribute that is defined in the context of the ATL module.
This section aims to highlight some common problems and errors that may be experienced while starting programming with ATL.
In ATL, an element of the input model should not be matched more than once. At present time, this constraint is not verified at compile time, and this kind of errors can lead to unexpected results. A typical case of multiple matching of an input model element appears with the definition, in the input metamodel, of an inheritance link in which the parent entity is not abstract. Here is a simple example of this kind of situation:
The multiple matching problem appears here when trying to respectively match A and B elements by means of two distinct rules ( ruleA and ruleB). With an intuitive source pattern such as a : MM!A, ruleA will match purely A elements as well as B elements. Since these last ones are also matched by ruleB, this raises a multiple matching problem.
To solve the problem, the developer has to ensure that ruleA only matches purely A elements. This is achieved by filtering, in the source pattern of ruleA, the type of the elements to be matched by the rule:
rule ruleA { from a : MM!A ( a.oclIsTypeOf(MM!A) ) ...
The OCL function oclIsTypeOf here tests whether the input model element is an instance of the metamodel element passed as parameter.
Overview of the Atlas Transformation Language | The ATL Tools |


text:
My Gear
Work With Us
My Gear
Work With Us
Show Course Contents
Introduction to Data Structures & Algorithms
Time Complexity and Big O Notation (with notes)
Asymptotic Notations: Big O, Big Omega and Big Theta Explained (With Notes)
Best Case, Worst Case and Average Case Analysis of an Algorithm (With Notes)
How to Calculate Time Complexity of an Algorithm + Solved Questions (With Notes)
Arrays and Abstract Data Type in Data Structure (With Notes)
Array as An Abstract Data Type in Data Structures(With Notes)
Implementing Array as an Abstract Data Type in C Language
Operations on Arrays in Data Structures: Traversal, Insertion, Deletion and Searching
Coding Insertion Operation in Array in Data Structures in C language
Coding Deletion Operation in Array Using C Language (With Notes)
Linear Vs Binary Search + Code in C Language (With Notes)
Introduction to Linked List in Data Structures (With Notes)
Linked List Data Structure: Creation and Traversal in C Language
Insertion of a Node in a Linked List Data Structure
Insertion in a Linked List in C Language
Deletion in a Linked List | Deleting a node from Linked List Data Structure
Delete a Node from Linked List (C Code For Deletion From Beginning, End, Specified Position & Key)
Circular Linked List and Operations in Data Structures (With Notes)
Circular Linked Lists: Operations in C Language
Doubly Linked Lists Explained With Code in C Language
Introduction to Stack in Data Structures
Implementing Stack Using Array in Data Structures
C Code For Implementing Stack Using Array in Data Structures
Push, Pop and Other Operations in Stack Implemented Using an Array
Coding Push(), Pop(), isEmpty() and isFull() Operations in Stack Using an Array| C Code For Stack
Peek Operation in Stack Using Arrays (With C Code & Explanation)
stackTop, stackBottom & Time Complexity of Operations in Stack Using Arrays
How to Implement Stack Using Linked List?
Implementing all the Stack Operations using Linked List (With Code in C)
peek(), stackTop() and Other Operations on Stack Using Linked List (with C Code)
Parenthesis Matching Problem Using Stack Data Structure (Applications of Stack)
Parenthesis Checking Using Stack in C Language
Multiple Parenthesis Matching Using Stack with C Code
Infix, Prefix and Postfix Expressions
Infix To Postfix Using Stack
Coding Infix to Postfix in C using Stack
Queue Data Structure in Hindi
Queue Implementation: Array Implementation of Queue in Data Structure
Array implementation of Queue and its Operations in Data Structure
C Code For Queue and its Operations Using Arrays in Data Structure
Introduction to Circular Queue in Data Structures
enqueue(), dequeue() & other Operations on Circular Queue
C Code For Circular Queue & Operations on Circular Queue in Hindi
Queue Using Linked Lists
Implementing Queue Using Linked List in C Language (With Code)
Double-Ended Queue in Data Structure (DE-Queue Explained)
Introduction to Sorting Algorithms
Criteria For Analysis of Sorting Algorithms
Bubble Sort Algorithm in Hindi
Bubble Sort Program in C
Insertion Sort Algorithm in Hindi
Insertion Sort in C Language (With Explanation)
Selection Sort Algorithm
Selection Sort Program in C
QuickSort Algorithm in Hindi (With Code in C)
Analysis of QuickSort Sorting Algorithm
MergeSort Sorting Algorithm in Hindi
MergeSort Source Code in C (Helpful Explanation)
Count Sort Algorithm
Introduction to Trees
What is a Binary Tree?
Types of Binary Trees
Representation of a Binary Tree
Linked Representation Of Binary Tree in C
Traversal in Binary Tree (InOrder, PostOrder and PreOrder Traversals)
Preorder Traversal in a Binary Tree (With C Code)
PostOrder Traversal in a Binary Tree (With C Code)
InOrder Traversal in a Binary Tree (With C Code)
*Best* Trick To Find PreOrder, InOrder & PostOrder Traversal
Binary Search Trees: Introduction & Properties
Checking if a binary tree is a binary search tree or not!
Searching in a Binary Search Trees (Search Operation)
C Code For Searching in a BST
Iterative Search in a Binary Search Tree
Insertion in a Binary Search Tree
Deletion in a Binary Search Tree
C Code For Deletion in a Binary Search Tree
AVL Trees - Introduction
Insertion and Rotation in AVL Tree
AVL Trees - LL LR RL and RR rotations
C Code For AVL Tree Insertion & Rotation (LL, RR, LR & RL Rotation)
Introduction to Graphs | Graph Data Structure
Representation of Graphs - Adjacency List, Adjacency Matrix & Other Representations
Graph traversal & Graph traversal algorithms
Breadth First Search (BFS) Graph Traversal in Data Structures
BFS Implementation in C | C Code For Breadth First Search
Depth First Search (DFS) Graph Traversal in Data Structures
DFS Implementation in C | C Code For Depth First Search
Spanning Trees & maximum no of possible spanning trees for complete graphs
Calculating Spanning tree Cost & Minimum Spanning Tree
Prims Minimum Spanning Tree Algorithm (Step by Step with examples)
Insertion of a Node in a Linked List Data Structure
< < Previous
Next > >

subset: commoncrawl


text:
Lara Logan apologizes for '60 Minutes' Benghazi report
| New York
CBS News admitted Friday it was wrong to trust a "60 Minutes" source who claimed to be at the scene of a 2012 attack on the U.S. mission in Benghazi, Libya, and the publisher of the source's book on the incident has halted its publication.
"There are so many people out there who have the potential to deceive a news organization," said Jeffrey Fager, CBS News chairman and "60 Minutes" executive producer, in an interview with The Associated Press on Friday. "We do our best and I think we do very well at spotting them. This time, I really feel like one got through and it's extremely disappointing."
The correspondent responsible for the Oct. 27 story, Lara Logan, said the newsmagazine would correct its story on Sunday. She had interviewed former security contractor Dylan Davies, who claimed he took part in fighting at the mission. His story had been quickly doubted, and his credibility crumbled with a New York Times report late Thursday that revealed the FBI said the story Davies told them didn't match what he told CBS.
"That's when we realized that we no longer had confidence in our source, and that we were wrong to put him on the air, and we apologize to our viewers," Logan said on "CBS: This Morning" on Friday.
With it now unclear where Davies had been, publisher Simon & Schuster said Friday it was withdrawing his book, "The Embassy House: The Explosive Eyewitness Account of the Libyan Embassy Siege by the Soldier Who Was There." It was published on the conservative Threshold Editions imprint two days after the "60 Minutes" story.
Davies had written the book under the pseudonym Morgan Jones, which is how "60 Minutes" identified him in Logan's story about Benghazi.
In that story, which was stripped from the "60 Minutes" website late Thursday, Davies talked about rushing to the scene of the attack where U.S. Ambassador J. Christopher Stevens was killed, and striking one attacker in the head with a rifle butt.
But the Washington Post last week uncovered the identity of "Morgan Jones" and wrote that Davies had provided a written report to the British security firm for whom he worked that he had spent most of the night at the Benghazi home where he was staying and his attempts to get to the mission were blocked.
CBS said it had known all along that Davies had told his bosses at the Blue Mountain security firm a different story, and that Davies had claimed the contradictory report had not been written by him. CBS said Davies said he told the true story — the one he recounted on "60 Minutes" — to the FBI. But the Times reported late Thursday that the story the FBI was told by Davies instead matched the written report to Blue Mountain.
Logan said CBS has tried and failed to reach Davies again. A spokesman for Davies' publisher told The Associated Press that their author is not talking.
CBS has also admitted it was wrong not to have disclosed to "60 Minutes" viewers that Simon & Schuster, like CBS News, is owned by the CBS Corp.
For CBS, the question will remain why it had put so much stock in what Davies was saying when he was already admitted that he had told his employer an incorrect story. But the network said Davies had had motivation to lie to Blue Mountain, because the company told him not to leave his home that night and he disobeyed his bosses to go to the scene.
Logan said CBS used U.S. government reports and congressional testimony to verify his story, "and everything checked out." Davies had also showed them photographs he had taken at the U.S. compound the morning after the Sept. 11, 2012 attack.
"The most important thing to every person at '60 Minutes' is the truth, and today the truth is that we made a mistake," Logan said.
Congressional Republicans have insisted that the Obama administration misled Americans about the Benghazi attack, playing down a terrorist assault in the heat of the presidential campaign. Five GOP-led House committees have investigated, demanding documents and witnesses from the administration while complaining that the Obama team has been stonewalling.
A day after the CBS report, Sen. Lindsey Graham, R-S.C., said he would block President Barack Obama's nominees for Federal Reserve chairman and Homeland Security chief until the administration allowed survivors of the assault on the U.S. diplomatic mission to talk to members of Congress.
Graham's office had no immediate comment on Friday when contacted by the AP, but he was due to discuss Benghazi on Sunday political talk shows.
The liberal media watchdog Media Matters for America, which had attacked Logan's story almost as soon as it was aired, called for an independent panel to investigate CBS News' reporting on the story. Media Matters said that would be consistent with the panel appointed by CBS that had concluded that a 2004 story on "60 Minutes II" about President George W. Bush's military service could not be substantiated — which led to Dan Rather's departure from the network after several decades.
CBS has not shown any inclination to appoint an independent panel in this case.
Associated Press National Writer Hillel Italie in New York and writer Donna Cassata in Washington also contributed to this report.


text:
Silica burp hypothesis
The ocean drilling vessel Chikyu
The "silica burp" hypothesis is a proposed explanation for a period of the Eocene epoch during which large quantities of biogenic silica were deposited on the seafloor. The hypothesis invokes changes in the supply of silicon to the ocean in conjunction with climate changes to explain these deposits, and these changes can be simulated, and the hypothesis tested, in the silicon-phosphorus model.
The Eocene epoch was a period of Earth history with enhanced crustal activity and a markedly warmer climate. In addition, there were prolonged periods during the Eocene when unusually large quantities of biogenic silica accumulated on the ocean floor. The peak of this accumulation occurred at approximately 50 Mya, forming what is known to paleoceanographers as Horizon AC, a layer of silica-rich sediments spanning approximately 4 My.
This layer is quite well known to those scientists who go out to sea on ocean drilling ships such as the Chikyu, because the hard layer is difficult to penetrate with their drill bits. To many of these scientists it is familiar primarily as an obstacle to research rather than as an interesting scientific phenomenon in need of explanation. However, Horizon AC is probably the largest silica deposition event during the last 100 My, so understanding the processes that formed it are of particular interest.
Because Horizon AC was not thought to be contemporaneous with any known processes which could have added silicic acid (dissolved silicate) to the ocean, a novel mechanism was proposed to explain it. This "silica burp" hypothesis suggested that climatic variation could allow a long time to pass between silicic acid supply and silicate burial (McGowran, 1989); they could be 'temporally decoupled'.
The "silica burp" hypothesis
By comparing the contents of a large number of cores drilled down through layers of ocean sediments, it is possible to construct an estimate of how the particle rain to the seafloor has changed over geological time. One consistent finding is that there was a period of unusually high flux of solid SiO2; (known as opal or biogenic silica) to the seafloor about 50 million years ago that lasted for about 4 million years. This silicon-rich seam of sediment is known as Horizon AC, and a colourfully named hypothesis, the "silica burp", was developed by McGowran (1989) to explain its origin.
McGowran's "silica burp" hypothesis suggested that a sequence of geological and climatological events conspired to create Horizon AC. Firstly, a period of vulcanism known as the Thulean (56.7 to 53.8 Mya) added a large quantity of dissolved silicon to the ocean. Next, the warming climate of the Early Eocene Climatic Optimum (EECO) acted to decrease ocean circulation and increase stratification, with the result that silicon added by this vulcanism was isolated from the productive surface waters and was able to accumulate within the ocean interior. This build-up of silicon in the ocean was then compounded by the further addition of silicic acid by increased, climate-induced weathering at high latitudes. The warming of the EECO period persisted until approximately 49 to 48 Mya, after which major climate cooling occurred. This reinvigorated the circulation of the ocean, and, so McGowran's hypothesis went, released the accumulated silicic acid from the deep to the surface ocean, the "silica burp", where it fuelled increased biogenic silica production which led to the creation of Horizon AC.
Essentially, McGowran's hypothesis proposed that changes to the Earth's climate around the period that Horizon AC was formed acted to decoupled in time the geophysical processes that supplied silicic acid to the ocean from the biological processes that acted to remove it.
Testing the hypothesis in JModels
The silicon-phosphorus model in the JModels suite includes a representation of the "silica burp" as one of its scenarios. This allows users to test whether the hypothesis is able to explain the formation of Horizon AC. The pictures below illustrate the steps to run this scenario using this model.
Select the "scenarios" option from the main window | Select the "McGowran" option from the scenarios | Simulate the model to test the "silica burp" hypothesis |
Further details about the "silica burp" hypothesis can also be found in McGowran (1989), Yool & Tyrrell (2005) and Muttoni & Kent (2007).
- McGowran, B. (1989). Silica burp in the Eocene ocean. Geology 17, 857–860.
- Yool, A. and Tyrrell, T. (2003). Role of diatoms in regulating the ocean's silicon cycle. Global Biogeochemical Cycles 17, 1103, doi:10.1029/2002GB002018.
- Yool, A. and Tyrrell, T. (2005). Implications for the history of Cenozoic opal deposition from a quantitative model. Palaeogeography, Palaeoclimatology, Palaeoecology 218, 239-255.
- Muttoni, G. and Kent, D.V. (2007). Widespread formation of cherts during the early Eocene climate optimum. Palaeogeography, Palaeoclimatology, Palaeoecology 253, 348-362.


text:
Phone Collaboration
Open More Windows
See an exact UI of your phone screen viewable on the MateBook screen. Open and use up to 3 phone app windows simultaneously, and achieve incredible levels of multitasking.
Easy File Access
Back-up your phone files to MateBook easily using drag-and-drop. Edit them with the keyboard and mouse, with all changes saved automatically to your phone, ready to instantly send out to your contacts.
Convenient Calls
Don't be dragged away from what you're working on. Take video calls on your phone seamlessly through the MateBook screen, so you can always stay focused on the task at hand.


text:
Many Americans are gearing up for a Fourth of July barbecue, dreaming of throwing meat or veggies on the grill. And if you've always wanted to try your hand as a grill master, but you don't know where to start, Ruth Tam from NPR's Life Kit has some tips for you.
RUTH TAM, BYLINE: For those of us who are used to cooking indoors, the idea of grilling can feel intimidating, chaotic, even. But don't worry. If you're new, start by choosing a grill. Generally, you have a choice of gas or charcoal. Here's food writer Jess Larson. She's the founder of Plays Well With Butter.
JESS LARSON: A gas grill will preheat really quickly, and the beauty of a gas grill is that the machine kind of takes care of a lot of the guesswork for you. You just set the dials to whatever your recipe calls for, and it manages the temperatures. And that's why I think that for many beginner grillers, gas is a great place to start.
TAM: Keep in mind, gas grills can be pricey. Charcoal grills, on the other hand, are cheaper, and not only that, they impart a lot of the classic charcoal flavor associated with grilling into your food. But they can take a little while to get used to.
LARSON: Managing a charcoal flame requires just a little bit of experience. There's just a little bit of a steeper learning curve associated with it.
TAM: If you're using a charcoal grill, how do you make up for the lack of easy temperature control? Jess says the key to managing heat on a charcoal grill is to create temperature zones.
LARSON: Based on how you arrange the charcoal in the bottom of your grill, you can create a super high, hot zone of temperature if you stack all of the charcoal on top of each other, off to the side, and then whatever area of the grill grate isn't over that or is further away from that stack of charcoal then becomes a cooler temperature zone.
TAM: Moving food around these temperature zones will mimic the experience of lowering the flame on a stovetop. Jess says you don't have to do everything the hard way. There are a number of shortcuts you can use to take the stress out of learning to grill.
LARSON: There are some types of charcoal that are, like, a quick-light charcoal that you don't need to use lighter fluid with because that's how the charcoal is designed to ignite easily, and I think that's a great place to start for a beginner.
TAM: Wherever you are in your mission to grill, safety first - read your owner's manual.
LARSON: It's always a good idea to have a fire extinguisher on hand and just the knowledge that, you know, air feeds flames, and when in doubt, close the lid and let the fire die out on its own.
TAM: Now for the fun part, figuring out what to make.
LARSON: At my house, you'll often find me grilling up sweet potatoes, grilling up cauliflower steaks, grilling up mushrooms, and making things like a veggie board for entertaining or like tacos for just, like, a weeknight dinner. Something else that's really fun to grill during the summer months is stone fruit, just because it's so beautiful, and it's so in season. But you'll want to look for a slightly less ripe peach, for example, or nectarine, and just give it a quick kiss over really high heat, and the sugars begin to caramelize.
TAM: Above all, grilling is about getting outside and making food you enjoy with people you love. Have fun with it.
DETROW: That was Ruth Tam with NPR's Life Kit. For more tips and lifestyle hacks, go to npr.org/lifekit.
(SOUNDBITE OF MUSIC) Transcript provided by NPR, Copyright NPR.
NPR transcripts are created on a rush deadline by an NPR contractor. This text may not be in its final form and may be updated or revised in the future. Accuracy and availability may vary. The authoritative record of NPR's programming is the audio record.


text:
Panjim, September 10: The much-awaited MOPA project is expected to be operational soon. It is one of the region's most anticipated projects, and rightly so. The current airport in Goa has been undeserving of the region. It does not have the requisite infrastructure nor scalability. With the MOPA airport, these problems will soon be a thing of the past. Analysts are expecting an influx of tourists to the entire Konkan belt. The result will be an overall development of the area. It is expected that this development will rub off on the real estate sector as well.
Property prices in the area will increase dramatically after operations begin, according to a study by 360 Realtors, one of India's top real estate advisors. In the next 12 months, property values in the airport's immediate vicinity, where the current average price per square foot is Rs 6,700, can increase by 27.8%. According to data by 360 Realtors, the average price of a home has grown over the last four years at a CAGR of 3.9 per cent. Following the opening of the airport, there will be an increase in real estate investment and tourism activity, which will be the cause of the price increase.
Average real estate prices in the neighbouring Sindhudurg region, which is also a popular location for second homes and natural resorts, may rise by 29.2 per cent yearly. Currently, the average cost per square foot is Rs 4,921. Property prices in Sindhudurg have increased on average at a CAGR of 6.8 per cent over the last four years.
Commenting on the MOPA Airport, Aditya Kushwaha, CEO and Director of Axis Ecorp says, "With the imminent inauguration of MOPA Airport, North of Goa and the neighbouring Konkan region are poised to experience a real estate investment boom. The infrastructure of a region is directly tied to the real estate industry. By constructing roads, motorways, and airports, numerous regions become more accessible. The holiday home market is already flourishing, and the aforementioned development will push the demand even further. Consumers looking for a sophisticated lifestyle similar to anywhere else on the globe will be increasingly drawn to this region."
Axis Ecorp has several upcoming projects in this region, which include Axis Blues, Axis Yog Vilas, Axis Lake City and Axis Lake City Plaza. The company has already exhausted its inventory for Axis Blues and as the inauguration of the new airport is drawing close, there has been a strong uptick in the inquiries for the Axis Yog Villas and Axis Lake City. These projects are just 18km away from the greenfield project and thus the valuation for the project is expected to skyrocket in the days to come.
Vinit Dungarwal, Director of AMs Project Consultants, "Infrastructure projects like the metro and airports always have a cumulative effect on real estate development. Goa is a great option for those seeking a second home away from the hustle and bustle of a city. We are quite bullish about the prospective real estate initiatives in the state."
Not just real estate prices but even rental prices in and around the year have seen a sharp uptick. Annual rental yields in the north Goa region are close to 4.1- 7.2 per cent. However, the yields around the MOPA are hovering around 6.7 per cent. Sindhudurg, on the other hand, is delivering outcomes north of 7.2 per cent. These are expected to rise even further once the MOPA airport becomes operational.
If you have any objection to this press release content, kindly contact pr.error.rectification[at]gmail.com to notify us. We will respond and rectify the situation in the next 24 hours.