/** * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Copyright (C) 2007 Ronny Brandt (Ronny_Brandt@web.de). * * All rights reserved. * * * * This work was done as a project at the Chair for Software Technology, * * Dresden University Of Technology, Germany (http://st.inf.tu-dresden.de). * * It is understood that any modification not identified as such is not * * covered by the preceding statement. * * * * This work is free software; you can redistribute it and/or modify it * * under the terms of the GNU Library General Public License as published * * by the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This work is distributed in the hope that it will be useful, but WITHOUT * * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public * * License for more details. * * * * You should have received a copy of the GNU Library General Public License * * along with this library; if not, you can view it online at * * http://www.fsf.org/licensing/licenses/gpl.html. * * * * To submit a bug report, send a comment, or get the latest news on this * * project, please visit the website: http://dresden-ocl.sourceforge.net. * * For more information on OCL and related projects visit the OCL Portal: * * http://st.inf.tu-dresden.de/ocl * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ package org.dresdenocl.standardlibrary.java.internal.library; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import org.dresdenocl.essentialocl.EssentialOclPlugin; import org.dresdenocl.essentialocl.standardlibrary.OclAny; import org.dresdenocl.essentialocl.standardlibrary.OclBoolean; import org.dresdenocl.essentialocl.standardlibrary.OclSet; import org.dresdenocl.essentialocl.standardlibrary.OclType; import org.dresdenocl.modelinstancetype.exception.AsTypeCastException; import org.dresdenocl.modelinstancetype.types.IModelInstanceElement; import org.dresdenocl.modelinstancetype.types.IModelInstanceInvalid; import org.dresdenocl.modelinstancetype.types.IModelInstanceVoid; import org.dresdenocl.pivotmodel.Operation; import org.dresdenocl.pivotmodel.Type; import org.dresdenocl.standardlibrary.java.factory.JavaStandardLibraryFactory; /** * <p> * Supertype of any Ocl Object/PrimitiveType/Collection in the Java standard * library. * </p> * * @author Ronny Brandt * @author Michael Thiele */ public abstract class JavaOclAny implements OclAny { /** The reason why this element is undefined - if it is. */ protected String undefinedreason = null; /** The reason why this element is invalid - if it is. */ protected Throwable invalidReason = null; protected IModelInstanceElement imiElement; /** * <p> * Constructor for null elements. These elements can provide a reason for * being undefined. * </p> * <p> * <strong>Note:</strong> Since <code>OclUndefined</code> is typed, i.e. * according to the OCL standard it conforms to all other types except * <code>OclInvalid</code>, <strong>every subclass of {@link OclAny} needs * to implement this constructor</strong>. * </p> * * @param undefinedReason * the reason why this element is undefined */ public JavaOclAny(String undefinedReason) { if (undefinedReason == null) throw new IllegalArgumentException( "Undefined reason cannot be null."); this.undefinedreason = undefinedReason; this.imiElement = IModelInstanceVoid.INSTANCE; } /** * <p> * Constructor for invalid elements. These elements have to provide a reason * for being invalid. * </p> * <p> * <strong>Note:</strong> Since <code>OclInvalid</code> is typed, i.e. * according to the OCL standard it conforms to all other types except * <code>OclUndefined</code>, <strong>every subclass of {@link OclAny} needs * to implement this constructor</strong>. * </p> * * @param invalidReason * the reason why this element is invalid, given by a * {@link Throwable} */ public JavaOclAny(Throwable invalidReason) { if (invalidReason == null) throw new IllegalArgumentException("Invalid reason cannot be null."); this.invalidReason = invalidReason; this.imiElement = IModelInstanceInvalid.INSTANCE; } /** * Constructor for normal {@link IModelInstanceElement}s that are neither * <code>undefined</code> nor <code>invalid</code>. * * @param imiElement * the {@link IModelInstanceElement} to adapt. */ public JavaOclAny(IModelInstanceElement imiElement) { if (imiElement == null) throw new IllegalArgumentException( "IModelInstanceElement cannot be null. Use constructor with String argument for OclUndefined or with Throwable argument for OclInvalid."); this.imiElement = imiElement; } // FIXME Michael: Bad smell! Each class should have its own static map -> // what // to do with getOperationNames; operations of supertypes have to be // considered /** * Contains the operation names which are different in the standard library * than in the OCL specification. The names are separated in sub maps * depending on their number of arguments. */ protected static Map<Integer, Map<String, String>> operationNames = new HashMap<Integer, Map<String, String>>(); /* Initializes the operation names map. */ static { /* * FIXME Michael: for testing purposes only; move to OclReal and find * solution to register these operations */ Map<String, String> unaryOperations; unaryOperations = new HashMap<String, String>(); unaryOperations.put("-", "negative"); unaryOperations.put("toString", "convertToString"); operationNames.put(1, unaryOperations); Map<String, String> binaryOperations; binaryOperations = new HashMap<String, String>(); binaryOperations.put("=", "isEqualTo"); binaryOperations.put("<>", "isNotEqualTo"); /* * FIXME Michael: for testing purposes only; move to OclReal and find * solution to register these operations */ binaryOperations.put("<=", "isLessEqual"); binaryOperations.put("<", "isLessThan"); binaryOperations.put(">=", "isGreaterEqual"); binaryOperations.put(">", "isGreaterThan"); binaryOperations.put("-", "subtract"); binaryOperations.put("+", "add"); binaryOperations.put("*", "multiply"); binaryOperations.put("/", "divide"); operationNames.put(2, binaryOperations); } public Throwable getInvalidReason() { return invalidReason; } /* * (non-Javadoc) * * @seeorg.dresdenocl.essentialocl.standardlibrary.OclAny# * getModelInstanceElement() */ public IModelInstanceElement getModelInstanceElement() { return imiElement; } /* * (non-Javadoc) * * @see * org.dresdenocl.essentialocl.standardlibrary.OclRoot#getUndefinedreason * () */ public String getUndefinedReason() { return undefinedreason; } /* * (non-Javadoc) * * @see * org.dresdenocl.essentialocl.standardlibrary.OclAny#invokeOperation * (org.dresdenocl.pivotmodel.Operation, * org.dresdenocl.essentialocl.standardlibrary.OclAny[]) */ @SuppressWarnings("unchecked") public OclAny invokeOperation(Operation operation, OclAny... args) { OclAny result = null; String opName; opName = operation.getName(); /* * possibly map from operation name to standard library operation name * (e.g., "+" -> "add") */ opName = getOperationName(opName, args.length + 1); /* translate arguments */ List<Class<? extends OclAny>> argClasses = new LinkedList<Class<? extends OclAny>>(); for (OclAny arg : args) { argClasses.add(arg.getClass()); } Class<? extends OclAny> thisClass = this.getClass(); /* try to invoke the operation */ try { Method methodToInvoke = findMethod(opName, thisClass, argClasses.toArray(new Class[0])); Object invocationResult = methodToInvoke.invoke(this, (Object[]) args); result = (OclAny) invocationResult; } /* if anything goes wrong, wrap it in an InvalidException */ catch (SecurityException e) { result = JavaStandardLibraryFactory.INSTANCE.createOclInvalid( operation.getType(), e); } catch (NoSuchMethodException e) { result = JavaStandardLibraryFactory.INSTANCE.createOclInvalid( operation.getType(), e); } catch (IllegalArgumentException e) { result = JavaStandardLibraryFactory.INSTANCE.createOclInvalid( operation.getType(), e); } catch (IllegalAccessException e) { result = JavaStandardLibraryFactory.INSTANCE.createOclInvalid( operation.getType(), e); } catch (InvocationTargetException e) { Throwable cause = e.getCause(); if (cause != null) { result = JavaStandardLibraryFactory.INSTANCE.createOclInvalid( operation.getType(), cause); } else { result = JavaStandardLibraryFactory.INSTANCE.createOclInvalid( operation.getType(), e); } } /* * Just in case, if the return type is not an instance of OclAny. */ catch (ClassCastException e) { result = JavaStandardLibraryFactory.INSTANCE.createOclInvalid( operation.getType(), e); } // } // no else. return result; } /* * (non-Javadoc) * * @see * org.dresdenocl.essentialocl.standardlibrary.OclRoot#isNotEqualTo * (org.dresdenocl.essentialocl.standardlibrary.OclRoot) */ public OclBoolean isNotEqualTo(OclAny that) { return isEqualTo(that).not(); } /* * (non-Javadoc) * * @see * org.dresdenocl.essentialocl.standardlibrary.OclRoot#isOclUndefined * () */ public OclBoolean oclIsUndefined() { /* * see standard, p. 139; here, we use a different semantics, since * invalid should not be catched by an undefined check */ if (this.invalidReason != null) return JavaStandardLibraryFactory.INSTANCE.createOclInvalid( EssentialOclPlugin.getOclLibraryProvider().getOclLibrary() .getOclBoolean(), invalidReason); else return JavaOclBoolean.getInstance((this.undefinedreason != null)); } /* * (non-Javadoc) * * @see * org.dresdenocl.essentialocl.standardlibrary.OclRoot#oclIsInvalid() */ public OclBoolean oclIsInvalid() { return JavaOclBoolean.getInstance(this.invalidReason != null); } /* * (non-Javadoc) * * @see * org.dresdenocl.essentialocl.standardlibrary.OclAny#oclAsType(tudresden * .ocl20.pivot.essentialocl.standardlibrary.OclType) */ @SuppressWarnings("unchecked") public <T extends OclAny> T oclAsType(OclType<T> type) { T result = null; result = (T) checkInvalid(type.getType(), this, type); if (result == null) result = (T) checkUndefined("oclAsType", type.getType(), this); if (result == null) { IModelInstanceElement castedTo; try { castedTo = imiElement.asType(type.getType()); result = (T) JavaStandardLibraryFactory.INSTANCE .createOclAny(castedTo); } catch (AsTypeCastException e) { result = JavaStandardLibraryFactory.INSTANCE.createOclInvalid( type.getType(), e); } } return result; } /* * (non-Javadoc) * * @see * org.dresdenocl.essentialocl.standardlibrary.OclAny#oclIsKindOf * (tudresden .ocl20.pivot.essentialocl.standardlibrary.OclType) */ public <T extends OclAny> OclBoolean oclIsKindOf(OclType<T> typespec) { OclBoolean result = null; result = checkInvalid(EssentialOclPlugin.getOclLibraryProvider() .getOclLibrary().getOclBoolean(), this, typespec); if (result == null) result = checkUndefined("oclIsKindOf", EssentialOclPlugin .getOclLibraryProvider().getOclLibrary().getOclBoolean(), this); if (result == null) { if (typespec.getType().equals( EssentialOclPlugin.getOclLibraryProvider().getOclLibrary() .getOclAny())) result = JavaStandardLibraryFactory.INSTANCE .createOclBoolean(true); else { final boolean isKindOf = imiElement .isKindOf(typespec.getType()); result = JavaStandardLibraryFactory.INSTANCE .createOclBoolean(isKindOf); } } return result; } /* * (non-Javadoc) * * @see * org.dresdenocl.essentialocl.standardlibrary.OclAny#oclIsTypeOf * (tudresden .ocl20.pivot.essentialocl.standardlibrary.OclType) */ public <T extends OclAny> OclBoolean oclIsTypeOf(OclType<T> typespec) { OclBoolean result = null; result = checkInvalid(EssentialOclPlugin.getOclLibraryProvider() .getOclLibrary().getOclBoolean(), this, typespec); if (result == null) result = checkUndefined("oclIsTypeOf", EssentialOclPlugin .getOclLibraryProvider().getOclLibrary().getOclBoolean(), this); if (result == null) { final boolean isTypeOf = imiElement.isTypeOf(typespec.getType()); result = JavaStandardLibraryFactory.INSTANCE .createOclBoolean(isTypeOf); } return result; } /* * (non-Javadoc) * * @see org.dresdenocl.essentialocl.standardlibrary.OclAny#oclType() */ public <T extends OclAny> OclType<T> oclType() { OclType<T> result; Type type = this.getModelInstanceElement().getType(); result = checkInvalid(EssentialOclPlugin.getOclLibraryProvider() .getOclLibrary().getOclType(), this); if (result == null) result = checkUndefined("oclType", EssentialOclPlugin .getOclLibraryProvider().getOclLibrary().getOclType(), this); if (result == null) result = JavaStandardLibraryFactory.INSTANCE.createOclType(type); return result; } /** * <p> * <strong>This method is required to support {@link Collection} operations * such as {@link Collection#contains(Object)}!</strong> * </p> * * @see org.dresdenocl.standardlibrary.java.internal.library.JavaOclAny#equals(java.lang.Object) */ @Override public boolean equals(Object other) { boolean result; if (other == null) { result = false; } else if (other instanceof OclAny) { OclBoolean oclResult; oclResult = this.isEqualTo((OclAny) other); if (oclResult.oclIsUndefined().isTrue() || oclResult.oclIsInvalid().isTrue()) { result = false; } else { result = oclResult.isTrue(); } } else { result = false; } return result; } /** * <p> * <strong>This method is required to support {@link Collection} operations * such as {@link Collection#contains(Object)}!</strong> * </p> * * @see java.lang.Object#hashCode() */ @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result; if (this.imiElement != null) { result = prime * result + imiElement.hashCode(); } // no else. if (this.invalidReason != null) { result = prime * result + this.oclIsInvalid().hashCode(); } // no else. if (this.undefinedreason != null) { result = prime * result + this.oclIsUndefined().hashCode(); } // no else. return result; } /** * This methods checks for all given {@link OclAny}s whether they are * invalid. If one of them is, the typed <code>invalid</code> value is * returned based on the given return {@link Type}. * * @param <T> * a concrete subclass of {@link OclAny} * @param returnType * the {@link Type} of the returned <code>invalid</code> value * @param objects * the {@link OclAny}s to check whether they are invalid * @return the typed <code>invalid</code> if one of the given objects is * <code>invalid</code>, or <code>null</code> (the Java * <code>null</code>) if there is no given <code>invalid</code> * value */ protected <T extends OclAny> T checkInvalid(Type returnType, OclAny... objects) { T result = null; for (OclAny object : objects) { if (object.getInvalidReason() != null) { result = JavaStandardLibraryFactory.INSTANCE.createOclInvalid( returnType, object.getInvalidReason()); break; } } return result; } /** * Helper method that can be used by every subclass of {@link OclAny} to * check whether the object itself and its argument are both null or one of * them is. * * @param that * the object to compare to * @return <code>true</code> if this and that are both undefined or invalid * or <code>false</code> if one of them is. Returns * <code>null</code> if both elements are neither undefined nor * invalid. */ protected OclBoolean checkIsEqualTo(OclAny that) { OclBoolean result = null; // both undefined if (this.undefinedreason != null && that.getUndefinedReason() != null) result = JavaOclBoolean.getInstance(true); // both invalid else if (this.invalidReason != null && that.getInvalidReason() != null) result = JavaOclBoolean.getInstance(true); // only one undefined else if (this.undefinedreason != null || that.getUndefinedReason() != null) result = JavaOclBoolean.getInstance(false); // only one invalid else if (this.invalidReason != null || that.getInvalidReason() != null) result = JavaOclBoolean.getInstance(false); return result; } /** * Checks for the given object and arguments of a method call whether the * values are undefined/null. If so, the appropriately typed * <code>invalid</code> value is returned. * * @param <T> * a concrete subclass of {@link OclAny} * @param methodName * the name of the method that needs to be checked for * undefined/null values; the method's name is used in the error * message of the returned <code>undefined</code> value * @param returnType * the {@link Type} of the returned <code>undefined</code> value * @param object * the object on which the method is called * @param args * the arguments of the method that should also be checked for * <code>undefined</code> values * @return the typed <code>undefined</code> value if there is a given * <code>undefined</code> value or <code>null</code> (the Java * <code>null</code>) if there is no given <code>undefined</code> * value. */ protected <T extends OclAny> T checkUndefined(String methodName, Type returnType, OclAny object, OclAny... args) { T result = null; if (object != null && object.getUndefinedReason() != null) { result = JavaStandardLibraryFactory.INSTANCE.createOclInvalid( returnType, new RuntimeException("Cannot invoke operation " + methodName + " on null. Reason: " + object.getUndefinedReason())); } else { for (OclAny arg : args) { if (arg.getUndefinedReason() != null) { result = JavaStandardLibraryFactory.INSTANCE .createOclInvalid( returnType, new RuntimeException( "Cannot invoke operation " + methodName + " with null as one of its arguments. Reason: " + arg.getUndefinedReason())); break; } } } return result; } /** * The method {@link OclAny#asSet()} is used in implicit conversions. Since * <code>undefined</code> is allowed to be a source of collection * operations, this method checks whether this object is * <code>undefined</code> and if so, it returns an empty {@link OclSet}. * * @param <T> * a subclass of {@link OclAny} that represents the generic type * of the {@link OclSet} * @param genericType * the {@link Type} of the element in question * @return an empty {@link OclSet} if this is <code>undefined</code> or * <code>null</code> if this is defined */ protected <T extends OclAny> OclSet<T> checkAsSet(Type genericType) { OclSet<T> result = null; if (this.undefinedreason != null) result = JavaStandardLibraryFactory.INSTANCE.createOclSet( new HashSet<Object>(), genericType); return result; } // FIXME Michael: Is this the right place for this code? protected Type commonSuperType(Type type1, Type type2) { Type result; if (type1 == null && type2 == null) throw new IllegalArgumentException( "Cannot determine common super type of null values."); else if (type1 == null || type1.equals(EssentialOclPlugin.getOclLibraryProvider() .getOclLibrary().getOclAny())) result = type2; else if (type2 == null || type2.equals(EssentialOclPlugin.getOclLibraryProvider() .getOclLibrary().getOclAny())) result = type1; else result = type1.commonSuperType(type2); return result; } /** * <p> * Tries to find a method with a given name and a given array of * parameterTypes. * </p> * * @param methodName * The name of the method to search for. * @param sourceType * The source type on which the operation shall be invoked. * @param parameterTypes * An Array representing the number and types of parameters to * look for in the method's signature. A null array is treated as * a zero-length array. * @param isModelMethod * Specifies whether or not the method which shall be found is an * OCL defined method or a model defined method. * @return Method object satisfying the given conditions. * @throws NoSuchMethodException * Thrown if no methods match the criteria, or if the reflective * call is ambiguous based on the parameter types, or if * methodName is null. */ protected Method findMethod(String methodName, Class<? extends OclAny> sourceType, Class<? extends OclAny>... parameterTypes) throws NoSuchMethodException { Method result; Method[] allMethods; allMethods = sourceType.getMethods(); result = null; /* Iterate through all methods. */ for (int index = 0; index < allMethods.length; index++) { Method curMethod = allMethods[index]; /* Check if the name match. */ if (curMethod.getName().equals(methodName)) { /* Check if the parameters match. */ Class<?>[] aMethodsParams; aMethodsParams = curMethod.getParameterTypes(); /* Check the count of parameters. */ if (aMethodsParams.length == parameterTypes.length) { boolean isConform; isConform = true; /* Check the conformance of all parameters. */ for (int index2 = 0; index2 < aMethodsParams.length; index2++) { if (!aMethodsParams[index2] .isAssignableFrom(parameterTypes[index2])) { isConform = false; break; } // no else. } if (isConform) { result = curMethod; break; } } // no else. } // no else. } if (result == null) { String msg; msg = "No method " + methodName + "("; for (int index = 0; index < parameterTypes.length; index++) { msg += parameterTypes[index]; if (index > 0) { msg += ", "; } // no else. } msg += ") found."; throw new NoSuchMethodException(msg); } return result; } /* * (non-Javadoc) * * @see * org.dresdenocl.modelbus.IModelInstance#getOperationName(java.lang * .String, int) */ protected String getOperationName(String name, int operatorCount) { String result; Map<String, String> operationMap; result = null; operationMap = operationNames.get(operatorCount); if (operationMap != null) { result = operationMap.get(name); } // no else. if (result == null) { result = name; } // no else. return result; } protected boolean toStringUndefinedOrInvalid(StringBuilder result) { if (this.oclIsInvalid().isTrue()) { result.append("invalid: "); result.append(this.getInvalidReason().getMessage()); return true; } else if (this.oclIsUndefined().isTrue()) { result.append("undefined: "); result.append(this.getUndefinedReason()); return true; } return false; } }