001package org.andromda.metafacades.uml14;
002
003import java.util.ArrayList;
004import java.util.Collection;
005import java.util.Iterator;
006import java.util.LinkedHashSet;
007import org.andromda.metafacades.uml.ClassifierFacade;
008import org.andromda.metafacades.uml.ConstraintFacade;
009import org.andromda.metafacades.uml.DependencyFacade;
010import org.andromda.metafacades.uml.MetafacadeUtils;
011import org.andromda.metafacades.uml.ModelElementFacade;
012import org.andromda.metafacades.uml.NameMasker;
013import org.andromda.metafacades.uml.OperationFacade;
014import org.andromda.metafacades.uml.ParameterFacade;
015import org.andromda.metafacades.uml.TypeMappings;
016import org.andromda.metafacades.uml.UMLMetafacadeProperties;
017import org.andromda.metafacades.uml.UMLProfile;
018import org.andromda.translation.ocl.ExpressionKinds;
019import org.andromda.utils.StringUtilsHelper;
020import org.apache.commons.collections.CollectionUtils;
021import org.apache.commons.collections.Predicate;
022import org.apache.commons.collections.Transformer;
023import org.apache.commons.lang.StringUtils;
024import org.apache.log4j.Logger;
025import org.omg.uml.foundation.core.Classifier;
026import org.omg.uml.foundation.core.Operation;
027import org.omg.uml.foundation.core.Parameter;
028import org.omg.uml.foundation.datatypes.CallConcurrencyKind;
029import org.omg.uml.foundation.datatypes.CallConcurrencyKindEnum;
030import org.omg.uml.foundation.datatypes.ParameterDirectionKindEnum;
031import org.omg.uml.foundation.datatypes.ScopeKindEnum;
032
033/**
034 * Metaclass facade implementation.
035 * @author Bob Fields
036 */
037public class OperationFacadeLogicImpl
038        extends OperationFacadeLogic
039{
040    private static final long serialVersionUID = 34L;
041    /**
042     * @param metaObject
043     * @param context
044     */
045    public OperationFacadeLogicImpl(Operation metaObject, String context)
046    {
047        super(metaObject, context);
048    }
049
050    /**
051     * The logger instance.
052     */
053    private static final Logger logger = Logger.getLogger(OperationFacadeLogicImpl.class);
054
055    /**
056     * Overridden to provide name masking.
057     *
058     * @see org.andromda.metafacades.uml.ModelElementFacade#getName()
059     */
060    @Override
061    protected String handleGetName()
062    {
063        final String nameMask = String.valueOf(this.getConfiguredProperty(UMLMetafacadeProperties.OPERATION_NAME_MASK));
064        String name = super.handleGetName();
065        if (this.isMany() && this.isPluralizeAssociationEndNames())
066        {
067            name = StringUtilsHelper.pluralize(name);
068        }
069        return NameMasker.mask(name, nameMask);
070        // UML14 does not support multiplicity > 1 on operation return parameter
071        //return NameMasker.mask(super.handleGetName(), nameMask);
072    }
073
074    /**
075     * @see org.andromda.core.metafacade.MetafacadeBase#getValidationOwner()
076     */
077    public ClassifierFacade getValidationOwner()
078    {
079        return this.getOwner();
080    }
081
082    /**
083     * @see org.andromda.metafacades.uml.OperationFacade#getSignature()
084     */
085    @Override
086    protected String handleGetSignature()
087    {
088        return this.getSignature(true);
089    }
090
091    /**
092     * @see org.andromda.metafacades.uml.OperationFacade#getSignature(boolean)
093     */
094    @Override
095    protected String handleGetSignature(boolean withArgumentNames)
096    {
097        return MetafacadeUtils.getSignature(this.getName(), this.getArguments(), withArgumentNames, null);
098    }
099
100    /**
101     * @see org.andromda.metafacades.uml.OperationFacade#getTypedArgumentList()
102     */
103    @Override
104    protected String handleGetTypedArgumentList()
105    {
106        return this.getTypedArgumentList(true);
107    }
108
109    private String getTypedArgumentList(boolean withArgumentNames)
110    {
111        return this.getTypedArgumentList(withArgumentNames, null);
112    }
113
114    /**
115     * @see org.andromda.metafacades.uml.OperationFacade#getCall()
116     */
117    @Override
118    protected String handleGetCall()
119    {
120        return this.getCall(this.getName());
121    }
122
123    /**
124     * Constructs the operation call with the given <code>name</code>
125     *
126     * @param name the name form which to construct the operation call.
127     * @return the operation call.
128     */
129    private String getCall(String name)
130    {
131        StringBuilder buffer = new StringBuilder();
132        buffer.append(name);
133        buffer.append('(');
134        buffer.append(this.getArgumentNames());
135        buffer.append(')');
136        return buffer.toString();
137    }
138
139    /**
140     * @see org.andromda.metafacades.uml.OperationFacade#getArgumentNames()
141     */
142    @Override
143    protected String handleGetArgumentNames()
144    {
145        StringBuilder buffer = new StringBuilder();
146
147        Iterator iterator = metaObject.getParameter().iterator();
148
149        boolean commaNeeded = false;
150        while (iterator.hasNext())
151        {
152            Parameter parameter = (Parameter)iterator.next();
153
154            if (!ParameterDirectionKindEnum.PDK_RETURN.equals(parameter.getKind()))
155            {
156                if (commaNeeded)
157                {
158                    buffer.append(", ");
159                }
160                ParameterFacade facade = (ParameterFacade)this.shieldedElement(parameter);
161                buffer.append(facade.getName());
162                commaNeeded = true;
163            }
164        }
165        return buffer.toString();
166    }
167
168    /**
169     * @see org.andromda.metafacades.uml.OperationFacade#getArgumentTypeNames()
170     */
171    @Override
172    protected String handleGetArgumentTypeNames()
173    {
174        StringBuilder buffer = new StringBuilder();
175
176        Iterator iterator = metaObject.getParameter().iterator();
177
178        boolean commaNeeded = false;
179        while (iterator.hasNext())
180        {
181            Parameter parameter = (Parameter)iterator.next();
182
183            if (!ParameterDirectionKindEnum.PDK_RETURN.equals(parameter.getKind()))
184            {
185                if (commaNeeded)
186                {
187                    buffer.append(", ");
188                }
189                ParameterFacade facade = (ParameterFacade)shieldedElement(parameter);
190                buffer.append(facade.getType().getFullyQualifiedName());
191                commaNeeded = true;
192            }
193        }
194        return buffer.toString();
195    }
196
197    /**
198     * @return fully qualified return type, including multiplicity
199     * @see org.andromda.metafacades.uml.OperationFacade#getGetterSetterReturnTypeName()
200     */
201    @Override
202    protected String handleGetGetterSetterReturnTypeName()
203    {
204        if (this.getReturnType()==null)
205        {
206            return "";
207        }
208        else
209        {
210            // Multiplicity in return type is only supported in UML2
211            return getReturnType().getFullyQualifiedName();
212        }
213    }
214
215    /**
216     * @see org.andromda.metafacades.uml.OperationFacade#getReturnType()
217     */
218    @Override
219    protected Classifier handleGetReturnType()
220    {
221        Classifier type = null;
222        final Collection<Parameter> parms = metaObject.getParameter();
223        for (final Parameter parameter : parms)
224        {
225            if (ParameterDirectionKindEnum.PDK_RETURN.equals(parameter.getKind()))
226            {
227                type = parameter.getType();
228                break;
229            }
230        }
231        return type;
232    }
233
234    /**
235     * @see org.andromda.metafacades.uml.OperationFacade#getArguments()
236     */
237    @Override
238    protected Collection<Parameter> handleGetArguments()
239    {
240        final Collection<Parameter> arguments = new ArrayList<Parameter>(metaObject.getParameter());
241        CollectionUtils.filter(arguments, new Predicate()
242        {
243            public boolean evaluate(Object object)
244            {
245                return !ParameterDirectionKindEnum.PDK_RETURN.equals(((Parameter)object).getKind());
246            }
247        });
248        return arguments;
249    }
250
251    /**
252     * Not yet implemented, always returns null. To implement: walk through the
253     * related elements from the Sequence Diagram in the UML model to produce compilable code.
254     * @return method body
255     * @see org.andromda.metafacades.uml.OperationFacade#getMethodBody()
256     */
257    @Override
258    protected String handleGetMethodBody()
259    {
260        return null;
261    }
262
263    /**
264     * @see org.andromda.metafacades.uml.OperationFacade#getOwner()
265     */
266    @Override
267    protected Classifier handleGetOwner()
268    {
269        return this.metaObject.getOwner();
270    }
271
272    /**
273     * @see org.andromda.metafacades.uml.OperationFacade#getParameters()
274     */
275    @Override
276    protected Collection<Parameter> handleGetParameters()
277    {
278        return metaObject.getParameter();
279    }
280
281    /**
282     * @see org.andromda.metafacades.uml.OperationFacade#findTaggedValue(String, boolean)
283     */
284    @Override
285    protected Object handleFindTaggedValue(String name, boolean follow)
286    {
287        name = StringUtils.trimToEmpty(name);
288        Object value = findTaggedValue(name);
289        if (follow)
290        {
291            ClassifierFacade type = this.getReturnType();
292            while (value == null && type != null)
293            {
294                value = type.findTaggedValue(name);
295                type = (ClassifierFacade)type.getGeneralization();
296            }
297        }
298        return value;
299    }
300
301    /**
302     * @see org.andromda.metafacades.uml.OperationFacade#isStatic()
303     */
304    @Override
305    protected boolean handleIsStatic()
306    {
307        return ScopeKindEnum.SK_CLASSIFIER.equals(this.metaObject.getOwnerScope());
308    }
309
310    /**
311     * @see org.andromda.metafacades.uml.OperationFacade#isAbstract()
312     */
313    @Override
314    protected boolean handleIsAbstract()
315    {
316        return metaObject.isAbstract();
317    }
318
319    /**
320     * @return metaObject.isLeaf()
321     * @see org.andromda.metafacades.uml.OperationFacade#isLeaf()
322     */
323    @Override
324    protected boolean handleIsLeaf()
325    {
326        return metaObject.isLeaf();
327    }
328
329    /**
330     * @return false always
331     * @see org.andromda.metafacades.uml.OperationFacade#isMany()
332     */
333    //@Override
334    protected boolean handleIsMany()
335    {
336        boolean isMany = false;
337        if (null!=this.getReturnParameter())
338        {
339            isMany = this.getReturnParameter().isMany();
340        }
341        return isMany;
342    }
343
344    /**
345     * @return false always
346     * @see org.andromda.metafacades.uml.OperationFacade#isOrdered()
347     */
348    //@Override
349    protected boolean handleIsOrdered()
350    {
351        return false;
352    }
353
354    /**
355     * @return false always
356     * @see org.andromda.metafacades.uml.OperationFacade#isOrdered()
357     */
358    //@Override
359    protected boolean handleIsUnique()
360    {
361        return this.hasStereotype(UMLProfile.STEREOTYPE_UNIQUE);
362    }
363
364    /**
365     * @see org.andromda.metafacades.uml.OperationFacade#isQuery()
366     */
367    @Override
368    protected boolean handleIsQuery()
369    {
370        return metaObject.isQuery();
371    }
372
373    /**
374     * @see org.andromda.metafacades.uml.OperationFacade#isExceptionsPresent()
375     */
376    @Override
377    protected boolean handleIsExceptionsPresent()
378    {
379        return !this.getExceptions().isEmpty();
380    }
381
382    /**
383     * @see org.andromda.metafacades.uml.OperationFacade#getExceptions()
384     */
385    @Override
386    protected Collection<ModelElementFacade> handleGetExceptions()
387    {
388        Collection <ModelElementFacade> exceptions = new LinkedHashSet<ModelElementFacade>();
389
390        // finds both exceptions and exception references
391        final class ExceptionFilter
392                implements Predicate
393        {
394            public boolean evaluate(Object object)
395            {
396                boolean hasException = object instanceof DependencyFacade;
397                if (hasException)
398                {
399                    DependencyFacade dependency = (DependencyFacade)object;
400                    // first check for exception references
401                    hasException = dependency.hasStereotype(UMLProfile.STEREOTYPE_EXCEPTION_REF);
402
403                    // if there wasn't any exception reference
404                    // now check for actual exceptions
405                    if (!hasException)
406                    {
407                        ModelElementFacade targetElement = dependency.getTargetElement();
408                        hasException = targetElement != null && targetElement.hasStereotype(
409                                UMLProfile.STEREOTYPE_EXCEPTION);
410                    }
411                }
412                return hasException;
413            }
414        }
415
416        // first get any dependencies on this operation's
417        // owner (because these will represent the default exception(s))
418        final Collection<DependencyFacade> ownerDependencies = new ArrayList<DependencyFacade>(this.getOwner().getSourceDependencies());
419        if (!ownerDependencies.isEmpty())
420        {
421            CollectionUtils.filter(ownerDependencies, new ExceptionFilter());
422            exceptions.addAll(ownerDependencies);
423        }
424
425        final Collection<DependencyFacade> operationDependencies = new ArrayList<DependencyFacade>(this.getSourceDependencies());
426        // now get any exceptions directly on the operation
427        if (!operationDependencies.isEmpty())
428        {
429            CollectionUtils.filter(operationDependencies, new ExceptionFilter());
430            exceptions.addAll(operationDependencies);
431        }
432
433        // now transform the dependency(s) to the actual exception(s)
434        CollectionUtils.transform(exceptions, new Transformer()
435        {
436            public ModelElementFacade transform(Object object)
437            {
438                return ((DependencyFacade)object).getTargetElement();
439            }
440        });
441        return exceptions;
442    }
443
444    /**
445     * @see org.andromda.metafacades.uml.OperationFacade#getExceptionList()
446     */
447    @Override
448    protected String handleGetExceptionList()
449    {
450        return this.getExceptionList(null);
451    }
452
453    /**
454     * @see org.andromda.metafacades.uml.OperationFacade#isReturnTypePresent()
455     */
456    @Override
457    protected boolean handleIsReturnTypePresent()
458    {
459        boolean hasReturnType = false;
460        if (this.getReturnType() != null)
461        {
462            hasReturnType = !("void".equalsIgnoreCase(StringUtils.trimToEmpty(
463                    this.getReturnType().getFullyQualifiedName()))
464              || StringUtils.trimToEmpty(
465                this.getReturnType().getFullyQualifiedName(true)).equals(UMLProfile.VOID_TYPE_NAME));
466        }
467        return hasReturnType;
468    }
469
470    /**
471     * @see org.andromda.metafacades.uml.OperationFacade#getExceptionList(String)
472     */
473    @Override
474    protected String handleGetExceptionList(String initialExceptions)
475    {
476        initialExceptions = StringUtils.trimToEmpty(initialExceptions);
477        StringBuilder exceptionList = new StringBuilder(initialExceptions);
478        Collection exceptions = this.getExceptions();
479        if (exceptions != null && !exceptions.isEmpty())
480        {
481            if (StringUtils.isNotBlank(initialExceptions))
482            {
483                exceptionList.append(", ");
484            }
485            Iterator exceptionIt = exceptions.iterator();
486            while (exceptionIt.hasNext())
487            {
488                ModelElementFacade exception = (ModelElementFacade)exceptionIt.next();
489                exceptionList.append(exception.getFullyQualifiedName());
490                if (exceptionIt.hasNext())
491                {
492                    exceptionList.append(", ");
493                }
494            }
495        }
496
497        return exceptionList.toString();
498    }
499
500    /**
501     * @see org.andromda.metafacades.uml.OperationFacade#getTypedArgumentList(String)
502     */
503    @Override
504    protected String handleGetTypedArgumentList(String modifier)
505    {
506        return this.getTypedArgumentList(true, modifier);
507    }
508
509    /**
510     * @see org.andromda.metafacades.uml.OperationFacade#getSignature(String)
511     */
512    @Override
513    protected String handleGetSignature(String argumentModifier)
514    {
515        return MetafacadeUtils.getSignature(this.getName(), this.getArguments(), true, argumentModifier);
516    }
517
518    private String getTypedArgumentList(boolean withArgumentNames, String modifier)
519    {
520        final StringBuilder buffer = new StringBuilder();
521        final Iterator parameterIterator = metaObject.getParameter().iterator();
522
523        boolean commaNeeded = false;
524        while (parameterIterator.hasNext())
525        {
526            Parameter paramter = (Parameter)parameterIterator.next();
527
528            if (!ParameterDirectionKindEnum.PDK_RETURN.equals(paramter.getKind()))
529            {
530                String type = null;
531                if (paramter.getType() == null)
532                {
533                    OperationFacadeLogicImpl.logger.error(
534                            "ERROR! No type specified for parameter --> '" + paramter.getName() +
535                            "' on operation --> '" +
536                            this.getName() +
537                            "', please check your model");
538                }
539                else
540                {
541                    type = ((ClassifierFacade)this.shieldedElement(paramter.getType())).getFullyQualifiedName();
542                }
543
544                if (commaNeeded)
545                {
546                    buffer.append(", ");
547                }
548                if (StringUtils.isNotBlank(modifier))
549                {
550                    buffer.append(modifier);
551                    buffer.append(' ');
552                }
553                buffer.append(type);
554                if (withArgumentNames)
555                {
556                    buffer.append(' ');
557                    buffer.append(paramter.getName());
558                }
559                commaNeeded = true;
560            }
561        }
562        return buffer.toString();
563    }
564
565    /**
566     * @see org.andromda.metafacades.uml.OperationFacade#getConcurrency()
567     */
568    @Override
569    protected String handleGetConcurrency()
570    {
571        String concurrency;
572
573        final CallConcurrencyKind concurrencyKind = metaObject.getConcurrency();
574        if (concurrencyKind == null || CallConcurrencyKindEnum.CCK_CONCURRENT.equals(concurrencyKind))
575        {
576            concurrency = "concurrent";
577        }
578        else if (CallConcurrencyKindEnum.CCK_GUARDED.equals(concurrencyKind))
579        {
580            concurrency = "guarded";
581        }
582        else // CallConcurrencyKindEnum.CCK_SEQUENTIAL
583        {
584            concurrency = "sequential";
585        }
586
587        final TypeMappings languageMappings = this.getLanguageMappings();
588        if (languageMappings != null)
589        {
590            concurrency = languageMappings.getTo(concurrency);
591        }
592
593        return concurrency;
594    }
595
596    /**
597     * @see org.andromda.metafacades.uml.OperationFacade#getPreconditionName()
598     */
599    @Override
600    protected String handleGetPreconditionName()
601    {
602        return this.getPreconditionPattern().replaceAll("\\{0\\}", this.getName());
603    }
604
605    /**
606     * @see org.andromda.metafacades.uml.OperationFacade#getPostconditionName()
607     */
608    @Override
609    protected String handleGetPostconditionName()
610    {
611        return this.getPostconditionPattern().replaceAll("\\{0\\}", this.getName());
612    }
613
614    /**
615     * @see org.andromda.metafacades.uml.OperationFacade#getPreconditionSignature()
616     */
617    @Override
618    protected String handleGetPreconditionSignature()
619    {
620        return MetafacadeUtils.getSignature(this.getPreconditionName(), this.getArguments(), true, null);
621    }
622
623    /**
624     * @see org.andromda.metafacades.uml.OperationFacade#getPreconditionCall()
625     */
626    @Override
627    protected String handleGetPreconditionCall()
628    {
629        return this.getCall(this.getPreconditionName());
630    }
631
632    /**
633     * Gets the pattern for constructing the precondition name.
634     *
635     * @return the precondition pattern.
636     */
637    private String getPreconditionPattern()
638    {
639        return String.valueOf(this.getConfiguredProperty(UMLMetafacadeProperties.PRECONDITION_NAME_PATTERN));
640    }
641
642    /**
643     * Gets the pattern for constructing the postcondition name.
644     *
645     * @return the postcondition pattern.
646     */
647    private String getPostconditionPattern()
648    {
649        return String.valueOf(this.getConfiguredProperty(UMLMetafacadeProperties.POSTCONDITION_NAME_PATTERN));
650    }
651
652    /**
653     * @see org.andromda.metafacades.uml.OperationFacade#isPreconditionsPresent()
654     */
655    @Override
656    protected boolean handleIsPreconditionsPresent()
657    {
658        final Collection<ConstraintFacade> preconditions = this.getPreconditions();
659        return preconditions != null && !preconditions.isEmpty();
660    }
661
662    /**
663     * @see org.andromda.metafacades.uml.OperationFacade#isPostconditionsPresent()
664     */
665    @Override
666    protected boolean handleIsPostconditionsPresent()
667    {
668        final Collection<ConstraintFacade> postconditions = this.getPostconditions();
669        return postconditions != null && !postconditions.isEmpty();
670    }
671
672    /**
673     * @see org.andromda.metafacades.uml.OperationFacade#getPreconditions()
674     */
675    @Override
676    protected Collection<ConstraintFacade> handleGetPreconditions()
677    {
678        return this.getConstraints(ExpressionKinds.PRE);
679    }
680
681    /**
682     * @see org.andromda.metafacades.uml.OperationFacade#getPostconditions()
683     */
684    @Override
685    protected Collection<ConstraintFacade> handleGetPostconditions()
686    {
687        return this.getConstraints(ExpressionKinds.POST);
688    }
689
690    /**
691     * @see  org.andromda.metafacades.uml.OperationFacade#findParameter(String)
692     */
693    @Override
694    protected ParameterFacade handleFindParameter(final String name)
695    {
696        return (ParameterFacade)CollectionUtils.find(
697            this.getParameters(),
698            new Predicate()
699            {
700                public boolean evaluate(Object object)
701                {
702                    final ParameterFacade parameter = (ParameterFacade)object;
703                    return StringUtils.trimToEmpty(parameter.getName()).equals(name);
704                }
705            });
706    }
707
708    /**
709     * Get the UML upper multiplicity
710     * @return -1 (UnlimitedNatural) is isMany, otherwise 1
711     */
712    @Override
713    protected int handleGetUpper()
714    {
715        if (this.isMany())
716        {
717            return -1;
718        }
719        return 1;
720     }
721
722    /**
723     * Get the UML lower multiplicity
724     * @return 1 if primitive, otherwise 0
725     */
726    @Override
727    protected int handleGetLower()
728    {
729        if (!this.getReturnParameter().hasStereotype("Nullable")
730            && this.getReturnType().isPrimitive())
731        {
732            return 1;
733        }
734        return 0;
735    }
736
737    /**
738     * @see org.andromda.metafacades.uml14.OperationFacadeLogic#handleGetReturnParameter()
739     */
740    @Override
741    public ParameterFacade handleGetReturnParameter()
742    {
743        //throw new UnsupportedOperationException("ReturnResults is not a UML1.4 feature");
744        ParameterFacade facade = null;
745        final Collection<Parameter> parms = metaObject.getParameter();
746        for (final Parameter parameter : parms)
747        {
748            if (ParameterDirectionKindEnum.PDK_RETURN.equals(parameter.getKind()))
749            {
750                facade = (ParameterFacade)shieldedElement(parameter);
751                break;
752            }
753        }
754        return facade;
755    }
756
757    /**
758     * @see org.andromda.metafacades.uml14.OperationFacadeLogic#handleIsOverriding()
759     */
760    protected boolean handleIsOverriding()
761    {
762        return this.getOverriddenOperation() != null;
763    }
764
765    /**
766     * @see org.andromda.metafacades.uml14.OperationFacadeLogic#handleGetOverriddenOperation()
767     */
768    protected OperationFacade handleGetOverriddenOperation()
769    {
770        OperationFacade overriddenOperation = null;
771
772        final String signature = this.getSignature(false);
773
774        ClassifierFacade ancestor = this.getOwner().getSuperClass();
775        while (overriddenOperation == null && ancestor != null)
776        {
777            for (Iterator operationIterator = ancestor.getOperations().iterator();
778                 overriddenOperation == null && operationIterator.hasNext();)
779            {
780                final OperationFacade ancestorOperation = (OperationFacade)operationIterator.next();
781                if (signature.equals(ancestorOperation.getSignature(false)))
782                {
783                    overriddenOperation = ancestorOperation;
784                }
785            }
786
787            ancestor = ancestor.getSuperClass();
788        }
789
790        return overriddenOperation;
791    }
792
793    /**
794     * Indicates whether or not we should pluralize association end names.
795     *
796     * @return true/false
797     */
798    //@SuppressWarnings("unused")
799    private boolean isPluralizeAssociationEndNames()
800    {
801        final Object value = this.getConfiguredProperty(UMLMetafacadeProperties.PLURALIZE_ASSOCIATION_END_NAMES);
802        return value != null && Boolean.valueOf(String.valueOf(value));
803    }
804}