/* $Id: TestAgainstUmlModel.java 17766 2010-01-11 21:21:20Z linus $
*****************************************************************************
* Copyright (c) 2009 Contributors - see below
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* tfmorris
*****************************************************************************
*
* Some portions of this file was previously release using the BSD License:
*/
// Copyright (c) 2003-2007 The Regents of the University of California. All
// Rights Reserved. Permission to use, copy, modify, and distribute this
// software and its documentation without fee, and without a written
// agreement is hereby granted, provided that the above copyright notice
// and this paragraph appear in all copies. This software program and
// documentation are copyrighted by The Regents of the University of
// California. The software program and documentation are supplied "AS
// IS", without any accompanying services from The Regents. The Regents
// does not warrant that the operation of the program will be
// uninterrupted or error-free. The end-user understands that the program
// was developed for research purposes and is advised not to rely
// exclusively on the program for any reason. IN NO EVENT SHALL THE
// UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
// SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS,
// ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
// THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE. THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY
// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
// PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
// CALIFORNIA HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT,
// UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
package org.argouml.model;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
import java.util.Set;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
/**
* Test against the UML model.
*/
public class TestAgainstUmlModel extends TestCase {
private static Set<String> dontTest = new HashSet<String>();
private static Hashtable<String, String> remap =
new Hashtable<String, String>();
private static boolean uml2 = false;
/**
* The constructor.
*
* @param n the name
*/
public TestAgainstUmlModel(String n) {
super(n);
}
/**
* @throws SAXException when things go wrong with SAX
* @throws IOException when there's an IO error
* @throws ParserConfigurationException when the parser finds wrong syntax
*
* TODO: Unused?
*/
public void testDataModel()
throws SAXException,
IOException,
ParserConfigurationException {
Document doc = prepareDocument();
if (doc == null) {
return; // Could not find model.
}
List<String> classNames = getMetaclassNames(doc);
for (String className : classNames ) {
processClass (className);
}
}
/*
* Get a list of UML metaclass names from the XMI document.
* <p>
* Though some of the DOM methods such as getAttributes
* may return null values under other conditions,
* in the context of this test
* and assuming a valid XMI file
* none should occur.
* <p>
* Hence there is no special checking for those abnormal
* cases, allowing the test to fail simply with a
* NullPointerException, with this comment indicating that
* either the input data is incorrect or the test needs
* to be improved.
*/
private static List<String> getMetaclassNames(Document doc) {
List<String> result = new ArrayList<String>();
int abstractCount = 0;
if (uml2) {
// The UML 2.1.1 metamodel is a MOF 2.0 CMOF model, so
// we want nodes with
// tag="ownedMember", attribute xmi:type="cmof:Class"
// TODO: This code is untested - tfm
NodeList list = doc.getElementsByTagName("ownedMember");
for (int i = 0; i < list.getLength(); i++) {
Node node = list.item(i);
Node typeAttr = node.getAttributes().getNamedItem("xmi:type");
if ("cmof:Class".equals(typeAttr.getNodeValue())) {
if (!isAbstract(node)) {
result.add(getNames(node));
} else {
abstractCount++;
}
}
}
} else {
// Handle UML 1.4 metamodel which is a MOF 1.3 model
NodeList list = doc.getElementsByTagName("Model:Class");
for (int i = 0; i < list.getLength(); i++) {
Node node = list.item(i);
if (!isAbstract(node)) {
result.add(getNames(node));
} else {
abstractCount++;
}
}
}
System.out.println("Skipping " + abstractCount + " abstract elements");
return result;
}
/*
* Get a node's name along with the name of its parent (which we'll use to
* find the factory to create it with.
*/
private static String getNames(Node node) {
// TODO: Do we want the top level package here instead of the immediate
// parent?
// Because UML 1.4 & 2.1 metamodels are organized differently we need
// to traverse the hierarchy looking for our owning Package.
Node pkg = node;
while (pkg != null && !isPackage(pkg)) {
pkg = pkg.getParentNode();
}
if (pkg == null) {
return getName(node);
}
return getName(pkg) + ":" + getName(node);
}
private static boolean isPackage(Node node) {
if ("Model:Package".equals(node.getNodeName())) {
// UML 1.4
return true;
} else {
Node type = node.getAttributes().getNamedItem("xmi:type");
if (type != null && "cmof:Package".equals(type.getNodeValue())) {
// UML 2.x
return true;
}
}
return false;
}
private static String getName(Node node) {
return node.getAttributes().getNamedItem("name").getNodeValue();
}
private static boolean isAbstract(Node node) {
Node isAbstract = node.getAttributes().getNamedItem("isAbstract");
return isAbstract != null && "true".equals(isAbstract.getNodeValue());
}
/**
* Print a message that this test case is inconclusive because of
* the UML file missing.
*
* @param message that is to me printed.
*/
private static void printInconclusiveMessage(String message) {
System.out.println(TestAgainstUmlModel.class.getName()
+ ": WARNING: INCONCLUSIVE TEST!");
System.out.println(message);
System.out.println("You will have to fetch the model using the command"
+ " ant junit-get-model");
}
/**
* Make all preparations for the tests by preparing the document.
*
* @return the document or null if not available.
* @throws SAXException when things go wrong with SAX
* @throws IOException when there's an IO error
* @throws ParserConfigurationException when the parser finds wrong syntax
*/
private static Document prepareDocument()
throws ParserConfigurationException, SAXException, IOException {
DocumentBuilder builder =
DocumentBuilderFactory.newInstance().newDocumentBuilder();
String fileName = System.getProperty("test.model.uml");
if (fileName == null) {
printInconclusiveMessage("The property test.model.uml "
+ "is not set.");
return null;
}
File file = new File(fileName);
if (!file.exists()) {
printInconclusiveMessage("The file " + fileName
+ " cannot be found.");
return null;
}
Document document = builder.parse(file);
Element root = document.getDocumentElement();
NamedNodeMap attributes = root.getAttributes();
Node versionNode = attributes.getNamedItem("xmi:version"); // XMI 2.1
if (versionNode == null) {
versionNode = attributes.getNamedItem("xmi.version"); // XMI 1.1
}
String version = versionNode.getNodeValue();
// This is the XMI version used to encode the metamodel. We could
// parse deeper to pull out the actual UML version. The UML 1.4
// version is at XMI/XMI.header/XMI.model[@xmi.version]. The UML 2.1.1
// metamodel doesn't actually seem to contain its version.
if ("1.1".equals(version)) {
uml2 = false;
} else if ("2.1".equals(version)) {
uml2 = true;
} else {
System.out.println("Unknown metamodel type");
}
return document;
}
/**
* Walk through the UML Classes found.
*/
private void processClass(String className) {
// Remap specific classes
String name;
if (remap.containsKey(className)) {
name = remap.get(className);
} else {
name = className;
}
String[] pieces = name.split(":");
String umlclass = pieces[1];
String pkgName = pieces[0].replaceAll("_", "");
// Only remap package if we didn't remap specific class
if (className.equals(name) && remap.containsKey(pkgName)) {
pkgName = remap.get(pkgName);
}
String getter = "get" + pkgName + "Factory";
try {
Method getMethod = Model.class.getMethod(getter, new Class[0]);
Object factory = getMethod.invoke(null, new Object[0]);
if (!(factory instanceof Factory)) {
fail("Factory for " + name
+ "isn't an instanceof Model.Factory");
}
if ( dontTest.contains(umlclass) || dontTest.contains(name)) {
System.out.println("Skipping " + name);
return;
}
String[] classarg = {umlclass, null};
CheckUMLModelHelper.createAndRelease(factory, classarg);
} catch (Exception e) {
fail("Failed to get factory for " + name + " - " + e);
}
}
/**
* @return the test suite
*/
public static Test suite() {
TestSuite suite =
new TestSuite("Tests for "
+ TestAgainstUmlModel.class.getPackage().getName());
Document doc = null;
try {
doc = prepareDocument();
} catch (ParserConfigurationException e) {
e.printStackTrace();
} catch (SAXException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
if (doc == null) {
return suite; // Could not find model.
}
for (String metaclassName : getMetaclassNames(doc)) {
suite.addTest(new TestAgainstUmlModel(metaclassName));
}
return suite;
}
protected void runTest() throws Throwable {
processClass(getName());
}
/**
* Initialize the lookup map to link the uml class names
* to the factories.
*
* This brute force method should be investigated
* in favor of determining the Uml Class namespace from
* the UML metamodel and computing the factory
* at run time.
*
* Certain classes that cannot be tested directly in this way
* should be calculated. Event and StateVertex, for example,
* are marked abstract in the model. But we need to make sure
* that the reverse is true, that there are no elements
* marked abstract in the model that in fact are instantiable
* by the model subsystem.
*/
static {
InitializeModel.initializeDefault();
/*
* The following UML 1.4 elements have been removed from UML 2.x, so we
* don't bother testing them.
*/
dontTest.add("Primitive");
dontTest.add("ProgrammingLanguageDataType");
dontTest.add("UseCaseInstance");
dontTest.add("ActivityGraph");
dontTest.add("ActionExpression");
dontTest.add("ArgListsExpression");
dontTest.add("BooleanExpression");
dontTest.add("IterationExpression");
dontTest.add("MappingExpression");
dontTest.add("ObjectSetExpression");
dontTest.add("ProcedureExpression");
dontTest.add("TimeExpression");
dontTest.add("TypeExpression");
// TODO: We'd like to test this in its new guise as PackageImport, but
// we don't have a good way to do it currently
dontTest.add("Permission");
/*
* A few of our factories are slightly different than as declared in the
* UML 1.4 metamodel, so we remap them here. <metamodel, argouml>
*/
remap.put("Core:Stereotype", "ExtensionMechanisms:Stereotype");
remap.put("Core:TaggedValue", "ExtensionMechanisms:TaggedValue");
remap.put("Core:TagDefinition", "ExtensionMechanisms:TagDefinition");
/*
* The UML 2.x package structure is *entirely* different, so we have to
* remap a bunch of stuff. Names without embedded colons (:) indicate
* that the entire package is remapped. e.g. Kernel->Core As a matter of
* fact the only package which did NOT get renamed or moved is UseCases.
*
* TODO: This section is very incomplete. - tfm
*/
// Specific classes to be remapped
// remap.put("", "");
remap.put("Kernel:Expression", "DataTypes:Expression");
// Packages to be remapped
remap.put("Kernel", "Core");
remap.put("Interfaces", "Core");
remap.put("Dependencies", "Core");
remap.put("Nodes", "Core");
remap.put("SimpleTime", "Core");
remap.put("AssociationClasses", "Core");
remap.put("Communications", "StateMachines");
remap.put("BehaviorStateMachines", "StateMachines");
remap.put("ProtocolStateMachines", "StateMachines");
remap.put("Models", "ModelManagement");
/*
* For those things which we've already migrated to UML 2.x syntax
* we need to map them back to their UML 1.4 equivalents during the
* migration period.
*/
// TODO: Except this won't work because the names are different which
// will cause a test in CheckUMLModelHelper to fail
// remap.put("Core:Permission", "Core:PackageImport");
}
}