1 /*******************************************************************************
2  * Copyright (c) 2000, 2015 IBM Corporation and others.
3  *
4  * This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License 2.0
6  * which accompanies this distribution, and is available at
7  * https://www.eclipse.org/legal/epl-2.0/
8  *
9  * SPDX-License-Identifier: EPL-2.0
10  *
11  * Contributors:
12  *     IBM Corporation - initial API and implementation
13  *******************************************************************************/
14 
15 package org.eclipse.ui.texteditor;
16 
17 import java.util.ArrayList;
18 import java.util.Arrays;
19 import java.util.Comparator;
20 import java.util.HashMap;
21 import java.util.HashSet;
22 import java.util.Iterator;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.Map.Entry;
26 import java.util.Set;
27 
28 import org.osgi.framework.Bundle;
29 import org.osgi.framework.BundleException;
30 import org.osgi.framework.Constants;
31 
32 import org.eclipse.osgi.util.ManifestElement;
33 
34 import org.eclipse.core.runtime.Assert;
35 import org.eclipse.core.runtime.IConfigurationElement;
36 import org.eclipse.core.runtime.IExtension;
37 import org.eclipse.core.runtime.IStatus;
38 import org.eclipse.core.runtime.Platform;
39 import org.eclipse.core.runtime.Status;
40 
41 import org.eclipse.ui.internal.texteditor.TextEditorPlugin;
42 
43 /**
44  * Allows to sort an array based on their elements' configuration elements
45  * according to the prerequisite relation of their defining plug-ins.
46  * <p>
47  * This class may be subclassed.
48  * </p>
49  *
50  * @since 3.0
51  */
52 public abstract class ConfigurationElementSorter {
53 
54 	/**
55 	 * Sorts the given array based on its elements' configuration elements
56 	 * according to the prerequisite relation of their defining plug-ins.
57 	 *
58 	 * @param elements the array to be sorted
59 	 */
sort(Object[] elements)60 	public final void sort(Object[] elements) {
61 		Arrays.sort(elements, new ConfigurationElementComparator(elements));
62 	}
63 
64 	/**
65 	 * Returns the configuration element for the given object.
66 	 *
67 	 * @param object the object
68 	 * @return the object's configuration element, must not be <code>null</code>
69 	 */
getConfigurationElement(Object object)70 	public abstract IConfigurationElement getConfigurationElement(Object object);
71 
72 	/**
73 	 * Compare configuration elements according to the prerequisite relation
74 	 * of their defining plug-ins.
75 	 */
76 	private class ConfigurationElementComparator implements Comparator<Object> {
77 
78 		private Map<Object, String> fDescriptorMapping;
79 		private Map<String, Set<String>> fPrereqsMapping;
80 
ConfigurationElementComparator(Object[] elements)81 		public ConfigurationElementComparator(Object[] elements) {
82 			Assert.isNotNull(elements);
83 			initialize(elements);
84 		}
85 
86 		@Override
compare(Object object0, Object object1)87 		public int compare(Object object0, Object object1) {
88 
89 			if (dependsOn(object0, object1))
90 				return -1;
91 
92 			if (dependsOn(object1, object0))
93 				return +1;
94 
95 			return 0;
96 		}
97 
98 		/**
99 		 * Returns whether one configuration element depends on the other element.
100 		 * This is done by checking the dependency chain of the defining plug-ins.
101 		 *
102 		 * @param element0 the first element
103 		 * @param element1 the second element
104 		 * @return <code>true</code> if <code>element0</code> depends on <code>element1</code>.
105 		 * @since 2.0
106 		 */
dependsOn(Object element0, Object element1)107 		private boolean dependsOn(Object element0, Object element1) {
108 			if (element0 == null || element1 == null)
109 				return false;
110 
111 			String pluginDesc0= fDescriptorMapping.get(element0);
112 			String pluginDesc1= fDescriptorMapping.get(element1);
113 
114 			// performance tuning - code below would give same result
115 			if (pluginDesc0.equals(pluginDesc1))
116 				return false;
117 
118 			Set<String> prereqUIds0= fPrereqsMapping.get(pluginDesc0);
119 
120 			return prereqUIds0.contains(pluginDesc1);
121 		}
122 
123 		/**
124 		 * Initialize this comparator.
125 		 *
126 		 * @param elements an array of Java editor hover descriptors
127 		 */
initialize(Object[] elements)128 		private void initialize(Object[] elements) {
129 			int length= elements.length;
130 			fDescriptorMapping= new HashMap<>(length);
131 			fPrereqsMapping= new HashMap<>(length);
132 			Set<Bundle> fBundleSet= new HashSet<>(length);
133 
134 			for (int i= 0; i < length; i++) {
135 				IConfigurationElement configElement= getConfigurationElement(elements[i]);
136 				Bundle bundle= Platform.getBundle(configElement.getContributor().getName());
137 				fDescriptorMapping.put(elements[i], bundle.getSymbolicName());
138 				fBundleSet.add(bundle);
139 			}
140 
141 			Iterator<Bundle> iter= fBundleSet.iterator();
142 			while (iter.hasNext()) {
143 				Bundle bundle= iter.next();
144 				List<Bundle> toTest= new ArrayList<>(fBundleSet);
145 				toTest.remove(bundle);
146 				Set<String> prereqUIds= new HashSet<>(Math.max(0, toTest.size() - 1));
147 				fPrereqsMapping.put(bundle.getSymbolicName(), prereqUIds);
148 
149 				String requires = bundle.getHeaders().get(Constants.REQUIRE_BUNDLE);
150 				ManifestElement[] manifestElements;
151 				try {
152 					manifestElements = ManifestElement.parseHeader(Constants.REQUIRE_BUNDLE, requires);
153 				} catch (BundleException e) {
154 					String uid= getExtensionPointUniqueIdentifier(bundle);
155 					String message= "ConfigurationElementSorter for '" + uid + "': getting required plug-ins for '" + bundle.getSymbolicName() + "' failed"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
156 					Status status= new Status(IStatus.ERROR, TextEditorPlugin.PLUGIN_ID, IStatus.OK, message, e);
157 					TextEditorPlugin.getDefault().getLog().log(status);
158 					continue;
159 				}
160 
161 				if (manifestElements == null)
162 					continue;
163 
164 				int i= 0;
165 				while (i < manifestElements.length && !toTest.isEmpty()) {
166 					String prereqUId= manifestElements[i].getValue();
167 					for (int j= 0; j < toTest.size();) {
168 						Bundle toTest_j= toTest.get(j);
169 						if (toTest_j.getSymbolicName().equals(prereqUId)) {
170 							toTest.remove(toTest_j);
171 							prereqUIds.add(toTest_j.getSymbolicName());
172 						} else
173 							j++;
174 					}
175 					i++;
176 				}
177 			}
178 		}
179 
180 		/**
181 		 * Returns the unique extension point identifier for the
182 		 * configuration element which belongs to the given bundle.
183 		 *
184 		 * @param bundle the bundle
185 		 * @return the unique extension point identifier or "unknown" if not found
186 		 * @since 3.0.1
187 		 */
getExtensionPointUniqueIdentifier(Bundle bundle)188 		private String getExtensionPointUniqueIdentifier(Bundle bundle) {
189 			if (bundle != null) {
190 				String bundleName= bundle.getSymbolicName();
191 				if (bundleName != null) {
192 					Set<Entry<Object, String>> entries= fDescriptorMapping.entrySet();
193 					Iterator<Entry<Object, String>> iter= entries.iterator();
194 					while (iter.hasNext()) {
195 						Entry<Object, String> entry= iter.next();
196 						if (bundleName.equals(entry.getValue())) {
197 							IExtension extension = getConfigurationElement(entry.getKey()).getDeclaringExtension();
198 							return extension.getExtensionPointUniqueIdentifier();
199 						}
200 					}
201 				}
202 			}
203 			return "unknown";  //$NON-NLS-1$
204 		}
205 
206 	}
207 }
208