001 package org.andromda.translation.ocl.testsuite;
002
003 import java.util.ArrayList;
004 import java.util.Collection;
005 import org.andromda.core.common.ExceptionUtils;
006 import org.andromda.core.metafacade.ModelAccessFacade;
007 import org.andromda.core.translation.Expression;
008 import org.andromda.metafacades.uml.ClassifierFacade;
009 import org.andromda.metafacades.uml.ModelElementFacade;
010 import org.andromda.metafacades.uml.OperationFacade;
011 import org.andromda.metafacades.uml.ParameterFacade;
012 import org.andromda.translation.ocl.BaseTranslator;
013 import org.andromda.translation.ocl.node.AOperationContextDeclaration;
014 import org.andromda.translation.ocl.node.POperation;
015 import org.andromda.translation.ocl.syntax.ConcreteSyntaxUtils;
016 import org.andromda.translation.ocl.syntax.OperationDeclaration;
017 import org.andromda.translation.ocl.syntax.VariableDeclaration;
018 import org.apache.commons.collections.CollectionUtils;
019 import org.apache.commons.collections.Predicate;
020 import org.apache.commons.lang.StringUtils;
021
022 /**
023 * Finds the context element defined in the OCL expression.
024 *
025 * @author Chad Brandon
026 */
027 public class ContextElementFinder
028 extends BaseTranslator
029 {
030 private ModelAccessFacade model;
031
032 /**
033 * The Constructor which takes <code>model</code>, which is an instance of ModelAccessFacade that will allow us to
034 * search the model for the context element.
035 *
036 * @param model the ModelAccessFacade to search.
037 */
038 public ContextElementFinder(ModelAccessFacade model)
039 {
040 ExceptionUtils.checkNull("model", model);
041 this.model = model;
042 }
043
044 /**
045 * Hide the default constructor
046 */
047 @SuppressWarnings("unused")
048 private ContextElementFinder()
049 {
050 }
051
052 /**
053 * The operation that is set if the context of the constraint happens to be an operation.
054 */
055 protected OperationDeclaration operation = null;
056
057 /**
058 * The found context type.
059 */
060 private Object contextElement = null;
061
062 /**
063 * @param declaration
064 * @see org.andromda.translation.ocl.syntax.ConcreteSyntaxUtils#getOperationDeclaration(POperation)
065 */
066 public void inAOperationContextDeclaration(AOperationContextDeclaration declaration)
067 {
068 super.inAOperationContextDeclaration(declaration);
069 if (declaration != null)
070 {
071 operation = ConcreteSyntaxUtils.getOperationDeclaration(declaration.getOperation());
072 }
073 }
074
075 /**
076 * We use the postProcess method to retrieve the contextType from the expression and then find the actual model
077 * element using metafacades.
078 *
079 * @see org.andromda.translation.ocl.BaseTranslator#postProcess()
080 */
081 @Override
082 public void postProcess()
083 {
084 Expression expression = this.getExpression();
085 if (expression != null)
086 {
087 String contextElementName = expression.getContextElement();
088 this.contextElement = this.findModelElement(contextElementName.replaceAll("::", "\\."));
089 if (this.contextElement != null)
090 {
091 logger.info("found context element --> '" + contextElementName + '\'');
092 } else
093 {
094 logger.info("Could not find model element --> '" + contextElementName + '\'');
095 }
096
097 if (this.contextElement != null && this.operation != null &&
098 ClassifierFacade.class.isAssignableFrom(contextElement.getClass()))
099 {
100 ClassifierFacade type = (ClassifierFacade) this.contextElement;
101 Collection operations = type.getOperations();
102 this.contextElement = CollectionUtils.find(operations, new OperationFinder());
103 if (this.contextElement == null)
104 {
105 throw new ContextElementFinderException("No operation matching '" + operation +
106 "' could be found on element --> '" + contextElementName + "', please check your model");
107 }
108
109 // if we only have one operation then we just set that
110 // as the context element, otherwise we'll need to figure
111 // out which operation is the context operation by checking
112 // the arguments.
113 if (operations.size() == 1)
114 {
115 this.contextElement = operations.iterator().next();
116 } else
117 {
118 // now find the correct operation since there are
119 // more than one with the same name
120 }
121 }
122 }
123 }
124
125 private final class OperationFinder
126 implements Predicate
127 {
128 public boolean evaluate(Object object)
129 {
130
131 OperationFacade facadeOperation = (OperationFacade) object;
132 boolean valid = StringUtils.trimToEmpty(facadeOperation.getName()).equals(
133 StringUtils.trimToEmpty(operation.getName()));
134 // if we've found an operation with a matching name
135 // check the parameters
136 if (valid)
137 {
138 valid = argumentsMatch(operation, facadeOperation);
139 }
140 return valid;
141 }
142 }
143
144 /**
145 * Returns true if the arguments contained within <code>oclOperation</code> and <code>facadeOperation</code> match,
146 * false otherwise.
147 *
148 * @param oclOperation an OCL Operation
149 * @param facadeOperation a metafacade Operation
150 * @return boolean whether the arguments match.
151 */
152 protected boolean argumentsMatch(OperationDeclaration oclOperation, OperationFacade facadeOperation)
153 {
154 boolean argumentsMatch = this.argumentCountsMatch(oclOperation, facadeOperation);
155 if (argumentsMatch)
156 {
157 argumentsMatch = this.argumentNamesMatch(oclOperation, facadeOperation);
158 }
159 return argumentsMatch;
160 }
161
162 /**
163 * Returns true if the number of arguments contained within <code>oclOperation</code> and
164 * <code>facadeOperation</code> match, false otherwise.
165 *
166 * @param oclOperation an OCL Operation
167 * @param facadeOperation a metafacade Operation
168 * @return boolean whether the count of the arguments match.
169 */
170 private boolean argumentCountsMatch(OperationDeclaration oclOperation, OperationFacade facadeOperation)
171 {
172 ExceptionUtils.checkNull("oclOperation", oclOperation);
173 ExceptionUtils.checkNull("facadeOperation", facadeOperation);
174 VariableDeclaration[] expressionOpArgs = oclOperation.getArguments();
175 Collection facadeOpArgs = facadeOperation.getArguments();
176 boolean countsMatch = (expressionOpArgs == null || expressionOpArgs.length == 0) &&
177 (facadeOpArgs == null || facadeOpArgs.isEmpty());
178 if (!countsMatch)
179 {
180 countsMatch = expressionOpArgs != null && facadeOpArgs != null &&
181 expressionOpArgs.length == facadeOpArgs.size();
182 }
183 return countsMatch;
184 }
185
186 /**
187 * Returns true if the argument names contained within <code>oclOperation</code> and <code>facadeOperation</code>
188 * match, false otherwise.
189 *
190 * @param oclOperation an OCL Operation
191 * @param facadeOperation a metafacade Operation
192 * @return boolean whether the arg names match or not.
193 */
194 private boolean argumentNamesMatch(OperationDeclaration oclOperation, OperationFacade facadeOperation)
195 {
196 ExceptionUtils.checkNull("oclOperation", oclOperation);
197 ExceptionUtils.checkNull("facadeOperation", facadeOperation);
198
199 Collection<ParameterFacade> facadeOpArguments = facadeOperation.getArguments();
200 VariableDeclaration[] expressionOpArgs = oclOperation.getArguments();
201 Collection<String> expressionArgNames = new ArrayList<String>();
202 if (expressionOpArgs != null)
203 {
204 for (VariableDeclaration expressionOpArg : expressionOpArgs)
205 {
206 expressionArgNames.add(expressionOpArg.getName());
207 }
208 }
209 Collection<String> facadeArgNames = new ArrayList<String>();
210 if (facadeOpArguments != null)
211 {
212 for (ParameterFacade facadeArg : facadeOpArguments)
213 {
214 facadeArgNames.add(facadeArg.getName());
215 }
216 }
217 return CollectionUtils.isEqualCollection(expressionArgNames, facadeArgNames);
218 }
219
220 /**
221 * Finds the model element with the given <code>modelElementName</code>. Will find either the non qualified name or
222 * qualified name. If more than one model element is found with the non qualified name an exception will be thrown.
223 *
224 * @param modelElementName
225 * @return Object the found model element
226 */
227 private Object findModelElement(final String modelElementName)
228 {
229 Object modelElement = null;
230 Collection modelElements = this.model.getModelElements();
231 CollectionUtils.filter(modelElements, new Predicate()
232 {
233 public boolean evaluate(Object object)
234 {
235 boolean valid = false;
236 if (ModelElementFacade.class.isAssignableFrom(object.getClass()))
237 {
238 ModelElementFacade modelElement = (ModelElementFacade) object;
239 String elementName = StringUtils.trimToEmpty(modelElement.getName());
240 String name = StringUtils.trimToEmpty(modelElementName);
241 valid = elementName.equals(name);
242 if (!valid)
243 {
244 elementName = StringUtils.trimToEmpty(modelElement.getFullyQualifiedName());
245 valid = elementName.equals(name);
246 }
247 }
248 return valid;
249 }
250 });
251 if (modelElements.size() > 1)
252 {
253 throw new ContextElementFinderException("More than one element named '" + modelElementName +
254 "' was found within your model," + " please give the fully qualified name");
255 } else if (modelElements.size() == 1)
256 {
257 modelElement = modelElements.iterator().next();
258 }
259 return modelElement;
260 }
261
262 /**
263 * Returns the context element found in the model from the expression.
264 *
265 * @return the context type as a ModelElementFacade.
266 */
267 public Object getContextElement()
268 {
269 return this.contextElement;
270 }
271
272 }