/**
 * <copyright>
 * 
 * Copyright (c) 2007 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *   IBM - Initial API and implementation
 *
 * </copyright>
 *
 * $Id: ExtensibilityTest.java,v 1.3 2009/11/28 17:47:24 ewillink Exp $
 */

package org.eclipse.ocl.ecore.tests;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.eclipse.emf.common.util.Diagnostic;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EEnumLiteral;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EOperation;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EParameter;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.EcorePackage;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.ocl.Environment;
import org.eclipse.ocl.EnvironmentFactory;
import org.eclipse.ocl.EvaluationEnvironment;
import org.eclipse.ocl.EvaluationVisitor;
import org.eclipse.ocl.LookupException;
import org.eclipse.ocl.OCLInput;
import org.eclipse.ocl.ParserException;
import org.eclipse.ocl.SemanticException;
import org.eclipse.ocl.TypeResolver;
import org.eclipse.ocl.ecore.CallOperationAction;
import org.eclipse.ocl.ecore.Constraint;
import org.eclipse.ocl.ecore.EcoreEnvironmentFactory;
import org.eclipse.ocl.ecore.OCL;
import org.eclipse.ocl.ecore.SendSignalAction;
import org.eclipse.ocl.expressions.OCLExpression;
import org.eclipse.ocl.expressions.Variable;
import org.eclipse.ocl.types.OCLStandardLibrary;
import org.eclipse.ocl.utilities.OCLFactory;
import org.eclipse.ocl.utilities.TypedElement;
import org.eclipse.ocl.utilities.UMLReflection;

import com.sap.emf.ocl.hiddenopposites.EcoreEnvironmentFactoryWithHiddenOpposites;
import com.sap.emf.ocl.hiddenopposites.EcoreEnvironmentWithHiddenOpposites;
import com.sap.emf.ocl.hiddenopposites.OCLWithHiddenOpposites;

/**
 * Tests the extensibility of the parser API.
 *
 * @author Christian W. Damus (cdamus)
 */
@SuppressWarnings("nls")
public class ExtensibilityTest
    extends AbstractTestSuite {

    /**
     * Tests that we can successfully parse with an environment that does not
     * implement the optional adapter protocols.
     */
    public void test_noAdapters_parseSuccessfully() {
        helper.setContext(EcorePackage.Literals.EPACKAGE);
        
        try {
            helper.createInvariant("not self.eClassifiers->isEmpty()");
        } catch (ParserException e) {
            fail("Failed to parse: " + e.getLocalizedMessage());
        }
        
        try {
            ocl.parse(new OCLInput(
                "context ecore::EPackage\n" +
                "inv: not self.eClassifiers->isEmpty()"));
        } catch (ParserException e) {
            fail("Failed to parse: " + e.getLocalizedMessage());
        }
    }

    /**
     * Tests that we can produce warnings with an environment that does not
     * implement the optional adapter protocols.
     */
    public void test_noAdapters_parseWithWarning() {
        helper.setContext(EcorePackage.Literals.EPACKAGE);
        
        try {
            helper.createInvariant("not self.\"eClassifiers\"->isEmpty()");
            
            Diagnostic diag = helper.getProblems();
            assertNotNull(diag);
            assertEquals(Diagnostic.WARNING, diag.getSeverity());
            
            System.out.println("Got expected warning: " + diag.getMessage());
        } catch (ParserException e) {
            fail("Failed to parse: " + e.getLocalizedMessage());
        }
        
        try {
            ocl.parse(new OCLInput(
                "context ecore::EPackage\n" +
                "inv: not self.\"eClassifiers\"->isEmpty()"));
            
            Diagnostic diag = ocl.getProblems();
            assertNotNull(diag);
            assertEquals(Diagnostic.WARNING, diag.getSeverity());
            
            System.out.println("Got expected warning: " + diag.getMessage());
        } catch (ParserException e) {
            fail("Failed to parse: " + e.getLocalizedMessage());
        }
    }

    /**
     * Tests that we can produce errors with an environment that does not
     * implement the optional adapter protocols.
     */
    public void test_noAdapters_parseWithError() {
        helper.setContext(EcorePackage.Literals.EPACKAGE);
        
        try {
            helper.createInvariant("not self.classifiers->isEmpty()");
            fail("Should not have parsed");
        } catch (ParserException e) {
            // success
            System.out.println("Got expected error: " + e.getLocalizedMessage());
        }
        
        try {
            ocl.parse(new OCLInput(
                "context ecore::EPackage\n" +
                "inv: not self.classifiers->isEmpty()"));
            fail("Should not have parsed");
        } catch (ParserException e) {
            // success
            System.out.println("Got expected error: " + e.getLocalizedMessage());
        }
    }

    /**
     * Tests that we can report lexer problems with an environment that does not
     * implement the optional adapter protocols.
     */
    public void test_noAdapters_parseWithTokenError() {
        helper.setContext(EcorePackage.Literals.EPACKAGE);
        
        try {
            helper.createInvariant("not self.;->isEmpty()");
            fail("Should not have parsed");
        } catch (ParserException e) {
            // success
            System.out.println("Got expected error: " + e.getLocalizedMessage());
        }
        
        try {
            ocl.parse(new OCLInput(
                "context ecore::EPackage\n" +
                "inv: not self.;->isEmpty()"));
            fail("Should not have parsed");
        } catch (ParserException e) {
            // success
            System.out.println("Got expected error: " + e.getLocalizedMessage());
        }
    }
    
	//
	// Framework methods
	//
	
	@Override
	protected OCL createOCL() {
	    return OCLWithHiddenOpposites.newInstance(new WrapperEnvironmentFactory());
	}
	
	class WrapperEnvironment implements EcoreEnvironmentWithHiddenOpposites {
	    
	    private final EcoreEnvironmentWithHiddenOpposites delegate;
	    
        private final EcoreEnvironmentWithHiddenOpposites parent;
        
        private final WrapperEnvironmentFactory factory;
        
	    WrapperEnvironment(WrapperEnvironmentFactory factory,
	            Environment<EPackage, EClassifier, EOperation, EStructuralFeature, EEnumLiteral, EParameter, EObject, CallOperationAction, SendSignalAction, Constraint, EClass, EObject> delegate) {
	        this(factory, delegate, null);
	    }

        WrapperEnvironment(WrapperEnvironmentFactory factory,
            Environment<EPackage, EClassifier, EOperation, EStructuralFeature, EEnumLiteral, EParameter, EObject, CallOperationAction, SendSignalAction, Constraint, EClass, EObject> delegate,
            Environment<EPackage, EClassifier, EOperation, EStructuralFeature, EEnumLiteral, EParameter, EObject, CallOperationAction, SendSignalAction, Constraint, EClass, EObject> parent) {
            
            this.factory = factory;
            this.delegate = (EcoreEnvironmentWithHiddenOpposites) delegate;
            this.parent = (EcoreEnvironmentWithHiddenOpposites) parent;
        }
    
        public Environment<EPackage, EClassifier, EOperation, EStructuralFeature, EEnumLiteral, EParameter, EObject, CallOperationAction, SendSignalAction, Constraint, EClass, EObject> getParent() {
            return parent;
        }

        public EnvironmentFactory<EPackage, EClassifier, EOperation, EStructuralFeature, EEnumLiteral, EParameter, EObject, CallOperationAction, SendSignalAction, Constraint, EClass, EObject> getFactory() {
            return factory;
        }

        public boolean addElement(String name,
                Variable<EClassifier, EParameter> elem, boolean explicit) {
            return delegate.addElement(name, elem, explicit);
        }

        public EStructuralFeature defineAttribute(EClassifier owner,
                Variable<EClassifier, EParameter> variable,
                Constraint constraint) {
            return delegate.defineAttribute(owner, variable, constraint);
        }

        public EOperation defineOperation(EClassifier owner, String name,
                EClassifier type,
                List<Variable<EClassifier, EParameter>> params,
                Constraint constraint) {
            return delegate.defineOperation(owner, name, type, params,
                constraint);
        }

        public void deleteElement(String name) {
            delegate.deleteElement(name);
        }

        public List<EStructuralFeature> getAdditionalAttributes(
                EClassifier classifier) {
            return delegate.getAdditionalAttributes(classifier);
        }

        public List<EOperation> getAdditionalOperations(EClassifier classifier) {
            return delegate.getAdditionalOperations(classifier);
        }

        public Constraint getBodyCondition(EOperation operation) {
            return delegate.getBodyCondition(operation);
        }

        public EClassifier getContextClassifier() {
            return delegate.getContextClassifier();
        }

        public EOperation getContextOperation() {
            return delegate.getContextOperation();
        }

        public EPackage getContextPackage() {
            return delegate.getContextPackage();
        }

        public EStructuralFeature getContextProperty() {
            return delegate.getContextProperty();
        }

        public Constraint getDefinition(Object feature) {
            return delegate.getDefinition(feature);
        }

        public Constraint getDeriveConstraint(EStructuralFeature property) {
            return delegate.getDeriveConstraint(property);
        }

        public Constraint getInitConstraint(EStructuralFeature property) {
            return delegate.getInitConstraint(property);
        }

        public OCLFactory getOCLFactory() {
            return delegate.getOCLFactory();
        }

        public OCLStandardLibrary<EClassifier> getOCLStandardLibrary() {
            return delegate.getOCLStandardLibrary();
        }

        public Variable<EClassifier, EParameter> getSelfVariable() {
            return delegate.getSelfVariable();
        }

        public List<EObject> getStates(EClassifier owner,
                List<String> pathPrefix) {
            return delegate.getStates(owner, pathPrefix);
        }

        public TypeResolver<EClassifier, EOperation, EStructuralFeature> getTypeResolver() {
            return delegate.getTypeResolver();
        }

        public UMLReflection<EPackage, EClassifier, EOperation, EStructuralFeature, EEnumLiteral, EParameter, EObject, CallOperationAction, SendSignalAction, Constraint> getUMLReflection() {
            return delegate.getUMLReflection();
        }

        public Collection<Variable<EClassifier, EParameter>> getVariables() {
            return delegate.getVariables();
        }

        public boolean isEmpty() {
            return delegate.isEmpty();
        }

        public boolean isInPostcondition(OCLExpression<EClassifier> exp) {
            return delegate.isInPostcondition(exp);
        }

        public Variable<EClassifier, EParameter> lookup(String name) {
            return delegate.lookup(name);
        }

        public EClassifier lookupAssociationClassReference(EClassifier owner,
                String name) {
            return delegate.lookupAssociationClassReference(owner, name);
        }

        public EClassifier lookupClassifier(List<String> names) {
            return delegate.lookupClassifier(names);
        }

        public Variable<EClassifier, EParameter> lookupImplicitSourceForAssociationClass(
                String name) {
            return delegate.lookupImplicitSourceForAssociationClass(name);
        }

        public Variable<EClassifier, EParameter> lookupImplicitSourceForOperation(
                String name, List<? extends TypedElement<EClassifier>> args) {
            return delegate.lookupImplicitSourceForOperation(name, args);
        }

        public Variable<EClassifier, EParameter> lookupImplicitSourceForProperty(
                String name) {
            return delegate.lookupImplicitSourceForProperty(name);
        }

        public Variable<EClassifier, EParameter> lookupImplicitSourceForSignal(
                String name, List<? extends TypedElement<EClassifier>> args) {
            return delegate.lookupImplicitSourceForSignal(name, args);
        }

        public Variable<EClassifier, EParameter> lookupImplicitSourceForState(
                List<String> path)
            throws LookupException, SemanticException {
            return delegate.lookupImplicitSourceForState(path);
        }

        public Variable<EClassifier, EParameter> lookupLocal(String name) {
            return delegate.lookupLocal(name);
        }

        public EOperation lookupOperation(EClassifier owner, String name,
                List<? extends TypedElement<EClassifier>> args) {
            return delegate.lookupOperation(owner, name, args);
        }

        public EPackage lookupPackage(List<String> names) {
            return delegate.lookupPackage(names);
        }

        public EStructuralFeature lookupProperty(EClassifier owner, String name) {
            return delegate.lookupProperty(owner, name);
        }

        public EReference lookupOppositeProperty(EClassifier owner, String name)
            throws LookupException {
	    return delegate.lookupOppositeProperty(owner, name);
	}

	public EClassifier getOppositePropertyType(EClassifier owner, EStructuralFeature property) {
		return delegate.getOppositePropertyType(owner, property);
	}

	public EClassifier lookupSignal(EClassifier owner, String name,
                List<? extends TypedElement<EClassifier>> args) {
            return delegate.lookupSignal(owner, name, args);
        }

        public EObject lookupState(EClassifier owner, List<String> path)
            throws LookupException, SemanticException {
            return delegate.lookupState(owner, path);
        }

        public void setBodyCondition(EOperation operation, Constraint constraint) {
            delegate.setBodyCondition(operation, constraint);
        }

        public void setDeriveConstraint(EStructuralFeature property,
                Constraint constraint) {
            delegate.setDeriveConstraint(property, constraint);
        }

        public void setInitConstraint(EStructuralFeature property,
                Constraint constraint) {
            delegate.setInitConstraint(property, constraint);
        }

        public void setParent(
                Environment<EPackage, EClassifier, EOperation, EStructuralFeature, EEnumLiteral, EParameter, EObject, CallOperationAction, SendSignalAction, Constraint, EClass, EObject> env) {
            delegate.setParent(env);
        }

        public void setSelfVariable(Variable<EClassifier, EParameter> var) {
            delegate.setSelfVariable(var);
        }

        public void undefine(Object feature) {
            delegate.undefine(feature);
        }

		public Map<String, EStructuralFeature> getHiddenOppositeProperties(
				EClassifier classifier) {
			return delegate.getHiddenOppositeProperties(classifier);
		}
	}

	class WrapperEnvironmentFactory implements EnvironmentFactory<
            EPackage, EClassifier, EOperation, EStructuralFeature,
            EEnumLiteral, EParameter,
            EObject, CallOperationAction, SendSignalAction, Constraint,
            EClass, EObject> {
	    
	    private final EnvironmentFactory<
            EPackage, EClassifier, EOperation, EStructuralFeature,
            EEnumLiteral, EParameter,
            EObject, CallOperationAction, SendSignalAction, Constraint,
            EClass, EObject> delegate;
	    
	    WrapperEnvironmentFactory(EnvironmentFactory<
                EPackage, EClassifier, EOperation, EStructuralFeature,
                EEnumLiteral, EParameter,
                EObject, CallOperationAction, SendSignalAction, Constraint,
                EClass, EObject> delegate) {
	        this.delegate = delegate;
	    }
	    
	    WrapperEnvironmentFactory() {
	        this(new EcoreEnvironmentFactoryWithHiddenOpposites());
	    }

	    private Environment<EPackage, EClassifier, EOperation, EStructuralFeature, EEnumLiteral, EParameter, EObject, CallOperationAction, SendSignalAction, Constraint, EClass, EObject>
	    getWrappedEnvironment(Environment<EPackage, EClassifier, EOperation, EStructuralFeature, EEnumLiteral, EParameter, EObject, CallOperationAction, SendSignalAction, Constraint, EClass, EObject> env) {
	        if (env instanceof WrapperEnvironment) {
	            return ((WrapperEnvironment) env).delegate;
	        } else {
	            return env;
	        }
	    }
	    
        public Environment<EPackage, EClassifier, EOperation, EStructuralFeature, EEnumLiteral, EParameter, EObject, CallOperationAction, SendSignalAction, Constraint, EClass, EObject> createAttributeContext(
                Environment<EPackage, EClassifier, EOperation, EStructuralFeature, EEnumLiteral, EParameter, EObject, CallOperationAction, SendSignalAction, Constraint, EClass, EObject> parent,
                EStructuralFeature property) {
            return new WrapperEnvironment(this, delegate.createAttributeContext(getWrappedEnvironment(parent), property), parent);
        }

        public Environment<EPackage, EClassifier, EOperation, EStructuralFeature, EEnumLiteral, EParameter, EObject, CallOperationAction, SendSignalAction, Constraint, EClass, EObject> createClassifierContext(
                Environment<EPackage, EClassifier, EOperation, EStructuralFeature, EEnumLiteral, EParameter, EObject, CallOperationAction, SendSignalAction, Constraint, EClass, EObject> parent,
                EClassifier context) {
            return new WrapperEnvironment(this, delegate.createClassifierContext(getWrappedEnvironment(parent), context), parent);
        }

        public Environment<EPackage, EClassifier, EOperation, EStructuralFeature, EEnumLiteral, EParameter, EObject, CallOperationAction, SendSignalAction, Constraint, EClass, EObject> createEnvironment() {
            return new WrapperEnvironment(this, delegate.createEnvironment());
        }

        public Environment<EPackage, EClassifier, EOperation, EStructuralFeature, EEnumLiteral, EParameter, EObject, CallOperationAction, SendSignalAction, Constraint, EClass, EObject> createEnvironment(
                Environment<EPackage, EClassifier, EOperation, EStructuralFeature, EEnumLiteral, EParameter, EObject, CallOperationAction, SendSignalAction, Constraint, EClass, EObject> parent) {
            return new WrapperEnvironment(this, delegate.createEnvironment(getWrappedEnvironment(parent)), parent);
        }

        public EvaluationEnvironment<EClassifier, EOperation, EStructuralFeature, EClass, EObject> createEvaluationEnvironment() {
            return delegate.createEvaluationEnvironment();
        }

        public EvaluationEnvironment<EClassifier, EOperation, EStructuralFeature, EClass, EObject> createEvaluationEnvironment(
                EvaluationEnvironment<EClassifier, EOperation, EStructuralFeature, EClass, EObject> parent) {
            return delegate.createEvaluationEnvironment(parent);
        }

        public EvaluationVisitor<EPackage, EClassifier, EOperation, EStructuralFeature, EEnumLiteral, EParameter, EObject, CallOperationAction, SendSignalAction, Constraint, EClass, EObject> createEvaluationVisitor(
                Environment<EPackage, EClassifier, EOperation, EStructuralFeature, EEnumLiteral, EParameter, EObject, CallOperationAction, SendSignalAction, Constraint, EClass, EObject> env,
                EvaluationEnvironment<EClassifier, EOperation, EStructuralFeature, EClass, EObject> evalEnv,
                Map<? extends EClass, ? extends Set<? extends EObject>> extentMap) {
            return delegate.createEvaluationVisitor(env, evalEnv, extentMap);
        }

        public Environment<EPackage, EClassifier, EOperation, EStructuralFeature, EEnumLiteral, EParameter, EObject, CallOperationAction, SendSignalAction, Constraint, EClass, EObject> createInstanceContext(
                Environment<EPackage, EClassifier, EOperation, EStructuralFeature, EEnumLiteral, EParameter, EObject, CallOperationAction, SendSignalAction, Constraint, EClass, EObject> parent,
                Object context) {
            return new WrapperEnvironment(this, delegate.createInstanceContext(getWrappedEnvironment(parent), context), parent);
        }

        public Environment<EPackage, EClassifier, EOperation, EStructuralFeature, EEnumLiteral, EParameter, EObject, CallOperationAction, SendSignalAction, Constraint, EClass, EObject> createOperationContext(
                Environment<EPackage, EClassifier, EOperation, EStructuralFeature, EEnumLiteral, EParameter, EObject, CallOperationAction, SendSignalAction, Constraint, EClass, EObject> parent,
                EOperation operation) {
            return new WrapperEnvironment(this, delegate.createOperationContext(getWrappedEnvironment(parent), operation), parent);
        }

        public Environment<EPackage, EClassifier, EOperation, EStructuralFeature, EEnumLiteral, EParameter, EObject, CallOperationAction, SendSignalAction, Constraint, EClass, EObject> createPackageContext(
                Environment<EPackage, EClassifier, EOperation, EStructuralFeature, EEnumLiteral, EParameter, EObject, CallOperationAction, SendSignalAction, Constraint, EClass, EObject> parent,
                List<String> pathname) {
            return new WrapperEnvironment(this, delegate.createPackageContext(getWrappedEnvironment(parent), pathname), parent);
        }

        public Environment<EPackage, EClassifier, EOperation, EStructuralFeature, EEnumLiteral, EParameter, EObject, CallOperationAction, SendSignalAction, Constraint, EClass, EObject> loadEnvironment(
                Resource resource) {
            return new WrapperEnvironment(this, delegate.loadEnvironment(resource));
        }
	}

}
