Survey
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
IT University of Copenhagen (ITU) 2 weeks project Reverse Engineering Analyzing dependencies between Models and custom java code in OFBiz By Leif Bochis Madsen [email protected] Shokran Ibrahim [email protected] Supervisor Anders Hessellund [email protected] May 2007 Contents: Preface and introduction ...................................................................................................................... 3 Background and description of the problem ........................................................................................ 3 Problem description ......................................................................................................................... 4 OFBiz service engine ................................................................................................................... 5 Abstract Syntax Tree .................................................................................................................... 6 Requirements for the solution .............................................................................................................. 7 Technical description of the implementation ....................................................................................... 7 General: OFBiz Service definitions ............................................................................................. 8 Xml Parser, extraction from service definition xml files ................................................................. 8 Extraction from the java source files ............................................................................................... 9 Java implementation of OFBiz services....................................................................................... 9 Arguments .................................................................................................................................. 10 Return values.............................................................................................................................. 10 Traversing the AST .................................................................................................................... 12 Persisting the extracted information .............................................................................................. 13 An Ecore model of an OFBiz Java customization ..................................................................... 13 Persisting the generated Service information ............................................................................. 14 Comparison of the information ...................................................................................................... 15 Implementation .................................................................................................................................. 15 Controlservice ............................................................................................................................ 15 Xmlparser ................................................................................................................................... 15 Traverser .................................................................................................................................... 16 Testing ................................................................................................................................................ 16 Testing the application ............................................................................................................... 16 Conclusion ......................................................................................................................................... 17 References: ......................................................................................................................................... 18 Appendix: Source code ...................................................................................................................... 19 package controlservice ................................................................................................................... 19 package xmlparser.......................................................................................................................... 23 package traverser............................................................................................................................ 27 2 Preface and introduction This report has been written in May 2007 as a two-week project (four weeks, half-time) under the supervision of Anders Hessellund. The project has been part of the Advanced Models and Programs course (AMP) at the IT University of Copenhagen (ITU). This project is a reverse engineering project with the purpose to fill the gap between models and custom code in model driven frameworks. It is not possible to check the dependencies between models and the customizations in the model driven frameworks until now. We use an example framework, namely OFBiz, the Apache Open For Business. OFBiz is an open source project and an excellent example of a state-of-the-art, industrial-strength application for building enterprise automation software, such as Content Management Systems (CMS), Customer Relationship Management (CRM), and eCommerce systems, etc. We were aiming to introduce SmartEMF [ref. 1] which is an extension of Eclipse Modelling Framework (EMF) as an interface for our program but because of time limitation, currently we use the console. Background and description of the problem The frameworks mentioned in the preface have in common that they are based on a Model-ViewControl design pattern and the interfaces between different parts of the systems are commonly described in xml. The problem is that there is lacking a way to automatically check the conformance between interface definitions and the actual implementations that apply these interfaces. In this section we give an overview of the problem and the techniques used to solve it and explain the OFBiz’s service engine and java’s Abstract Syntax Tree. 3 Problem description The purpose of the project is to check the dependencies between models and customizations. The model is described in an xml document containing definitions of the services used by the framework. These services are implemented in java and the java code is available as source code in a location also described by the xml document. The overall idea of the project is illustrated by the diagram below: The xml file is parsed and the data about localization of java source code and the service interface retrieved. The relevant java source file is then read and an abstract syntax tree constructed, from which interface information is extracted through a traversal of the tree. The two interfaces – extracted from the xml service definition as well as the java source code – are then compared and possible contradictions between them easily detected. That’s the theory. Service definitions Extract interfaces Location info to identify java source Construct java AST Compare java AST java interface Extract interface 4 OFBiz service engine OFbiz provides some useful practice tools to make enterprise software with independent artifacts in various tiers and application processes they can be easily reuse and work together even in sometimes implemented using different kind of tools. The services can be invoked from few places typically an event can be implemented as a java method or as service and the input parameters and output parameters are mapped to the service as attributes. The services are implemented as a java or a simple method. A java method is basically a static java method has an input context which holds information about service engine and related entity, security context, etc. and it has a map as input parameters and will return a map to the method that invoked the service. Service Engine: Definition Services *.xml: <service…./> Java method 5 Abstract Syntax Tree In order to be able to retrieve the data from java files, we shall take a look at the Abstract Syntax Tree (AST) for java files. The purpose of an AST is to represent the source code in a structured way for further processing. You could say, that construction of an AST takes human readable tokens into a tree structure to make it easy to computer to understand and process by traversal. These input tokens from java file are represented in the two-dimensional structure of the tree in the AST. AST’s are used in general for compilation of source code. In the context of our project we use the AST constructor which is part of the Eclipse Java Development Tools. The purpose of this AST constructor is to support the Eclipse Development Environment in e.g. resolving links between different modules, helping with refactorings etc. Ex. java method: Method definition public int length() { return super.size(); } The AST of the length method The example above shows how the java file is represented in AST [ref. 6] 6 Requirements for the solution OFBiz belongs to a large family of frameworks using the Model-View-Control design pattern to separate business logic from presentation. A range of these frameworks also use xml to describe interfaces between different parts of the system. The overall problem is about checking consistency between the xml interface definitions and the actual implementation. The requirements for a solution to this kind of problem involve extraction of information about the actual implementations and compare this information with the descriptions published in the xml service definitions. As mentioned earlier, the object for this case study is the OFBiz framework and the relation between xml service definitions and services implemented in java. Specifically, it should be possible to extract the information about arguments to and return values from java methods implementing an OFBiz service. This information should comprise names and types of parameters. Furthermore, it should be possible to compare the extracted information with the information published in the service definition in order to determine whether the implemented methods conform to the published interfaces. Additional requirements about user interface, adoption to certain, popular frameworks and other kinds of usability could have been described, too. However, we have not had any intentions to spend our spare time on such issues but focussed in stead on the subject of analyzing models and extracting information from models of java programs. Technical description of the implementation As mentioned in the requirements section, the implementation of services in the OFBiz framework consists of two parts: 1) A description of a service definition in an xml file 2) An implementation of the service by a method in a java source file Our case study consists of several parts to fulfil each requirement specified in the list of requirements. First, extract the needed information from the service definition xml files. Second, construct an Abstract Syntax Tree from the java source files and traverse the AST while producing information about the arguments and return values of the methods analyzed. Third, persist the produced information to an xmi file according to a model of a java service implementation. 7 Fourth, compare the information extracted from the service definition xml files with the information extracted from the java source files. General: OFBiz Service definitions The service interface is described to other parts of the framework through an xml file as shown in fig. below. From this example we can extract the information that the service – in order of appearance – has the name calcBillingAccountBalance, is implemented as a java method, is located in the package org.ofbiz.accounting.payment in class BillingAccountWorker and is invoked by a call to the method calcBillingAccountBalance. Service interface definition <services> <service name="calcBillingAccountBalance" engine="java" location="org.ofbiz.accounting.payment.BillingAccountWorker" invoke="calcBillingAccountBalance"> <description>Calculate the balance of a Billing Account</description> <attribute name="billingAccountId" type="String" mode="IN" optional="false" /> <attribute name="accountBalance" type="Double" mode="OUT" optional="false" /> <attribute name="netAccountBalance" type="Double" mode="OUT" optional="false" /> <attribute name="availableBalance" type="Double" mode="OUT" optional="false" /> <attribute name="availableToCapture" type="Double" mode="OUT" optional="false" /> <attribute name="billingAccount" type="GenericValue" mode="OUT" optional="false"/> </service> </services> Furthermore, the method accepts an argument billingAccountId of type String and returns five values – billingAccount of type GenericValue and accountBalance, netAccountBalance, availableBalance and availableToCapture all of type Double. None of the arguments or return values is optional. Xml Parser, extraction from service definition xml files The parser reads the XML file and creates a class for each services file where each service turns to a method with its attributes. The program uses node iterator for linear traverse of DOM-document, then creates the corresponding class, methods and attributes. The diagram below show the parsing action that turns the service in the xml file into an object oriented (OO) model . 8 <services> <service name="calcBillingAccountBalance" engine="java" location="org.ofbiz.accounting.payment.BillingAccountWorker" invoke="calcBillingAccountBalance"> <description>Calculate the balance of a Billing Account</description> <attribute name="billingAccountId" type="String" mode="IN" optional="false" /> <attribute name="accountBalance" type="Double" mode="OUT" optional="false" /> <attribute name="netAccountBalance" type="Double" mode="OUT" optional="false" /> <attribute name="availableBalance" type="Double" mode="OUT" optional="false" /> <attribute name="availableToCapture" type="Double" mode="OUT" optional="false" /> <attribute name="billingAccount" type="GenericValue" mode="OUT" optional="false"/> </service> </services> XML file XML Parser Methods Attributes s Method name: Attribute name: calcBillingAccountBal ance billingAccountId type: String mode: IN optional: false location: org.ofbiz.accounting. payment.BillingAccoun tWorker Attributes Attribute name: accountBalance type: Double mode: OUT optional: false This diagram shows only 2 examples out of 6 Attributes Extraction from the java source files Java implementation of OFBiz services All services in the OFBiz framework implemented in Java share a common signature, where arguments are passed to the Java method and return values passed back as named parameters wrapped in single objects of type Map. Thus, a method declaration implementing a service will conform to the following header: public Map myService (DispatchContext dispatchContextPointer, Map arguments) 9 Arguments Inside the method the extraction of argument(s) have the following canonical format: Type myArgument = (Type) arguments.get(“argumentName”); An AST representation of this extraction is shown in fig. 1. Fig. 1: AST representation of a typical (canonical) argument extraction All services studied conform to this pattern which makes it quite easy to extract the names and types of arguments from the AST representation by the help of a visitor that may identify all CastExpressions inside the method body with the following characteristics: 1) A SimpleType node with a SimpleName representing the type name 2) A MethodInvocation node with two SimpleName nodes representing the name of the parameter (“context” in fig. 1) and the name of the method (“get”) that extracts a value from a Map variable followed by a StringLiteral containing the name of the argument as published in the interface definition in the xml file The StringLiteral might possibly be replaced by an expression containing a string variable containing the name of the argument. Because we haven’t found any example using this scheme, we have abstained from the implementation. It should, however, not be too complicated also to extract the type name from this way of programming the extraction. Return values Construction of the return values, however, may be done in different – slightly more complicated – ways. Generation of return values: The Map.put() method call A simple example, generating 2 return values, the method may contain the following statements: 10 String myValue1 = <some expression>; int myValue2 = <some other expression>; Map myReturnValues = ServiceUtil.returnSuccess(); myReturnValues.put(“myValueName1”, myValue1); myReturnValues.put(“myValueName2”, myValue2); return myReturnValues; Here, in bold we have emphasized the names and types we want to compare with the service definition and in italics internal names that may be used to map types and local variables. Fig. 2: AST representation of using the Map.put() method for storing return values. The method call ServiceUtil.returnSuccess() returns an instance of type Map. It also adds an element named Success to the Map. Likewise, the way to report errors from service calls are done through a call to a method ServiceUtil.returnError() which also initializes a Map instance. For example, a call ServiceUtil.returnError(“some error message”); initializes a Map instance and adds an element named “Error” of type string with the passed message as value. This Map may be tied directly as an argument to the return statement or to a variable applied by that. In the rest of our analysis we shall almost ignore this part of the interface scheme – including the special case of error reporting – and concentrate on the ‘real’ parameters passed. However, the error reporting from the services and alternative ways to fill the return values do complicate the detection of output values a bit. In the case described above it is possible to extract the information by 1) Initializing a set of variable names together with their types from the variable declarations 2) Catch the name of the return variable from the return statement 3) Extract all put method calls to the return variable Even easier, it should be possible to extract the relevant information from the type bindings of the AST nodes. This, however, has not yet been possible to get to work. Our findings in this respect showed that the AST constructor simply ignores a request for resolving method and type bindings if 11 it hasn’t been equipped with some information about class path and project, i.e. the AST constructor should work in the context of a Java Model – not just on a mere source file [see further ref. 4]. When trying to initialize a Java Model we met an error message about missing workspace – and that is the state of these investigations just before project deadline. Generation of return values: The UtilMisc.toMap() method call Variables of type Map, however, may also be constructed in several other ways, for example, by alternative method calls, like in the following template: TypeName1 firstValue = <some expression>; TypeName2 secondValue = <some expression>; Map result = UtilMisc.toMap("firstName", firstValue, "secondName", secondValue); return result; Again, in bold the information we want to extract and in italics the internal information needed to connect the information. Fig. 4: AST representation of using the UtilMisc.toMap() method for storing return values. The method UtilMisc.toMap() has seven overloads: Six with 1, 2, .., 6 name/value pairs and one with an object array of even numbered size. Also, any other method returning or modifying an instance of a Map variable may be used. In our implementation, we have considered these two ways to fill a map instance – the Map.put() and the UtilMisc.toMap() method calls – and have chosen to ignore any other way to return values from a service. Traversing the AST Construction as well as traversal of the Abstract Syntax Tree has been implemented through the use of the Eclipse Java Development Tools library [ref. http://help.eclipse.org]. This library offers type declarations for all kinds of nodes constructed by the AST generator and an abstract class ASTVisitor. The traversal has been implemented by writing a set of visitor subclasses according to 12 the visitor design pattern [see: Gamma et al.: Design patterns, 1994]. Examples of the implementation may be viewed in the appendix. Persisting the extracted information An Ecore model of an OFBiz Java customization The Eclipse Modelling Framework (EMF) is an implementation of the Meta Object Facility (MOF) for the Eclipse IDE. Ecore (EMF Core) is thus equivalent to the Essential MOF, i.e. a core modelling tool for Eclipse. Using Ecore we could easily describe a model of the information extracted from the java source files with the benefit of having access to an API that helps in persisting this information. Therefore, using the Ecore modelling language, we defined a model of a java method implementing an OFBiz service. This model could then be used to describe each of the java methods implementing a concrete service interface as shown in fig. 3. The model should reflect the following characteristics: 1) Java services in the OFBiz Framework are identified by a single method in a java class. 2) Java Customization files in the OFBiz Framework consists of a single class with one or more methods - and each method with a set of parameters. This model comprises three classes to describe the distinct objects: Classes, Methods and Parameters. A class may contain several methods and a method may contain any number of arguments and return values. The parameters are defined by a name and a type and in this respect there is no difference between the definition of arguments and return values as both kinds of parameters are carried by instances of type Map. The distinction between input and output could alternatively have been modelled through an extra attribute to the ParameterClass class – just like the mode attribute in the service definition (see fig. 3) – but we have chosen in stead to describe this feature as two distinct references in the MethodClass class, because we think the distinction between input and output is quite fundamental to a method and therefore should be addressed here. Obviously, the viewpoint from which this model has been developed is the viewpoint from extracting information from a java customization. The purpose of the project has been to make it possible to check the consistency between instances of this model and instances of service definition models, i.e. we presume the existence of a defined model for service definitions – a model that explicitly is reflected by the service definitions in the xml files. 13 ClassClass className: String 0 * MethodClass methodName: String 0 0 * * arguments returnValues 1 1 1 1 ParameterClass parameterName: String parameterType: String Fig. 3: Model of services (from java method perspective) Persisting the generated Service information The definition of an Ecore model of a service makes it possible to export the analysed java methods for other purposes. One of the examples – the calcBillingAccountBalance service – may thus be persisted to an XMI representation as shown below. <?xml version="1.0" encoding="ASCII"?> <serviceInterface:ClassClass xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:serviceInterface="http:///dk.itu.serviceinterface.ecore" className="BillingAccountWorker"> <methods methodName="calcBillingAccountBalance"> <arguments parameterName="billingAccountId" parameterType="String"/> <returnValues parameterName="billingAccount" parameterType="GenericValue"/> <returnValues parameterName="accountBalance" parameterType="Double"/> <returnValues parameterName="netAccountBalance" parameterType="Double"/> <returnValues parameterName="availableBalance" parameterType="Double"/> <returnValues parameterName="availableToCapture" parameterType="Double"/> </methods> </serviceInterface:ClassClass> 14 This representation may then be imported by another kind of analysis tool – for example the SmartEMF tool [ref. 1] – and used for e.g. overall checking of model consistency. Comparison of the information The comparison is implemented as a java method that compares the persisted model instances from above to check the equality between the information extracted from the java methods and the information from the xml service definitions. The two instances are considered equal if they share the same interface, i.e. if the names and types of arguments and return values are the same in the xml service definition and in the java method. The implementation may be viewed in the appendix. Implementation Implementation details may be viewed in the appendix. The purpose of this section is to serve as an introduction to the appendix while describing the roles of the different classes. First of all, this case study hasn’t resulted in a finished application ready for use but only in a java project comprising the source files needed to run the program and do the analysis. The java project has been organized in three packages, controlservice for running and controlling the application, xmlparser for extracting information from the xml service definitions and traverser for constructing and traversing the AST and extract information from java source files. Controlservice Contains a static class ControlService containing a main() method and a compare() method. Method main() initializes everything and constructs the proper class instances for extraction of information from xml service definitions as well as java source files and, eventually, compares the extracted information. Method compare() compares the outcome from the extractions. Xmlparser Contains: 1) A model of the service through the classes Class, Method and Attribute representing a service interface 15 2) A class XmlParserMain that takes care of the role of extracting the information from a particular xml file and construct and populate instances of the classes of the model described. Traverser Contains a class Traverser that takes care of construction of the AST for a java source file and for the initiating the traversal of the AST. The class Traverser contains the methods parseIt() responsible for construction of an AST and traverse() responsible for traversing the AST through a set of visitors. Furthermore, the traverser package contains a number of visitors, subclasses of an ASTVisitor class, each responsible for the extraction certain parts of the information needed. Class MyASTVisitor is the main visitor for a java source file. It also contains methods needed for constructing a representation of the Ecore model (see Technical description) and for persisting the collected data. Classes InputParameterVisitor, LocalVariableVisitor, subVisitor and OutputParameterVisitor are visitors responsible for collection of argument names and types, names and types of local variables, information about return statements and return value names and types, respectively. Testing Testing the application The application has been developed to meet the demands of two services in the OFBiz Framework: 1) Method calcBillingAccountBalance in the BillingAccountWorker class in the package org.ofbiz.accounting.payment 2) Method simpleTaxCalc in the SimpleTaxServices class in the package org.ofbiz.accounting.tax The two methods are characterized by the two different ways of passing values to the return variable. Method (1) uses the put method of class Map, while (2) uses the static method UtilMisc.toMap to pass the values. Afterwards the application has been tested extensively towards a range of the services in the framework. The result of this testing shows that java methods strictly conforming to the selected way (‘canonical way’) of extracting arguments and supplying return values are analyzed well by the application, java methods that use other means are not! 16 The application should be regarded as a prototype with the purpose of investigating the possibilities of extracting actual arguments and return values. Therefore, we haven’t applied automated testing techniques in order to verify correctness of the application. The testing has thus been concentrated on merely controlling that the output generated conformed to the values expected from extracting the same information from the definitions in the xml files describing the service interfaces. Conclusion In this project we have succeeded in extracting argument and return value names and types from some java methods used as customizations in the Apache OFBiz framework. This was achieved by a reverse engineering effort on java methods via the Eclipse AST representation of java source files. Through this effort it was possible to compare the interface to these methods with the corresponding service definitions published in the relevant xml files of the OFBiz framework. For a range of services it was possible to check the conformance between the service definition and the implementation – as long as the implementing java methods conform to the short range of canonical ways to extract arguments and supply return values. However, when java methods e.g. supply return values by a call to some secondary method responsible for filling a Map variable, we have not succeeded in extracting names and types of these return values. According to the description of the Abstract Syntax Tree it should be possible to resolve bindings of types and methods upon the construction of the AST. Thus, theoretically, it should be possible to delegate the search for return values further by recursive investigation of methods filling values into a return variable. Unfortunately, we haven’t (yet) succeeded to get method bindings work for our example studies, why we couldn’t examine this road further. As described in technical description we have gained some experience in that field, though. Besides, one should be aware that an AST is just a representation of the presently available source code, which means that it is only possible to extract information from methods where the source code is available, and not when some java method is called through a compiled jar file (this might be of lesser importance, probably the number of standard Map filling routines are limited). Still, it should be relevant to further investigate the possibilities of traversing method and type bindings that it should be possible to decorate the AST with, according to the literature. Eventually, considering this was a case study in the field of turning java source files into models, a lot of things in the implementation are of a primitive and intermediate kind. For example, the local variables of the java methods are merely extracted to a list without regard to the scoping inside the java method – this, of course, is relevant for deciding which of the local variables that are actually 17 visible to the methods generating return values. In other words, some of the visitors could have been more intelligent than the actual implementation suggests. In the context of the studied services, however, this has not been necessary. All the short comings despite, this case study shows that it is possible –at least to a certain extent – to check the conformance between xml service definitions and java implementations automatically. The principles from above isn’t limited to the OFBiz framework, as the concept of modelling frameworks supported by customizations written in e.g. java plays a role in current trends in software development. References: [1] Anders Hessellund, Krzysztof Czarnecki, and Andrzej Wasowski: Guided Development with Multiple Domain-Specific Languages http://www.itu.dk/people/hessellund/AMPpt2s07/Hessellund07a.pdf [2] The Apache Open for Business Project Homepage, http://ofbiz.apache.org/ (Last viewed, May 22nd, 2007) [3] Manoel Marques: Exploring Eclipse's ASTParser - How to use the parser to generate code, 2005 http://www-128.ibm.com/developerworks/opensource/library/os-ast/ (Last viewed, May 18th, 2007) [4] Thomas Kuhn, Olivier Thomann: Abstract Syntax Tree, 2006 http://www.eclipse.org/articles/article.php?file=Article-JavaCodeManipulation_AST/index.html (Last viewed, May 18th, 2007) [5] Elena Litani, Ed Merks and Dave Steinberg: Discover the Eclipse Modeling Framework (EMF) and Its Dynamic Capabilities, 2005 (artikel om EMF, Ecore og dynamisk modelgenerering - incl. demo-kode) http://www.devx.com/Java/Article/29093/1954?pf=true (Last viewed, May 18th, 2007) [6] Terence Parr: Trees Construction For Java in: Programming Languages, course notes http://www.cs.usfca.edu/~parrt/course/652/lectures/java.ast.html (Last viewed, May 22nd, 2007) [7] Budinsky et al.: Eclipse Modeling Framework, 2003 [8] The Eclipse Modeling Framework (EMF) Overview, 2006 http://dev.eclipse.org/viewcvs/indextools.cgi/org.eclipse.emf/doc/org.eclipse.emf.doc/references/ov erview/EMF.html [Last viewed, May 22nd, 2007] 18 Appendix: Source code package controlservice package controlservice; import import import import import import import import import import import traverser.Traverser; xmlparser.*; xmlparser.Class; java.io.*; java.util.ArrayList; java.util.LinkedList; java.util.Iterator; java.util.Properties; javax.xml.parsers.DocumentBuilder; javax.xml.parsers.DocumentBuilderFactory; javax.xml.parsers.ParserConfigurationException; import org.w3c.dom.Document; import org.w3c.dom.NodeList; import org.w3c.dom.*; public class ControlService { static boolean debugMode = false; static static static static static String String String String String ofbizRoot; projectRoot; applPath; outputPath; ecoreModelURI; public static void main(String[] args) { init(); // initializes environment String subAppl = "accounting/"; String xmlServicePath = ofbizRoot + applPath + subAppl + "servicedef/"; String javaSourcePathRoot = ofbizRoot + applPath + subAppl + "src/"; // three relevant examples... analyzeService(xmlServicePath, javaSourcePathRoot, "services_billing.xml"); analyzeService(xmlServicePath, javaSourcePathRoot, "services_tax.xml"); analyzeService(xmlServicePath, javaSourcePathRoot, "services_invoice.xml"); } private static void analyzeService(String xmlPath, String javaPath, String service) { report("==========================================================", false); report("First checking services in " + service, false); // First, extract info from OFBiz service definition XmlParserMain xp = new XmlParserMain(xmlPath + service); xp.parseXml("java"); // finds services of engine "java" LinkedList classes = xp.getClasses(); Class fromXml = (Class) classes.getFirst(); // There is only one "class" in current implementation // Second, for each relevant service definition... 19 for (int i=0; i<fromXml.getMethods().size(); i++) { Method m = (Method) fromXml.getMethods().get(i); report("-------------------------------------------------------", false); report("checking services in '" + fromXml.getName() + "'.", false); // find the corresponding java source file String javaClassFile = m.getLocation().replace(".", "/") + ".java"; String methodName = m.getInvoke(); // construct an AST try { Traverser trav1 = new Traverser(methodName, javaPath + javaClassFile, ecoreModelURI, outputPath); String result = trav1.traverse(); if (m.getAttributes().size() > 0) { // and check the equivalence System.out.println("Comparison: service & java class:"); if (Compare(fromXml, result)) report(">>>>>>>>>>>>>>>>>>>>>>>>>>>>> Definitions EQUALS!", false); else report(">>>>>>>>>>>>>>>>>>>>>>>>>>>>> Definitions NON-EQUALS!", false); } else { report("No attributes in service/method: " + m.getName(), false); } } catch (NullPointerException npe) { showError("NullPointerException", npe.getMessage()); } } } public static void report(String s) { report(s, true); } public static void report(String s, boolean onlyDebug) { if ((!onlyDebug) || (debugMode)) System.out.println(s); } static boolean Compare(Class fromXml, String xmiFileName) { boolean returnValue = true; // optimistic! ArrayList<Attribute> definedArgs = new ArrayList<Attribute>(); ArrayList<Attribute> definedVals = new ArrayList<Attribute>(); report("... start of comparison", false); // actually, we know there is only one method in the class, // but with a number of attributes for (int i=0; i<fromXml.getMethods().get(0).getAttributes().size(); i++) { Attribute att = fromXml.getMethods().get(0).getAttributes().get(i); if (att.getMode().equals("IN")) 20 definedArgs.add(att); else if (att.getMode().equals("OUT")) definedVals.add(att); else report("problem with defined attribute mode, " + att.getName() + "=" + att.getMode(), false); } DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); try { DocumentBuilder loader = factory.newDocumentBuilder(); Document document = loader.parse(xmiFileName); NodeList args = document.getElementsByTagName("arguments"); returnValue = compareNumbers("Number of arguments", definedArgs.size(), args.getLength()); for (int i=0; i<args.getLength(); i++) { Node pN = (Node)args.item(i).getAttributes().getNamedItem("parameterName"); Node pT = (Node)args.item(i).getAttributes().getNamedItem("parameterType"); report("argument " + pN.getNodeValue() + " : " + pT.getNodeValue(), false); } NodeList vals = document.getElementsByTagName("returnValues"); returnValue = compareNumbers("Number of return values", definedVals.size(), vals.getLength()); for (int i=0; i<vals.getLength(); i++) { Node pN = (Node)vals.item(i).getAttributes().getNamedItem("parameterName"); Node pT = (Node)vals.item(i).getAttributes().getNamedItem("parameterType"); report("return value " + pN.getNodeValue() + " : " + pT.getNodeValue(), false); } // compare equality of arguments for (int i=0; i<definedArgs.size(); i++) { String defName = definedArgs.get(i).getName(); String defType = definedArgs.get(i).getType(); Node argNode = findParameterElementInXMI(args, defName); if (argNode != null) { String extrType = argNode.getAttributes().getNamedItem("parameterType").getNodeValue(); if (extrType.equals(defType)) { report("Extracted and defined types are equal!", false); } else { report("ERROR: Extracted and defined types are NOT EQUAL (" + extrType + "," + defType + ")", false); returnValue = false; } } else { report("ERROR: Defined argument '" + defName + "' not found among extracted arguments!", false); returnValue = false; } } // compare equality of return values 21 for (int i=0; i<definedVals.size(); i++) { String defName = definedVals.get(i).getName(); String defType = definedVals.get(i).getType(); Node valNode = findParameterElementInXMI(vals, defName); if (valNode != null) { String extrType = valNode.getAttributes().getNamedItem("parameterType").getNodeValue(); if (extrType.equals(defType)) { report("Extracted and defined types are equal!", false); } else { report("ERROR: Extracted and defined types are NOT EQUAL (" + extrType + "," + defType + ")", false); returnValue = false; } } else { report("ERROR: Defined return value '" + defName + "' not found among extracted return values!", false); returnValue = false; } } } catch (ParserConfigurationException pce) { report("problem reading xmi file", false); } catch (Exception pce) { report("problem reading or parsing xmi file", false); } report("... end of comparison", false); return returnValue; } static boolean compareNumbers(String s, int defined, int extracted) { report(s + ", defined: " + defined, false); report(s + ", extracted: " + extracted, false); if (defined == extracted) { report("==> EQUALS", false); return true; } else { report("==================> NOT EQUALS", false); return false; } } static Node findParameterElementInXMI(NodeList nl, String name) { for (int i=0; i<nl.getLength(); i++) { Node n = nl.item(i); 22 if (n.getAttributes().getNamedItem("parameterName").getNodeValue().equals(name)) return n; } return null; } private static void init() { Properties properties = new Properties(); try { FileInputStream stream = new FileInputStream("conf/settings.properties"); properties.load(stream); stream.close(); } catch(FileNotFoundException e) { // If data can not be loaded, there is no reason to continue showError("File not found, the program will exit!", e.getMessage()); System.exit(1); } catch(IOException e) { // If data can not be loaded, there is no reason to continue showError("I/O error, the program will exit!", e.getMessage()); System.exit(1); } // Reads configuration information ofbizRoot = properties.getProperty("ofbizRoot"); projectRoot = properties.getProperty("projectRoot"); applPath = properties.getProperty("applPath"); outputPath = properties.getProperty("outputPath"); ecoreModelURI = properties.getProperty("ecoreModelURI"); } private static void showError(String s, String e) { String msg = s + "\n" + e; report(msg, false); } } package xmlparser package xmlparser; import java.util.LinkedList; public class Class { private String name; private LinkedList<Method> methods; private LinkedList<Attribute> attributes; public Class(String name, LinkedList<Method> methods) { super(); this.name = name; this.methods = methods; } public Class(String name) { super(); this.name = name; } public Class(String name, LinkedList<Method> methods, LinkedList<Attribute> attributes) { super(); this.name = name; 23 this.methods = methods; this.attributes = attributes; } public LinkedList<Attribute> getAttributes() { return attributes; } public void setAttributes(LinkedList<Attribute> attributes) { this.attributes = attributes; } public LinkedList<Method> getMethods() { return methods; } public void setMethods(LinkedList<Method> methods) { this.methods = methods; } public String getName() { return name; } public void setName(String name) { this.name = name; } } package xmlparser; import java.util.LinkedList; public class Method { private String name; private String rType; private String location; private String invoke; private LinkedList<Attribute> attributes; public Method() { super(); } public Method(String name, String type) { super(); this.name = name; rType = type; } public Method(String name) { super(); this.name = name; } public LinkedList<Attribute> getAttributes() { return attributes; } public void setAttribute(LinkedList<Attribute> attributes) { this.attributes=attributes; } public String getName() { return name; } public void setName(String name) { this.name = name; } 24 public String getRType() { return rType; } public void setRType(String type) { rType = type; } public String getLocation() { return location; } public void setLocation(String location) { this.location = location; } public String getInvoke() { return invoke; } public void setInvoke(String invoke) { this.invoke = invoke; } } package xmlparser; public class Attribute { private String name; private String type; private String mode; private String optional; public Attribute() { super(); } public Attribute(String name, String type, String mode, String optional) { super(); this.name = name; this.type = type; this.mode = mode; this.optional = optional; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getType() { return type; } public void setType(String type) { this.type = type; } public String getMode() { return mode; } public void setMode(String mode) { this.mode = mode; } public String getOptional() { return optional; } public void setOptional(String optional) { this.optional = optional; } } 25 package xmlparser; import import import import import import import import import java.util.Iterator; java.util.LinkedList; javax.xml.parsers.DocumentBuilder; javax.xml.parsers.DocumentBuilderFactory; org.w3c.dom.traversal.DocumentTraversal; org.w3c.dom.traversal.NodeFilter; org.w3c.dom.traversal.NodeIterator; org.w3c.dom.*; sun.swing.PrintColorUIResource; public class XmlParserMain { String xmlFileName; LinkedList serviceClasses; public XmlParserMain(String xmlFileName) { this.xmlFileName = xmlFileName; } public void parseXml(String engineIdentifier) { System.out.println("starting"); LinkedList classes = xmlParse(this.xmlFileName, engineIdentifier); printClasses(classes); this.serviceClasses = classes; } public LinkedList getClasses() { return this.serviceClasses; } public static LinkedList xmlParse(String fileName,String eng){ LinkedList <Class> classes =new LinkedList<Class>(); try { // load the document from a file: DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder loader = factory.newDocumentBuilder(); Document document = loader.parse(fileName); Class cls=new Class("class1"); NodeList method=document.getElementsByTagName("service"); LinkedList<Method> methods=new LinkedList<Method>(); for(int i=0;i<method.getLength();i++){ Node n=(Node)method.item(i); String tagName=((Element)n).getTagName(); String engine=((Element)n).getAttribute("engine"); String name=((Element)n).getAttribute("name"); String location=((Element)n).getAttribute("location"); String invoke=((Element)n).getAttribute("invoke"); if(tagName.equals("service") && engine.equals(eng) && n.hasChildNodes()){ Method mtd=new Method(name); // added location & invoke mtd.setLocation(location); mtd.setInvoke(invoke); 26 NodeList attribute= n.getChildNodes(); LinkedList<Attribute> attributes=new LinkedList<Attribute>(); for (int ai = 0 ; ai < attribute.getLength() ; ai++ ){ Node na=(Node)attribute.item(ai); if(na.getNodeName().equals("attribute")){ String nodeName=((Element)na).getAttribute("name"); String nodeType=((Element)na).getAttribute("type"); String nodeMode=((Element)na).getAttribute("mode"); String nodeOption=((Element)na).getAttribute("optional"); Attribute atr= new Attribute(nodeName,nodeType,nodeMode,nodeOption); attributes.add(atr); } } mtd.setAttribute(attributes); methods.add(mtd); } } cls.setMethods(methods); classes.add(cls); } catch (Exception ex) { ex.printStackTrace(); } return classes; } public static void printClasses(LinkedList<Class>classes){ Iterator clsitr=classes.iterator(); while(clsitr.hasNext()){ Class thisClass=(Class) clsitr.next(); System.out.println("Class name:"+thisClass.getName()); Iterator mtditr=thisClass.getMethods().iterator(); while(mtditr.hasNext()){ Method thisMethod=(Method) mtditr.next(); System.out.println("\tMethod Name: "+thisMethod.getName()); Iterator atritr=thisMethod.getAttributes().iterator(); while(atritr.hasNext()){ Attribute thisAttribute=(Attribute) atritr.next(); System.out.println("\t\tAttribute name: "+thisAttribute.getName()); } } } } } package traverser package traverser; import import import import import java.io.BufferedReader; java.io.File; java.io.FileNotFoundException; java.io.FileReader; java.io.IOException; import import import import org.eclipse.jdt.core.dom.AST; org.eclipse.jdt.core.dom.ASTNode; org.eclipse.jdt.core.dom.ASTParser; org.eclipse.jdt.core.dom.CompilationUnit; public class Traverser { 27 // context information: String ecoreModelURIPath; String ecoreModelInstanceOutputPath; String name; // name of method implementing service String location; // location (filename of java source file including full path) public Traverser(String name, String location, String modelPath, String outputPath) { this.name = name; this.location = location; this.ecoreModelURIPath = modelPath; this.ecoreModelInstanceOutputPath = outputPath; } public String traverse() { String javaSrc = getFileContents(this.location); ASTNode cu = parseIt(javaSrc); MyASTVisitor v = new MyASTVisitor(this.name, this.ecoreModelURIPath, this.ecoreModelInstanceOutputPath); cu.accept(v); String xmiFile = v.serializeModel(); // generate a XMI file return xmiFile; } protected CompilationUnit parseIt(String lwUnit) { ASTParser parser = ASTParser.newParser(AST.JLS3); parser.setKind(ASTParser.K_COMPILATION_UNIT); parser.setResolveBindings(true); // we need bindings later on parser.setSource(lwUnit.toCharArray()); // set source return (CompilationUnit) parser.createAST(null); } protected String getFileContents(String path){ String text = null; try { File file = new File(path); BufferedReader in = new BufferedReader(new FileReader(file)); StringBuffer buffer = new StringBuffer(); String line = null; while (null != (line = in.readLine())) { buffer.append("\t" + line); buffer.append("\n"); } text = buffer.toString(); } catch (FileNotFoundException fe) { System.out.println("File not found: " + path); } catch (IOException ie) { System.out.println("IO error: " + path); } finally{} return text; } } 28 package traverser; import import import import import import import import import org.eclipse.jdt.core.dom.*; org.eclipse.emf.ecore.*; java.util.*; org.eclipse.emf.ecore.resource.*; org.eclipse.emf.ecore.resource.impl.*; org.eclipse.emf.common.util.*; org.eclipse.emf.ecore.xmi.impl.XMIResourceFactoryImpl; org.eclipse.emf.ecore.xmi.impl.EcoreResourceFactoryImpl; controlservice.*; public class MyASTVisitor extends ASTVisitor { boolean test = false; EPackage myPackage = null; EFactory myFactory = null; EObject myClassInstance = null; String relevantMethodName; boolean methodReturnStatementsRelevant = false; // context information: String ecoreModelURIPath; String ecoreModelInstanceOutputPath; public MyASTVisitor(String relevantMethodName, String modelURIPath, String modelInstancePath) { this.relevantMethodName = relevantMethodName; this.ecoreModelInstanceOutputPath = modelInstancePath; this.ecoreModelURIPath = modelURIPath; deSerializeModel(); // reads the model instead... } public MyASTVisitor(boolean visitDocTags) { super(visitDocTags); // TODO Auto-generated constructor stub } public boolean endvisit(MethodDeclaration m) { methodReturnStatementsRelevant = false; return true; } public boolean visit(MethodDeclaration m) { if (m.getName().toString().equals(this.relevantMethodName)) { EClass eMC = (EClass) myPackage.getEClassifier("MethodClass"); EAttribute eMN = (EAttribute)eMC.getEStructuralFeature("methodName"); EObject meth = myFactory.create(eMC); meth.eSet(eMN, m.getName().toString()); EClass c = myClassInstance.eClass(); EReference mRef = (EReference)c.getEStructuralFeature("methods"); ((List)myClassInstance.eGet(mRef)).add(meth); ControlService.report("Relevant method name = " + m.getName(), false); 29 ControlService.report(" return type = " + m.getReturnType2().toString(), false); ControlService.report(" binding: " + (m.resolveBinding() == null ? "NULL" : m.resolveBinding().toString()), false); for (int i=0; i<m.parameters().size();i++) { SingleVariableDeclaration svd = (SingleVariableDeclaration) m.parameters().get(i); ControlService.report(" parameter, name=" + svd.getName() + ", type=" + svd.getType().toString()); } // preliminary catch of Map field name SingleVariableDeclaration svd2 = (SingleVariableDeclaration) m.parameters().get(1); String searchName = svd2.getName().toString(); // collect arguments InputParamVisitor ipv = new InputParamVisitor(searchName); m.accept(ipv); ipv.printParam(); EClass eP =(EClass) myPackage.getEClassifier("ParameterClass"); EAttribute ePN = (EAttribute)eP.getEStructuralFeature("parameterName"); EAttribute ePT = (EAttribute)eP.getEStructuralFeature("parameterType"); EClass mc = meth.eClass(); EReference pRef = (EReference)mc.getEStructuralFeature("arguments"); if (pRef == null) ControlService.report("pRef NULL"); else ControlService.report("pRef not-NULL"); for (int i=0; i < ipv.alFields.size(); i++) { String fname = (i < ipv.alFields.size() ? ipv.alFields.get(i) : "UNINITIALIZED"); String ftype = (i < ipv.alTypes.size() ? ipv.alTypes.get(i) : "UNINITIALIZED"); EObject param = myFactory.create(eP); param.eSet(ePN, fname); param.eSet(ePT, ftype); ((List)meth.eGet(pRef)).add(param); } subVisitor sb = new subVisitor(); m.accept(sb); // collects a list of variable names used in a return statement // collect local variables LocalVariableVisitor lvd = new LocalVariableVisitor(); m.accept(lvd); Map localvars = lvd.returnVariables(); // collect outputparameters OutputParamVisitor opv = new OutputParamVisitor(sb.names, localvars); m.accept(opv); opv.printParam(); EReference rvRef = (EReference)mc.getEStructuralFeature("returnValues"); for (int i=0; i < opv.alFields.size(); i++) { EObject param = myFactory.create(eP); param.eSet(ePN, opv.alFields.get(i)); param.eSet(ePT, opv.alTypes.get(i)); ((List)meth.eGet(rvRef)).add(param); } 30 } return false; } public boolean visit(MethodInvocation mi) { for (Iterator iter = mi.properties().keySet().iterator(); iter.hasNext();) { ControlService.report(" property: " + iter.toString()); } for (int i=0; i < mi.arguments().size(); i++) { ControlService.report(" argument: " + mi.arguments().get(i).toString()); } for (int i=0; i<mi.typeArguments().size(); i++) { ControlService.report(" argumenttype: " + mi.typeArguments().get(i).toString()); } return false; } public boolean visit(TypeDeclaration m) { EClass eCC = (EClass) myPackage.getEClassifier("ClassClass"); myClassInstance = myFactory.create(eCC); myClassInstance.eSet(eCC.getEStructuralFeature("className"), m.getName().toString()); return true; } public void deSerializeModel() { // create from persisted model: URI uri = URI.createFileURI(ecoreModelURIPath); ResourceSet rst=new ResourceSetImpl(); Resource.Factory ecoreResourceFactory =new EcoreResourceFactoryImpl(); Resource.Factory.Registry reg = Resource.Factory.Registry.INSTANCE; Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap().put ("ecore", ecoreResourceFactory); EPackage ecorepack = EcorePackage.eINSTANCE; Resource rs=rst.getResource(uri,true); ecorepack=(EPackage) rs.getContents().get(0); rst.setResourceFactoryRegistry(reg); myPackage = ecorepack; } public String serializeModel() { // create resource set and resource ResourceSet resourceSet = new ResourceSetImpl(); // Register XML resource factory resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put("xmi", new XMIResourceFactoryImpl()); String outputXMIFileName = this.ecoreModelInstanceOutputPath + "/" + this.relevantMethodName + ".xmi"; 31 Resource resource2 = resourceSet.createResource(URI.createFileURI(outputXMIFileName)); resource2.getContents().add(myClassInstance); try { resource2.save(null); ControlService.report("serialized new fashion resource"); } catch (java.io.IOException ioe) { ControlService.report("IO error ..."); } return outputXMIFileName; } } package traverser; import org.eclipse.jdt.core.dom.*; import java.util.*; /** This visitor is intended for searching for extractions of input parameters * the result is two ArrayLists of equal length: alFields and alTypes * */ public class InputParamVisitor extends ASTVisitor { String inputFieldName; // the name of the Map parameter boolean relevantCastExpressionVisited = false; ArrayList<String> alFields = null; ArrayList<String> alTypes = null; boolean debugMode = false; public InputParamVisitor(String inputFieldName) { this.inputFieldName = inputFieldName; alFields = new ArrayList<String>(); alTypes = new ArrayList<String>(); } public boolean visit(ReturnStatement rs) { return false; } /** * The canonical way of extracting the input parameter is something like: * * String myString = (String)inputparameter.get("inputparametername"); * * But might also be: * * String inputparametername = "inputparametername"; * String myString = (String)inputparameter.get(inputparametername); * */ public boolean visit(CastExpression ce) { String s = inputFieldName + ".get("; if (ce.toString().contains(s)) { alTypes.add(ce.getType().toString()); this.relevantCastExpressionVisited = true; return true; } 32 else return false; } public void endVisit(CastExpression ce) { this.relevantCastExpressionVisited = false; } public boolean visit(MethodInvocation mi) { // MethodInvocation is only relevant when part of a CastExpression if (!this.relevantCastExpressionVisited) return false; if (mi.getName().getIdentifier().equals("get")) { StringLiteral sl = (StringLiteral) mi.arguments().get(0); alFields.add(sl.getLiteralValue()); // TODO: Consider indirect naming! } return false; } public void printParam() { report("Parameter(s) resolved from InputParamVisitor:", true); if (alFields.size() != alTypes.size()) report(" something wrong: unequal number of fields and types..."); for (int i=0; i < alFields.size(); i++) { String fname = (i < alFields.size() ? alFields.get(i) : "UNINITIALIZED"); String ftype = (i < alTypes.size() ? alTypes.get(i) : "UNINITIALIZED"); report(" field: " + fname + " of type: " + ftype, true); } } private void report(String s) { report(s, false); } private void report(String s, boolean enforce) { if (debugMode || enforce) System.out.println(s); } } package traverser; import org.eclipse.jdt.core.dom.*; import java.util.*; /** This visitor is intended for searching for extractions of input parameters * the result is two ArrayLists of equal length: alFields and alTypes * */ public class OutputParamVisitor extends ASTVisitor { Map locals; ArrayList<String> returnFieldNames = null; ArrayList<String> alFields = null; ArrayList<String> alTypes = null; // list of possible return variables 33 boolean returnFieldFound = false; boolean insidePutMethodInvocation = false; boolean debugMode = false; public OutputParamVisitor(ArrayList<String> outputFieldNames, Map locals) { this.returnFieldNames = outputFieldNames; this.locals = locals; alFields = new ArrayList<String>(); alTypes = new ArrayList<String>(); } /** * The canonical way of producing output parameters is something like: * * String myString = <some value> * returnvariable.put("outputparametername", myString); * * But might also be: * * UtilMisc.toMap("outputparametername", myString, ... <an even number of arguments> ); * */ /** * An ExpressionStatement of the format: * returnvariablename.put(name, value) * adds a return value to the return Map instance * * Other kinds of ExpressionStatements are irrelevant and should stop traversal */ public boolean visit(ExpressionStatement es) { // if (e.toString().contains("UtilMisc")) return true; boolean relevant = false; for (int i=0;i<this.returnFieldNames.size();i++) { String s = this.returnFieldNames.get(i) + ".put("; if (es.toString().contains(s)) relevant = true; } report("Expression: " + es.toString() + "=" + relevant); return relevant; } /** * A VariableDeclarationStatement may be relevant if it declares a Map * for example: * Map returnvariablename = UtilMisc.toMap(<key,value-pairs>) * */ public boolean visit(VariableDeclarationStatement vds) { if (vds.getType().toString().equals("Map")) { report("vds: Map declaration"); return true; } else return false; } /** * A VariableDeclarationFragment is part of VariableDeclarationStatement * and is relevant if it declares (one of) the return variables, 34 * for example: * Map returnvariablename = UtilMisc.toMap(<key,value-pairs>) * */ public boolean visit(VariableDeclarationFragment vdf) { report("vdf: " + vdf.toString()); report("vdf: " + vdf.getName()); if (returnFieldNames.contains(vdf.getName().getIdentifier())) return true; else return false; } public boolean visit(MethodInvocation mi) { if (mi.getName().toString().equals("put")) // && returnFieldFound) { StringLiteral sl = (StringLiteral) mi.arguments().get(0); String fieldName = sl.getLiteralValue(); alFields.add(fieldName); // TODO: Consider indirect naming! ASTNode node = (ASTNode)mi.arguments().get(1); if (node.getNodeType() == ASTNode.SIMPLE_NAME) { SimpleName sn = (SimpleName) node; alTypes.add(getTypeFromFieldName(sn.getIdentifier())); } else if (node.getNodeType() == ASTNode.STRING_LITERAL) { alTypes.add("String"); } else if (node.getNodeType() == ASTNode.CAST_EXPRESSION) { CastExpression ce = (CastExpression) node; alTypes.add(ce.getType().toString()); } else if (node.getNodeType() == ASTNode.CONSTRUCTOR_INVOCATION) { ConstructorInvocation ci = (ConstructorInvocation) node; //ci. ??? alTypes.add("UnknownCons"); } else if (node.getNodeType() == ASTNode.CLASS_INSTANCE_CREATION) { ClassInstanceCreation cic = (ClassInstanceCreation) node; alTypes.add(cic.getType().toString()); } else alTypes.add("Unknown"); } else if (mi.getName().toString().equals("toMap")) { // a first very primitive try to get values from UtilMisc.toMap invocations int i=0; String fieldName = null; // we presume an even number of arguments... for (Iterator iter= mi.arguments().iterator(); iter.hasNext();) { Expression exp= (Expression) iter.next(); if ((i%2) == 0) 35 { if (exp.getNodeType() == ASTNode.STRING_LITERAL) { StringLiteral sl = (StringLiteral) exp; fieldName = sl.getLiteralValue(); } else fieldName = exp.toString(); alFields.add(fieldName); } else alTypes.add(getTypeFromFieldName(fieldName)); i++; } } return false; } private String getTypeFromFieldName(String fieldName) { String res; report("looking up " + fieldName); if (locals.containsKey(fieldName)) { res = locals.get(fieldName).toString(); report(" --- found!"); } else { res = "Unknown"; // TODO: preliminary 'guessing' the type report(" --- NOT found!"); } return res; } public void printParam() { report("Parameter(s) resolved from OutputParamVisitor:", true); if (alFields.size() != alTypes.size()) report(" something wrong: unequal number of fields and types..."); for (int i=0; i < alFields.size(); i++) { report(" field: " + alFields.get(i) + " of type: " + alTypes.get(i), true); } } 36 private void report(String s) { report(s, false); } private void report(String s, boolean enforce) { if (debugMode || enforce) System.out.println(s); } } package traverser; import org.eclipse.jdt.core.dom.*; import java.util.*; public class LocalVariableVisitor extends ASTVisitor { HashMap vars = null; boolean debugMode = false; public LocalVariableVisitor() { vars = new HashMap<String, String>(); } /** * This is quite primitive, though it works for our examples */ public boolean visit(SingleVariableDeclaration svd) { String identifier = svd.getName().getIdentifier(); String typeName = svd.getType().toString(); vars.put(identifier, typeName); return false; } public boolean visit(VariableDeclarationStatement vds) { String typeName = vds.getType().toString(); for (Iterator iter = vds.fragments().iterator(); iter.hasNext();) { VariableDeclarationFragment f = (VariableDeclarationFragment) iter.next(); String identifier = f.getName().getIdentifier(); vars.put(identifier, typeName); } return false; } public Map returnVariables() { return vars; } } 37