/******************************************************************************* * Copyright (c) 2013, 2015 Willink Transformations 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: * E.D.Willink - initial API and implementation *******************************************************************************/ package org.eclipse.ocl.pivot.utilities; import java.util.List; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EReference; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.ocl.pivot.CollectionType; import org.eclipse.ocl.pivot.Constraint; import org.eclipse.ocl.pivot.Element; import org.eclipse.ocl.pivot.Iteration; import org.eclipse.ocl.pivot.LambdaType; import org.eclipse.ocl.pivot.MapType; import org.eclipse.ocl.pivot.Model; import org.eclipse.ocl.pivot.NamedElement; import org.eclipse.ocl.pivot.Operation; import org.eclipse.ocl.pivot.Parameter; import org.eclipse.ocl.pivot.PivotPackage; import org.eclipse.ocl.pivot.Precedence; import org.eclipse.ocl.pivot.PrimitiveType; import org.eclipse.ocl.pivot.Property; import org.eclipse.ocl.pivot.SelfType; import org.eclipse.ocl.pivot.TemplateParameter; import org.eclipse.ocl.pivot.TemplateSignature; import org.eclipse.ocl.pivot.TemplateableElement; import org.eclipse.ocl.pivot.TupleType; import org.eclipse.ocl.pivot.Type; import org.eclipse.ocl.pivot.VariableDeclaration; import org.eclipse.ocl.pivot.internal.manager.Orphanage; import org.eclipse.ocl.pivot.internal.utilities.AS2XMIid; import org.eclipse.ocl.pivot.internal.utilities.PivotConstantsInternal; import org.eclipse.ocl.pivot.util.AbstractExtendingVisitor; import org.eclipse.ocl.pivot.util.Visitable; /** * The AS2XMIidVisitor generates an xmi:id for an AS element. Using one of three policies. * <p> * null - no xmi:id generated - saves space * <p> * false - xmi:id generated/reuses UUID - UUID only used internally so no need for predicatability * <p> * true - xmi:id generated/reuses friendly name - ID may be independently generated - must be predictable * <p> * Simple elements such as Package/Type/Property get a dot-separated hierarchical name. * <p> * Operations get a dot-separated hierarchical name suffixed by dot-dot-separated argument types. * <p> * Internally referenceable elements such as TemplateSignature get a UUID, reusing any xmi:id provided * in the context Moniker to XMIId Map. * */ public class AS2XMIidVisitor extends AbstractExtendingVisitor<Boolean, AS2XMIid> { public static final int OVERFLOW_LIMIT = 1024; public static final @NonNull String OVERFLOW_MARKER = "##"; //$NON-NLS-1$ public static final @NonNull String NULL_MARKER = "-null-element-"; //$NON-NLS-1$ public static final @NonNull String FRAGMENT_SEPARATOR = "#"; //$NON-NLS-1$ public static final @NonNull String ACCUMULATOR_PREFIX = "a"; //$NON-NLS-1$ public static final @NonNull String BODYCONDITION_PREFIX = "c="; //$NON-NLS-1$ public static final @NonNull String INVARIANT_PREFIX = "ci"; //$NON-NLS-1$ public static final @NonNull String ITERATION_PREFIX = "i."; //$NON-NLS-1$ public static final @NonNull String ITERATOR_PREFIX = "i"; //$NON-NLS-1$ public static final @NonNull String OPERATION_PREFIX = "o."; //$NON-NLS-1$ public static final @NonNull String PARAMETER_PREFIX = "p"; //$NON-NLS-1$ public static final @NonNull String PACKAGE_PREFIX = "P."; //$NON-NLS-1$ public static final @NonNull String POSTCONDITION_PREFIX = "c+"; //$NON-NLS-1$ public static final @NonNull String PRECONDITION_PREFIX = "c-"; //$NON-NLS-1$ public static final @NonNull String PRECEDENCE_PREFIX = "Z."; //$NON-NLS-1$ public static final @NonNull String PROPERTY_PREFIX = "p."; //$NON-NLS-1$ public static final @NonNull String TEMPLATE_PARAMETER_PREFIX = "t."; //$NON-NLS-1$ public static final @NonNull String TEMPLATE_SIGNATURE_PREFIX = "s."; //$NON-NLS-1$ public static final @NonNull String TYPE_PREFIX = "T."; //$NON-NLS-1$ public static final @NonNull String OPERATION_PARAMETER_SEPARATOR = ".."; //$NON-NLS-1$ public static final @NonNull String SCOPE_SEPARATOR = "."; //$NON-NLS-1$ public static final @NonNull String TEMPLATE_PARAMETER_SEPARATOR = ".."; //$NON-NLS-1$ protected final @NonNull StringBuilder s = new StringBuilder(); public AS2XMIidVisitor(@NonNull AS2XMIid context) { super(context); } protected void appendName(@Nullable String name) { if (name == null) { s.append(NULL_MARKER); } else { for (int i = 0; i < name.length(); i++) { char c = name.charAt(i); if (('0' <= c) && (c <= '9')) { s.append(c); } else if (('A' <= c) && (c <= 'Z')) { s.append(c); } else if (('a' <= c) && (c <= 'z')) { s.append(c); } else if (c == '_') { s.append(c); } else if (c == '$') { s.append(c); } else if (c == '%') { s.append("%%"); } else { s.append("%"); s.append((int)c); s.append("%"); } } } } protected void appendNameOf(@NonNull Object element) { if (element instanceof Nameable) { s.append(((Nameable)element).getName()); } else { s.append(System.identityHashCode(element)); } } protected void appendOperation(Operation object) { appendParent(object); appendName(object.getName()); List<Parameter> parameters = object instanceof Iteration ? ((Iteration)object).getOwnedIterators() : object.getOwnedParameters(); for (Parameter parameter : parameters) { s.append(OPERATION_PARAMETER_SEPARATOR); appendType(parameter.getType()); } } protected void appendParent(@Nullable EObject element) { if (toString().length() >= OVERFLOW_LIMIT) { s.append(OVERFLOW_MARKER); } else if (element == null) { s.append(NULL_MARKER); s.append(SCOPE_SEPARATOR); } else { EObject eContainer = element.eContainer(); if (eContainer instanceof Model) { } else if (eContainer != null) { // if (!(eContainer instanceof TemplateParameter)) { appendParent(eContainer); // } appendNameOf(eContainer); s.append(SCOPE_SEPARATOR); } } } protected void appendType(@Nullable Type type) { if (type == null) { s.append(NULL_MARKER); } else { if (type.isClass() != null) { appendParent(type); } appendName(type.getName()); } } public @Nullable String getID(@NonNull Element element, boolean internalUUIDs) { Boolean status = element.accept(this); if (status == null) { return null; } else if (status) { return s.toString(); } else { return context.getID(element, internalUUIDs); } } @Override public String toString() { return s.toString(); } @Override public Boolean visitClass(org.eclipse.ocl.pivot.@NonNull Class object) { if (Orphanage.isTypeOrphanage(object.getOwningPackage())) { return false; } String name = object.getName(); if (PivotConstantsInternal.WILDCARD_NAME.equals(name)) { if (Orphanage.isTypeOrphanage(PivotUtil.getContainingPackage(object))) { return false; } } s.append(TYPE_PREFIX); appendParent(object); appendName(name); return true; } @Override public @Nullable Boolean visitCollectionType(@NonNull CollectionType object) { if (object.getOwnedBindings().isEmpty()) { appendName(object.getName()); return true; } else { return false; } } @Override public @Nullable Boolean visitConstraint(@NonNull Constraint object) { String name = object.getName(); if ((name != null) && !"".equals(name)) { EReference eContainmentFeature = object.eContainmentFeature(); if (eContainmentFeature == PivotPackage.Literals.OPERATION__OWNED_PRECONDITIONS) { s.append(PRECONDITION_PREFIX); } else if (eContainmentFeature == PivotPackage.Literals.OPERATION__BODY_EXPRESSION) { s.append(BODYCONDITION_PREFIX); } else if (eContainmentFeature == PivotPackage.Literals.OPERATION__OWNED_POSTCONDITIONS) { s.append(POSTCONDITION_PREFIX); } else { s.append(INVARIANT_PREFIX); } appendParent(object); appendName(name); return true; } return null; } @Override public @Nullable Boolean visitElement(@NonNull Element object) { return null; } @Override public @Nullable Boolean visitLambdaType(@NonNull LambdaType object) { return false; } @Override public Boolean visitIteration(@NonNull Iteration object) { s.append(ITERATION_PREFIX); appendOperation(object); return true; } @Override public @Nullable Boolean visitMapType(@NonNull MapType object) { if (object.getOwnedBindings().isEmpty()) { appendName(object.getName()); return true; } else { return false; } } @Override public @Nullable Boolean visitOperation(@NonNull Operation object) { s.append(OPERATION_PREFIX); appendOperation(object); return true; } @Override public @Nullable Boolean visitPackage(org.eclipse.ocl.pivot.@NonNull Package object) { String name = object.getName(); if (name != null) { s.append(PACKAGE_PREFIX); appendParent(object); appendName(name); return true; } else if (object.getURI() != null) { appendName(object.getURI()); return true; } else { return false; } } @Override public @Nullable Boolean visitParameter(@NonNull Parameter object) { Operation operation = (Operation)object.eContainer(); int index = operation.getOwnedParameters().indexOf(object); if (index >= 0) { s.append(PARAMETER_PREFIX); s.append(index); } else if (operation instanceof Iteration) { Iteration iteration = (Iteration)operation; index = iteration.getOwnedIterators().indexOf(object); if (index >= 0) { s.append(ITERATOR_PREFIX); s.append(index); } else { index = iteration.getOwnedAccumulators().indexOf(object); if (index >= 0) { s.append(ACCUMULATOR_PREFIX); s.append(index); } } } operation.accept(this); return true; } @Override public @Nullable Boolean visitPrecedence(@NonNull Precedence object) { s.append(PRECEDENCE_PREFIX); appendName(object.getName()); return true; } @Override public @Nullable Boolean visitPrimitiveType(@NonNull PrimitiveType object) { appendName(object.getName()); return true; } @Override public @Nullable Boolean visitProperty(@NonNull Property object) { EObject eContainer = object.eContainer(); if (eContainer instanceof TupleType) { return false; // TupleParts of synthesized types use UUIDs } String name = object.getName(); if (object.isIsImplicit() && (eContainer instanceof org.eclipse.ocl.pivot.Class)) { for (Property asProperty : ((org.eclipse.ocl.pivot.Class)eContainer).getOwnedProperties()) { if ((asProperty != object) && name.equals(asProperty.getName())) { return false; // Ambiguous implicit opposites must use UUIDs } } } s.append(PROPERTY_PREFIX); appendParent(object); appendName(name); return true; } @Override public @Nullable Boolean visitSelfType(@NonNull SelfType object) { appendName(object.getName()); return true; } @Override public @Nullable Boolean visitTemplateParameter(@NonNull TemplateParameter object) { NamedElement template = (NamedElement) object.getOwningSignature().getOwningElement(); if ((template instanceof org.eclipse.ocl.pivot.Class) && Orphanage.isTypeOrphanage(((org.eclipse.ocl.pivot.Class)template).getOwningPackage())) { return false; } s.append(TEMPLATE_PARAMETER_PREFIX); appendParent(template); s.append(SCOPE_SEPARATOR); appendName(template.getName()); appendName(object.getName()); return true; } @Override public @Nullable Boolean visitTemplateSignature(@NonNull TemplateSignature object) { s.append(TEMPLATE_SIGNATURE_PREFIX); TemplateableElement template = object.getOwningElement(); template.accept(this); return true; } @Override public @Nullable Boolean visitTupleType(@NonNull TupleType object) { return false; } @Override public @Nullable Boolean visitVariableDeclaration(@NonNull VariableDeclaration object) { return null; } @Override public @Nullable Boolean visiting(@NonNull Visitable visitable) { throw new IllegalArgumentException("Unsupported " + visitable.eClass().getName() + " for " + getClass().getSimpleName()); } }