Aadl2Utils.java

/**
 * AADL-Utils
 *
 * Copyright © 2012 TELECOM ParisTech and CNRS
 *
 * TELECOM ParisTech/LTCI
 *
 * Authors: see AUTHORS
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the Eclipse Public License as published by Eclipse,
 * either version 2.0 of the License, or (at your option) any later version.
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * Eclipse Public License for more details.
 * You should have received a copy of the Eclipse Public License
 * along with this program.  If not, see
 * https://www.eclipse.org/legal/epl-2.0/
 */

package org.osate.utils.internal;

import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;

import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.Platform;
import org.eclipse.xtext.nodemodel.ICompositeNode;
import org.eclipse.xtext.nodemodel.INode;
import org.eclipse.xtext.nodemodel.util.NodeModelUtils;
import org.osate.aadl2.AbstractFeature;
import org.osate.aadl2.AccessConnection;
import org.osate.aadl2.BehavioredImplementation;
import org.osate.aadl2.Classifier;
import org.osate.aadl2.ComponentPrototypeActual;
import org.osate.aadl2.ComponentPrototypeBinding;
import org.osate.aadl2.ConnectedElement;
import org.osate.aadl2.ConnectionEnd;
import org.osate.aadl2.DataAccess;
import org.osate.aadl2.DataClassifier;
import org.osate.aadl2.DataPort;
import org.osate.aadl2.DataPrototype;
import org.osate.aadl2.DirectionType;
import org.osate.aadl2.Element;
import org.osate.aadl2.EnumerationLiteral;
import org.osate.aadl2.EventDataPort;
import org.osate.aadl2.EventPort;
import org.osate.aadl2.Feature;
import org.osate.aadl2.FeatureClassifier;
import org.osate.aadl2.NamedElement;
import org.osate.aadl2.NamedValue;
import org.osate.aadl2.Parameter;
import org.osate.aadl2.ParameterConnection;
import org.osate.aadl2.Property;
import org.osate.aadl2.Prototype;
import org.osate.aadl2.PrototypeBinding;
import org.osate.aadl2.SubcomponentType;
import org.osate.aadl2.SubprogramCall;
import org.osate.aadl2.parsesupport.LocationReference;
import org.osate.xtext.aadl2.properties.util.GetProperties;
import org.osgi.framework.Bundle;

/**
 * @since 2.0
 */
public class Aadl2Utils {
	public static String DEFAULT_ACCESS_RIGHT = null;

	/**
	 * Returns the sorted (see FeaturePositionComparator) list of features (included
	 * inherited features) owned by the given Classifier object.
	 *
	 * @param cpt the given Classifier object
	 * @return the sorted list of features owned by the given Component object
	 */
	public static List<Feature> orderFeatures(Classifier cpt) {
		List<PrototypeBinding> inheritedBindings = Collections.emptyList();
		return orderFeatures(cpt, inheritedBindings);
	}

	/**
	 * Returns the sorted (see FeaturePositionComparator) list of features (included
	 * inherited features) owned by the given Classifier object.
	 *
	 * @param cpt the given Classifier object
	 * @param inheritedBindings inherited prototype bindings
	 * @return the sorted list of features owned by the given Component object
	 */
	public static List<Feature> orderFeatures(Classifier cpt, List<PrototypeBinding> inheritedBindings) {
		// List<PrototypeBinding> bindings = cpt.getOwnedPrototypeBindings();
		List<PrototypeBinding> bindings = new ArrayList<PrototypeBinding>();
		bindings.addAll(cpt.getOwnedPrototypeBindings());
		bindings.addAll(inheritedBindings);

		List<Feature> res = new ArrayList<Feature>();
		for (Feature f : cpt.getAllFeatures()) {
			res.add(getBindedFeature(bindings, f));
		}

		// res.addAll(cpt.getAllFeatures()) ;
		FeaturePositionComparator comparator = new FeaturePositionComparator();
		Collections.sort(res, comparator);
		return res;
	}

	private static Feature getBindedFeature(List<PrototypeBinding> bindings, Feature f) {
		FeatureClassifier cl = f.getFeatureClassifier();
		if ((!(cl instanceof DataPrototype)) || bindings == null || bindings.isEmpty()) {
			return f;
		}
		if ((f instanceof EventPort) || (f instanceof AbstractFeature)) {
			return f;
		}

		DataPrototype proto = (DataPrototype) cl;

		for (PrototypeBinding b : bindings) {
			if (!(b instanceof ComponentPrototypeBinding)) {
				continue;
			}

			ComponentPrototypeBinding cpb = (ComponentPrototypeBinding) b;
			Prototype p = b.getFormal();
			if (p.getName().equals(proto.getName())) {
				List<ComponentPrototypeActual> actuals = cpb.getActuals();
				if (actuals != null && !actuals.isEmpty()) {
					ComponentPrototypeActual actual = actuals.get(0);
					SubcomponentType st = actual.getSubcomponentType();
					if (!(st instanceof DataClassifier)) {
						continue;
					}
					DataClassifier dc = (DataClassifier) st;
					return setFeatureClassifier(f, dc);
				}
			}
		}

		return f;
	}

	private static Feature setFeatureClassifier(Feature f, DataClassifier dc) {
		if (f instanceof Parameter) {
			((Parameter) f).setDataFeatureClassifier(dc);
		} else if (f instanceof DataAccess) {
			((DataAccess) f).setDataFeatureClassifier(dc);
		} else if (f instanceof DataPort) {
			((DataPort) f).setDataFeatureClassifier(dc);
		} else if (f instanceof EventDataPort) {
			((EventDataPort) f).setDataFeatureClassifier(dc);
		}
		return f;
	}

	/*
	 * public static List<StructuralFeature> orderFeaturesAndFeaturesPrototype(ComponentType cpt)
	 * {
	 * List<StructuralFeature> result = new ArrayList<StructuralFeature>() ;
	 * 
	 * for (NamedElement ne : Aadl2Visitors.getMembers(cpt))
	 * {
	 * if (ne instanceof Feature || ne instanceof FeaturePrototype)
	 * {
	 * result.add((StructuralFeature) ne) ;
	 * }
	 * }
	 * 
	 * FeaturePositionComparator comparator = new FeaturePositionComparator() ;
	 * Collections.sort(result, comparator) ;
	 * 
	 * return result ;
	 * }
	 */
	public static ConnectionEnd getConnectedEnd(SubprogramCall sc, Feature p) {
		NamedElement parent = (NamedElement) sc.eContainer().eContainer();
		if (parent instanceof BehavioredImplementation) {
			BehavioredImplementation bi = (BehavioredImplementation) parent;
			for (ParameterConnection paramCnx : bi.getOwnedParameterConnections()) {
				ConnectedElement sourceConnectedElement = paramCnx.getSource();
				ConnectedElement destinationConnectedElement = paramCnx.getDestination();
				if (sourceConnectedElement.getContext() == sc && sourceConnectedElement.getConnectionEnd() == p) {
					return destinationConnectedElement.getConnectionEnd();
				} else if (destinationConnectedElement.getContext() == sc
						&& destinationConnectedElement.getConnectionEnd() == p) {
					return sourceConnectedElement.getConnectionEnd();
				}
			}
			for (AccessConnection accessCnx : bi.getOwnedAccessConnections()) {
				ConnectedElement sourceConnectedElement = accessCnx.getSource();
				ConnectedElement destinationConnectedElement = accessCnx.getDestination();
				if (sourceConnectedElement.getContext() == sc && sourceConnectedElement.getConnectionEnd() == p) {
					return destinationConnectedElement.getConnectionEnd();
				} else if (destinationConnectedElement.getContext() == sc
						&& destinationConnectedElement.getConnectionEnd() == p) {
					return sourceConnectedElement.getConnectionEnd();
				}
			}
		}
		return null;
	}

	public static boolean isInOutParameter(Parameter p) {
		return p.getDirection().equals(DirectionType.IN_OUT);
	}

	public static boolean isOutParameter(Parameter p) {
		return p.getDirection().equals(DirectionType.OUT);
	}

	public static boolean isReadWriteDataAccess(DataAccess da) {
		String accessRight = getAccessRight(da);
		return accessRight.equalsIgnoreCase("Read_Write");
	}

	public static boolean isWriteOnlyDataAccess(DataAccess da) {
		String accessRight = getAccessRight(da);
		return accessRight.equalsIgnoreCase("Write_Only");
	}

	/**
	 * Implementation of a feature comparator based on the postion of the features
	 * in the aadl code.
	 * <BR><BR>
	 * Returns 1 if arg1 is declared after arg0.<BR>
	 * Returns -1 if arg0 is declared after arg1.<BR>
	 * Returns 0 if arg0 and arg1 are declared on the same line.
	 */
	public static class FeaturePositionComparator implements Comparator<Feature> {

		@Override
		public int compare(Feature arg0, Feature arg1) {
			Feature ancestor0 = arg0.getRefined() != null ? arg0.getRefined() : arg0;
			Feature ancestor1 = arg1.getRefined() != null ? arg1.getRefined() : arg1;

			INode node0 = NodeModelUtils.findActualNodeFor(ancestor0);
			int offset0 = node0.getOffset();
			int line0 = node0.getStartLine();
			INode node1 = NodeModelUtils.findActualNodeFor(ancestor1);
			int offset1 = node1.getOffset();
			int line1 = node1.getStartLine();

			if (line0 < line1) {
				return -1;
			}

			if (line0 == line1) {
				if (offset0 < offset1) {
					return -1;
				}

				if (offset0 > offset1) {
					return 1;
				}
			}

			if (line0 > line1) {
				return 1;
			}

			return 0;
		}
	}

	/**
	 * Returns the parameter usage (by_reference, by_value) of the given NamedElement object.<BR>
	 * <BR>
	 *
	 * Returns the local "Parameter_Usage" property value, if it is set. Otherwise,
	 * returns the default parameter value found in Generation_Properties (home
	 * made property set). If the default parameter value is not found,
	 * returns an empty string (not {@code null}).
	 *
	 * @param ne the given NamedElement object
	 * @return local parameter usage or default parameter usage or an empty string
	 */
	public static String getParameterUsage(NamedElement ne) {
		String result = PropertyUtils.getEnumValue(ne, "Parameter_Usage");
		if (result == null) {
			Property prop = GetProperties.lookupPropertyDefinition(ne, "Code_Generation_Properties", "Parameter_Usage");
			if (prop != null) {
				NamedValue nv = (NamedValue) prop.getDefaultValue();
				if (nv != null) {
					result = ((EnumerationLiteral) nv.getNamedValue()).getName();
				} else {
					return "";
				}
			} else {
				return "";
			}
		}
		return result;
	}

	/**
	 * Returns the access right of the given NamedElement object.<BR>
	 * <BR>
	 *
	 * Returns the local "Access_Right" property value, if it is set. Otherwise,
	 * returns the default access right value found in Memory_Properties (pre
	 * declared property set). If the default access right is not found, it
	 * returns "unknown".
	 *
	 * @param ne the given NamedElement object
	 * @return local access right or default access right or "unknown"
	 */
	public static String getAccessRight(NamedElement ne) {
		String result = PropertyUtils.getEnumValue(ne, "Access_Right");
		if (result == null) {
			if (DEFAULT_ACCESS_RIGHT == null) {
				Property prop = GetProperties.lookupPropertyDefinition(ne, "Memory_Properties", "Access_Right");
				if (prop != null) {
					NamedValue nv = (NamedValue) prop.getDefaultValue();
					result = ((EnumerationLiteral) nv.getNamedValue()).getName();
					DEFAULT_ACCESS_RIGHT = result;
				} else {
					return "unknown";
				}
			} else {
				return DEFAULT_ACCESS_RIGHT;
			}
		}

		return result;
	}

	/**
	 * Same as {@link #getAccessRight(NamedElement)}, it returns the access right
	 * of the given NamedElement object, using DataAccessRight enumeration.
	 * <BR><BR>
	 *
	 * Returns the local "Access_Right" property value, if it is set. Otherwise,
	 * returns the default access right value found in Memory_Properties (pre
	 * declared property set). If the default access right is not found, it
	 * returns DataAccessRight.unknown .
	 *
	 * @param ne the given NamedElement object
	 * @return local access right or default access right or DataAccessRight.unknown
	 */
	public static DataAccessRight getDataAccessRight(NamedElement ne) {
		return DataAccessRight.getDataAccessRight(getAccessRight(ne));
	}

	/**
	 * Convenient enumeration class for data access right checking.
	 * <BR><BR>
	 *
	 * {@link DataAccessRight#getDataAccessRight(String)} to translate
	 * data access right expressed in string object into a DataAccessRight
	 * enumeration reference.
	 */
	public enum DataAccessRight {
		unknown("unknown"), read_only("read only"), write_only("write only"), read_write("read and write");

		String literal;

		DataAccessRight(String literal) {
			this.literal = literal;
		}

		// Do not use Enum.Valueof because if literal is null, Enum will throw an
		// uncatched exceptions.
		public static DataAccessRight getDataAccessRight(String literal) {
			try {
				return valueOf(literal);
			} catch (IllegalArgumentException e) {
				return unknown;
			}
		}
	}

	/**
	 * Concatenates the strings contained in the given array by inserting
	 * between the strings, the given separator.
	 *
	 * @param separator the given string separator
	 * @param toBeConcatenated the given string array
	 * @return the concatenated strings
	 */
	public static String concatenateString(String separator, String... toBeConcatenated) {
		StringBuilder result = new StringBuilder();

		for (String s : toBeConcatenated) {
			result.append(s);
			result.append(separator);
		}

		// Remove the last separator.
		result.setLength(result.length() - separator.length());

		return result.toString();
	}

	/**
	 * Returns {@code true} if the given string is found (case not sensitive)
	 * into the given string array. Otherwise returns {@code false}.
	 * If the given string is {@code null}, it returns {@code false}.
	 *
	 * @param s the given string or {@code null}
	 * @param stringArray the given string array
	 * @return {@code true} if the given string is found (case not sensitive)
	 * into the given string array. Otherwise {@code false}.
	 */
	public static boolean contains(String s, Iterable<String> stringArray) {
		for (String tmp : stringArray) {
			if (tmp.equalsIgnoreCase(s)) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Returns {@code true} if the given object is found (based on equals method)
	 * into the given object array. Otherwise returns {@code false}.
	 * If the given object is {@code null}, it returns {@code false}.
	 *
	 * @param element the given object or {@code null}
	 * @param array the given object array
	 * @return {@code true} if the given object is found ((based on java address)
	 * into the given object array. Otherwise {@code false}.
	 */
	public static boolean contains(Object element, Object[] array) {
		for (Object tmp : array) {
			if (tmp.equals(element)) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Compare the given lists of strings. Return {@code true} if they are
	 * equivalents: they contain the same strings in any order.
	 * Case is insensitive. {@code false} otherwise.<BR><BR>
	 * This method is different from List.equals see {@link List#equals(Object)}.
	 *
	 * @param list1 an list of strings
	 * @param list2 an other list of strings
	 * @return {@code true} if the lists are equivalents, {@code false} otherwise
	 */
	public static boolean compareStringList(List<String> list1, List<String> list2) {
		if (list1.size() == list2.size()) {
			ArrayList<String> l1 = new ArrayList<String>(list1);
			ArrayList<String> l2 = new ArrayList<String>(list2);

			Comparator<String> c = (o1, o2) -> o1.compareToIgnoreCase(o2);

			Collections.sort(l1, c);
			Collections.sort(l2, c);

			Iterator<String> it1 = l1.iterator();
			Iterator<String> it2 = l2.iterator();

			String s1, s2;

			while (it1.hasNext()) {
				s1 = it1.next();
				s2 = it2.next();

				if (!s1.equalsIgnoreCase(s2)) {
					return false;
				}
			}

			return true;
		} else {
			return false;
		}
	}

	/**
	 * Return the location reference of the given Element object.
	 *
	 * @param e the given Element
	 * @return a LocationReference object
	 */
	public static LocationReference getLocationReference(Element e) {
		LocationReference result = null;

		result = e.getLocationReference();

		if (result == null) {
			ICompositeNode node = NodeModelUtils.findActualNodeFor(e);

			result = new LocationReference();

			if (node != null && e.eResource() != null) {
				result.setFilename(e.eResource().getURI().lastSegment());
				result.setOffset(node.getOffset());
				result.setLength(node.getLength());
				result.setLine(node.getStartLine());
			} else {
				// DEBUG
				System.err.println("Aadl2Utils.getLocationReference: node or eResource " + "not found for " + e);
			}
		}

		return result;
	}

	/**
	 * Convert a relative path of file or directory contained in a given plugin
	 * into a absolute path.
	 *
	 * @param pluginId the given plugin identification
	 * @param relativePath the relative path of file or directory
	 * @return the absolute path
	 * @throws Exception raised if the plugin is not found or the relative path
	 * points to nowhere
	 */
	public static File getPluginFile(String pluginId, String relativePath) throws Exception {
		File result = null;

		if (Platform.isRunning()) {
			Bundle bundle = Platform.getBundle(pluginId);
			if (bundle == null) {
				throw new Exception("plugin: " + pluginId + " is not found");
			}

			URL rootURL = bundle.getEntry(relativePath);
			if (rootURL == null) {
				throw new Exception("file or directory: " + relativePath + " is not found");
			}

			result = new File(FileLocator.toFileURL(rootURL).getFile());
		}

		return result;
	}

	/**
	 * Returns the absolute path of the corresponding plugin identificated by
	 * the given plugin id.
	 *
	 * @param pluginId the given plugin identification
	 * @return the plugin in absolute path
	 * @throws Exception raised if the plugin is not found
	 */
	public static File getAbsolutePluginPath(String pluginId) throws Exception {
		File result = getPluginFile(pluginId, "");
		return result;
	}
}