DefaultSystemInstanceLoadingService.java

/**
 * Copyright (c) 2004-2025 Carnegie Mellon University and others. (see Contributors file).
 * All Rights Reserved.
 *
 * NO WARRANTY. ALL MATERIAL IS FURNISHED ON AN "AS-IS" BASIS. CARNEGIE MELLON UNIVERSITY MAKES NO WARRANTIES OF ANY
 * KIND, EITHER EXPRESSED OR IMPLIED, AS TO ANY MATTER INCLUDING, BUT NOT LIMITED TO, WARRANTY OF FITNESS FOR PURPOSE
 * OR MERCHANTABILITY, EXCLUSIVITY, OR RESULTS OBTAINED FROM USE OF THE MATERIAL. CARNEGIE MELLON UNIVERSITY DOES NOT
 * MAKE ANY WARRANTY OF ANY KIND WITH RESPECT TO FREEDOM FROM PATENT, TRADEMARK, OR COPYRIGHT INFRINGEMENT.
 *
 * This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0
 * which is available at https://www.eclipse.org/legal/epl-2.0/
 * SPDX-License-Identifier: EPL-2.0
 *
 * Created, in part, with funding and support from the United States Government. (see Acknowledgments file).
 *
 * This program includes and/or can make use of certain third party source code, object code, documentation and other
 * files ("Third Party Software"). The Third Party Software that is used by this program is dependent upon your system
 * configuration. By using this program, You agree to comply with any and all relevant Third Party Software terms and
 * conditions contained in any such Third Party Software or separate license file distributed with such Third Party
 * Software. The parties who own the Third Party Software ("Third Party Licensors") are intended third party benefici-
 * aries to this license with respect to the terms applicable to their Third Party Software. Third Party Software li-
 * censes only apply to the Third Party Software and not any other portion of this program or this program as a whole.
 */
package org.osate.ge.internal.services.impl;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IResourceDeltaVisitor;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
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 org.eclipse.ui.statushandlers.StatusManager;
import org.osate.aadl2.instance.SystemInstance;
import org.osate.aadl2.modelsupport.FileNameConstants;
import org.osate.ge.internal.GraphicalEditorException;
import org.osate.ge.internal.services.SystemInstanceLoadingService;
import org.osate.ge.internal.util.Log;
import org.osgi.framework.FrameworkUtil;

/**
 * {@link SystemInstanceLoadingService} implementation
 *
 */
public class DefaultSystemInstanceLoadingService implements SystemInstanceLoadingService {
	private final Object lock = new Object();

	private final Map<String, IPath> keyToPathMap = new HashMap<>();
	private final Map<IPath, String> pathToKeyMap = new HashMap<>();
	private final Set<IFile> pendingInstanceModels = new HashSet<>(); // For populating the maps once a request is made. This is needed to prevent from
	// triggering a modification during a resource change notifications

	/**
	 * Creates a new instance
	 */
	public DefaultSystemInstanceLoadingService() {
		// Listen for resource changes. Keep mapping up to date.
		ResourcesPlugin.getWorkspace().addResourceChangeListener(new IResourceChangeListener() {
			@Override
			public void resourceChanged(final IResourceChangeEvent event) {
				if(event.getType() == IResourceChangeEvent.POST_CHANGE) {
					try {
						synchronized(lock) {
							event.getDelta().accept(deltaVisitor);
						}
					} catch (CoreException e) {
						throw new GraphicalEditorException(e);
					}
				}
			}

			private IResourceDeltaVisitor deltaVisitor = delta -> {
				// Check AADL Instance Model Files
				final IResource resource = delta.getResource();
				if (resource.getType() == IResource.FILE
						&& FileNameConstants.INSTANCE_FILE_EXT.equalsIgnoreCase(resource.getFileExtension())) {
					// Update mappings
					if(delta.getKind() == IResourceDelta.REMOVED) {
						removeFromMaps((IFile)resource);
					} else {
						// Remove then re-add the file
						removeFromMaps((IFile)resource);
						pendingInstanceModels.add((IFile)resource);
					}
				}

				return true;
			};
		});

		// Add existing instance files to the pending files collection
		synchronized(lock) {
			for(final IFile instanceFile : findInstanceFiles(ResourcesPlugin.getWorkspace().getRoot(), null)) {
				pendingInstanceModels.add(instanceFile);
			}
		}
	}

	@Override
	public SystemInstance loadSystemInstance(final String key) {
		synchronized(lock) {
			// Process pending files
			for(final IFile pendingInstanceFile : pendingInstanceModels) {
				addToMaps(pendingInstanceFile);
			}
			pendingInstanceModels.clear();

			final IPath path = keyToPathMap.get(key);
			return path == null ? null : loadSystemInstance(path);
		}
	}

	/**
	 * Returns a unique key for a system instance.
	 */
	@Override
	public String getKey(final SystemInstance systemInstance) {
		final Resource resource = systemInstance.eResource();
		if (resource == null) {
			return null;
		}

		final URI uri = resource.getURI();
		if (uri == null) {
			return null;
		}

		return getKey(new Path(uri.toPlatformString(true)));
	}

	private String getKey(final IFile systemInstanceFile) {
		return getKey(systemInstanceFile.getFullPath());
	}

	@Override
	public String getKey(final IPath systemInstancePath) {
		return systemInstancePath.toPortableString().toLowerCase();
	}

	private List<IFile> findInstanceFiles(final IContainer container, List<IFile> instanceFiles) {
		if(instanceFiles == null) {
			instanceFiles = new ArrayList<>();
		}

		try {
			if(container.isAccessible()) {
				for(final IResource resource : container.members()) {
					if (resource instanceof IContainer) {
						findInstanceFiles((IContainer)resource, instanceFiles);
					} else if (resource instanceof IFile) {
						final IFile file = (IFile) resource;
						if (FileNameConstants.INSTANCE_FILE_EXT.equalsIgnoreCase(file.getFileExtension())) {
							instanceFiles.add(file);
						}
					}
				}
			}
		} catch (CoreException e) {
			Log.error("Error finding instance models", e);
			throw new GraphicalEditorException(e);
		}

		return instanceFiles;
	}

	private void addToMaps(final IFile instanceFile) {
		final IPath fullPath = instanceFile.getFullPath();
		final IProject project = instanceFile.getProject();
		if(project != null) {
			final String key = getKey(instanceFile);
			if (key != null) {
				pathToKeyMap.put(fullPath, key);
				keyToPathMap.put(key, fullPath);
			}
		}
	}

	private void removeFromMaps(final IFile instanceFile) {
		final IPath instancePath = instanceFile.getFullPath();
		final String key = pathToKeyMap.get(instancePath);
		if(key != null) {
			pathToKeyMap.remove(instancePath);
			keyToPathMap.remove(key);
		}

		// Remove it from the pending instance files collection in case it is awaiting processing
		pendingInstanceModels.remove(instanceFile);
	}

	private SystemInstance loadSystemInstance(final IPath fullInstancePath) {
		final URI uri = URI.createPlatformResourceURI(fullInstancePath.toString(), true);
		final ResourceSet resourceSet = new ResourceSetImpl();
		try {
			final Resource resource = resourceSet.getResource(uri, true);

			// If the resource was loaded, retrieve and return the SystemInstance
			if(resource != null) {
				for(final EObject obj : resource.getContents()) {
					if(obj instanceof SystemInstance) {
						return (SystemInstance)obj;
					}
				}
			}
		} catch(final Exception ex) {
			// Suppress the exception but log it. The exception is suppressed because it could happen under normal circumstances. For example
			// the exception could be thrown if there is a system instance anywhere in the workspace that cannot be loaded.
			StatusManager.getManager()
			.handle(new Status(IStatus.ERROR, FrameworkUtil.getBundle(getClass()).getSymbolicName(),
					"Error loading system instance resource", ex), StatusManager.LOG);
		}

		return null;
	}
}