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    }