Show in Frame No Frame
Up Previous Next Title Page Index Contents Search

9.2.1 Getting started with your own application

While each API client program is – understandably – different from any other, they quite often share certain common parts. Typically, an API client needs to access various graphs and their contents and representations in order to fetch information for further processing or to change them. Many clients also need to create new conceptual and representational elements.

The MetaEdit+ API interface may initially appear fairly complex but from a client programmer’s point of view, the basic usage of the API remains rather simple. At the core of the interface are three API-specific data types: MEOop, METype and MEAny. Most API commands use these types when passing data as API call arguments or as return values. The types are described in more detail in Section 0, but we will give a brief overview here.

MEOop stands for an object, graph, representation etc., and is the only way of referring to these in the API. There would be no way of returning whole objects or graphs, since these would be of a different class for each object type. Also there would be no way of knowing where to break the chain of objects: asking for a single graph could return the entire contents of the repository. For these reasons MEOop is used instead as a kind of proxy: answers are returned to you as MEOops, and you make your next query using some of those MEOops.

There are two ways to create MEOops: one is to build them from numeric values for their areaID and objectID, obtained for example by parsing the output of the oid command of the MERL generator language. The other, easier, way is to use API methods that return an MEOop (return type MEOop or MEOopArray, also often MEAny contains an MEOop).

METype simply contains a string that stands for a specific type or GOPPRR metatype. For possible uses of METype, consider the following API methods for finding a graph:
*If you just want all graphs of a given type, use allGoodInstances(myType), where myType is an METype whose name is set to the graph type’s name.
*If you want all graphs regardless of type, you can use allSimilarInstances (graphMetaType), where graphMetaType is an METype with name "Graph".
*If you want to search for a specific graph by name, use instancesNamed (graphMetaType, instanceName, typeName), again with graphMetaType being an METype with name "Graph" and instanceName and typeName being strings.
MEAny is used to handle polymorphism, to insulate ourselves from the patchy support offered in different SOAP versions and framework implementations. For example, the value of a property can be a String, a Number, an object (an MEOop), or a collection (an MEOopArray). Since the data type of the return value varies so much, the API method to return a property value returns an MEAny, setting its meType to the name of the data type, and meValue to a string representation of the value. Like MEOop, an MEAny can be created either by assigning proper string values for meType and meValue or by having one returned from an API method.

We can use the Digital Watch example found in the MetaEdit+ demo repository to illustrate how these basic types and API methods are used to put together a non-trivial API client application. We will walk through a program that creates a new Note object, adds it into the WatchFamily diagram, and highlights the newly-added Note. The program is explained below step by step, with pseudo-code examples. Real implementations of this program in C#, Java and C/C++ can be found in the subsequent sections. The API methods called will be linked to their entry in the API Method Reference in Section 9.4.
1)Create a new METype variable called graphType:

METype graphType
2)Pass graphType as the argument for the allGoodInstances method (Section 9.4.1: Types, instances and strings) and store the retrieved graphs into an array of MEOops called graphs:

MEOop[] graphs = allGoodInstances(graphType)
3)Create a new METype variable for new object type called objectType and assign its name:

METype objectType
ObjectType.name = "Note [Watch]"
4)Create a new temporary MEAny variable for null values called nullAny and assign it into the first element of an array of MEAnys called props:

MEAny nullAny
nullAny.meType = "MENull"
nullAny.meValue = ""
MEAny[] props
props[1] = nullAny
5)Create an MEAny variable for Note text and assign it as the first element of an array of MEAnys called values:

MEAny textAny
textAny.meType = "Text"
textAny.meValue = "A new note created by the API"
MEAny[] values
values[1] = textAny
6)Create two further MEAnys called area and np and assign them with nullAny (i.e. create two new null instances):

MEAny area, np
area = np = nullAny
7)To create a new instance of the Note object type, call instProps (Section 9.4.2: Non-Properties and Properties) with objectType, props, values, np and area as parameters and store the return value in a variable called newObjectAny (of type MEAny):

MEAny newObjectAny = instProps(objectType, props, values, np, area)
8)newObjectAny has “MEOop” as meType and a string as meValue. The string has format xx_yyyy, where xx stands of object’s areaID and yyyy its objectID. We need to parse or tokenize this string to separate areaID and objectID and convert them to integers so that we can create a new MEOop for our new object:

MEOop newObject
String[] tokens = <get tokens using "_" as splitter>
newObject.areaID = tokens[1].asInteger
newObject.objectID = tokens[2].asInteger
9)To add our new object into the graph fetched in steps 1 and 2, call addToGraph (Section 9.4.7: Graphs and bindings) with newObject and the first graph in the graphs array as parameter:

addToGraph(newObject, graphs[1])
10)The object instance has now been added to the graph but we still need to add a new object representation into the respective diagram representation of the graph. Therefore, we have to fetch the existing diagrams first:

MEOop[] diagrams = diagrams(graphs[1])
11)To add a new representation for newObject in an existing diagram, create an MEAny variable called place (with “Point” as type and a point string “390,180” as value) and call addNewObjectRepFor (Section 9.4.9: Diagram) with parameters: the first diagram, newObject, an integer denoting the representation’s position in the z-order, and place.

MEAny place
place.meType = "Point"
place.meValue = "400,360"
addNewRepFor(diagrams[1], newObject, 0, place)
12)Finally, to open the diagram and highlight the newly-added object, call animate (Section 9.4.6: Control) with the first graph and newObject as parameters:

animate(graphs[1], newObject)
Running the client against the standard MetaEdit+ demo repository will result in a new Note object with the label “A new note created by the API” appearing in the ‘WatchModels’ diagram, as in Figure 9–2:

Note created by API

Figure 9–2. A new Note created by the API.

Let us have a closer look at a few special usages of MEOops and MEAnys in our example. The allGoodInstances API method returns an MEOopArray containing several graphs of the type you specified. You can find the one you want by using the userPrintString command, which returns the name of the graph. Once you have a graph you want, you can find its contents using the 'Graph contents read' messages, e.g. objectSet. Note also the handy findString(graphOop, objectName), which returns all elements in the graph matching the wildcard string, objectName.

Another thing worth pointing out is the parameters of the instProps method found in step 7. The instProps method can be used to create new instances of any non-property type, as here, and also to change property values of an existing instance. The parameters are the type, the properties, the values, the existing instance (if any), and the project. Let’s look at those in more detail.

The first parameter, receiver, is an METype for the non-property type of the instance we are creating or changing.

The second parameter, propColl, is a collection of MEAnys that refer to the properties to be created or changed. Normally, you do not need to worry about specifying properties, and each element can be an MEAny containing an MENull. It this case, when creating a new non-property, a new property of the default type will be created, and when modifying an existing non-property, its existing properties will be left in place (although their values may be changed by the third parameter). If you do want to explicitly specify properties, e.g. to set up property sharing, each MEAny in this collection can contain an MEOop that refers to a particular existing property.

The third paramenter, valueColl, is a collection of MEAnys that refer to the values to be assigned to the properties. The structure of the collections for the properties and values is shown in Figure 9–3.

The fourth parameter, np, determines whether this is a creation or change. If this MEAny has an MENull as its value, a new instance will be created; if the MEAny has an MEOop as its value, we change the instance referred to by that argument.

The fifth parameter, inArea, is an MEAny containing either an integer for the area number of the home project for the instance, or an MENull for the default (the same project as np if it exists already, or the current default project if creating).

instProps arrays

Figure 9–3. The array structures of parameters of the instProps method.

Most API functions return an MEOop directly, but in this case the function has to also be able to return an MENull value if the object creation failed, and so must return its value as an MEAny. From this MEAny we read the area and object id to reconstruct a true MEOop for the new object (step 8). Once we have that, we can add the new object to the WatchFamily graph (we can use the first graph in the array as we know that there is only WatchFamily graph in our demo repository).

Before we proceed, let us conclude the exploration of our API example with a few words about the final animate operation. While being a relatively simple API operation with its two MEOop parameters (the receiver graph and the object to be highlighted), animate requires some special consideration when appearing in the generated code if you want to use it for visualizing the execution of the code. If that is the case, the strategy of fetching the required MEOops with other API commands as presented in our example above does not work, but rather the oids for the objects to be highlighted must be supplied at generation time. For example, if you are animating the execution of a state machine generated from a state transition diagram, you have to tie the oid of each state with its implementation in the code and use this oid when highlighting the object during the execution. In the generator, you can output the oid with the oid MERL command and parse the areadID and objectID from the generated oid string as in our example above, or alternatively, use projectId and objectId MERL commands to separate the two components already during the generation. For more information about the use of animate for visual debugging purposes please see the Digital Watch example in the MetaEdit+ demo repository and refer to its documentation.

Finally, note that animate will open the diagram if it is not already open (as is the case here). However, if the diagram is already open animate will not refresh the diagram to show any new representations created since it was opened (as we would need it to do here). Thus the WatchModels diagram should be closed before running the API program (or a refresh(newOop) command could be added before animate).

Show in Frame No Frame
Up Previous Next Title Page Index Contents Search