Bug 552194 - Show warning when bundle's BREE lower than its dependencies

Change-Id: I04db9e86deda7319256bab4194115d1a9a4ffd69
Signed-off-by: Andrew Obuchowicz <aobuchow@redhat.com>
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/PDECoreMessages.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/PDECoreMessages.java
index c1f05ff..2178d14 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/PDECoreMessages.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/PDECoreMessages.java
@@ -213,6 +213,7 @@
 	public static String BundleErrorReporter_R4SyntaxInR3Bundle;
 	public static String BundleErrorReporter_NotExistPDE;
 	public static String BundleErrorReporter_EmptyTargetPlatform;
+	public static String BundleErrorReporter_ExecEnv_tooLow;
 	public static String BundleErrorReporter_HostNotExistPDE;
 	public static String BundleErrorReporter_HostNeeded;
 	public static String BundleErrorReporter_PackageNotExported;
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/builders/BundleErrorReporter.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/builders/BundleErrorReporter.java
index f86bef9..0b08f49 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/builders/BundleErrorReporter.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/builders/BundleErrorReporter.java
@@ -18,7 +18,10 @@
 package org.eclipse.pde.internal.core.builders;
 
 import java.io.File;
+import java.util.ArrayDeque;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Deque;
 import java.util.Enumeration;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -27,6 +30,8 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.StringTokenizer;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 import org.eclipse.core.resources.IContainer;
 import org.eclipse.core.resources.IFile;
 import org.eclipse.core.resources.IProject;
@@ -636,6 +641,128 @@
 				break;
 			}
 		}
+
+		// Check for highest BREE of bundle dependencies
+		String highestDependencyEE = checkBREE(desc);
+		String highestBundleEE = getHighestBREE(bundleEnvs);
+		try {
+			if (highestBundleEE != getHighestEE(highestDependencyEE, highestBundleEE)) {
+				VirtualMarker marker = report(
+						NLS.bind(PDECoreMessages.BundleErrorReporter_ExecEnv_tooLow,
+								highestDependencyEE, highestDependencyEE),
+						getLine(header, highestBundleEE), sev, PDEMarkerFactory.M_EXEC_ENV_TOO_LOW,
+						PDEMarkerFactory.CAT_EE);
+				addMarkerAttribute(marker, PDEMarkerFactory.REQUIRED_EXEC_ENV, highestDependencyEE);
+			}
+		} catch (Exception e) {
+			PDECore.log(e);
+		}
+	}
+
+	static final List<String> EXECUTION_ENVIRONMENT_NAMES = Arrays.asList("OSGi/Minimum", //$NON-NLS-1$
+			"CDC-1.0/Foundation", //$NON-NLS-1$
+			"CDC-1.1/Foundation", "JRE", "J2SE", "JavaSE"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
+
+	private static String getHighestEE(String execEnv1, String execEnv2) throws IllegalArgumentException {
+		if (execEnv1 == null) {
+			return execEnv2;
+		} else if (execEnv2 == null) {
+			return execEnv1;
+		}
+
+		Pattern p = Pattern.compile("(.*)-(\\d+)\\.?(\\d+)?(.*)?"); //$NON-NLS-1$
+		Matcher eeMatcher1 = p.matcher(execEnv1);
+		Matcher eeMatcher2 = p.matcher(execEnv2);
+
+		if (!eeMatcher1.matches()) {
+			throw new IllegalArgumentException(String.format("%s is not a valid Execution Environment", execEnv1)); //$NON-NLS-1$
+		}
+		if (!eeMatcher2.matches()) {
+			throw new IllegalArgumentException(String.format("%s is not a valid Execution Environment", execEnv2)); //$NON-NLS-1$
+		}
+
+		String eeName1 = eeMatcher1.group(1);
+		String eeName2 = eeMatcher2.group(1);
+		int eeNameIndex1 = EXECUTION_ENVIRONMENT_NAMES.indexOf(eeName1);
+		int eeNameIndex2 = EXECUTION_ENVIRONMENT_NAMES.indexOf(eeName2);
+		int eeMajorVersion1 = Integer.parseInt(eeMatcher1.group(2));
+		int eeMajorVersion2 = Integer.parseInt(eeMatcher2.group(2));
+		Integer eeMinorVersion1 = null;
+		Integer eeMinorVersion2 = null;
+
+		if (eeMatcher1.groupCount() > 2) {
+			eeMinorVersion1 = Integer.valueOf(eeMatcher1.group(3));
+		}
+		if (eeMatcher2.groupCount() > 2) {
+			eeMinorVersion2 = Integer.valueOf(eeMatcher2.group(3));
+		}
+
+		if (eeNameIndex1 > eeNameIndex2) {
+			return execEnv1;
+		} else if (eeNameIndex1 < eeNameIndex2) {
+			return execEnv2;
+		}
+
+		// EE1 and EE2 have the same EE name
+		if (eeMajorVersion1 > eeMajorVersion2) {
+			return execEnv1;
+		} else if (eeMajorVersion1 < eeMajorVersion2) {
+			return execEnv2;
+		}
+
+		// EE1 and EE2 have the same major version
+		if (eeMinorVersion1 != null && eeMinorVersion2 != null) {
+			if (eeMinorVersion1 > eeMinorVersion2) {
+				return execEnv1;
+			} else if (eeMinorVersion1 < eeMinorVersion2) {
+				return execEnv2;
+			}
+		}
+
+		// EE1 == EE2
+		return execEnv1;
+	}
+
+	private String getHighestBREE(String[] executionEnvironments) {
+		if (executionEnvironments.length == 0) {
+			return null;
+		}
+		String highestExecEnv = executionEnvironments[0];
+		if (executionEnvironments.length > 1) {
+			for (String execEnv : executionEnvironments) {
+				try {
+				highestExecEnv = getHighestEE(highestExecEnv, execEnv);
+				} catch (Exception e) {
+					PDECore.log(e);
+					return null;
+				}
+
+			}
+		}
+
+		return highestExecEnv;
+	}
+
+	private String checkBREE(BundleDescription desc) {
+		String highestBREE = getHighestBREE(desc.getExecutionEnvironments());
+		HashSet<BundleDescription> visitedBundles = new HashSet<>();
+		Deque<BundleDescription> bundleDescriptions = new ArrayDeque<>();
+		bundleDescriptions.push(desc);
+		while (!bundleDescriptions.isEmpty()) {
+			BundleDescription dependencyDesc = bundleDescriptions.pop();
+			visitedBundles.add(dependencyDesc);
+			for (BundleDescription transitiveDependencyDesc : dependencyDesc.getResolvedRequires()) {
+				if (!visitedBundles.contains(transitiveDependencyDesc)) {
+				bundleDescriptions.push(transitiveDependencyDesc);
+				}
+			}
+			try {
+				highestBREE = getHighestEE(highestBREE, getHighestBREE(dependencyDesc.getExecutionEnvironments()));
+			} catch (Exception e) {
+				PDECore.log(e);
+			}
+		}
+		return highestBREE;
 	}
 
 	/**
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/builders/PDEMarkerFactory.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/builders/PDEMarkerFactory.java
index 85f0dc3..ea6af55 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/builders/PDEMarkerFactory.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/builders/PDEMarkerFactory.java
@@ -77,6 +77,7 @@
 	public static final int M_SERVICECOMPONENT_MISSING_LAZY = 0x1026; // other problem
 	public static final int M_ONLY_CONFIG_SEV = 0x1027; // other problem
 	public static final int M_NO_AUTOMATIC_MODULE = 0x1028; // other problem
+	public static final int M_EXEC_ENV_TOO_LOW = 0x1029; // other problem
 
 	// build properties fixes
 	public static final int B_APPEND_SLASH_FOLDER_ENTRY = 0x2001;
@@ -99,6 +100,7 @@
 	public static final String MPK_LOCATION_PATH = "xmlTree.locationPath"; //$NON-NLS-1$
 	public static final String ATTR_CAN_ADD = "deprecatedAutostart.canAdd"; //$NON-NLS-1$
 	public static final String ATTR_HEADER = "deprecatedAutostart.header"; //$NON-NLS-1$
+	public static final String REQUIRED_EXEC_ENV = "executionEnvironment.key"; //$NON-NLS-1$
 	/**
 	 * Boolean attribute for marker added when no newline is found at the end of a manifest. Value is
 	 * <code>true</code> if there is character content on the last line that should be
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/pderesources.properties b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/pderesources.properties
index 894576d..2051238 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/pderesources.properties
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/pderesources.properties
@@ -174,6 +174,7 @@
 BundleErrorReporter_BundleRangeInvalidInBundleVersion=Unsatisfied version constraint: ''{0}''
 BundleErrorReporter_NotExistPDE=Bundle ''{0}'' cannot be resolved
 BundleErrorReporter_EmptyTargetPlatform=Target Platform is not set
+BundleErrorReporter_ExecEnv_tooLow=One of the bundles dependencies requires an Execution Environment of {0}, thus this bundles Execution Environment should be set to at least {1}.
 BundleErrorReporter_exportNoJRE=Cannot export packages prefixed with 'java'
 BundleErrorReporter_importNoJRE=Cannot import packages prefixed with 'java'
 BundleErrorReporter_HostNotExistPDE=Host bundle ''{0}'' cannot be resolved
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/correction/Messages.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/correction/Messages.java
new file mode 100644
index 0000000..71a608d
--- /dev/null
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/correction/Messages.java
@@ -0,0 +1,28 @@
+/*******************************************************************************
+ *  Copyright (c) 2019 IBM Corporation and others.
+ *
+ *  This program and the accompanying materials
+ *  are made available under the terms of the Eclipse Public License 2.0
+ *  which accompanies this distribution, and is available at
+ *  https://www.eclipse.org/legal/epl-2.0/
+ *
+ *  SPDX-License-Identifier: EPL-2.0
+ *
+ *  Contributors:
+ *     Andrew Obuchowicz <aobuchow@redhat.com>
+ *******************************************************************************/
+package org.eclipse.pde.internal.ui.correction;
+
+import org.eclipse.osgi.util.NLS;
+
+public class Messages extends NLS {
+	private static final String BUNDLE_NAME = "org.eclipse.pde.internal.ui.correction.messages"; //$NON-NLS-1$
+	public static String ReplaceExecEnvironment_Marker_Label;
+	static {
+		// initialize resource bundle
+		NLS.initializeMessages(BUNDLE_NAME, Messages.class);
+	}
+
+	private Messages() {
+	}
+}
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/correction/ReplaceExecEnvironment.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/correction/ReplaceExecEnvironment.java
new file mode 100644
index 0000000..e38d17d
--- /dev/null
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/correction/ReplaceExecEnvironment.java
@@ -0,0 +1,61 @@
+/*******************************************************************************
+ *  Copyright (c) 2019 IBM Corporation and others.
+ *
+ *  This program and the accompanying materials
+ *  are made available under the terms of the Eclipse Public License 2.0
+ *  which accompanies this distribution, and is available at
+ *  https://www.eclipse.org/legal/epl-2.0/
+ *
+ *  SPDX-License-Identifier: EPL-2.0
+ *
+ *  Contributors:
+ *     Andrew Obuchowicz <aobuchow@redhat.com> - Initial Implementation
+ *******************************************************************************/
+package org.eclipse.pde.internal.ui.correction;
+
+import org.eclipse.core.resources.IMarker;
+import org.eclipse.jdt.launching.JavaRuntime;
+import org.eclipse.jdt.launching.environments.IExecutionEnvironment;
+import org.eclipse.osgi.util.NLS;
+import org.eclipse.pde.internal.core.builders.PDEMarkerFactory;
+import org.eclipse.pde.internal.core.ibundle.IManifestHeader;
+import org.eclipse.pde.internal.core.text.bundle.*;
+import org.osgi.framework.Constants;
+
+public class ReplaceExecEnvironment extends AbstractManifestMarkerResolution {
+
+	String fRequiredEE;
+
+	public ReplaceExecEnvironment(int type, IMarker marker) {
+		super(type, marker);
+		this.fRequiredEE = super.marker.getAttribute(PDEMarkerFactory.REQUIRED_EXEC_ENV, "JavaSE-1.8"); //$NON-NLS-1$
+	}
+
+	@Override
+	public String getLabel() {
+		return NLS.bind(Messages.ReplaceExecEnvironment_Marker_Label, this.fRequiredEE);
+	}
+
+	@Override
+	protected void createChange(BundleModel model) {
+		IManifestHeader header = model.getBundle().getManifestHeader(Constants.BUNDLE_REQUIREDEXECUTIONENVIRONMENT);
+		if (header instanceof RequiredExecutionEnvironmentHeader) {
+			RequiredExecutionEnvironmentHeader reqHeader = (RequiredExecutionEnvironmentHeader) header;
+			ExecutionEnvironment[] bundleEnvs = reqHeader.getEnvironments();
+			IExecutionEnvironment[] systemEnvs = JavaRuntime.getExecutionEnvironmentsManager()
+					.getExecutionEnvironments();
+
+			for (IExecutionEnvironment systemEnv : systemEnvs) {
+				if (systemEnv.getId().equals(fRequiredEE)) {
+					for (ExecutionEnvironment bundleEnv : bundleEnvs) {
+						reqHeader.removeExecutionEnvironment(bundleEnv);
+					}
+					reqHeader.addExecutionEnvironment(systemEnv);
+				}
+
+			}
+		}
+
+	}
+
+}
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/correction/ResolutionGenerator.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/correction/ResolutionGenerator.java
index 332ed9a..0d6e3fa 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/correction/ResolutionGenerator.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/correction/ResolutionGenerator.java
@@ -80,7 +80,11 @@
 			case PDEMarkerFactory.M_MISMATCHED_EXEC_ENV :
 				return new IMarkerResolution[] {new UpdateClasspathResolution(AbstractPDEMarkerResolution.RENAME_TYPE, marker)};
 			case PDEMarkerFactory.M_UNKNOW_EXEC_ENV :
-				return new IMarkerResolution[] {new RemoveUnknownExecEnvironments(AbstractPDEMarkerResolution.REMOVE_TYPE,marker)};
+				return new IMarkerResolution[] {
+						new RemoveUnknownExecEnvironments(AbstractPDEMarkerResolution.REMOVE_TYPE, marker) };
+			case PDEMarkerFactory.M_EXEC_ENV_TOO_LOW:
+				return new IMarkerResolution[] {
+						new ReplaceExecEnvironment(AbstractPDEMarkerResolution.RENAME_TYPE, marker) };
 			case PDEMarkerFactory.M_DEPRECATED_IMPORT_SERVICE :
 				return new IMarkerResolution[] {new RemoveImportExportServicesResolution(AbstractPDEMarkerResolution.REMOVE_TYPE, ICoreConstants.IMPORT_SERVICE, marker)};
 			case PDEMarkerFactory.M_DEPRECATED_EXPORT_SERVICE :
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/correction/messages.properties b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/correction/messages.properties
new file mode 100644
index 0000000..6193e6e
--- /dev/null
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/correction/messages.properties
@@ -0,0 +1,10 @@
+###############################################################################
+#  Copyright (c) 2019 IBM Corporation 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:
+#     Andrew Obuchowicz <aobuchow@redhat.com>
+###############################################################################
+ReplaceExecEnvironment_Marker_Label=Replace Bundle Execution Environment with ''{0}''
\ No newline at end of file