/******************************************************************************* * Copyright (c) 2010, 2015 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 *******************************************************************************/ package org.eclipse.ocl.pivot.utilities; import java.io.IOException; import java.io.InputStream; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EOperation; import org.eclipse.emf.ecore.EPackage; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.ecore.resource.ResourceSet; import org.eclipse.emf.ecore.resource.URIConverter; import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.ocl.pivot.CompleteEnvironment; import org.eclipse.ocl.pivot.Constraint; import org.eclipse.ocl.pivot.ExpressionInOCL; import org.eclipse.ocl.pivot.LanguageExpression; import org.eclipse.ocl.pivot.Model; import org.eclipse.ocl.pivot.OCLExpression; import org.eclipse.ocl.pivot.StandardLibrary; import org.eclipse.ocl.pivot.evaluation.EvaluationVisitor; import org.eclipse.ocl.pivot.evaluation.ModelManager; import org.eclipse.ocl.pivot.ids.IdResolver; import org.eclipse.ocl.pivot.internal.ecore.as2es.AS2Ecore; import org.eclipse.ocl.pivot.internal.ecore.es2as.Ecore2AS; import org.eclipse.ocl.pivot.internal.helper.HelperUtil; import org.eclipse.ocl.pivot.internal.helper.OCLHelperImpl; import org.eclipse.ocl.pivot.internal.helper.QueryImpl; import org.eclipse.ocl.pivot.internal.manager.MetamodelManagerInternal; import org.eclipse.ocl.pivot.internal.utilities.EnvironmentFactoryInternal; import org.eclipse.ocl.pivot.internal.utilities.OCLDebugOptions; import org.eclipse.ocl.pivot.internal.utilities.OCLInternal; import org.eclipse.ocl.pivot.resource.ASResource; import org.eclipse.ocl.pivot.resource.BasicProjectManager; import org.eclipse.ocl.pivot.resource.CSResource; import org.eclipse.ocl.pivot.resource.ProjectManager; import org.eclipse.ocl.pivot.values.InvalidValueException; /** * Convenient subclass of the <code>OCL</code> façade that binds the * Ecore metamodel to the superclass's generic type parameters. This frees * client code from the long list of parameter substitutions. This subclass * also provides a shortcut to creating an <code>OCL</code> on the shared * {@link EnvironmentFactoryInternal} instance. * * @see EnvironmentFactoryInternal */ public class OCL { /** * A very lightweight ProjectManager that provodes no access to external projects. */ public static final @NonNull ProjectManager NO_PROJECTS = BasicProjectManager.NO_PROJECTS; /** * A heavyweight shared read-only ProjectManager that provides access to all external projects. * When running standalone, the available projects are determined by analysis of plugin.xml * files on the classpath. When running within Eclipse, the Eclipse workspace and extension points * are used. */ public static final @NonNull ProjectManager CLASS_PATH = BasicProjectManager.CLASS_PATH; /** * Creates a new <code>OCL</code> with a new heavyweight ProjectManager and a new * ResourceSet for loaded models. */ public static @NonNull OCL newInstance() { return OCLInternal.newInstance(); } /** * Creates a new <code>OCL</code> using the specified ProjectManager and a new * ResourceSet for loaded models. */ public static @NonNull OCL newInstance(@NonNull ProjectManager projectManager) { return OCLInternal.newInstance(projectManager, null); } /** * Creates a new <code>OCL</code> using the specified ProjectManager and exploiting the * already loaded models and configuration of ResourceSet. */ public static @NonNull OCL newInstance(@NonNull ProjectManager projectManager, @NonNull ResourceSet resourceSet) { OCL ocl = OCLInternal.newInstance(projectManager, resourceSet); // ocl.getEnvironmentFactory().adapt(resourceSet); return ocl; } /** * Creates a new <code>OCL</code> with a new heavyweight ProjectManager and exploiting the * already loaded models and configuration of ResourceSet. */ public static @NonNull OCL newInstance(@NonNull ResourceSet resourceSet) { OCL ocl = OCLInternal.newInstance(BasicProjectManager.createDefaultProjectManager(), resourceSet); // ocl.getEnvironmentFactory().adapt(resourceSet); return ocl; } /** * Creates a new <code>OCL</code> instance using the specified Ecore package registry. * * Note that the returned {@link OCL} instance will use their own clean {@link ResourceSet} * * @see OCL#getResourceSet() */ public static @NonNull OCL newInstance(EPackage.@NonNull Registry ePackageRegistry) { ResourceSet resourceSet = new ResourceSetImpl(); resourceSet.setPackageRegistry(ePackageRegistry); return newInstance(NO_PROJECTS, resourceSet); } /** * Creates a new <code>OCL</code> using the specified Ecore environment * factory. * * @param environmentFactory an environment factory for Ecore * @return the new <code>OCL</code> * * @deprecated use environmentFactory.createOCL() */ @Deprecated public static @NonNull OCL newInstance(@NonNull EnvironmentFactory environmentFactory) { return environmentFactory.createOCL(); } /** * The EnvironmentFactory that can create objects and which provides the MetamodelManager, CompleteEnvironment and StandardLibrary. * This is non-null until the OCL is disposed. Any subsequent usage will provoke NPEs. */ protected /*@NonNull*/ EnvironmentFactoryInternal environmentFactory; // Set null once disposed, so NPE is use after dispose private @Nullable ModelManager modelManager; private boolean traceEvaluation = HelperUtil.shouldTrace(OCLDebugOptions.EVALUATION); /** * Initializes me with my environment factory and root environment. * * @param environmentFactory * my environment factory */ protected OCL(@NonNull EnvironmentFactoryInternal environmentFactory) { this.environmentFactory = environmentFactory; environmentFactory.attach(this); environmentFactory.setEvaluationTracingEnabled(traceEvaluation); } /** * Update the CS resource from a asResource. * * For a first update, the csResource may be created by something like * <p><tt> * (BaseResource) resourceSet.createResource(outputURI, OCLinEcoreCSPackage.eCONTENT_TYPE); * </tt> */ public void as2cs(@NonNull ASResource asResource, @NonNull CSResource csResource) { csResource.updateFrom(asResource, getEnvironmentFactory()); } /** * Return the Ecore resource counterpart of a asResource, specifying the uri of the resulting Ecore resource. */ public @NonNull Resource as2ecore(@NonNull Resource asResource, @NonNull URI uri) throws IOException { assert environmentFactory != null; Resource ecoreResource = AS2Ecore.createResource(environmentFactory, asResource, uri, null); return ecoreResource; } /** * Checks whether a constraint is satisfied by an object. If the constraint * is an invariant constraint, then no additional variable bindings are * required. If it is an operation precondition or postcondition, however, * then the appropriate parameter variables and (in the postcondition case) * result variable should be bound in the evaluation environment. * * @param context * the <tt>self</tt> object of the constraint * @param constraint * the constraint to check * * @return whether the context object satisfies the constraint * * @see #check(Object, ExpressionInOCL) * @see #evaluate(Object, ExpressionInOCL) */ public boolean check(Object context, @NonNull Constraint constraint) { LanguageExpression specification = constraint.getOwnedSpecification(); if (specification == null) { return false; } ExpressionInOCL query; try { query = getMetamodelManager().parseSpecification(specification); } catch (ParserException e) { // e.printStackTrace(); return false; } return check(context, query); } /** * Checks whether a constraint, specified simply as an OCL expression, is * satisfied by an object. If the constraint is an invariant constraint, * then no additional variable bindings are required. If it is an operation * precondition or postcondition, however, then the appropriate parameter * variables and (in the postcondition case) result variable should be bound * in the evaluation environment. * * @param context * the <tt>self</tt> object of the constraint * @param specification * the constraint to check, which must be a boolean-valued * expression * * @return whether the context object satisfies the constraint * * @see #check(Object, ExpressionInOCL) * @see #evaluate(Object, ExpressionInOCL) * * @throws IllegalArgumentException * if the constraint expression is not boolean-valued */ public boolean check(Object context, @NonNull ExpressionInOCL specification) { StandardLibrary stdlib = getStandardLibrary(); if (specification.getOwnedBody().getType() != stdlib.getBooleanType()) { throw new IllegalArgumentException("constraint is not boolean"); //$NON-NLS-1$ } try { Object result = evaluate(context, specification); return result == ValueUtil.TRUE_VALUE; } catch (InvalidValueException e) { return false; } } /** * Creates a new evaluation visitor, for the evaluation of an OCL expression in a context. * The evaluationVisitor reuses any previously established ModelManager. */ public @NonNull EvaluationVisitor createEvaluationVisitor(@Nullable Object context, @NonNull ExpressionInOCL expression) { return environmentFactory.createEvaluationVisitor(context, expression, modelManager); } /** * Parse oclExpression using selfType as the type of each run-time self object. * @throws ParserException */ public @NonNull ExpressionInOCL createInvariant(@NonNull EObject contextElement, @NonNull String oclExpression) throws ParserException { return createOCLHelper(contextElement).createInvariant(oclExpression); } public ExpressionInOCL createPostcondition(@NonNull EOperation contextOperation, @NonNull String oclExpression) throws ParserException { return createOCLHelper(contextOperation).createPostcondition(oclExpression); } /** * Creates a new {@link OCLHelper} instance for convenient parsing of * embedded constraints and query expressions for the specified context * which may be an Ecore EClassifier/EOperation/EStructuralFeature or * or Pivot Class/Operation/Property. * * @return a new helper object */ public @NonNull OCLHelper createOCLHelper(@Nullable EObject contextElement) { return new OCLHelperImpl(this, contextElement); } public @NonNull ExpressionInOCL createQuery(@Nullable EObject contextElement, @NonNull String oclExpression) throws ParserException { return createOCLHelper(contextElement).createQuery(oclExpression); } /** * Creates a new {@link Query} encapsulating a query expression with the * current environment and extent map. This is convenient for repeated * evaluation of expressions and for filtering/transforming objects using a * query or constraint expression. * <p> * Every query maintains its own evaluation environment, which enables * concurrent evaluation (where this may be safe in an EMF-based model) and * different bindings for client-supplied "global" variables. * </p> * * @param query * the OCL query expression, which may be interpreted as a * constraint if it is boolean-valued * * @return the new query object */ public @NonNull Query createQuery(@NonNull ExpressionInOCL query) { return new QueryImpl(this, query); } /** * Creates a new {@link Query} encapsulating a constraint with the current * environment and extent map. This is convenient for repeated evaluation of * constraints and for filtering objects using the constraint expression. * <p> * Every query maintains its own evaluation environment, which enables * concurrent evaluation (where this may be safe in an EMF-based model) and * different bindings for client-supplied "global" variables. * </p> * * @param constraint * the OCL constraint * * @return the new query object * @throws ParserException * * @see #createQuery(ExpressionInOCL) */ public Query createQuery(@NonNull Constraint constraint) throws ParserException { LanguageExpression specification = ClassUtil.nonNullState(constraint.getOwnedSpecification()); ExpressionInOCL query = getMetamodelManager().parseSpecification(specification); return new QueryImpl(this, query); } /** * Return the Pivot resource counterpart of an Xtext csResource. */ public @NonNull ASResource cs2as(@NonNull CSResource csResource) { ASResource asResource = csResource.getASResource(); return asResource; } /** * <p>Disposes any objects that I have created while I have been in use. This * detaches the {@link OCL} instance from the corresponding environment * factory which will release its resources once there are no further * attached OCL clients.</p> * <p>dispose() should be invoked to release resources promptly. If a call to dispose() is * omitted, finalize() will detach() when garbage collection of the OCL instance occurs.</p> */ public synchronized void dispose() { EnvironmentFactoryInternal environmentFactory2 = environmentFactory; if (environmentFactory2 != null) { environmentFactory = null; environmentFactory2.detach(this); } } /** * Return the Pivot resource counterpart of an ecoreResource. */ public @NonNull ASResource ecore2as(@NonNull Resource ecoreResource) throws ParserException { assert environmentFactory != null; Ecore2AS ecore2as = Ecore2AS.getAdapter(ecoreResource, environmentFactory); Model pivotModel = ecore2as.getASModel(); ASResource asResource = (ASResource) pivotModel.eResource(); return ClassUtil.nonNullModel(asResource); } /** * Evaluates a query expression on a context object (which is bound to the * <tt>self</tt> variable). * * @param context * the context (self) object * @param expression * the OCL expression to evaluate * * @return the value of the expression, or <tt>OclInvalid</tt> if the * evaluation fails for reasons other than a run-time exception * * @see #check(Object, ExpressionInOCL) */ public @Nullable Object evaluate(@Nullable Object context, @NonNull ExpressionInOCL expression) { EvaluationVisitor evaluationVisitor = createEvaluationVisitor(context, expression); return expression.accept(evaluationVisitor); } /** * If the user neglects to dispose(), then detach() the EnvironmentFactory to give it a chance to clean up. */ @Override public synchronized void finalize() { EnvironmentFactoryInternal environmentFactory2 = environmentFactory; if (environmentFactory2 != null) { environmentFactory = null; environmentFactory2.detach(this); } } public @NonNull CSResource getCSResource(@NonNull URI uri) throws IOException { Resource resource = getResourceSet().createResource(uri); if (!(resource instanceof CSResource)) { String doSetup = environmentFactory.getDoSetupName(uri); if (doSetup != null) { throw new IllegalStateException("Use of Xtext parsing of '" + uri + "' requires use of " + doSetup); } else { throw new IllegalStateException("No Xtext parsing support registered for '" + uri + "'"); } } CSResource csResource = (CSResource) resource; getEnvironmentFactory().adapt(csResource); csResource.load(null); return csResource; } public @NonNull CSResource getCSResource(@NonNull URI uri, @NonNull InputStream inputStream) throws IOException { Resource resource = new ResourceSetImpl().createResource(uri); if (!(resource instanceof CSResource)) { String doSetup = environmentFactory.getDoSetupName(uri); if (doSetup != null) { throw new IllegalStateException("Use of Xtext parsing of '" + uri + "' requires use of " + doSetup); } else { throw new IllegalStateException("No Xtext parsing support registered for '" + uri + "'"); } } CSResource csResource = (CSResource) resource; getEnvironmentFactory().adapt(csResource); csResource.load(inputStream, null); return csResource; } public @NonNull CSResource getCSResource(@NonNull URI uri, @NonNull String testDocument) throws IOException { InputStream inputStream = new URIConverter.ReadableInputStream(testDocument, "UTF-8"); return getCSResource(uri, inputStream); } public @NonNull CompleteEnvironment getCompleteEnvironment() { return environmentFactory.getCompleteEnvironment(); } public org.eclipse.ocl.pivot.@NonNull Class getContextType(@Nullable Object contextObject) { MetamodelManagerInternal metamodelManager = environmentFactory.getMetamodelManager(); IdResolver idResolver = getIdResolver(); org.eclipse.ocl.pivot.Class staticTypeOf = idResolver.getStaticTypeOf(contextObject); return metamodelManager.getPrimaryClass(staticTypeOf); } public @NonNull EnvironmentFactory getEnvironmentFactory() { assert environmentFactory != null; return environmentFactory; } public @NonNull IdResolver getIdResolver() { return environmentFactory.getIdResolver(); } public @NonNull MetamodelManager getMetamodelManager() { return environmentFactory.getMetamodelManager(); } /** * Obtains the model manager, if any, provided by the client to customize the * evaluation of constraints. * * @return the client-provided custom model manager, or <code>null</code> if * thie OCL is using the default dynamic extent map implementation */ public @Nullable ModelManager getModelManager() { return modelManager; } public EPackage.@NonNull Registry getPackageRegistry() { return ClassUtil.nonNullEMF(getResourceSet().getPackageRegistry()); } public @NonNull ProjectManager getProjectManager() { return environmentFactory.getProjectManager(); } public @NonNull ResourceSet getResourceSet() { return environmentFactory.getResourceSet(); } /** * Return the Constraint specification as an ExpressionInOCL, parsing any OpaqueExpression * that may be encountered. */ public @Nullable ExpressionInOCL getSpecification(@NonNull Constraint constraint) throws ParserException { LanguageExpression specification = constraint.getOwnedSpecification(); if (specification == null) { return null; } if (specification instanceof ExpressionInOCL) { ExpressionInOCL query = (ExpressionInOCL)specification; if ((query.getOwnedBody() != null) || (query.getBody() == null)) { return query; } } return getMetamodelManager().parseSpecification(specification); } public @NonNull StandardLibrary getStandardLibrary() { return getEnvironmentFactory().getStandardLibrary(); } /** * Queries whether tracing of evaluation is enabled. Tracing logs the * progress of evaluation to the console, which may be of use in diagnosing * problems. * <p> * In an Eclipse environment, tracing is also enabled by turning on the * <tt>org.eclipse.ocl/debug/evaluation</tt> debug option. * </p> * * @return whether evaluation tracing is enabled * * @see #setEvaluationTracingEnabled(boolean) */ @Deprecated // Has no functionality public boolean isEvaluationTracingEnabled() { return traceEvaluation; } /** * Load the Complete OCL document specified by the URI into the external ResourceSet and * return the concrete syntax resource. */ @Deprecated // Use getCSResource public @Nullable CSResource load(@NonNull URI uri) { ResourceSet externalResourceSet = getResourceSet(); return (CSResource) externalResourceSet.getResource(uri, true); } /** * Load the Complete OCL document specified by the URI into the external ResourceSet and * parse the concrete syntax resource returning the resulting abstract syntax resource. */ public @Nullable ASResource parse(@NonNull URI uri) { CSResource csResource = load(uri); return csResource != null ? cs2as(csResource) : null; } /** * Convert the specification of an OCL expression from textual CS form to parsed executable AS form. The textual form typically * results from simple construction from source text or a UML OpaqueExpression. * <p> * The returned object may be the same object as the specification, but with the more derived type to signify successful conversion * from textual to executable form. Redundant re-invocation of parseSpecification is harmless. * <p> * The specification's container, typically a Constraint or Operation is used as the contextElement to determine self within the expression. * * @throws ParserException if text parsing fails */ public @NonNull ExpressionInOCL parseSpecification(@NonNull LanguageExpression specification) throws ParserException { return getMetamodelManager().parseSpecification(specification); } /** * @throws ParserException * @deprecated use parseSpecification(specification) */ @Deprecated public @NonNull ExpressionInOCL parseSpecification(@Nullable Object unusedObject, @NonNull LanguageExpression specification) throws ParserException { return getMetamodelManager().parseSpecification(specification); } /** * Sets whether tracing of evaluation is enabled. Tracing logs the progress * of parsing to the console, which may be of use in diagnosing problems. * <p> * In an Eclipse environment, tracing is also enabled by turning on the * <tt>org.eclipse.ocl/debug/evaluation</tt> debug option. * </p> * * @param b * whether evaluation tracing is enabled * * @see #isEvaluationTracingEnabled() */ @Deprecated // Has no functionality public void setEvaluationTracingEnabled(boolean b) { traceEvaluation = b; environmentFactory.setEvaluationTracingEnabled(traceEvaluation); } /** * Assigns a custom extent map to define the extents of classes in * evaluation of OCL constraints. This is only needed if the default dynamic * extent-map implementation is not suitable. * * @param modelManager * a custom extent map, or <code>null</code> to use the default * dynamic extent map implementation */ public void setModelManager(@Nullable ModelManager modelManager) { this.modelManager = modelManager; } /** * Validates an OCL expression, which may have been loaded from some * resource or constructed via the API (perhaps by translation from some * other language). * * @param expression * an expression to validate * * @throws SemanticException * on detection of any well-formedness problem in the expression * * @see #validate(Constraint) */ @Deprecated // Has no functionality public void validate(@NonNull OCLExpression expression) throws SemanticException { throw new UnsupportedOperationException(getClass().getName() + ".validate"); // clear out old diagnostics /* ProblemHandler ph = OCLUtil.getAdapter(rootEnvironment, ProblemHandler.class); if (ph != null) { ph.beginValidation(); } expression.accept(ValidationVisitor.getInstance(rootEnvironment)); if (ph != null) { ph.endValidation(); try { OCLUtil.checkForErrors(ph); } catch (SyntaxException e) { // shouldn't actually be able to get this from validation throw new SemanticException(e.getDiagnostic()); } } */ } /** * Validates an OCL constraint, which may have been loaded from some * resource or constructed via the API (perhaps by translation from some * other language). * * @param constraint * a constraint to validate * * @throws SemanticException * on detection of any well-formedness problem in the constraint */ @Deprecated // Has no functionality public void validate(@NonNull Constraint constraint) throws SemanticException { throw new UnsupportedOperationException(getClass().getName() + ".validate"); // clear out old diagnostics /* ProblemHandler ph = OCLUtil.getAdapter(rootEnvironment, ProblemHandler.class); if (ph != null) { ph.beginValidation(); } ValidationVisitor.getInstance(rootEnvironment).visitConstraint( constraint); if (ph != null) { ph.endValidation(); try { OCLUtil.checkForErrors(ph); } catch (SyntaxException e) { // shouldn't actually be able to get this from validation throw new SemanticException(e.getDiagnostic()); } } */ } }