//   Copyright 2012 Georg-August-Universität Göttingen, Germany
//
//   Licensed under the Apache License, Version 2.0 (the "License");
//   you may not use this file except in compliance with the License.
//   You may obtain a copy of the License at
//
//       http://www.apache.org/licenses/LICENSE-2.0
//
//   Unless required by applicable law or agreed to in writing, software
//   distributed under the License is distributed on an "AS IS" BASIS,
//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//   See the License for the specific language governing permissions and
//   limitations under the License.

package de.ugoe.cs.autoquest.plugin.uml;

import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;

import org.apache.commons.lang.mutable.MutableInt;
import org.eclipse.emf.common.util.EList;
import org.eclipse.uml2.uml.CallConcurrencyKind;
import org.eclipse.uml2.uml.Component;
import org.eclipse.uml2.uml.Connector;
import org.eclipse.uml2.uml.DataType;
import org.eclipse.uml2.uml.Element;
import org.eclipse.uml2.uml.Enumeration;
import org.eclipse.uml2.uml.Generalization;
import org.eclipse.uml2.uml.InstanceSpecification;
import org.eclipse.uml2.uml.InstanceValue;
import org.eclipse.uml2.uml.Interaction;
import org.eclipse.uml2.uml.Interface;
import org.eclipse.uml2.uml.Lifeline;
import org.eclipse.uml2.uml.LiteralBoolean;
import org.eclipse.uml2.uml.LiteralInteger;
import org.eclipse.uml2.uml.LiteralReal;
import org.eclipse.uml2.uml.LiteralString;
import org.eclipse.uml2.uml.Message;
import org.eclipse.uml2.uml.MessageOccurrenceSpecification;
import org.eclipse.uml2.uml.MessageSort;
import org.eclipse.uml2.uml.Model;
import org.eclipse.uml2.uml.Operation;
import org.eclipse.uml2.uml.Package;
import org.eclipse.uml2.uml.Parameter;
import org.eclipse.uml2.uml.PrimitiveType;
import org.eclipse.uml2.uml.Property;
import org.eclipse.uml2.uml.Relationship;
import org.eclipse.uml2.uml.Slot;
import org.eclipse.uml2.uml.Type;
import org.eclipse.uml2.uml.UMLPackage;

import de.ugoe.cs.autoquest.eventcore.Event;
import de.ugoe.cs.autoquest.plugin.http.SOAPUtils;
import de.ugoe.cs.autoquest.plugin.http.eventcore.SimpleSOAPEventType.CallType;
import de.ugoe.cs.util.StringTools;
import de.ugoe.cs.util.console.Console;

/**
 * <p>
 * Utility class to create UML Interactions from event sequences
 * </p>
 * 
 * @author Steffen Herbold
 */
class UMLInteractionCreator {

    /**
     * model for which the interactions are created
     */
    private final Model model;

    /**
     * name of the test context in which the interactions are created
     */
    private final String testContextName;

    /**
     * defines whether random message bodies are used or the ones associated with the SOAP events
     */
    private final boolean useRandomMsgBodies;

    /**
     * a cache of the already created instance specification; used to create the overall number of generated specifications
     */
    private final Map<String, InstanceSpecification> instanceSpecificationCache;

    /**
     * <p>
     * Creates a new UMLInteractionCreator
     * </p>
     * 
     * @param model
     *            UML model to which the interaction is added
     * @param testContextName
     *            Name of the test context that should be used. If this value is null, the first
     *            test context found is used.
     * @param useRandomRequestBodies
     *            defines is random request bodies are used or the body of the associated event
     */
    public UMLInteractionCreator(Model model, String testContextName, boolean useRandomMsgBodies) {
        this.model = model;
        this.testContextName = testContextName;
        this.useRandomMsgBodies = useRandomMsgBodies;
        instanceSpecificationCache = new HashMap<String, InstanceSpecification>();
    }

    /**
     * <p>
     * Creates the UML Interaction for the given sequence
     * </p>
     * 
     * @param sequence
     *            for which the interaction is created
     * @param interactionName
     *            name of the interaction
     * @return created interaction
     */
    public Interaction createInteraction(List<Event> sequence, String interactionName) {
        Console.traceln(Level.FINEST, "creating interaction: " + interactionName);
        final Component testContext = UMLUtils.fetchTestContext(model, testContextName);
        if (testContext == null) {
            throw new RuntimeException("Could not find any test context in the model");
        }

        final Operation operation = testContext.createOwnedOperation(interactionName, null, null);
        operation.applyStereotype(UTPUtils.getTestCaseStereotype(model));

        final Interaction interaction =
            (Interaction) testContext.createPackagedElement(interactionName + "_Impl",
                                                            UMLPackage.Literals.INTERACTION);
        operation.getMethods().add(interaction);

        // create lifelines
        Lifeline userLifeline = null;

        for (Property property : UMLUtils.fetchAllSUTProperties(testContext)) {
            String serviceName = property.getName();
            Lifeline targetLifeline = interaction.createLifeline(serviceName);
            targetLifeline.setRepresents(property);
        }
        for (Property property : UMLUtils.fetchAllTestComponentProperties(testContext)) {
            userLifeline = interaction.createLifeline(property.getName());
            userLifeline.setRepresents(property);
        }

        if (userLifeline == null) {
            throw new RuntimeException("No TestComponent found, could not create user lifeline.");
        }
        if (interaction.getLifelines().size() < 2) {
            throw new RuntimeException("Fewer than two lifelines created. No SUT found.");
        }

        int i = 0;
        for (Event event : sequence) {
            Console.traceln(Level.FINEST, "creating message for event: " + event.toString());
            if (!(event.equals(Event.STARTEVENT) || event.equals(Event.ENDEVENT))) {
                String serviceName = SOAPUtils.getServiceNameFromEvent(event);
                String methodName = SOAPUtils.getCalledMethodFromEvent(event);
                String clientName = SOAPUtils.getClientNameFromEvent(event);
                String prefix = interactionName + "_" + i + "_" + methodName + "_";
                // determine lifelines
                Lifeline msgTargetLifeline;
                Lifeline msgSourceLifeline;

                msgSourceLifeline = interaction.getLifeline(clientName);
                msgTargetLifeline = interaction.getLifeline(serviceName);

                if (msgSourceLifeline == null) {
                    throw new RuntimeException(
                                               "Error creating message: could not determine source lifeline for component: " +
                                                   clientName);
                }
                if (msgTargetLifeline == null) {
                    throw new RuntimeException(
                                               "Error creating message: could not determine target lifeline for component: " +
                                                   serviceName);
                }
                // determine correct target interface
                Set<Interface> targetInterfaces =
                    UMLUtils.getRealizedInterfacesFromProperty((Property) msgTargetLifeline
                        .getRepresents());
                if (targetInterfaces.isEmpty()) {
                    throw new RuntimeException("no interface associated with the property " +
                        msgTargetLifeline.getRepresents().getName());
                }
                Interface targetInterface = null;
                for (Interface intface : targetInterfaces) {
                    if (UMLUtils.getOperationFromName(intface.getOperations(), methodName) != null)
                    {
                        // interface found
                        targetInterface = intface;
                        break;
                    }
                }
                if (targetInterface == null) {
                    StringBuilder errStrBuilder = new StringBuilder();
                    errStrBuilder
                        .append("Error creating message: operation not found in the implementing interfaces (");
                    Iterator<Interface> iter = targetInterfaces.iterator();
                    while (iter.hasNext()) {
                        String interfaceName = iter.next().getName();
                        errStrBuilder.append(interfaceName);
                        if (iter.hasNext()) {
                            errStrBuilder.append(",");
                        }
                        else {
                            errStrBuilder.append("): " + methodName);
                        }
                    }
                    throw new RuntimeException(errStrBuilder.toString());
                }

                Operation calledOperation =
                    UMLUtils.getOperationFromName(targetInterface.getOperations(), methodName);
                // get connector
                Connector connector =
                    UMLUtils.inferConnector(msgSourceLifeline, msgTargetLifeline, targetInterface);
                if (connector == null) {
                    throw new RuntimeException(
                                               "Error creating message: could not find connector between the two life lines that supports the target interface at the target lifeline");
                }

                boolean asynch = false;
                if (calledOperation.getConcurrency() == CallConcurrencyKind.CONCURRENT_LITERAL) {
                    asynch = true;
                }

                if (SOAPUtils.isSOAPRequest(event)) {
                    // setup for both SYNCH and ASYNCH calls
                    MessageOccurrenceSpecification callSendFragment =
                        (MessageOccurrenceSpecification) interaction
                            .createFragment(prefix + "callSendFragment",
                                            UMLPackage.Literals.MESSAGE_OCCURRENCE_SPECIFICATION);
                    MessageOccurrenceSpecification callRecvFragment =
                        (MessageOccurrenceSpecification) interaction
                            .createFragment(prefix + "callRecvFragment",
                                            UMLPackage.Literals.MESSAGE_OCCURRENCE_SPECIFICATION);

                    callSendFragment.setCovered(msgSourceLifeline);
                    callRecvFragment.setCovered(msgTargetLifeline);

                    // create call
                    Message callMessage = interaction.createMessage(prefix + "call");
                    callMessage.setSignature(calledOperation);
                    setMessageParameters(callMessage, calledOperation, event, prefix);
                    callMessage.setConnector(connector);
                    callMessage.setSendEvent(callSendFragment);
                    callMessage.setReceiveEvent(callRecvFragment);
                    callSendFragment.setMessage(callMessage);
                    callRecvFragment.setMessage(callMessage);

                    if (asynch) {
                        // Create ASYNCH call
                        callMessage.setMessageSort(MessageSort.ASYNCH_CALL_LITERAL);
                    }
                    else {
                        // SYNCH call
                        callMessage.setMessageSort(MessageSort.SYNCH_CALL_LITERAL);
                    }
                }
                if (!asynch && SOAPUtils.isSOAPResponse(event)) {
                    // setup reply and behavior execution specifications
                    MessageOccurrenceSpecification replySendFragment =
                        (MessageOccurrenceSpecification) interaction
                            .createFragment(prefix + "replySendFragment",
                                            UMLPackage.Literals.MESSAGE_OCCURRENCE_SPECIFICATION);
                    MessageOccurrenceSpecification replyRecvFragment =
                        (MessageOccurrenceSpecification) interaction
                            .createFragment(prefix + "replyRecvFragment",
                                            UMLPackage.Literals.MESSAGE_OCCURRENCE_SPECIFICATION);

                    replySendFragment.setCovered(msgTargetLifeline);
                    replyRecvFragment.setCovered(msgSourceLifeline);

                    /*
                     * BehaviorExecutionSpecification sourceBehaviorExecutionSpecification =
                     * (BehaviorExecutionSpecification) interaction .createFragment(":" + methodName
                     * + "_sourceBhvExecSpec",
                     * UMLPackage.Literals.BEHAVIOR_EXECUTION_SPECIFICATION);
                     * BehaviorExecutionSpecification targetBehaviorExecutionSpecification =
                     * (BehaviorExecutionSpecification) interaction .createFragment(":" + methodName
                     * + "_targetBhvExecSpec",
                     * UMLPackage.Literals.BEHAVIOR_EXECUTION_SPECIFICATION);
                     * 
                     * sourceBehaviorExecutionSpecification.setStart(callSendFragment);
                     * sourceBehaviorExecutionSpecification.setFinish(replyRecvFragment);
                     * targetBehaviorExecutionSpecification.setStart(callRecvFragment);
                     * targetBehaviorExecutionSpecification.setFinish(replySendFragment);
                     */

                    // create reply
                    Message replyMessage = interaction.createMessage(prefix + "_reply");
                    replyMessage.setMessageSort(MessageSort.REPLY_LITERAL);
                    replyMessage.setSignature(calledOperation);
                    // setReplyMessageParameters(replyMessage, calledOperation);
                    setMessageParameters(replyMessage, calledOperation, event, prefix);
                    replyMessage.setConnector(connector);
                    replyMessage.setSendEvent(replySendFragment);
                    replyMessage.setReceiveEvent(replyRecvFragment);
                    replySendFragment.setMessage(replyMessage);
                    replyRecvFragment.setMessage(replyMessage);
                }

                i++;
            }
        }
        return interaction;
    }

    /**
     * <p>
     * Sets values for the parameters of a call message. The values are, if possible, inferred from
     * the event that is provided.
     * </p>
     * 
     * @param message
     *            call message for which the parameters are set
     * @param calledOperation
     *            operation that is called by the message
     * @param event
     *            event that provides the parameters; in case of null, default values are assumed
     * @param prefix
     *            prefix of the call message; used to create good warnings and debugging information
     */
    private void setMessageParameters(Message message,
                                      Operation calledOperation,
                                      Event event,
                                      String prefix)
    {
        // printParameters(calledOperation); // FOR DEBUGGING
        org.w3c.dom.Element requestBody;
        if (SOAPUtils.isSOAPRequest(event)) {
            requestBody =
                SOAPUtils.getSoapBodyFromEvent(event, useRandomMsgBodies, CallType.REQUEST);
        }
        else {
            requestBody =
                SOAPUtils.getSoapBodyFromEvent(event, useRandomMsgBodies, CallType.RESPONSE);
        }
        Package instSpecPkg = null;
        MutableInt instSpecNumber = new MutableInt(0);

        // Set parameters of operation
        for (Parameter param : calledOperation.getOwnedParameters()) {
            if (instSpecPkg == null) {
                instSpecPkg = getOrCreateInstanceSpecificationPackage(event);
            }

            String path = calledOperation.getName() + ":" + getNormalizedTypeName(param.getType());
            if ((UMLUtils.isInParameter(param) && SOAPUtils.isSOAPRequest(event)) ||
                (UMLUtils.isOutParameter(param) && SOAPUtils.isSOAPResponse(event)))
            {

                // create parameters node
                if (!(param.getType() instanceof DataType)) {
                    throw new RuntimeException("TODO error handling; parameters missing");
                }
                DataType parametersNode = (DataType) param.getType();
                InstanceSpecification instSpecParameters =
                    (InstanceSpecification) instSpecPkg
                        .createPackagedElement(prefix + "instspec" + instSpecNumber.intValue() +
                                                   "_" + getNormalizedTypeName(param.getType()),
                                               UMLPackage.Literals.INSTANCE_SPECIFICATION);
                instSpecParameters.getClassifiers().add((DataType) param.getType());
                instSpecNumber.setValue(instSpecNumber.intValue() + 1);
                InstanceValue instanceValue =
                    (InstanceValue) message.createArgument(param.getName(), param.getType(),
                                                           UMLPackage.Literals.INSTANCE_VALUE);
                instanceValue.setInstance(instSpecParameters);

                for (Property internalParameter : parametersNode.getAllAttributes()) {
                    if (internalParameter.getType() instanceof DataType) {
                        List<org.w3c.dom.Element> paramNodes =
                            SOAPUtils.getMatchingChildNode(getNormalizedTypeName(internalParameter
                                .getType()), requestBody);
                        int multiplicityChosen = paramNodes.size();

                        if (multiplicityChosen == 0 && internalParameter.getLower() > 0) {
                            Console
                                .traceln(Level.WARNING,
                                         "required attribute not found in SOAP message: " + path);
                            Console
                                .traceln(Level.WARNING,
                                         "setting default values for this attribute and all its children");
                            Console.traceln(Level.FINE, "XML structure of path:" +
                                StringTools.ENDLINE + SOAPUtils.getSerialization(requestBody));
                            multiplicityChosen = internalParameter.getLower();
                        }
                        for (int i = 0; i < multiplicityChosen; i++) {
                            org.w3c.dom.Element paramNode = null;
                            if (!paramNodes.isEmpty()) {
                                paramNode = paramNodes.get(i);
                            }

                            Slot slot = instSpecParameters.createSlot();
                            slot.setDefiningFeature(internalParameter);

                            InstanceValue value =
                                (InstanceValue) slot
                                    .createValue(internalParameter.getName() + "_" + i,
                                                 internalParameter.getType(),
                                                 UMLPackage.Literals.INSTANCE_VALUE);
                            InstanceSpecification instSpec =
                                createInstanceSpecification((DataType) internalParameter.getType(),
                                                            instSpecPkg, prefix, instSpecNumber,
                                                            paramNode, path);
                            value.setInstance(instSpec);
                            /*
                             * InstanceValue value = (InstanceValue) argument .createOperand(null,
                             * internalParameter.getType(), UMLPackage.Literals.INSTANCE_VALUE);
                             * value.setInstance(instSpec);
                             */
                        }
                    }
                    else if (internalParameter.getType() instanceof PrimitiveType) {
                        createSlotPrimitiveType(instSpecParameters, internalParameter, requestBody,
                                                path);
                    }
                }
            }
            else {
                // set literalNull for out and return parameters
                // argument.createOperand(null, param.getType(), UMLPackage.Literals.LITERAL_NULL);
                message.createArgument(param.getName(), param.getType(),
                                       UMLPackage.Literals.LITERAL_NULL);
            }
        }
    }

    /**
     * <p>
     * Creates an {@link InstanceSpecification} for a data type in the given package. The values are
     * inferred, if possible, from the DOM node. The prefix and the path are used for naming the
     * instance specification and to provide good warnings and debug information in case of
     * problems.
     * </p>
     * 
     * @param type
     *            DataType for which the {@link InstanceSpecification} is created
     * @param pkg
     *            package in which the {@link InstanceSpecification} is created
     * @param prefix
     *            prefix used for naming the {@link InstanceSpecification}
     * @param currentNode
     *            node of a DOM from which values are inferred
     * @param path
     *            used for warnings and debug information
     * @return {@link InstanceSpecification} for the given type
     */
    private InstanceSpecification createInstanceSpecification(DataType type,
                                                              Package pkg,
                                                              String prefix,
                                                              MutableInt instSpecNumber,
                                                              org.w3c.dom.Element currentNode,
                                                              String path)
    {
        if (instanceSpecificationCache.containsKey(SOAPUtils.getSerialization(currentNode))) {
            return instanceSpecificationCache.get(SOAPUtils.getSerialization(currentNode));
        }
        if ("".equals(path)) {
            path = type.getName();
        }

        InstanceSpecification instSpec =
            (InstanceSpecification) pkg
                .createPackagedElement(prefix + "instspec" + instSpecNumber.intValue() + "_" +
                                           type.getName(),
                                       UMLPackage.Literals.INSTANCE_SPECIFICATION);
        instSpec.getClassifiers().add(type);
        instSpecNumber.setValue(instSpecNumber.intValue() + 1);
        for (Property prop : type.getAllAttributes()) {
            if (prop.getType() instanceof PrimitiveType) {
                createSlotPrimitiveType(instSpec, prop, currentNode, path);
            }
            else if (prop.getType() instanceof Enumeration ) {
                createSlotEnumeration(instSpec, prop, currentNode, path);
            }
            else if (prop.getType() instanceof DataType) {
                if( isXSDSequence(prop.getType()) ) { // XSD sequence, no real type
                    List<String> childNames = SOAPUtils.getChildNodeNames(currentNode);
                    List<org.w3c.dom.Element> childNodes = SOAPUtils.getChildElements(currentNode);
                    EList<Property> sequenceProperties = ((DataType) prop.getType()).getAllAttributes();
                    boolean sequenceIsMatch = true;
                    if( sequenceProperties.size()==childNames.size()) {
                        for(int i=0; sequenceIsMatch && i<sequenceProperties.size() ; i++) {
                            Property propSeq = sequenceProperties.get(i);
                            String currentChildName = childNames.get(i);
                            if( isXSDChoice(propSeq.getType() ) ) {
                                boolean choiceMatchFound = false;
                                for( Property propChoice : ((DataType) propSeq.getType()).getAllAttributes() ) {
                                    if( currentChildName.equals(propChoice.getName()) ) {
                                        choiceMatchFound = true;
                                    }
                                }
                                sequenceIsMatch &= choiceMatchFound;
                            } else {
                                sequenceIsMatch &= currentChildName.equals(propSeq.getName());
                            }
                        }
                        if( sequenceIsMatch ) {
                            // this is the correct sequence, it matches; now appropriate data must be created
                            
                            // first we create the slot and instance specification for the sequence
                            Slot slot = instSpec.createSlot();
                            slot.setDefiningFeature(prop);
                            InstanceValue value =
                                (InstanceValue) slot.createValue(prop.getName(), prop.getType(), UMLPackage.Literals.INSTANCE_VALUE);
                            InstanceSpecification instSpecSequence =
                                    (InstanceSpecification) pkg
                                        .createPackagedElement(prefix + prop.getName() + "_instspec" + instSpecNumber.intValue() + "_" +
                                                                   type.getName(),
                                                               UMLPackage.Literals.INSTANCE_SPECIFICATION);
                            instSpecSequence.getClassifiers().add((DataType) prop.getType());
                            instSpecNumber.setValue(instSpecNumber.intValue() + 1);
                            value.setInstance(instSpecSequence);
                            
                            // now we create the slots and instance specifications for the elements of the sequence
                            for(int i=0; i<sequenceProperties.size() ; i++) {
                                Property propSeq = sequenceProperties.get(i);
                                String currentChildName = childNames.get(i);
                                slot = instSpecSequence.createSlot();
                                slot.setDefiningFeature(propSeq);
                                value = (InstanceValue) slot.createValue(propSeq.getName(), propSeq.getType(), UMLPackage.Literals.INSTANCE_VALUE);
                                if( isXSDChoice(propSeq.getType() ) ) {
                                    // create the inner choice instance spec
                                    InstanceSpecification instSpecSequenceChoice = (InstanceSpecification) pkg
                                            .createPackagedElement(prefix + propSeq.getName() + "_instspec" + instSpecNumber.intValue() + "_" +
                                                    propSeq.getType().getName(),
                                                UMLPackage.Literals.INSTANCE_SPECIFICATION);
                                    instSpecSequenceChoice.getClassifiers().add((DataType) propSeq.getType());
                                    instSpecNumber.setValue(instSpecNumber.intValue()+1);
                                    value.setInstance(instSpecSequenceChoice);
                                    for( Property propChoice : ((DataType) propSeq.getType()).getAllAttributes() ) {
                                        if( currentChildName.equals(propChoice.getName()) ) {
                                            slot = instSpecSequenceChoice.createSlot();
                                            slot.setDefiningFeature(propChoice);
                                            value = (InstanceValue) slot.createValue(propChoice.getName(), propChoice.getType(), UMLPackage.Literals.INSTANCE_VALUE);
                                            value.setInstance(createInstanceSpecification((DataType) propChoice.getType(), pkg, prefix, instSpecNumber, childNodes.get(i), path+"."+propChoice.getName()));
                                            break;
                                        }
                                    }
                                } else {
                                    value.setInstance(createInstanceSpecification((DataType) propSeq.getType(), pkg, prefix, instSpecNumber, childNodes.get(i), path+"."+propSeq.getName()));
                                }
                            } 
                        }
                    }
                }
                else if( isXSDChoice(prop.getType()) ) { 
                    // TODO implement handling of choices, if required for the MIDAS pilots
                    Console.traceln(Level.SEVERE, "cannot handle choices that are the child elements of normal data types yet!");
                    Console.traceln(Level.SEVERE, "choice is ignored and no data is created.");
                } else {
                    List<org.w3c.dom.Element> attributeNodes = null;
                    int multiplicityChosen = 0;
                    if (currentNode != null) {
                        attributeNodes = SOAPUtils.getMatchingChildNode(prop.getName(), currentNode);
                        multiplicityChosen = attributeNodes.size();
                    }
    
                    if (multiplicityChosen == 0 && prop.getLower() > 0) {
                        if (currentNode != null) {
                            Console.traceln(Level.WARNING,
                                            "required attribute not found in SOAP message: " + path +
                                                "." + prop.getName());
                            Console
                                .traceln(Level.WARNING,
                                         "setting default values for this attribute and all its children");
                            Console.traceln(Level.FINE, "XML structure of path:" + StringTools.ENDLINE +
                                SOAPUtils.getSerialization(currentNode));
                        }
                        multiplicityChosen = prop.getLower();
                    }
                    for (int i = 0; i < multiplicityChosen; i++) {
                        org.w3c.dom.Element attributeNode = null;
                        if (attributeNodes != null && !attributeNodes.isEmpty()) {
                            attributeNode = attributeNodes.get(i);
                        }
    
                        Slot slot = instSpec.createSlot();
                        slot.setDefiningFeature(prop);
    
                        InstanceValue value =
                            (InstanceValue) slot.createValue(prop.getName() + "_" + i, prop.getType(),
                                                             UMLPackage.Literals.INSTANCE_VALUE);
                        value.setInstance(createInstanceSpecification((DataType) prop.getType(), pkg,
                                                                      prefix, instSpecNumber,
                                                                      attributeNode,
                                                                      path + "." + prop.getName()));
                    }
                }
            }
            else {
                Console.traceln(Level.SEVERE, "property neither DataType nor PrimitiveType: " +
                    prop.getType());
                throw new RuntimeException(
                                           "can only handle DataType and PrimitiveType properties but was: " +
                                               prop.getType().getClass().getName());
            }
        }
        instanceSpecificationCache.put(SOAPUtils.getSerialization(currentNode), instSpec);
        return instSpec;
    }

    /**
     * <p>
     * Creates a {@link Slot} in an {@link InstanceSpecification} for a primitive type.
     * </p>
     * 
     * @param instSpec
     *            instance specification to which the slot is added
     * @param prop
     *            property that describes the slot
     * @param currentNode
     *            DOM node from which is value for the slot is inferred
     * @param path
     *            used for warnings and debug information
     */
    private void createSlotPrimitiveType(InstanceSpecification instSpec,
                                         Property prop,
                                         org.w3c.dom.Element currentNode,
                                         String path)
    {
        List<String> attributeValues = SOAPUtils.getValuesFromElement(prop.getName(), currentNode);

        if (attributeValues.isEmpty()) {
            if (prop.getLower() == 0) {
                // ignoring optional attribute
                return;
            }
            else {
                if (currentNode != null) {
                    Console.traceln(Level.WARNING,
                                    "required attribute not found in SOAP message: " + path + "." +
                                        prop.getName());
                    Console.traceln(Level.WARNING, "setting default values for this attribute");
                    Console.traceln(Level.FINE, "XML structure of path:" + StringTools.ENDLINE +
                        SOAPUtils.getSerialization(currentNode));
                }
                attributeValues.add(null);
            }
        }
        for (String attributeValue : attributeValues) {
            Slot slot = instSpec.createSlot();
            slot.setDefiningFeature(prop);
            if (isPrimitiveTypeOf(prop.getType(), "String")) {
                LiteralString value =
                    (LiteralString) slot.createValue(prop.getName(), null,
                                                     UMLPackage.Literals.LITERAL_STRING);
                if (attributeValue != null) {
                    value.setValue(attributeValue);
                }
                else {
                    value.setValue("foobar");
                }
            }
            else if (isPrimitiveTypeOf(prop.getType(), "Integer")) {
                LiteralInteger value =
                    (LiteralInteger) slot.createValue(prop.getName(), null,
                                                      UMLPackage.Literals.LITERAL_INTEGER);
                if (attributeValue != null) {
                    value.setValue(Integer.parseInt(attributeValue));
                }
                else {
                    value.setValue(42);
                }
            }
            else if (isPrimitiveTypeOf(prop.getType(), "Boolean")) {
                LiteralBoolean value =
                    (LiteralBoolean) slot.createValue(prop.getName(), null,
                                                      UMLPackage.Literals.LITERAL_BOOLEAN);
                if (attributeValue != null) {
                    value.setValue(Boolean.parseBoolean(attributeValue));
                }
                else {
                    value.setValue(true);
                }
            }
            else if (isPrimitiveTypeOf(prop.getType(), "Real")) {
                LiteralReal value =
                    (LiteralReal) slot.createValue(prop.getName(), null,
                                                   UMLPackage.Literals.LITERAL_REAL);
                if (attributeValue != null) {
                    value.setValue(Double.parseDouble(attributeValue));
                }
                else {
                    value.setValue(3.14);
                }
            }
            else {
                Console.traceln(Level.SEVERE, "could not create literal for primitive type: " +
                    prop.getType().getName());
                Console.traceln(Level.SEVERE, "attribute is ignored!");
                // TODO
                //throw new RuntimeException("unknown primitive type: " + prop.getType().getName());
            }
        }
    }
    
    /**
     * <p>
     * Creates a {@link Slot} in an {@link InstanceSpecification} for an enumeration.
     * </p>
     * 
     * @param instSpec
     *            instance specification to which the slot is added
     * @param prop
     *            property that describes the slot
     * @param currentNode
     *            DOM node from which is value for the slot is inferred
     * @param path
     *            used for warnings and debug information
     */
    private void createSlotEnumeration(InstanceSpecification instSpec,
                                         Property prop,
                                         org.w3c.dom.Element currentNode,
                                         String path)
    {
        List<String> attributeValues = SOAPUtils.getValuesFromElement(prop.getName(), currentNode);

        if (attributeValues.isEmpty()) {
            if (prop.getLower() == 0) {
                // ignoring optional attribute
                return;
            }
            else {
                if (currentNode != null) {
                    Console.traceln(Level.WARNING,
                                    "required attribute not found in SOAP message: " + path + "." +
                                        prop.getName());
                    Console.traceln(Level.WARNING, "setting default values for this attribute");
                    Console.traceln(Level.FINE, "XML structure of path:" + StringTools.ENDLINE +
                        SOAPUtils.getSerialization(currentNode));
                }
                attributeValues.add(null);
            }
        }
        for (String attributeValue : attributeValues) {
            Slot slot = instSpec.createSlot();
            slot.setDefiningFeature(prop);
            
            InstanceValue value = (InstanceValue) slot.createValue(prop.getName(), null, UMLPackage.Literals.INSTANCE_VALUE);
            if( attributeValue!=null ) {
                value.setInstance(((Enumeration) prop.getType()).getOwnedLiteral(attributeValue));
            } else {
                throw new RuntimeException("cannot create enumeration literal with dummy value");
            }
        }
    }

    /**
     * <p>
     * Gets or creates a {@link Package} for {@link InstanceSpecification} created by the
     * usage-based testing. Each service gets its own sub-package within a package called
     * UBT_InstanceSpecifications. "
     * </p>
     * 
     * @param event
     *            event from which the service name is inferred
     * @return package for the {@link InstanceSpecification}s
     */
    private Package getOrCreateInstanceSpecificationPackage(Event event) {
        String pkgUBTInstSpecs = "UBT_InstanceSpecifications";
        Package ubtInstSpecPkg = (Package) model.getOwnedMember(pkgUBTInstSpecs);
        if (ubtInstSpecPkg == null) {
            ubtInstSpecPkg =
                (Package) model.createPackagedElement(pkgUBTInstSpecs, UMLPackage.Literals.PACKAGE);
        }
        String serviceName = SOAPUtils.getServiceNameFromEvent(event);
        Package serviceInstSpecPkg = (Package) ubtInstSpecPkg.getOwnedMember(serviceName);
        if (serviceInstSpecPkg == null) {
            serviceInstSpecPkg =
                (Package) ubtInstSpecPkg.createPackagedElement(serviceName,
                                                               UMLPackage.Literals.PACKAGE);
        }
        return serviceInstSpecPkg;
    }

    /**
     * <p>
     * Checks if a type is a instance of a primitive type defined by its name, e.g., String,
     * Integer, or Boolean. The functions also checks whether the type is a generalization.
     * </p>
     * 
     * @param type
     *            type that is checked
     * @param typeNam
     *            name of the primitive type
     * @return true if the type is of the specified primitive type; false otherwise
     */
    private boolean isPrimitiveTypeOf(Type type, String typeName) {
        if (typeName == null) {
            throw new RuntimeException("typename must not be null");
        }
        if (!(type instanceof PrimitiveType)) {
            return false;
        }
        if (typeName.equals(type.getName())) {
            return true;
        }
        for (Relationship relationship : type.getRelationships()) {
            if (relationship instanceof Generalization) {
                Element target = ((Generalization) relationship).getTargets().get(0);
                if (target instanceof PrimitiveType) {
                    return typeName.equals(((PrimitiveType) target).getName());
                }
            }
        }
        return false;
    }
    
    /**
     * <p>
     * Checks if a type is an anonymous inner type that represents an XSD sequence
     * </p>
     *
     * @param type type that is checked
     * @return true if XSD sequence; false otherwise
     */
    private boolean isXSDSequence(Type type) {
        if( type==null || type.getName()==null ) {
            return false;
        }
        return type.getName().matches("sequence\\d*");
    }
    
    /**
     * <p>
     * Checks if a type is an anonymous inner type that represents an XSD choice
     * </p>
     *
     * @param type type that is checked
     * @return true if XSD choice; false otherwise
     */
    private boolean isXSDChoice(Type type) {
        if( type==null || type.getName()==null ) {
            return false;
        }
        return type.getName().matches("choice\\d*");
    }

    /**
     * <p>
     * Returns the name of a type and removes the suffix _._type.
     * </p>
     * 
     * @param the
     *            type
     * @return type name of the type without the possible suffix
     */
    private String getNormalizedTypeName(Type type) {
        if (type == null) {
            throw new RuntimeException("type must not be null");
        }
        String typeName = type.getName();
        if (typeName != null && typeName.endsWith("_._type")) {
            typeName = typeName.substring(0, typeName.length() - 7);
        }
        return typeName;
    }

    // /////////////////////
    // DEBUGGING METHODS //
    // /////////////////////

    @SuppressWarnings("unused")
    private void printParameters(Operation operation) {
        System.out.println("operation name: " + operation.getName());
        for (Parameter param : operation.getOwnedParameters()) {
            printParameters(param, 0);
        }
    }

    private void printParameters(Parameter param, int depth) {
        for (int i = 0; i < depth; i++) {
            System.out.print(" ");
        }
        System.out.println(param.getName() + " - " + getNormalizedTypeName(param.getType()));
        if (param.getType() instanceof DataType) {
            for (Property prop : ((DataType) param.getType()).getAllAttributes()) {
                printParameters(prop, depth + 2);
            }
        }
    }

    private void printParameters(Property prop, int depth) {
        if (depth > 10)
            return;
        for (int i = 0; i < depth; i++) {
            System.out.print(" ");
        }
        System.out.println(prop.getName() + " - " + getNormalizedTypeName(prop.getType()));
        if (prop.getType() instanceof DataType) {
            for (Property prop2 : ((DataType) prop.getType()).getAllAttributes()) {
                printParameters(prop2, depth + 2);
            }
        }
    }

}
