diff --git a/bundles/tools.vitruv.framework.vsum/src/tools/vitruv/framework/vsum/internal/ResourceRepositoryImpl.java b/bundles/tools.vitruv.framework.vsum/src/tools/vitruv/framework/vsum/internal/ResourceRepositoryImpl.java new file mode 100644 index 0000000000..22c1f38508 --- /dev/null +++ b/bundles/tools.vitruv.framework.vsum/src/tools/vitruv/framework/vsum/internal/ResourceRepositoryImpl.java @@ -0,0 +1,230 @@ +package tools.vitruv.framework.vsum.internal; + +import static com.google.common.base.Preconditions.checkState; +import static edu.kit.ipd.sdq.commons.util.org.eclipse.emf.ecore.resource.ResourceSetUtil.getOrCreateResource; +import static edu.kit.ipd.sdq.commons.util.org.eclipse.emf.ecore.resource.ResourceSetUtil.loadOrCreateResource; +import static edu.kit.ipd.sdq.commons.util.org.eclipse.emf.ecore.resource.ResourceSetUtil.withGlobalFactories; +import static tools.vitruv.change.correspondence.model.CorrespondenceModelFactory.createPersistableCorrespondenceModel; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.stream.Collectors; + +import org.apache.log4j.Logger; +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.emf.ecore.resource.ResourceSet; +import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl; + +import tools.vitruv.change.composite.description.TransactionalChange; +import tools.vitruv.change.composite.description.VitruviusChange; +import tools.vitruv.change.composite.recording.ChangeRecorder; +import tools.vitruv.change.correspondence.Correspondence; +import tools.vitruv.change.correspondence.model.PersistableCorrespondenceModel; +import tools.vitruv.change.correspondence.view.CorrespondenceModelViewFactory; +import tools.vitruv.change.correspondence.view.EditableCorrespondenceModelView; +import tools.vitruv.change.propagation.impl.ResourceRegistrationAdapter; +import tools.vitruv.framework.vsum.helper.VsumFileSystemLayout; + +class ResourceRepositoryImpl implements ModelRepository { + private static final Logger LOGGER = Logger.getLogger(ResourceRepositoryImpl.class); + + private final ResourceSet modelsResourceSet = withGlobalFactories(new ResourceSetImpl()); + private final Map modelInstances = new HashMap<>(); + private final FileExtensionRecorderMapping fileExtensionRecorderMapping = new FileExtensionRecorderMapping(); + private final PersistableCorrespondenceModel correspondenceModel; + + private final VsumFileSystemLayout fileSystemLayout; + + private boolean isRecording = false; + private boolean isLoading = false; + + /** + * Manages change recorders for file extensions. Ensures that only one change + * recorder per file extension exists. A recorder is assigned to a set of file + * extensions (for the case that multiple file extensions belong to the same + * domain of models and should be recorder together) and recorders can be + * retrieved for a given file extension. + */ + private static class FileExtensionRecorderMapping { + private final Map, ChangeRecorder> fileExtensionsToRecorder = new HashMap<>(); + private final Map> fileExtensionToExtensionsSet = new HashMap<>(); + + Set getRecorders() { + return new HashSet<>(fileExtensionsToRecorder.values()); + } + + boolean hasRecorder(String fileExtension) { + return fileExtensionsToRecorder.containsKey(fileExtensionToExtensionsSet.get(fileExtension)); + } + + ChangeRecorder getRecorder(String fileExtension) { + return fileExtensionsToRecorder.get(fileExtensionToExtensionsSet.get(fileExtension)); + } + + void registerRecorder(Set fileExtensions, ResourceSet recorderResourceSet) { + fileExtensionToExtensionsSet.keySet().forEach( + it -> checkState(!fileExtensions.contains(it), "there already is a recorder for metamodel " + it)); + Set fileExtensionsSet = new HashSet<>(fileExtensions); + fileExtensions.forEach(it -> fileExtensionToExtensionsSet.put(it, fileExtensionsSet)); + ChangeRecorder recorder = new ChangeRecorder(recorderResourceSet); + fileExtensionsToRecorder.put(fileExtensionsSet, recorder); + } + } + + ResourceRepositoryImpl(VsumFileSystemLayout fileSystemLayout) { + this.fileSystemLayout = fileSystemLayout; + this.correspondenceModel = createPersistableCorrespondenceModel(fileSystemLayout.getCorrespondencesURI()); + modelsResourceSet.eAdapters() + .add(new ResourceRegistrationAdapter(resource -> getCreateOrLoadModelUnlessLoading(resource.getURI()))); + } + + @Override + public void loadExistingModels() { + isLoading = true; + try { + readModelsFile(); + } catch (IOException e) { + throw new IllegalStateException(e); + } + correspondenceModel.loadSerializedCorrespondences(modelsResourceSet); + isLoading = false; + } + + private void writeModelsFile() throws IOException { + Files.write(fileSystemLayout.getModelsNamesFilesPath(), + modelsResourceSet.getResources().stream().map(Resource::getURI).map(URI::toString).collect(Collectors.toList())); + } + + private void readModelsFile() throws IOException { + try { + for (String modelPath : Files.readAllLines(fileSystemLayout.getModelsNamesFilesPath())) { + URI uri = URI.createURI(modelPath); + loadOrCreateResource(modelsResourceSet, uri); + createOrLoadModel(uri); + } + } catch (NoSuchFileException e) { + // There are no existing models, so don't do anything + } + } + + @Override + public EditableCorrespondenceModelView getCorrespondenceModel() { + return CorrespondenceModelViewFactory.createEditableCorrespondenceModelView(correspondenceModel); + } + + @Override + public ModelInstance getModel(URI modelUri) { + return modelInstances.get(modelUri); + } + + private ModelInstance getCreateOrLoadModelUnlessLoading(URI modelUri) { + if (isLoading) { + return null; + } + return getCreateOrLoadModel(modelUri); + } + + private ModelInstance getCreateOrLoadModel(URI modelUri) { + ModelInstance instance = getModel(modelUri); + if (instance != null) { + return instance; + } + return createOrLoadModel(modelUri); + } + + private ModelInstance createOrLoadModel(URI modelUri) { + Resource resource; + if (modelUri.isFile() || modelUri.isPlatform()) { + resource = getOrCreateResource(modelsResourceSet, modelUri); + } else { + resource = loadOrCreateResource(modelsResourceSet, modelUri); + } + ModelInstance modelInstance = new ModelInstance(resource); + modelInstances.put(modelUri, modelInstance); + registerRecorder(modelInstance); + return modelInstance; + } + + private void registerRecorder(ModelInstance modelInstance) { + // Only monitor modifiable models (file / platform URIs, not pathmap URIs) + if (modelInstance.getURI().isFile() || modelInstance.getURI().isPlatform()) { + String fileExtension = modelInstance.getURI().fileExtension(); + if (!fileExtensionRecorderMapping.hasRecorder(fileExtension)) { + fileExtensionRecorderMapping.registerRecorder(Set.of(fileExtension), modelsResourceSet); + } + ChangeRecorder recorder = fileExtensionRecorderMapping.getRecorder(fileExtension); + recorder.addToRecording(modelInstance.getResource()); + if (isRecording && !recorder.isRecording()) { + recorder.beginRecording(); + } + } + } + + @Override + public void persistAsRoot(EObject rootObject, URI uri) { + getCreateOrLoadModel(uri).addRoot(rootObject); + } + + @Override + public void saveOrDeleteModels() { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Saving all models of model repository for VSUM " + fileSystemLayout); + } + Iterator> modelInstancesIterator = modelInstances.entrySet().iterator(); + while (modelInstancesIterator.hasNext()) { + ModelInstance modelInstance = modelInstancesIterator.next().getValue(); + if (modelInstance.isEmpty()) { + modelInstance.delete(); + modelInstancesIterator.remove(); + } else { + modelInstance.save(); + } + } + correspondenceModel.save(); + try { + writeModelsFile(); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + @Override + public Iterable recordChanges(Runnable changeApplicator) { + fileExtensionRecorderMapping.getRecorders().forEach(ChangeRecorder::beginRecording); + isRecording = true; + LOGGER.debug("Start recording virtual model"); + changeApplicator.run(); + LOGGER.debug("End recording virtual model"); + isRecording = false; + fileExtensionRecorderMapping.getRecorders().forEach(ChangeRecorder::endRecording); + return fileExtensionRecorderMapping.getRecorders().stream().map(ChangeRecorder::getChange) + .filter(TransactionalChange::containsConcreteChange).collect(Collectors.toList()); + } + + @Override + public VitruviusChange applyChange(VitruviusChange change) { + return change.resolveAndApply(modelsResourceSet); + } + + @Override + public Collection getModelResources() { + return modelsResourceSet.getResources(); + } + + @Override + public void close() { + fileExtensionRecorderMapping.fileExtensionsToRecorder.values().forEach(ChangeRecorder::close); + modelsResourceSet.getResources().forEach(Resource::unload); + modelsResourceSet.getResources().clear(); + } +} diff --git a/bundles/tools.vitruv.framework.vsum/src/tools/vitruv/framework/vsum/internal/ResourceRepositoryImpl.xtend b/bundles/tools.vitruv.framework.vsum/src/tools/vitruv/framework/vsum/internal/ResourceRepositoryImpl.xtend deleted file mode 100644 index 40e187163e..0000000000 --- a/bundles/tools.vitruv.framework.vsum/src/tools/vitruv/framework/vsum/internal/ResourceRepositoryImpl.xtend +++ /dev/null @@ -1,201 +0,0 @@ -package tools.vitruv.framework.vsum.internal - -import java.util.HashMap -import java.util.Map -import org.apache.log4j.Logger -import org.eclipse.emf.ecore.EObject -import org.eclipse.emf.ecore.resource.Resource -import org.eclipse.emf.ecore.resource.ResourceSet - -import static extension edu.kit.ipd.sdq.commons.util.org.eclipse.emf.ecore.resource.ResourceSetUtil.loadOrCreateResource -import static extension edu.kit.ipd.sdq.commons.util.org.eclipse.emf.ecore.resource.ResourceSetUtil.getOrCreateResource -import static extension edu.kit.ipd.sdq.commons.util.org.eclipse.emf.ecore.resource.ResourceSetUtil.withGlobalFactories -import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl -import tools.vitruv.framework.vsum.helper.VsumFileSystemLayout -import tools.vitruv.change.composite.recording.ChangeRecorder -import static tools.vitruv.change.correspondence.model.CorrespondenceModelFactory.createPersistableCorrespondenceModel -import org.eclipse.emf.common.util.URI -import java.nio.file.Files -import java.nio.file.NoSuchFileException -import tools.vitruv.change.composite.description.VitruviusChange -import static extension edu.kit.ipd.sdq.commons.util.java.lang.IterableUtil.mapFixed -import java.util.Set -import static com.google.common.base.Preconditions.checkState -import java.util.HashSet -import tools.vitruv.change.composite.description.TransactionalChange -import tools.vitruv.change.correspondence.model.PersistableCorrespondenceModel -import tools.vitruv.change.correspondence.view.CorrespondenceModelViewFactory -import tools.vitruv.change.propagation.impl.ResourceRegistrationAdapter - -package class ResourceRepositoryImpl implements ModelRepository { - static val logger = Logger.getLogger(ResourceRepositoryImpl) - val ResourceSet modelsResourceSet - val ResourceSet correspondencesResourceSet - val Map modelInstances = new HashMap() - val VsumFileSystemLayout fileSystemLayout - val PersistableCorrespondenceModel correspondenceModel - val extension FileExtensionRecorderMapping fileExtensionsRecorderMapping = new FileExtensionRecorderMapping - - var isRecording = false - var isLoading = false - - /** - * Manages change recorders for file extensions. Ensures that only one change recorder per file extension exists. - * A recorder is assigned to a set of file extensions (for the case that multiple file extensions belong to - * the same domain of models and should be recorder together) and recorders can be retrieved for a given - * file extension. - */ - private static class FileExtensionRecorderMapping { - val Map, ChangeRecorder> fileExtensionsToRecorder = new HashMap() - val Map> fileExtensionToExtensionsSet = new HashMap() - - def Set getRecorders() { - fileExtensionsToRecorder.values.toSet - } - - def boolean hasRecorder(String fileExtension) { - fileExtensionsToRecorder.containsKey(fileExtensionToExtensionsSet.get(fileExtension)) - } - - def ChangeRecorder getRecorder(String fileExtension) { - fileExtensionsToRecorder.get(fileExtensionToExtensionsSet.get(fileExtension)) - } - - def void registerRecorder(Set fileExtensions, ResourceSet recorderResourceSet) { - fileExtensionToExtensionsSet.keySet.forEach [ - checkState(!fileExtensions.contains(it), "there already is a recorder for metamodel %s", it) - ] - val fileExtensionsSet = new HashSet(fileExtensions) - fileExtensions.forEach[fileExtensionToExtensionsSet.put(it, fileExtensionsSet)] - val recorder = new ChangeRecorder(recorderResourceSet) - fileExtensionsToRecorder.put(fileExtensionsSet, recorder) - } - } - - new(VsumFileSystemLayout fileSystemLayout) { - this.fileSystemLayout = fileSystemLayout - this.modelsResourceSet = new ResourceSetImpl().withGlobalFactories() - this.correspondencesResourceSet = new ResourceSetImpl().withGlobalFactories() - this.correspondenceModel = createPersistableCorrespondenceModel(fileSystemLayout.correspondencesURI) - this.modelsResourceSet.eAdapters += new ResourceRegistrationAdapter [ - if(!isLoading) getCreateOrLoadModel(it.URI) - ] - } - - override loadExistingModels() { - isLoading = true - readModelsFile() - correspondenceModel.loadSerializedCorrespondences(modelsResourceSet) - isLoading = false - } - - private def writeModelsFile() { - Files.write(fileSystemLayout.modelsNamesFilesPath, modelsResourceSet.resources.map[URI.toString]) - } - - private def readModelsFile() { - try { - for (modelPath : Files.readAllLines(fileSystemLayout.modelsNamesFilesPath)) { - val uri = URI.createURI(modelPath) - modelsResourceSet.loadOrCreateResource(uri) - createOrLoadModel(uri) - } - } catch (NoSuchFileException e) { - // There are no existing models, so don't do anything - } - } - - override getCorrespondenceModel() { - return CorrespondenceModelViewFactory.createEditableCorrespondenceModelView(correspondenceModel) - } - - override getModel(URI modelURI) { - modelInstances.get(modelURI) - } - - private def getCreateOrLoadModel(URI modelURI) { - getModel(modelURI) ?: createOrLoadModel(modelURI) - } - - def private createOrLoadModel(URI modelURI) { - val resource = if ((modelURI.isFile || modelURI.isPlatform)) { - modelsResourceSet.getOrCreateResource(modelURI) - } else { - modelsResourceSet.loadOrCreateResource(modelURI) - } - val modelInstance = new ModelInstance(resource) - this.modelInstances.put(modelURI, modelInstance) - modelInstance.registerRecorder() - return modelInstance - } - - def private void registerRecorder(ModelInstance modelInstance) { - // Only monitor modifiable models (file / platform URIs, not pathmap URIs) - if (modelInstance.URI.isFile || modelInstance.URI.isPlatform) { - if (!hasRecorder(modelInstance.URI.fileExtension)) { - registerRecorder(#{modelInstance.URI.fileExtension}, modelsResourceSet) - } - val recorder = getRecorder(modelInstance.URI.fileExtension) - recorder.addToRecording(modelInstance.resource) - if (this.isRecording && !recorder.isRecording) { - recorder.beginRecording() - } - } - } - - override void persistAsRoot(EObject rootEObject, URI uri) { - getCreateOrLoadModel(uri).addRoot(rootEObject) - } - - override void saveOrDeleteModels() { - if(logger.isDebugEnabled) logger.debug('''Saving all models of model repository for VSUM «fileSystemLayout»''') - val modelInstancesIterator = modelInstances.entrySet.iterator - while (modelInstancesIterator.hasNext()) { - val modelInstance = modelInstancesIterator.next().value - if (modelInstance.empty) { - modelInstance.delete() - modelInstancesIterator.remove() - } else { - modelInstance.save() - } - } - correspondenceModel.save() - writeModelsFile() - } - - override Iterable recordChanges(Runnable changeApplicator) { - recorders.forEach[beginRecording()] - isRecording = true - logger.debug("Start recording virtual model") - changeApplicator.run() - logger.debug("End recording virtual model") - isRecording = false - recorders.forEach[endRecording()] - return fileExtensionsRecorderMapping.recorders.filter[change.containsConcreteChange].mapFixed[change] - } - - override VitruviusChange applyChange(VitruviusChange change) { - change.resolveAndApply(modelsResourceSet) - } - - override URI getMetadataModelURI(String... metadataKey) { - fileSystemLayout.getConsistencyMetadataModelURI(metadataKey) - } - - override Resource getModelResource(URI uri) { - getCreateOrLoadModel(uri).resource - } - - override close() { - fileExtensionsToRecorder.values.forEach[close()] - modelsResourceSet.resources.forEach[unload] - correspondencesResourceSet.resources.forEach[unload] - modelsResourceSet.resources.clear() - correspondencesResourceSet.resources.clear() - } - - override getModelResources() { - return modelsResourceSet.resources - } - -}