| /******************************************************************************* |
| * Copyright (c) 2014, 2022 Willink Transformations and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v2.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v20.html |
| * |
| * Contributors: |
| * E.D.Willink - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.ocl.pivot.internal.complete; |
| |
| import java.lang.ref.WeakReference; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.ListIterator; |
| import java.util.Map; |
| |
| import org.apache.log4j.Logger; |
| import org.eclipse.emf.ecore.EClass; |
| import org.eclipse.emf.ecore.EFactory; |
| import org.eclipse.emf.ecore.util.EObjectContainmentWithInverseEList; |
| import org.eclipse.jdt.annotation.NonNull; |
| import org.eclipse.jdt.annotation.Nullable; |
| import org.eclipse.ocl.pivot.CollectionType; |
| import org.eclipse.ocl.pivot.CompleteClass; |
| import org.eclipse.ocl.pivot.MapType; |
| import org.eclipse.ocl.pivot.PivotFactory; |
| import org.eclipse.ocl.pivot.PivotPackage; |
| import org.eclipse.ocl.pivot.PrimitiveType; |
| import org.eclipse.ocl.pivot.TemplateBinding; |
| import org.eclipse.ocl.pivot.TemplateParameter; |
| import org.eclipse.ocl.pivot.TemplateParameterSubstitution; |
| import org.eclipse.ocl.pivot.TemplateSignature; |
| import org.eclipse.ocl.pivot.Type; |
| import org.eclipse.ocl.pivot.internal.CompleteClassImpl; |
| import org.eclipse.ocl.pivot.internal.CompletePackageImpl; |
| import org.eclipse.ocl.pivot.internal.manager.Orphanage; |
| import org.eclipse.ocl.pivot.util.PivotPlugin; |
| import org.eclipse.ocl.pivot.utilities.ClassUtil; |
| import org.eclipse.ocl.pivot.utilities.PivotConstants; |
| import org.eclipse.ocl.pivot.utilities.TracingOption; |
| import org.eclipse.ocl.pivot.values.CollectionTypeParameters; |
| import org.eclipse.ocl.pivot.values.InvalidValueException; |
| import org.eclipse.ocl.pivot.values.MapTypeParameters; |
| |
| public class CompleteClasses extends EObjectContainmentWithInverseEList<CompleteClass> |
| { |
| private static final Logger logger = Logger.getLogger(CompleteClasses.class); |
| |
| public static final @NonNull TracingOption COMPLETE_CLASSES = new TracingOption(PivotPlugin.PLUGIN_ID, "completeClasses"); |
| // static { COMPLETE_CLASSES.setState(true); } |
| private static final long serialVersionUID = 1L; |
| |
| protected static class CollectionCompleteClassImpl extends CompleteClassImpl |
| { |
| /** |
| * Map from actual types to specialization. |
| * <br> |
| * The specializations are weakly referenced so that stale specializations are garbage collected. |
| */ |
| // FIXME tests fail if keys are weak since GC is too aggressive across tests |
| // The actual types are weak keys so that parameterizations using stale types are garbage collected. |
| // No. The problem is that CollectionTypeParameters is not a singleton since it passes an element type. Attempting to use |
| // a SingletonScope needs to use the IdResolver to convert the TemplateParameterId to its type which seemed reluctant |
| // to work, and failing to GC within the scope of this CompleteClass is not a disaster. May change once CompleteClass goes. |
| // |
| private @Nullable /*WeakHash*/Map<@NonNull CollectionTypeParameters<@NonNull Type>, @NonNull WeakReference<@Nullable CollectionType>> collections = null; |
| |
| protected @NonNull CollectionType createSpecialization(@NonNull CollectionTypeParameters<@NonNull Type> typeParameters) { |
| org.eclipse.ocl.pivot.Class unspecializedType = getPrimaryClass(); |
| String typeName = unspecializedType.getName(); |
| TemplateSignature templateSignature = unspecializedType.getOwnedSignature(); |
| List<@NonNull TemplateParameter> templateParameters = ClassUtil.nullFree(templateSignature.getOwnedParameters()); |
| EClass eClass = unspecializedType.eClass(); |
| EFactory eFactoryInstance = eClass.getEPackage().getEFactoryInstance(); |
| CollectionType specializedType = (CollectionType) eFactoryInstance.create(eClass); |
| specializedType.setName(typeName); |
| TemplateBinding templateBinding = PivotFactory.eINSTANCE.createTemplateBinding(); |
| TemplateParameter formalParameter = ClassUtil.nonNull(templateParameters.get(0)); |
| assert formalParameter != null; |
| Type elementType = typeParameters.getElementType(); |
| TemplateParameterSubstitution templateParameterSubstitution = CompleteInheritanceImpl.createTemplateParameterSubstitution(formalParameter, elementType); |
| templateBinding.getOwnedSubstitutions().add(templateParameterSubstitution); |
| specializedType.getOwnedBindings().add(templateBinding); |
| getCompleteModel().resolveSuperClasses(specializedType, unspecializedType); |
| CollectionType specializedCollectionType = specializedType; |
| specializedCollectionType.setIsNullFree(typeParameters.isNullFree()); |
| try { |
| specializedCollectionType.setLowerValue(typeParameters.getLower()); |
| } catch (InvalidValueException e) { |
| logger.error("Out of range lower bound", e); |
| } |
| try { |
| specializedCollectionType.setUpperValue(typeParameters.getUpper()); |
| } catch (InvalidValueException e) { |
| logger.error("Out of range upper bound", e); |
| } |
| specializedType.setUnspecializedElement(unspecializedType); |
| Orphanage orphanage = Orphanage.getOrphanage(); |
| specializedType.setOwningPackage(orphanage); |
| return specializedType; |
| } |
| |
| @Override |
| public synchronized @Nullable CollectionType findCollectionType(@NonNull CollectionTypeParameters<@NonNull Type> typeParameters) { |
| TemplateSignature templateSignature = getPrimaryClass().getOwnedSignature(); |
| List<@NonNull TemplateParameter> templateParameters = ClassUtil.nullFree(templateSignature.getOwnedParameters()); |
| if (templateParameters.size() != 1) { |
| return null; |
| } |
| Map<@NonNull CollectionTypeParameters<@NonNull Type>, @NonNull WeakReference<@Nullable CollectionType>> specializations2 = collections; |
| if (specializations2 == null) { |
| return null; |
| } |
| WeakReference<CollectionType> weakReference = specializations2.get(typeParameters); |
| if (weakReference == null) { |
| return null; |
| } |
| CollectionType type = weakReference.get(); |
| if (type == null) { |
| synchronized (specializations2) { |
| type = weakReference.get(); |
| if (type == null) { |
| specializations2.remove(typeParameters); |
| } |
| } |
| } |
| return type; |
| } |
| |
| @Override |
| public synchronized @NonNull CollectionType getCollectionType(@NonNull CollectionTypeParameters<@NonNull Type> typeParameters) { |
| Map<@NonNull CollectionTypeParameters<@NonNull Type>, @NonNull WeakReference<@Nullable CollectionType>> specializations2 = collections; |
| if (specializations2 == null) { |
| synchronized(this) { |
| specializations2 = collections; |
| if (specializations2 == null) { |
| specializations2 = collections = new /*Weak*/HashMap<@NonNull CollectionTypeParameters<@NonNull Type>, @NonNull WeakReference<@Nullable CollectionType>>(); |
| } |
| } |
| } |
| synchronized (specializations2) { |
| CollectionType specializedType = null; |
| WeakReference<@Nullable CollectionType> weakReference = specializations2.get(typeParameters); |
| if (weakReference != null) { |
| specializedType = weakReference.get(); |
| } |
| if (specializedType == null) { |
| specializedType = createSpecialization(typeParameters); |
| specializations2.put(typeParameters, new WeakReference<@Nullable CollectionType>(specializedType)); |
| } |
| return specializedType; |
| } |
| } |
| } |
| |
| protected static class MapCompleteClassImpl extends CompleteClassImpl |
| { |
| /** |
| * Map from actual types to specialization. |
| * <br> |
| * The specializations are weakly referenced so that stale specializations are garbage collected. |
| */ |
| // FIXME tests fail if keys are weak since GC is too aggressive across tests |
| // The actual types are weak keys so that parameterizations using stale types are garbage collected. |
| // No. The problem is that MapTypeParameters is not a singleton since it passes key/value types. Attempting to use |
| // a SingletonScope needs to use the IdResolver to convert the TemplateParameterId to its type which seemed reluctant |
| // to work, and failing to GC within the scope of this CompleteClass is not a disaster. May change once CompleteClass goes. |
| // |
| private @Nullable /*WeakHash*/Map<@NonNull MapTypeParameters<@NonNull Type, @NonNull Type>, @NonNull WeakReference<@Nullable MapType>> maps = null; |
| |
| protected @NonNull MapType createSpecialization(@NonNull MapTypeParameters<@NonNull Type, @NonNull Type> typeParameters) { |
| org.eclipse.ocl.pivot.Class unspecializedType = getPrimaryClass(); |
| String typeName = unspecializedType.getName(); |
| TemplateSignature templateSignature = unspecializedType.getOwnedSignature(); |
| List<TemplateParameter> templateParameters = templateSignature.getOwnedParameters(); |
| EClass eClass = unspecializedType.eClass(); |
| EFactory eFactoryInstance = eClass.getEPackage().getEFactoryInstance(); |
| MapType specializedMapType = (MapType) eFactoryInstance.create(eClass); |
| specializedMapType.setName(typeName); |
| TemplateBinding templateBinding = PivotFactory.eINSTANCE.createTemplateBinding(); |
| TemplateParameter keyFormalParameter = templateParameters.get(0); |
| TemplateParameter valueFormalParameter = templateParameters.get(1); |
| assert keyFormalParameter != null; |
| assert valueFormalParameter != null; |
| Type keyType = typeParameters.getKeyType(); |
| Type valueType = typeParameters.getValueType(); |
| TemplateParameterSubstitution keyTemplateParameterSubstitution = CompleteInheritanceImpl.createTemplateParameterSubstitution(keyFormalParameter, keyType); |
| TemplateParameterSubstitution valueTemplateParameterSubstitution = CompleteInheritanceImpl.createTemplateParameterSubstitution(valueFormalParameter, valueType); |
| templateBinding.getOwnedSubstitutions().add(keyTemplateParameterSubstitution); |
| templateBinding.getOwnedSubstitutions().add(valueTemplateParameterSubstitution); |
| specializedMapType.getOwnedBindings().add(templateBinding); |
| getCompleteModel().resolveSuperClasses(specializedMapType, unspecializedType); |
| specializedMapType.setKeysAreNullFree(typeParameters.isKeysAreNullFree()); |
| specializedMapType.setValuesAreNullFree(typeParameters.isValuesAreNullFree()); |
| specializedMapType.setUnspecializedElement(unspecializedType); |
| Orphanage orphanage = Orphanage.getOrphanage(); |
| specializedMapType.setOwningPackage(orphanage); |
| specializedMapType.setEntryClass(typeParameters.getEntryClass()); |
| return specializedMapType; |
| } |
| |
| @Override |
| public synchronized @Nullable MapType findMapType(@NonNull MapTypeParameters<@NonNull Type, @NonNull Type> typeParameters) { |
| TemplateSignature templateSignature = getPrimaryClass().getOwnedSignature(); |
| List<TemplateParameter> templateParameters = templateSignature.getOwnedParameters(); |
| if (templateParameters.size() != 1) { |
| return null; |
| } |
| Map<@NonNull MapTypeParameters<@NonNull Type, @NonNull Type>, @NonNull WeakReference<@Nullable MapType>> specializations2 = maps; |
| if (specializations2 == null) { |
| return null; |
| } |
| WeakReference<MapType> weakReference = specializations2.get(typeParameters); |
| if (weakReference == null) { |
| return null; |
| } |
| MapType type = weakReference.get(); |
| if (type == null) { |
| synchronized (specializations2) { |
| type = weakReference.get(); |
| if (type == null) { |
| specializations2.remove(typeParameters); |
| } |
| } |
| } |
| return type; |
| } |
| |
| @Override |
| public synchronized @NonNull MapType getMapType(@NonNull MapTypeParameters<@NonNull Type, @NonNull Type> typeParameters) { |
| Map<@NonNull MapTypeParameters<@NonNull Type, @NonNull Type>, @NonNull WeakReference<@Nullable MapType>> specializations2 = maps; |
| if (specializations2 == null) { |
| synchronized(this) { |
| specializations2 = maps; |
| if (specializations2 == null) { |
| specializations2 = maps = new /*Weak*/HashMap<@NonNull MapTypeParameters<@NonNull Type, @NonNull Type>, @NonNull WeakReference<@Nullable MapType>>(); |
| } |
| } |
| } |
| synchronized (specializations2) { |
| MapType specializedType = null; |
| WeakReference<@Nullable MapType> weakReference = specializations2.get(typeParameters); |
| if (weakReference != null) { |
| specializedType = weakReference.get(); |
| } |
| if (specializedType == null) { |
| specializedType = createSpecialization(typeParameters); |
| specializations2.put(typeParameters, new WeakReference<@Nullable MapType>(specializedType)); |
| } |
| return specializedType; |
| } |
| } |
| } |
| |
| protected @Nullable Map<String, CompleteClassInternal> name2completeClass = null; |
| |
| public CompleteClasses(@NonNull CompletePackageImpl owner) { |
| super(CompleteClass.class, owner, PivotPackage.Literals.COMPLETE_PACKAGE__OWNED_COMPLETE_CLASSES.getFeatureID(), PivotPackage.Literals.COMPLETE_CLASS__OWNING_COMPLETE_PACKAGE.getFeatureID()); |
| if (COMPLETE_CLASSES.isActive()) { |
| COMPLETE_CLASSES.println("Create " + this); |
| } |
| } |
| |
| @Override |
| protected void didAdd(int index, CompleteClass completeClass) { |
| assert completeClass != null; |
| super.didAdd(index, completeClass); |
| didAdd((CompleteClassInternal) completeClass); |
| } |
| |
| public void didAdd(@NonNull CompleteClassInternal completeClass) { |
| Map<String, CompleteClassInternal> name2completeClass2 = name2completeClass; |
| if (name2completeClass2 != null) { |
| String name = completeClass.getName(); |
| if (name != null) { |
| CompleteClass oldCompleteClass = name2completeClass2.put(name, completeClass); |
| assert oldCompleteClass == null; |
| } |
| } |
| } |
| |
| public void didAddClass(org.eclipse.ocl.pivot.@NonNull Class partialClass) { |
| if (name2completeClass != null) { |
| CompleteClassInternal completeClass = name2completeClass.get(partialClass.getName()); |
| if (completeClass == null) { |
| doRefreshPartialClass(partialClass); |
| } |
| else { |
| completeClass.addClass(partialClass); |
| } |
| } |
| } |
| |
| public void didAddPackage(org.eclipse.ocl.pivot.@NonNull Package partialPackage) { |
| if (name2completeClass != null) { |
| doRefreshPartialClasses(partialPackage); |
| } |
| } |
| |
| @Override |
| protected void didRemove(int index, CompleteClass completeClass) { |
| assert completeClass != null; |
| didRemove(completeClass); |
| super.didRemove(index, completeClass); |
| } |
| |
| protected void didRemove(@NonNull CompleteClass completeClass) { |
| Map<String, CompleteClassInternal> name2completeClass2 = name2completeClass; |
| if (name2completeClass2 != null) { |
| String name = completeClass.getName(); |
| if (name != null) { |
| CompleteClassInternal oldCompleteClass = name2completeClass2.remove(name); |
| assert oldCompleteClass == completeClass; |
| } |
| } |
| } |
| |
| public void didRemoveClass(org.eclipse.ocl.pivot.@NonNull Class partialClass) { |
| if (name2completeClass != null) { |
| CompleteClassInternal completeClass = name2completeClass.get(partialClass.getName()); |
| if ((completeClass != null) && completeClass.didRemoveClass(partialClass)) { |
| remove(completeClass); |
| completeClass.dispose(); |
| } |
| } |
| if (partialClass.getUnspecializedElement() == null) { |
| getCompleteModel().didRemoveClass(partialClass); |
| } |
| } |
| |
| public void didRemovePackage(org.eclipse.ocl.pivot.@NonNull Package partialPackage) { |
| Map<String, CompleteClassInternal> name2completeClass2 = name2completeClass; |
| if (name2completeClass2 != null) { |
| for (org.eclipse.ocl.pivot.Class partialClass : partialPackage.getOwnedClasses()) { |
| if (partialClass != null) { |
| didRemoveClass(partialClass); |
| } |
| } |
| } |
| } |
| |
| protected void doRefreshPartialClass(org.eclipse.ocl.pivot.@NonNull Class partialClass) { |
| Map<String, CompleteClassInternal> name2completeClass2 = name2completeClass; |
| assert name2completeClass2 != null; |
| CompleteModelInternal completeModel = getCompleteModel(); |
| String name = partialClass.getName(); |
| if (name != null) { |
| CompleteClassInternal completeClass = null; |
| if (partialClass instanceof PrimitiveType) { |
| CompletePackageInternal primitiveCompletePackage = completeModel.getPrimitiveCompletePackage(); |
| completeClass = primitiveCompletePackage.getCompleteClass(partialClass); |
| } |
| else if ((partialClass instanceof MapType) && (partialClass.getUnspecializedElement() != null)) { |
| CompletePackageInternal orphanCompletePackage = completeModel.getOrphanCompletePackage(); |
| completeClass = orphanCompletePackage.getCompleteClass(partialClass); |
| } |
| else if (PivotConstants.METAMODEL_NAME.equals(getCompletePackage().getURI())) { |
| CompletePackageInternal primitiveCompletePackage = completeModel.getPrimitiveCompletePackage(); |
| completeClass = primitiveCompletePackage.getOwnedCompleteClass(name); |
| } |
| if (completeClass == null) { |
| completeClass = name2completeClass2.get(name); |
| if (completeClass == null) { |
| if (partialClass.getOwnedSignature() == null) { |
| completeClass = (CompleteClassInternal) PivotFactory.eINSTANCE.createCompleteClass(); |
| } |
| else if (partialClass instanceof CollectionType) { |
| completeClass = new CollectionCompleteClassImpl(); |
| } |
| else if (partialClass instanceof MapType) { |
| completeClass = new MapCompleteClassImpl(); |
| } |
| else { |
| completeClass = (CompleteClassInternal) PivotFactory.eINSTANCE.createCompleteClass(); |
| } |
| completeClass.setName(name); |
| add(completeClass); |
| } |
| } |
| completeClass.addClass(partialClass); |
| } |
| } |
| |
| protected @NonNull Map<String, CompleteClassInternal> doRefreshPartialClasses() { |
| Map<String, CompleteClassInternal> name2completeClass2 = name2completeClass; |
| if (name2completeClass2 == null) { |
| name2completeClass2 = name2completeClass = new HashMap<String, CompleteClassInternal>(); |
| } |
| for (org.eclipse.ocl.pivot.Package partialPackage : getCompletePackage().getPartialPackages()) { |
| if (partialPackage != null) { |
| doRefreshPartialClasses(partialPackage); |
| } |
| } |
| return name2completeClass2; |
| } |
| |
| protected void doRefreshPartialClasses(org.eclipse.ocl.pivot.@NonNull Package partialPackage) { |
| for (org.eclipse.ocl.pivot.Class partialClass : partialPackage.getOwnedClasses()) { |
| if (partialClass != null) { |
| doRefreshPartialClass(partialClass); |
| } |
| } |
| } |
| |
| public @NonNull CompleteModelInternal getCompleteModel() { |
| return getCompletePackage().getCompleteModel(); |
| } |
| |
| @SuppressWarnings("null") |
| public @NonNull CompletePackageInternal getCompletePackage() { |
| return (@NonNull CompletePackageInternal) owner; |
| } |
| |
| public @Nullable CompleteClassInternal getOwnedCompleteClass(String name) { |
| Map<String, CompleteClassInternal> name2completeClass2 = name2completeClass; |
| if (name2completeClass2 == null) { |
| name2completeClass2 = doRefreshPartialClasses(); |
| } |
| return name2completeClass2.get(name); |
| } |
| |
| @Override |
| public @NonNull Iterator<CompleteClass> iterator() { |
| if (name2completeClass == null) { |
| doRefreshPartialClasses(); |
| } |
| return super.iterator(); |
| } |
| |
| @Override |
| public @NonNull ListIterator<CompleteClass> listIterator() { |
| if (name2completeClass == null) { |
| doRefreshPartialClasses(); |
| } |
| return super.listIterator(); |
| } |
| |
| @Override |
| public @NonNull ListIterator<CompleteClass> listIterator(int index) { |
| if (name2completeClass == null) { |
| doRefreshPartialClasses(); |
| } |
| return super.listIterator(index); |
| } |
| |
| @Override |
| public String toString() { |
| return getClass().getSimpleName() + ": " + owner.toString(); |
| } |
| } |