/* $Id: CheckUMLModelHelper.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) 2002-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.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import junit.framework.TestCase;
/**
* This class is a helper class of tests that test
* the model stuff.<p>
*
* This class is used for testing the Model subsystem. It should not have any
* dependency on any specific implementation.
*
* @author Linus Tolke
* @since 0.11.2
*/
public final class CheckUMLModelHelper {
/**
* Constructor to forbid creation.
*/
private CheckUMLModelHelper() {
}
/**
* Delete a model object, frees the reference and then checks that
* the object is reclaimed.
*
* This must be called with just one reference to the object or
* else it will fail.
*
* @param mo the model object that we try to delete, release and reclaim.
*/
public static void deleteAndRelease(Object mo) {
Class c = mo.getClass();
// Call methods that exists for all objects and that always return
// something meaningful
TestCase.assertNotNull("toString() corrupt in " + c,
mo.toString());
Model.getUmlFactory().delete(mo);
Model.getPump().flushModelEvents();
TestCase.assertTrue("Could not delete " + c,
Model.getUmlFactory().isRemoved(mo));
}
/**
* Delete a model object, frees the reference and then checks that
* the object is reclaimed.
*
* This must be called with just one reference to the object or
* else it will fail.
*
* @param mo the model object that we try to delete, release and reclaim.
* @param name the class name of the uml object
*/
private static void deleteAndRelease(Object mo, String name) {
Class c = mo.getClass();
// Call methods that exists for all objects and that always return
// something meaningful
TestCase.assertNotNull("toString() corrupt in " + c,
mo.toString());
TestCase.assertNotNull("getUMLClassName() corrupt in " + c,
Model.getFacade().getUMLClassName(mo));
TestCase.assertEquals(
"getUMLClassName() different from expected in " + c,
name, Model.getFacade().getUMLClassName(mo));
Model.getUmlFactory().delete(mo);
Model.getPump().flushModelEvents();
TestCase.assertTrue("Could not delete " + c,
Model.getUmlFactory().isRemoved(mo));
}
public static void createAndRelease(Object factory,
String[] names,
Object[] arguments) {
createAndRelease(factory, Arrays.asList(names), arguments);
}
/**
* Create a UML modelelement (i.e. check if a creation function exists).
* Then deletes it, looses the reference and then checks that
* the object is reclaimed.
*
* @param factory the DataTypesFactory
* @param names the UML elements to test
* @param arguments the arguments of the UML elements
*/
public static void createAndRelease(Object factory,
Iterable<String> names,
Object[] arguments) {
Class [] argTypes = new Class[arguments.length];
for (int i = 0; i < arguments.length; i++) {
argTypes[i] = arguments[i].getClass();
}
// Multiplicity, MultiplicityRange, and all Expression subtypes
// don't have 0-argument create methods, so we special case them.
Integer[] multArgs = {1, 1};
Class[] multArgTypes = {int.class, int.class};
String [] exprArgs = {"body text", "language text"};
Class[] exprArgTypes = {String.class, String.class};
for (String name : names) {
Class[] types;
Object[] args;
if (name == null) {
continue;
} else if (name.startsWith("Multiplicity")) {
types = multArgTypes;
args = multArgs;
} else if (name.endsWith("Expression")) {
types = exprArgTypes;
args = exprArgs;
} else {
types = argTypes;
args = arguments;
}
String methodName = "create" + name;
Method createMethod;
// Find the create method in the offical API
createMethod = findMethod(factory.getClass(), Factory.class,
methodName, types);
if (createMethod == null) {
TestCase.fail("Method " + methodName
+ " does not exist in any interface of factory "
+ factory.getClass().getName());
return;
}
Method isAMethod;
String isAMethodName = "isA" + name;
Object facade = Model.getFacade();
try {
// Now get the factory implementation method to be invoked
isAMethod =
Facade.class.getDeclaredMethod(
isAMethodName,
new Class[] {Object.class});
} catch (NoSuchMethodException e) {
TestCase.fail("Method " + isAMethodName
+ " does not exist in Facade");
return;
}
Method getMethod;
String getMethodName = "get" + name;
if ("Class".equals(name)) {
getMethodName = "getUMLClass";
}
Object metatypes = Model.getMetaTypes();
try {
getMethod =
MetaTypes.class.getDeclaredMethod(
getMethodName,
new Class[] {});
} catch (NoSuchMethodException e) {
TestCase.fail("Method " + getMethodName
+ " does not exist in MetaTypes");
return;
}
try {
// Extra careful now, not to keep any references to the
// second argument.
try {
Object element = invoke(createMethod, factory, args);
TestCase.assertTrue("Facade method " + isAMethodName
+ " returned false", (Boolean) invoke(
isAMethod, facade, new Object[] {
element
}));
TestCase.assertTrue(Model.getFacade().isA(name, element));
Object metaElement = invoke(getMethod, metatypes,
new Object[0]);
TestCase.assertTrue(((Class) metaElement)
.isAssignableFrom(element.getClass()));
deleteAndRelease(createMethod.invoke(factory, args), name);
} catch (ClassCastException e) {
// Here it is another object sent to the test.
deleteAndRelease(createMethod.invoke(factory, args));
} catch (IllegalArgumentException e) {
// Here it is another object sent to the test.
deleteAndRelease(createMethod.invoke(factory, args));
}
} catch (IllegalAccessException e) {
TestCase.fail("Method create" + name
+ " in " + factory + " cannot be called");
return;
} catch (InvocationTargetException e) {
TestCase.fail("Method create" + name
+ " in " + factory + " throws an exception.");
return;
}
}
}
/**
* Convenience method to invoke a method and convert any thrown exceptions
* to test failures with a useful message.
*/
private static Object invoke(Method method, Object object, Object[] args) {
try {
return method.invoke(object, args);
} catch (IllegalAccessException e) {
TestCase.fail("Method " + method.getName() + " in " + object
+ " cannot be called");
} catch (InvocationTargetException e) {
TestCase.fail("Method " + method.getName() + " in " + object
+ " throws an exception.");
}
return null;
}
/**
* Check all interfaces of the given class for an interface which both: 1)
* extends the given marker interface (in our case called 'Factory') 2)
* contains the given method.
* <p>
*
* This extra check is to make sure that the public methods of the given
* factory are actually part of the public API interface.
*
* @param factory
* the factory class to test
* @param markerInterface
* the class of the marker interface to look for
* @param methodName
* the name of the object that we want to create
* @param argTypes
* the types of the arguments for the method
* @return the requested method or null if no match is found
*/
private static Method findMethod(Class factory,
Class markerInterface, String methodName, Class[] argTypes) {
Class[] interfaces = factory.getInterfaces();
for (int i = 0; i < interfaces.length; i++) {
if (markerInterface.equals(interfaces[i])) {
Method m = getMethod(factory, methodName, argTypes);
if (m != null) {
return m;
}
} else {
Method m =
findMethod(
interfaces[i], markerInterface, methodName,
argTypes);
if (m != null) {
return m;
}
}
}
return null;
}
private static Method getMethod(Class clazz, String methodName,
Class[] classes) {
try {
return clazz.getDeclaredMethod(methodName, classes);
} catch (NoSuchMethodException e) {
return null;
}
}
/**
* @param f
* the DataTypesFactory
* @param names
* the UML elements to test
*/
public static void createAndRelease(Object f, String[] names) {
createAndRelease(f, Arrays.asList(names));
}
public static void createAndRelease(Object f, Iterable<String> names) {
createAndRelease(f, names, new Object[] {});
}
/**
* Test the presence of deletion methods for a list of modelelements.
*
* @param f the model factory that provides the "delete" methods
* for the modelelements
* @param names the names of the modelelements
*/
public static void hasDeleteMethod(Object f, String[] names) {
for (String name : names) {
String methodName = "delete" + name;
try {
f.getClass().getDeclaredMethod(methodName,
new Class[] {Object.class});
} catch (SecurityException se) {
TestCase.fail(
"SecurityException while retrieving all methods from "
+ f.getClass().getName());
return;
} catch (NoSuchMethodException e) {
TestCase.fail("Method " + methodName + " not found in "
+ f.getClass().getName());
}
}
}
/**
* Check if for every metamodel element name a create function exists.
*
* @param factory the modelfactory that should contain the create function
* @param names the metamodel class names
*/
public static void metaModelNameCorrect(Object factory,
Iterable<String> names) {
Set<String> metaNames = new HashSet<String>();
metaNames.addAll(Arrays.asList(Model.getFacade().getMetatypeNames()));
try {
for (String name : names) {
Method createMethod = findMethod(factory.getClass(), Factory.class,
"create" + name, new Class[] {});
TestCase.assertNotNull("Failed to find method create"
+ name, createMethod);
Object element = createMethod.invoke(factory, new Object[] {});
TestCase.assertTrue("Not a UML Element",
Model.getFacade().isAUMLElement(element));
String metaName = Model.getMetaTypes().getName(element);
TestCase.assertEquals(
"not a valid metaModelName " + name,
metaName, name);
TestCase.assertTrue(metaNames.contains(metaName));
TestCase.assertTrue(Model.getFacade().isA(metaName, element));
Method m2 = findMethod(MetaTypes.class, MetaTypes.class,
"get" + name, new Class[] {});
Method getMethod;
String getMethodName = "get" + name;
if ("Class".equals(name)) {
getMethodName = "getUMLClass";
}
Object metatypes = Model.getMetaTypes();
try {
getMethod =
MetaTypes.class.getDeclaredMethod(
getMethodName,
new Class[] {});
} catch (NoSuchMethodException e) {
TestCase.fail("Method " + getMethodName
+ " does not exist in MetaTypes");
return;
}
TestCase.assertNotNull("Failed to find method get"
+ getMethodName, getMethod);
Object metaElement = createMethod.invoke(factory,
new Object[] {});
TestCase.assertTrue(metaElement.getClass().isAssignableFrom(
element.getClass()));
}
} catch (Exception ex) {
ex.printStackTrace();
TestCase.fail(
"Exception during test metaModelnameCorrect. Message: "
+ ex.getMessage());
}
}
public static void metaModelNameCorrect(Object factory,
String[] names) {
metaModelNameCorrect(factory, Arrays.asList(names));
}
/**
* Try creating a stereotype for every modelelement type.
*
* @param f the factory containing the creation function for
* all the given metamodel element names
* @param names the metamodel element names
*/
public static void isValidStereoType(Object f, Iterable<String> names) {
try {
Object ns = Model.getModelManagementFactory().createPackage();
Object clazz = Model.getCoreFactory().buildClass(ns);
Object stereo1 =
Model.getExtensionMechanismsFactory()
.buildStereotype(clazz, "test1", ns);
for (String name : names) {
Method m = findMethod(f.getClass(), Factory.class,
"create" + name, new Class[] {});
if (m == null) {
TestCase.fail("Failed to find method create" + name);
}
Object base = m.invoke(f, new Object[] {});
if (Model.getFacade().isAModelElement(base)) {
Object stereo2 =
Model.getExtensionMechanismsFactory()
.buildStereotype(base, "test2", ns);
TestCase.assertTrue(
"Unexpected invalid stereotype",
Model.getExtensionMechanismsHelper()
.isValidStereotype(base, stereo2));
if (!(Model.getFacade().isAClass(base))) {
TestCase.assertTrue(
"Stereotype with base class of Class"
+ " incorrectly allowed for this metaclass",
!Model.getExtensionMechanismsHelper()
.isValidStereotype(base, stereo1));
} else {
Object inter =
Model.getCoreFactory().createInterface();
Object stereo3 =
Model.getExtensionMechanismsFactory()
.buildStereotype(inter, "test3", ns);
TestCase.assertTrue(
"Unexpected invalid stereotype",
!Model.getExtensionMechanismsHelper()
.isValidStereotype(base, stereo3));
}
}
}
} catch (Exception ex) {
ex.printStackTrace();
TestCase.fail(
"Exception during test metaModelnameCorrect. Message: "
+ ex.getMessage());
}
}
public static void isValidStereoType(Object f, String[] names) {
isValidStereoType(f, Arrays.asList(names));
}
}