1 /*******************************************************************************
2  * Copyright (c) 2000, 2017 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 package org.eclipse.pde.internal.build;
15 
16 import java.io.*;
17 import java.net.MalformedURLException;
18 import java.net.URL;
19 import java.util.*;
20 import java.util.Map.Entry;
21 import java.util.zip.ZipFile;
22 import org.eclipse.core.runtime.*;
23 import org.eclipse.equinox.p2.publisher.eclipse.FeatureEntry;
24 import org.eclipse.osgi.service.resolver.BundleDescription;
25 import org.eclipse.osgi.service.resolver.VersionRange;
26 import org.eclipse.osgi.util.ManifestElement;
27 import org.eclipse.osgi.util.NLS;
28 import org.eclipse.pde.internal.build.ant.AntScript;
29 import org.eclipse.pde.internal.build.site.BuildTimeFeature;
30 import org.eclipse.pde.internal.build.site.BuildTimeSite;
31 import org.osgi.framework.BundleException;
32 import org.osgi.framework.Version;
33 
34 /**
35  * General utility class.
36  */
37 public final class Utils implements IPDEBuildConstants, IBuildPropertiesConstants, IXMLConstants {
38 	static class ArrayEnumeration implements Enumeration<Object> {
39 		private final Object[] array;
40 		int cur = 0;
41 
ArrayEnumeration(Object[] array)42 		public ArrayEnumeration(Object[] array) {
43 			this.array = new Object[array.length];
44 			System.arraycopy(array, 0, this.array, 0, this.array.length);
45 		}
46 
47 		@Override
hasMoreElements()48 		public boolean hasMoreElements() {
49 			return cur < array.length;
50 		}
51 
52 		@Override
nextElement()53 		public Object nextElement() {
54 			return array[cur++];
55 		}
56 	}
57 
58 	// The 64 characters that are legal in a version qualifier, in lexicographical order.
59 	private static final String BASE_64_ENCODING = "-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz"; //$NON-NLS-1$
60 
61 	// regex expressions and keys for parsing feature root properties
62 	private static final String REGEX_ROOT_CONFIG = "^root((\\.[\\w-\\*]+){3})$"; //$NON-NLS-1$
63 	private static final String REGEX_ROOT_CONFIG_FOLDER = "^root((\\.[\\w-\\*]+){3})?\\.folder\\.(.*)$"; //$NON-NLS-1$
64 	private static final String REGEX_ROOT_CONFIG_PERMISSIONS = "^root((\\.[\\w-\\*]+){3})?\\.permissions\\.(.*)$"; //$NON-NLS-1$
65 	private static final String REGEX_ROOT_CONFIG_LINK = "^root((\\.[\\w-\\*]+){3})?\\.link$"; //$NON-NLS-1$
66 	public static final String ROOT_PERMISSIONS = "!!ROOT.PERMISSIONS!!"; //$NON-NLS-1$
67 	public static final String ROOT_LINK = "!!ROOT.LINK!!"; //$NON-NLS-1$
68 	public static final String ROOT_COMMON = "!!COMMON!!"; //$NON-NLS-1$
69 
70 	/**
71 	 * returns a value 1 - 64 for valid qualifier characters.  Returns 0 for non-valid characters
72 	 */
qualifierCharValue(char c)73 	public static int qualifierCharValue(char c) {
74 		int index = BASE_64_ENCODING.indexOf(c);
75 		// The "+ 1" is very intentional.  For a blank (or anything else that
76 		// is not a legal character), we want to return 0.  For legal
77 		// characters, we want to return one greater than their position, so
78 		// that a blank is correctly distinguished from '-'.
79 		return index + 1;
80 	}
81 
82 	// Integer to character conversion in our base-64 encoding scheme.  If the
83 	// input is out of range, an illegal character will be returned.
base64Character(int number)84 	public static char base64Character(int number) {
85 		if (number < 0 || number > 63) {
86 			return ' ';
87 		}
88 		return BASE_64_ENCODING.charAt(number);
89 	}
90 
createVersionRange(String versionId)91 	public static VersionRange createVersionRange(String versionId) {
92 		VersionRange range = null;
93 		if (versionId == null || versionId.length() == 0 || GENERIC_VERSION_NUMBER.equals(versionId))
94 			range = VersionRange.emptyRange;
95 		else {
96 			int qualifierIdx = versionId.indexOf(IBuildPropertiesConstants.PROPERTY_QUALIFIER);
97 			if (qualifierIdx != -1) {
98 				String newVersion = versionId.substring(0, qualifierIdx);
99 				if (newVersion.endsWith(".")) //$NON-NLS-1$
100 					newVersion = newVersion.substring(0, newVersion.length() - 1);
101 
102 				Version lower = new Version(newVersion);
103 				Version upper = null;
104 				String newQualifier = incrementQualifier(lower.getQualifier());
105 				if (newQualifier == null)
106 					upper = new Version(lower.getMajor(), lower.getMinor(), lower.getMicro() + 1);
107 				else
108 					upper = new Version(lower.getMajor(), lower.getMinor(), lower.getMicro(), newQualifier);
109 				range = new VersionRange(lower, true, upper, false);
110 			} else {
111 				range = new VersionRange(new Version(versionId), true, new Version(versionId), true);
112 			}
113 		}
114 		return range;
115 	}
116 
createVersionRange(FeatureEntry entry)117 	public static VersionRange createVersionRange(FeatureEntry entry) {
118 		String versionSpec = entry.getVersion();
119 		if (versionSpec == null)
120 			return VersionRange.emptyRange;
121 
122 		Version version = new Version(versionSpec);
123 		if (version.equals(Version.emptyVersion))
124 			return VersionRange.emptyRange;
125 
126 		String match = entry.getMatch();
127 		if (!entry.isRequires() || match == null) {
128 			return createVersionRange(versionSpec);
129 		}
130 
131 		if (match.equals("perfect")) //$NON-NLS-1$
132 			return new VersionRange(version, true, version, true);
133 		if (match.equals("equivalent")) { //$NON-NLS-1$
134 			Version upper = new Version(version.getMajor(), version.getMinor() + 1, 0);
135 			return new VersionRange(version, true, upper, false);
136 		}
137 		if (match.equals("compatible")) { //$NON-NLS-1$
138 			Version upper = new Version(version.getMajor() + 1, 0, 0);
139 			return new VersionRange(version, true, upper, false);
140 		}
141 		if (match.equals("greaterOrEqual")) //$NON-NLS-1$
142 			return new VersionRange(version, true, new VersionRange(null).getMaximum(), true);
143 
144 		return VersionRange.emptyRange;
145 	}
146 
incrementQualifier(String qualifier)147 	private static String incrementQualifier(String qualifier) {
148 		int idx = qualifier.length() - 1;
149 
150 		for (; idx >= 0; idx--) {
151 			//finding last non-'z' character
152 			if (qualifier.charAt(idx) != 'z')
153 				break;
154 		}
155 
156 		if (idx >= 0) {
157 			// qualifierCharValue returns 1 - 64, this is an implicit +1 over
158 			// the characters returned by base64Character
159 			int c = Utils.qualifierCharValue(qualifier.charAt(idx));
160 			String newQualifier = qualifier.substring(0, idx);
161 			newQualifier += Utils.base64Character(c);
162 			return newQualifier;
163 		}
164 
165 		return null;
166 	}
167 
168 	/**
169 	 * Convert a list of tokens into an array. The list separator has to be
170 	 * specified.
171 	 */
getArrayFromString(String list, String separator)172 	public static String[] getArrayFromString(String list, String separator) {
173 		if (list == null || list.trim().equals("")) //$NON-NLS-1$
174 			return new String[0];
175 		List<String> result = new ArrayList<>();
176 		for (StringTokenizer tokens = new StringTokenizer(list, separator); tokens.hasMoreTokens();) {
177 			String token = tokens.nextToken().trim();
178 			if (!token.equals("")) //$NON-NLS-1$
179 				result.add(token);
180 		}
181 		return result.toArray(new String[result.size()]);
182 	}
183 
184 	/**
185 	 * Convert a list of tokens into an array. The list separator has to be
186 	 * specified. The spcecificity of this method is that it returns an empty
187 	 * element when to same separators are following each others. For example
188 	 * the string a,,b returns the following array [a, ,b]
189 	 *
190 	 */
getArrayFromStringWithBlank(String list, String separator)191 	public static String[] getArrayFromStringWithBlank(String list, String separator) {
192 		if (list == null || list.trim().length() == 0)
193 			return new String[0];
194 		List<String> result = new ArrayList<>();
195 		boolean previousWasSeparator = true;
196 		for (StringTokenizer tokens = new StringTokenizer(list, separator, true); tokens.hasMoreTokens();) {
197 			String token = tokens.nextToken().trim();
198 			if (token.equals(separator)) {
199 				if (previousWasSeparator)
200 					result.add(""); //$NON-NLS-1$
201 				previousWasSeparator = true;
202 			} else {
203 				result.add(token);
204 				previousWasSeparator = false;
205 			}
206 		}
207 		return result.toArray(new String[result.size()]);
208 	}
209 
210 	/**
211 	 * Return a string array constructed from the given list of comma-separated
212 	 * tokens.
213 	 *
214 	 * @param list
215 	 *            the list to convert
216 	 * @return the array of strings
217 	 */
getArrayFromString(String list)218 	public static String[] getArrayFromString(String list) {
219 		return getArrayFromString(list, ","); //$NON-NLS-1$
220 	}
221 
222 	/**
223 	 * Converts an array of strings into an array of URLs.
224 	 *
225 	 * @param target
226 	 * @return URL[]
227 	 * @throws CoreException
228 	 */
asURL(String[] target)229 	public static URL[] asURL(String[] target) throws CoreException {
230 		if (target == null)
231 			return null;
232 		try {
233 			URL[] result = new URL[target.length];
234 			for (int i = 0; i < target.length; i++)
235 				result[i] = new URL(target[i]);
236 			return result;
237 		} catch (MalformedURLException e) {
238 			throw new CoreException(new Status(IStatus.ERROR, PI_PDEBUILD, EXCEPTION_MALFORMED_URL, e.getMessage(), e));
239 		}
240 	}
241 
asURL(Collection<File> target)242 	public static URL[] asURL(Collection<File> target) throws CoreException {
243 		if (target == null)
244 			return null;
245 		try {
246 			URL[] result = new URL[target.size()];
247 			int i = 0;
248 			for (Iterator<File> iter = target.iterator(); iter.hasNext();) {
249 				result[i++] = iter.next().toURL();
250 			}
251 			return result;
252 		} catch (MalformedURLException e) {
253 			throw new CoreException(new Status(IStatus.ERROR, PI_PDEBUILD, EXCEPTION_MALFORMED_URL, e.getMessage(), e));
254 		}
255 	}
256 
asFile(String[] target)257 	public static File[] asFile(String[] target) {
258 		if (target == null)
259 			return new File[0];
260 		File[] result = new File[target.length];
261 		for (int i = 0; i < result.length; i++) {
262 			result[i] = new File(target[i]);
263 		}
264 		return result;
265 	}
266 
asFile(URL[] target)267 	public static File[] asFile(URL[] target) {
268 		if (target == null)
269 			return new File[0];
270 		File[] result = new File[target.length];
271 		for (int i = 0; i < result.length; i++) {
272 			result[i] = new File(target[i].getFile());
273 		}
274 		return result;
275 	}
276 
asFile(Collection<?> collection)277 	public static File[] asFile(Collection<?> collection) {
278 		if (collection.size() == 0)
279 			return new File[0];
280 		Object first = collection.iterator().next();
281 		if (first instanceof String)
282 			return asFile(collection.toArray(new String[collection.size()]));
283 		else if (first instanceof URL)
284 			return asFile(collection.toArray(new URL[collection.size()]));
285 		else if (first instanceof File)
286 			return collection.toArray(new File[collection.size()]);
287 		throw new IllegalArgumentException();
288 	}
289 
290 	/**
291 	 * Return a string which is a concatination of each member of the given
292 	 * collection, separated by the given separator.
293 	 *
294 	 * @param collection
295 	 *            the collection to concatinate
296 	 * @param separator
297 	 *            the separator to use
298 	 * @return String
299 	 */
getStringFromCollection(Collection<?> collection, String separator)300 	public static String getStringFromCollection(Collection<?> collection, String separator) {
301 		StringBuffer result = new StringBuffer();
302 		boolean first = true;
303 		for (Iterator<?> i = collection.iterator(); i.hasNext();) {
304 			if (first)
305 				first = false;
306 			else
307 				result.append(separator);
308 			result.append(i.next());
309 		}
310 		return result.toString();
311 	}
312 
313 	/**
314 	 * Return a string which is a concatination of each member of the given
315 	 * array, separated by the given separator.
316 	 *
317 	 * @param values
318 	 *            the array to concatinate
319 	 * @param separator
320 	 *            the separator to use
321 	 * @return String
322 	 */
getStringFromArray(String[] values, String separator)323 	public static String getStringFromArray(String[] values, String separator) {
324 		StringBuffer result = new StringBuffer();
325 		for (int i = 0; i < values.length; i++) {
326 			if (values[i] != null) {
327 				if (i > 0)
328 					result.append(separator);
329 				result.append(values[i]);
330 			}
331 		}
332 		return result.toString();
333 	}
334 
335 	/**
336 	 * Return a path which is equivalent to the given location relative to the
337 	 * specified base path.
338 	 *
339 	 * @param location
340 	 *            the location to convert
341 	 * @param base
342 	 *            the base path
343 	 * @return IPath
344 	 */
makeRelative(IPath location, IPath base)345 	public static IPath makeRelative(IPath location, IPath base) {
346 		//can't make relative if the devices don't match
347 		if (location.getDevice() == null) {
348 			if (base.getDevice() != null)
349 				return location;
350 		} else {
351 			if (!location.getDevice().equalsIgnoreCase(base.getDevice()))
352 				return location;
353 		}
354 		int baseCount = base.segmentCount();
355 		int count = base.matchingFirstSegments(location);
356 		String temp = ""; //$NON-NLS-1$
357 		for (int j = 0; j < baseCount - count; j++)
358 			temp += "../"; //$NON-NLS-1$
359 		return new Path(temp).append(location.removeFirstSegments(count));
360 	}
361 
362 	/**
363 	 * Transfers all available bytes from the given input stream to the given
364 	 * output stream. Regardless of failure, this method closes both streams.
365 	 *
366 	 * @param source
367 	 * @param destination
368 	 * @throws IOException
369 	 */
transferStreams(InputStream source, OutputStream destination)370 	public static void transferStreams(InputStream source, OutputStream destination) throws IOException {
371 		source = new BufferedInputStream(source);
372 		destination = new BufferedOutputStream(destination);
373 		try {
374 			byte[] buffer = new byte[8192];
375 			while (true) {
376 				int bytesRead = -1;
377 				if ((bytesRead = source.read(buffer)) == -1)
378 					break;
379 				destination.write(buffer, 0, bytesRead);
380 			}
381 		} finally {
382 			try {
383 				source.close();
384 			} catch (IOException e) {
385 				// ignore
386 			}
387 			try {
388 				destination.close();
389 			} catch (IOException e) {
390 				// ignore
391 			}
392 		}
393 	}
394 
copyFile(String src, String dest)395 	static public void copyFile(String src, String dest) throws IOException {
396 		File source = new File(src);
397 		if (!source.exists())
398 			return;
399 		File destination = new File(dest);
400 		File destDir = destination.getParentFile();
401 		if ((!destDir.exists() && !destDir.mkdirs()) || destDir.isFile())
402 			return; //we will fail trying to create the file, TODO log warning/error
403 
404 		copy(source, destination);
405 	}
406 
copy(File source, File destination)407 	public static void copy(File source, File destination) throws IOException {
408 		org.eclipse.pde.internal.publishing.Utils.copy(source, destination);
409 	}
410 
writeBuffer(StringBuffer buffer, File outputFile)411 	public static void writeBuffer(StringBuffer buffer, File outputFile) throws IOException {
412 		FileOutputStream stream = null;
413 		try {
414 			outputFile.getParentFile().mkdirs();
415 			stream = new FileOutputStream(outputFile);
416 			stream.write(buffer.toString().getBytes());
417 		} finally {
418 			close(stream);
419 		}
420 	}
421 
writeProperties(Properties properites, File outputFile, String comment)422 	public static void writeProperties(Properties properites, File outputFile, String comment) throws IOException {
423 		outputFile.getParentFile().mkdirs();
424 		OutputStream buildFile = new BufferedOutputStream(new FileOutputStream(outputFile));
425 		try {
426 			properites.store(buildFile, comment);
427 		} finally {
428 			close(buildFile);
429 		}
430 	}
431 
getPluginEntry(BuildTimeFeature feature, String pluginId, boolean raw)432 	public static FeatureEntry[] getPluginEntry(BuildTimeFeature feature, String pluginId, boolean raw) {
433 		FeatureEntry[] plugins;
434 		if (raw)
435 			plugins = feature.getRawPluginEntries();
436 		else
437 			plugins = feature.getPluginEntries();
438 		List<FeatureEntry> foundEntries = new ArrayList<>(5);
439 
440 		for (int i = 0; i < plugins.length; i++) {
441 			if (plugins[i].getId().equals(pluginId))
442 				foundEntries.add(plugins[i]);
443 		}
444 		return foundEntries.toArray(new FeatureEntry[foundEntries.size()]);
445 
446 	}
447 
448 	// Return a collection of File, the result can be null
findFiles(File from, String foldername, final String filename)449 	public static Collection<File> findFiles(File from, String foldername, final String filename) {
450 		// if from is a file which name match filename, then simply return the
451 		// file
452 		File root = from;
453 		if (root.isFile() && root.getName().equals(filename)) {
454 			Collection<File> coll = new ArrayList<>(1);
455 			coll.add(root);
456 			return coll;
457 		}
458 
459 		Collection<File> collectedElements = new ArrayList<>(10);
460 
461 		File[] featureDirectoryContent = new File(from, foldername).listFiles();
462 		if (featureDirectoryContent == null)
463 			return null;
464 
465 		for (int i = 0; i < featureDirectoryContent.length; i++) {
466 			if (featureDirectoryContent[i].isDirectory()) {
467 				File[] featureFiles = featureDirectoryContent[i].listFiles(new FilenameFilter() {
468 					@Override
469 					public boolean accept(File dir, String name) {
470 						return name.equals(filename);
471 					}
472 				});
473 				if (featureFiles.length != 0)
474 					collectedElements.add(featureFiles[0]);
475 			}
476 		}
477 		return collectedElements;
478 	}
479 
isIn(FeatureEntry[] array, FeatureEntry element)480 	public static boolean isIn(FeatureEntry[] array, FeatureEntry element) {
481 		for (int i = 0; i < array.length; i++) {
482 			if (array[i].getId().equals(element.getId()) && array[i].getVersion().equals(element.getVersion()))
483 				return true;
484 		}
485 		return false;
486 	}
487 
copyFiles(String fromDir, String toDir)488 	public static Collection<String> copyFiles(String fromDir, String toDir) throws CoreException {
489 		File templateLocation = new File(fromDir);
490 		Collection<String> copiedFiles = new ArrayList<>();
491 		if (templateLocation.exists()) {
492 			File[] files = templateLocation.listFiles();
493 			if (files != null) {
494 				for (int i = 0; i < files.length; i++) {
495 					if (files[i].isDirectory()) {
496 						File subDir = new File(toDir, files[i].getName());
497 						if (!subDir.exists())
498 							subDir.mkdirs();
499 						Collection<String> subFiles = copyFiles(fromDir + '/' + files[i].getName(), toDir + '/' + files[i].getName());
500 						for (Iterator<String> iter = subFiles.iterator(); iter.hasNext();) {
501 							String sub = iter.next();
502 							copiedFiles.add(files[i].getName() + '/' + sub);
503 						}
504 						continue;
505 					}
506 
507 					FileInputStream inputStream = null;
508 					FileOutputStream outputStream = null;
509 
510 					try {
511 						inputStream = new FileInputStream(files[i]);
512 					} catch (FileNotFoundException e) {
513 						String message = NLS.bind(Messages.exception_missingFile, files[i].getAbsolutePath());
514 						throw new CoreException(new Status(IStatus.ERROR, PI_PDEBUILD, EXCEPTION_READING_FILE, message, e));
515 					}
516 
517 					String fileToCopy = toDir + '/' + files[i].getName();
518 					try {
519 						outputStream = new FileOutputStream(fileToCopy);
520 					} catch (FileNotFoundException e) {
521 						try {
522 							inputStream.close();
523 						} catch (IOException e1) {
524 							// Ignored
525 						}
526 						String message = NLS.bind(Messages.exception_missingFile, fileToCopy);
527 						throw new CoreException(new Status(IStatus.ERROR, PI_PDEBUILD, EXCEPTION_READING_FILE, message, e));
528 					}
529 
530 					try {
531 						Utils.transferStreams(inputStream, outputStream);
532 						copiedFiles.add(files[i].getName());
533 					} catch (IOException e) {
534 						String message = NLS.bind(Messages.exception_writingFile, fileToCopy);
535 						throw new CoreException(new Status(IStatus.ERROR, PI_PDEBUILD, EXCEPTION_WRITING_FILE, message, e));
536 					}
537 				}
538 			}
539 		}
540 		return copiedFiles;
541 	}
542 
extractPlugins(List<BundleDescription> initialList, Set<BundleDescription> toExtract)543 	public static List<BundleDescription> extractPlugins(List<BundleDescription> initialList, Set<BundleDescription> toExtract) {
544 		//TODO This algorithm needs to be  improved
545 		if (initialList.size() == toExtract.size())
546 			return initialList;
547 		List<BundleDescription> result = new ArrayList<>(toExtract.size());
548 		for (Iterator<BundleDescription> iter = initialList.iterator(); iter.hasNext();) {
549 			BundleDescription element = iter.next();
550 			if (toExtract.contains(element)) {
551 				result.add(element);
552 				if (result.size() == toExtract.size())
553 					break;
554 			}
555 		}
556 		return result;
557 	}
558 
isStringIn(String[] searched, String toSearch)559 	public static int isStringIn(String[] searched, String toSearch) {
560 		if (searched == null || toSearch == null)
561 			return -1;
562 		for (int i = 0; i < searched.length; i++) {
563 			if (toSearch.startsWith(searched[i]))
564 				return i;
565 		}
566 		return -1;
567 	}
568 
getOldExecutableRootOverrides()569 	static public Properties getOldExecutableRootOverrides() {
570 		Properties overrides = new Properties();
571 		overrides.put("root.win32.win32.x86_64", "file:bin/win32/win32/x86_64/launcher.exe"); //$NON-NLS-1$ //$NON-NLS-2$
572 		return overrides;
573 	}
574 
575 	/**
576 	 * Process root file properties.
577 	 * Resulting map is from config string to a property map.  The format of the property map is:
578 	 * 1) folder -> fileset to copy.  folder can be "" (the root) or an actual folder
579 	 * 2) ROOT_PERMISSIONS + rights -> fileset to set rights for
580 	 * 3) ROOT_LINK -> comma separated list: (target, link)*
581 	 *
582 	 * Properties that are common across all configs are available under the ROOT_COMMON key.
583 	 * They are also optionally merged into each individual config.
584 
585 	 * @param properties - build.properties for a feature
586 	 * @param mergeCommon - whether or not to merge the common properties into each config
587 	 * @return Map
588 	 */
processRootProperties(Properties properties, boolean mergeCommon)589 	static public Map<String, Map<String, String>> processRootProperties(Properties properties, boolean mergeCommon) {
590 		Map<String, Map<String, String>> map = new HashMap<>();
591 		Map<String, String> common = new HashMap<>();
592 		for (Enumeration<Object> keys = properties.keys(); keys.hasMoreElements();) {
593 			String entry = (String) keys.nextElement();
594 			String config = null;
595 			String entryKey = null;
596 
597 			if (entry.equals(ROOT) || entry.matches(REGEX_ROOT_CONFIG)) {
598 				config = entry.length() > 4 ? entry.substring(5) : ""; //$NON-NLS-1$
599 				entryKey = ""; //$NON-NLS-1$
600 			} else if (entry.matches(REGEX_ROOT_CONFIG_FOLDER)) {
601 				int folderIdx = entry.indexOf(FOLDER_INFIX);
602 				config = (folderIdx > 5) ? entry.substring(5, folderIdx) : ""; //$NON-NLS-1$
603 				entryKey = entry.substring(folderIdx + 8);
604 			} else if (entry.matches(REGEX_ROOT_CONFIG_PERMISSIONS)) {
605 				int permissionIdx = entry.indexOf(PERMISSIONS_INFIX);
606 				config = (permissionIdx > 5) ? entry.substring(5, permissionIdx) : ""; //$NON-NLS-1$
607 				entryKey = ROOT_PERMISSIONS + entry.substring(permissionIdx + 13);
608 			} else if (entry.matches(REGEX_ROOT_CONFIG_LINK)) {
609 				int linkIdx = entry.indexOf(LINK_SUFFIX);
610 				config = (linkIdx > 5) ? entry.substring(5, linkIdx) : ""; //$NON-NLS-1$
611 				entryKey = ROOT_LINK;
612 			}
613 
614 			if (config != null) {
615 				Map<String, String> submap = (config.length() == 0) ? common : map.get(config);
616 				if (submap == null) {
617 					submap = new HashMap<>();
618 					map.put(config, submap);
619 				}
620 				if (submap.containsKey(entryKey)) {
621 					String existing = submap.get(entryKey);
622 					submap.put(entryKey, existing + "," + properties.getProperty(entry)); //$NON-NLS-1$
623 				} else {
624 					submap.put(entryKey, (String) properties.get(entry));
625 				}
626 			}
627 		}
628 
629 		//merge the common properties into each of the configs
630 		if (common.size() > 0 && mergeCommon) {
631 			for (Iterator<String> iterator = map.keySet().iterator(); iterator.hasNext();) {
632 				String key = iterator.next();
633 				Map<String, String> submap = map.get(key);
634 				for (Iterator<String> commonKeys = common.keySet().iterator(); commonKeys.hasNext();) {
635 					String commonKey = commonKeys.next();
636 					if (submap.containsKey(commonKey)) {
637 						String existing = submap.get(commonKey);
638 						submap.put(commonKey, existing + "," + common.get(commonKey)); //$NON-NLS-1$
639 					} else {
640 						submap.put(commonKey, common.get(commonKey));
641 					}
642 				}
643 			}
644 		}
645 
646 		//and also add the common properties independently
647 		if (mergeCommon || common.size() > 0)
648 			map.put(ROOT_COMMON, common);
649 		return map;
650 	}
651 
generatePermissions(Properties featureProperties, Config aConfig, String targetRootProperty, AntScript script)652 	public static void generatePermissions(Properties featureProperties, Config aConfig, String targetRootProperty, AntScript script) {
653 		if (featureProperties == null)
654 			return;
655 		String configInfix = aConfig.toString("."); //$NON-NLS-1$
656 		String configPath = aConfig.toStringReplacingAny(".", ANY_STRING); //$NON-NLS-1$
657 		String prefixPermissions = ROOT_PREFIX + configInfix + '.' + PERMISSIONS + '.';
658 		String prefixLinks = ROOT_PREFIX + configInfix + '.' + LINK;
659 		String commonPermissions = ROOT_PREFIX + PERMISSIONS + '.';
660 		String commonLinks = ROOT_PREFIX + LINK;
661 		for (Iterator<Entry<Object, Object>> iter = featureProperties.entrySet().iterator(); iter.hasNext();) {
662 			Map.Entry<Object, Object> permission = iter.next();
663 			String instruction = (String) permission.getKey();
664 			String parameters = removeEndingSlashes((String) permission.getValue());
665 			if (instruction.startsWith(prefixPermissions)) {
666 				generateChmodInstruction(script, getPropertyFormat(targetRootProperty) + '/' + configPath + '/' + getPropertyFormat(PROPERTY_COLLECTING_FOLDER), instruction.substring(prefixPermissions.length()), parameters);
667 				continue;
668 			}
669 			if (instruction.startsWith(prefixLinks)) {
670 				generateLinkInstruction(script, getPropertyFormat(targetRootProperty) + '/' + configPath + '/' + getPropertyFormat(PROPERTY_COLLECTING_FOLDER), parameters);
671 				continue;
672 			}
673 			if (instruction.startsWith(commonPermissions)) {
674 				generateChmodInstruction(script, getPropertyFormat(targetRootProperty) + '/' + configPath + '/' + getPropertyFormat(PROPERTY_COLLECTING_FOLDER), instruction.substring(commonPermissions.length()), parameters);
675 				continue;
676 			}
677 			if (instruction.startsWith(commonLinks)) {
678 				generateLinkInstruction(script, getPropertyFormat(targetRootProperty) + '/' + configPath + '/' + getPropertyFormat(PROPERTY_COLLECTING_FOLDER), parameters);
679 				continue;
680 			}
681 		}
682 	}
683 
removeEndingSlashes(String value)684 	public static String removeEndingSlashes(String value) {
685 		String[] params = Utils.getArrayFromString(value, ","); //$NON-NLS-1$
686 		for (int i = 0; i < params.length; i++) {
687 			if (params[i].endsWith("/")) //$NON-NLS-1$
688 				params[i] = params[i].substring(0, params[i].length() - 1);
689 		}
690 		return Utils.getStringFromArray(params, ","); //$NON-NLS-1$
691 	}
692 
generateChmodInstruction(AntScript script, String dir, String rights, String files)693 	private static void generateChmodInstruction(AntScript script, String dir, String rights, String files) {
694 		if (rights.equals(EXECUTABLE)) {
695 			rights = "755"; //$NON-NLS-1$
696 		}
697 		script.printChmod(dir, rights, files);
698 	}
699 
generateLinkInstruction(AntScript script, String dir, String files)700 	private static void generateLinkInstruction(AntScript script, String dir, String files) {
701 		String[] links = Utils.getArrayFromString(files, ","); //$NON-NLS-1$
702 		List<String> arguments = new ArrayList<>(2);
703 		for (int i = 0; i < links.length; i += 2) {
704 			arguments.add("-sf"); //$NON-NLS-1$
705 			arguments.add(links[i]);
706 			arguments.add(links[i + 1]);
707 			script.printExecTask("ln", dir, arguments, "Linux,FreeBSD"); //$NON-NLS-1$ //$NON-NLS-2$
708 			arguments.clear();
709 		}
710 	}
711 
712 	/**
713 	 * Return a string with the given property name in the format:
714 	 * <pre>${propertyName}</pre>.
715 	 *
716 	 * @param propertyName the name of the property
717 	 * @return String
718 	 */
getPropertyFormat(String propertyName)719 	public static String getPropertyFormat(String propertyName) {
720 		StringBuffer sb = new StringBuffer();
721 		sb.append(PROPERTY_ASSIGNMENT_PREFIX);
722 		sb.append(propertyName);
723 		sb.append(PROPERTY_ASSIGNMENT_SUFFIX);
724 		return sb.toString();
725 	}
726 
getMacroFormat(String propertyName)727 	public static String getMacroFormat(String propertyName) {
728 		StringBuffer sb = new StringBuffer();
729 		sb.append(MACRO_ASSIGNMENT_PREFIX);
730 		sb.append(propertyName);
731 		sb.append(PROPERTY_ASSIGNMENT_SUFFIX);
732 		return sb.toString();
733 	}
734 
isBinary(BundleDescription bundle)735 	public static boolean isBinary(BundleDescription bundle) {
736 		Properties bundleProperties = ((Properties) bundle.getUserObject());
737 		if (bundleProperties == null || bundleProperties.get(IS_COMPILED) == null) {
738 			File props = new File(bundle.getLocation(), PROPERTIES_FILE);
739 			return !(props.exists() && props.isFile());
740 		}
741 		return (Boolean.FALSE == bundleProperties.get(IS_COMPILED));
742 	}
743 
isSourceBundle(BundleDescription bundle)744 	public static boolean isSourceBundle(BundleDescription bundle) {
745 		Properties bundleProperties = (Properties) bundle.getUserObject();
746 		return (bundleProperties != null && bundleProperties.containsKey(ECLIPSE_SOURCE_BUNDLE));
747 	}
748 
hasBundleShapeHeader(BundleDescription bundle)749 	public static boolean hasBundleShapeHeader(BundleDescription bundle) {
750 		Properties bundleProperties = (Properties) bundle.getUserObject();
751 		return (bundleProperties != null && bundleProperties.containsKey(ECLIPSE_BUNDLE_SHAPE));
752 	}
753 
getSourceBundleHeader(BundleDescription bundle)754 	public static String getSourceBundleHeader(BundleDescription bundle) {
755 		Properties bundleProperties = (Properties) bundle.getUserObject();
756 		if (bundleProperties == null || !bundleProperties.containsKey(ECLIPSE_SOURCE_BUNDLE))
757 			return ""; //$NON-NLS-1$
758 
759 		String header = bundleProperties.getProperty(ECLIPSE_SOURCE_BUNDLE);
760 		return header;
761 	}
762 
763 	/**
764 	 * Given a newly generated old-style source bundle for which there was a previously existing
765 	 * version in the target, return the location of the src folder in that earlier version
766 	 * @param bundle
767 	 * @return the old version's src folder, or null
768 	 */
getOldSourceLocation(BundleDescription bundle)769 	public static File getOldSourceLocation(BundleDescription bundle) {
770 		Properties props = (Properties) bundle.getUserObject();
771 		if (props == null || !props.containsKey(OLD_BUNDLE_LOCATION))
772 			return null;
773 
774 		String oldBundleLocation = props.getProperty(OLD_BUNDLE_LOCATION);
775 		if (oldBundleLocation != null) {
776 			File previousSrcRoot = new File(oldBundleLocation, "src"); //$NON-NLS-1$
777 			if (previousSrcRoot.exists())
778 				return previousSrcRoot;
779 		}
780 
781 		return null;
782 	}
783 
parseSourceBundleEntry(BundleDescription bundle)784 	public static Map<String, Map<String, String>> parseSourceBundleEntry(BundleDescription bundle) {
785 		String header = getSourceBundleHeader(bundle);
786 		if (header.length() == 0)
787 			return Collections.emptyMap();
788 
789 		HashMap<String, Map<String, String>> map = new HashMap<>();
790 		ManifestElement[] elements;
791 		try {
792 			elements = ManifestElement.parseHeader(ECLIPSE_SOURCE_BUNDLE, header);
793 		} catch (BundleException e1) {
794 			return Collections.emptyMap();
795 		}
796 		for (int i = 0; i < elements.length; i++) {
797 			String key = elements[i].getValue();
798 			HashMap<String, String> subMap = new HashMap<>(2);
799 			map.put(key, subMap);
800 			for (Enumeration<String> e = elements[i].getDirectiveKeys(); e != null && e.hasMoreElements();) {
801 				String directive = e.nextElement();
802 				subMap.put(directive, elements[i].getDirective(directive));
803 			}
804 			for (Enumeration<String> e = elements[i].getKeys(); e != null && e.hasMoreElements();) {
805 				String attribute = e.nextElement();
806 				subMap.put(attribute, elements[i].getAttribute(attribute));
807 			}
808 		}
809 		return map;
810 	}
811 
812 	public static final String EXTRA_ID = "id"; //$NON-NLS-1$
813 	public static final String EXTRA_VERSION = "version"; //$NON-NLS-1$
814 	public static final String EXTRA_UNPACK = "unpack"; //$NON-NLS-1$
815 	public static final String EXTRA_OPTIONAL = "optional"; //$NON-NLS-1$
816 	public static final String EXTRA_OS = "os"; //$NON-NLS-1$
817 	public static final String EXTRA_WS = "ws"; //$NON-NLS-1$
818 	public static final String EXTRA_ARCH = "arch"; //$NON-NLS-1$
819 
parseExtraBundlesString(String input, boolean onlyId)820 	public static Map<String, Object> parseExtraBundlesString(String input, boolean onlyId) {
821 		Map<String, Object> results = new HashMap<>();
822 		StringTokenizer tokenizer = null;
823 		if (onlyId)
824 			if (input.startsWith("plugin@")) //$NON-NLS-1$
825 				tokenizer = new StringTokenizer(input.substring(7), ";"); //$NON-NLS-1$
826 			else if (input.startsWith("exclude@") || input.startsWith("feature@")) //$NON-NLS-1$ //$NON-NLS-2$
827 				tokenizer = new StringTokenizer(input.substring(8), ";"); //$NON-NLS-1$
828 			else
829 				tokenizer = new StringTokenizer(input, ";"); //$NON-NLS-1$
830 		else
831 			tokenizer = new StringTokenizer(input, ";"); //$NON-NLS-1$
832 
833 		results.put(EXTRA_ID, tokenizer.nextToken());
834 		results.put(EXTRA_VERSION, Version.emptyVersion);
835 		results.put(EXTRA_UNPACK, Boolean.TRUE);
836 
837 		while (tokenizer.hasMoreTokens()) {
838 			String token = tokenizer.nextToken();
839 			String value = null;
840 			int idx = token.indexOf('=');
841 			if (idx > 0 && idx < token.length() - 1) {
842 				value = token.substring(idx + 1).trim();
843 				if (value.charAt(0) == '"' && value.charAt(value.length() - 1) == '"')
844 					value = value.substring(1, value.length() - 1);
845 			}
846 			if (token.startsWith(EXTRA_VERSION))
847 				results.put(EXTRA_VERSION, new Version(value));
848 			else if (token.startsWith(EXTRA_UNPACK))
849 				results.put(EXTRA_UNPACK, Boolean.valueOf(value));
850 			else if (token.startsWith(EXTRA_OS))
851 				results.put(EXTRA_OS, value);
852 			else if (token.startsWith(EXTRA_WS))
853 				results.put(EXTRA_WS, value);
854 			else if (token.startsWith(EXTRA_ARCH))
855 				results.put(EXTRA_ARCH, value);
856 			else if (token.startsWith(EXTRA_OPTIONAL))
857 				results.put(EXTRA_OPTIONAL, Boolean.valueOf(value));
858 		}
859 		return results;
860 	}
861 
matchVersions(String version1, String version2)862 	static public boolean matchVersions(String version1, String version2) {
863 		if (version1 == null)
864 			version1 = GENERIC_VERSION_NUMBER;
865 		if (version2 == null)
866 			version2 = GENERIC_VERSION_NUMBER;
867 
868 		if (version1.equals(version2) || version1.equals(GENERIC_VERSION_NUMBER) || version2.equals(GENERIC_VERSION_NUMBER))
869 			return true;
870 
871 		if (version1.endsWith(PROPERTY_QUALIFIER) || version2.endsWith(PROPERTY_QUALIFIER)) {
872 			int idx = version1.indexOf(PROPERTY_QUALIFIER);
873 			if (idx > -1)
874 				version1 = version1.substring(0, idx);
875 			idx = version2.indexOf(PROPERTY_QUALIFIER);
876 
877 			version1 = version1.substring(0, idx);
878 			return (version1.length() > version2.length()) ? version1.startsWith(version2) : version2.startsWith(version1);
879 		}
880 
881 		return false;
882 	}
883 
884 	/**
885 	 *
886 	 * @param buf
887 	 * @param start
888 	 * @param target
889 	 * @return int
890 	 */
scan(StringBuffer buf, int start, String target)891 	static public int scan(StringBuffer buf, int start, String target) {
892 		return scan(buf, start, new String[] {target});
893 	}
894 
895 	/**
896 	 *
897 	 * @param buf
898 	 * @param start
899 	 * @param targets
900 	 * @return int
901 	 */
scan(StringBuffer buf, int start, String[] targets)902 	static public int scan(StringBuffer buf, int start, String[] targets) {
903 		for (int i = start; i < buf.length(); i++) {
904 			for (int j = 0; j < targets.length; j++) {
905 				if (i < buf.length() - targets[j].length()) {
906 					String match = buf.substring(i, i + targets[j].length());
907 					if (targets[j].equals(match))
908 						return i;
909 				}
910 			}
911 		}
912 		return -1;
913 	}
914 
915 	/**
916 	 * Return a buffer containing the contents of the file at the specified location.
917 	 *
918 	 * @param target the file
919 	 * @return StringBuffer
920 	 * @throws IOException
921 	 */
readFile(File target)922 	static public StringBuffer readFile(File target) throws IOException {
923 		return readFile(new FileInputStream(target));
924 	}
925 
readFile(InputStream stream)926 	static public StringBuffer readFile(InputStream stream) throws IOException {
927 		StringBuffer result = new StringBuffer();
928 		char[] buf = new char[4096];
929 		int count;
930 		try (InputStreamReader reader = new InputStreamReader(new BufferedInputStream(stream))) {
931 			count = reader.read(buf, 0, buf.length);
932 			while (count != -1) {
933 				result.append(buf, 0, count);
934 				count = reader.read(buf, 0, buf.length);
935 			}
936 		}
937 		return result;
938 	}
939 
940 	/**
941 	 * Custom build scripts should have their version number matching the
942 	 * version number defined by the feature/plugin/fragment descriptor.
943 	 * This is a best effort job so do not worry if the expected tags were
944 	 * not found and just return without modifying the file.
945 	 *
946 	 * @param buildFile
947 	 * @param propertyName
948 	 * @param version
949 	 * @throws IOException
950 	 *
951 	 */
updateVersion(File buildFile, String propertyName, String version)952 	public static void updateVersion(File buildFile, String propertyName, String version) throws IOException {
953 		StringBuffer buffer = readFile(buildFile);
954 		int pos = scan(buffer, 0, propertyName);
955 		if (pos == -1)
956 			return;
957 		pos = scan(buffer, pos, "value"); //$NON-NLS-1$
958 		if (pos == -1)
959 			return;
960 		int begin = scan(buffer, pos, "\""); //$NON-NLS-1$
961 		if (begin == -1)
962 			return;
963 		begin++;
964 		int end = scan(buffer, begin, "\""); //$NON-NLS-1$
965 		if (end == -1)
966 			return;
967 		String currentVersion = buffer.substring(begin, end);
968 		String newVersion = version;
969 		if (currentVersion.equals(newVersion))
970 			return;
971 		buffer.replace(begin, end, newVersion);
972 		transferStreams(new ByteArrayInputStream(buffer.toString().getBytes()), new FileOutputStream(buildFile));
973 	}
974 
getArrayEnumerator(Object[] array)975 	public static Enumeration<Object> getArrayEnumerator(Object[] array) {
976 		return new ArrayEnumeration(array);
977 	}
978 
close(Object obj)979 	public static void close(Object obj) {
980 		if (obj == null)
981 			return;
982 		try {
983 			if (obj instanceof InputStream)
984 				((InputStream) obj).close();
985 			else if (obj instanceof ZipFile)
986 				((ZipFile) obj).close();
987 			else if (obj instanceof OutputStream)
988 				((OutputStream) obj).close();
989 		} catch (IOException e) {
990 			//boo
991 		}
992 	}
993 
guessUnpack(BundleDescription bundle, String[] classpath)994 	public static boolean guessUnpack(BundleDescription bundle, String[] classpath) {
995 		return org.eclipse.pde.internal.publishing.Utils.guessUnpack(bundle, classpath);
996 	}
997 
extract3Segments(String s)998 	public static Version extract3Segments(String s) {
999 		Version tmp = new Version(s);
1000 		return new Version(tmp.getMajor(), tmp.getMinor(), tmp.getMicro());
1001 	}
1002 
needsReplacement(String s)1003 	private static boolean needsReplacement(String s) {
1004 		if (s.equalsIgnoreCase(GENERIC_VERSION_NUMBER) || s.endsWith(PROPERTY_QUALIFIER))
1005 			return true;
1006 		return false;
1007 	}
1008 
getEntryVersionMappings(FeatureEntry[] entries, BuildTimeSite site)1009 	public static String getEntryVersionMappings(FeatureEntry[] entries, BuildTimeSite site) {
1010 		return getEntryVersionMappings(entries, site, null);
1011 	}
1012 
getEntryVersionMappings(FeatureEntry[] entries, BuildTimeSite site, AssemblyInformation assembly)1013 	public static String getEntryVersionMappings(FeatureEntry[] entries, BuildTimeSite site, AssemblyInformation assembly) {
1014 		if (entries == null || site == null)
1015 			return null;
1016 
1017 		StringBuffer result = new StringBuffer();
1018 		for (int i = 0; i < entries.length; i++) {
1019 			String versionRequested = entries[i].getVersion();
1020 			if (versionRequested == null)
1021 				versionRequested = GENERIC_VERSION_NUMBER;
1022 			String id = entries[i].getId();
1023 			String newVersion = null;
1024 
1025 			if (!needsReplacement(versionRequested))
1026 				continue;
1027 
1028 			try {
1029 				if (entries[i].isPlugin()) {
1030 					BundleDescription model = null;
1031 					if (assembly != null)
1032 						model = assembly.getPlugin(entries[i].getId(), versionRequested);
1033 					if (model == null)
1034 						model = site.getRegistry().getResolvedBundle(id, versionRequested);
1035 					if (model != null)
1036 						newVersion = model.getVersion().toString();
1037 				} else {
1038 					BuildTimeFeature feature = site.findFeature(id, versionRequested, false);
1039 					if (feature != null)
1040 						newVersion = feature.getVersion();
1041 				}
1042 			} catch (CoreException e) {
1043 				continue;
1044 			}
1045 			if (newVersion != null) {
1046 				result.append(id);
1047 				result.append(':');
1048 				result.append(extract3Segments(versionRequested));
1049 				result.append(',');
1050 				result.append(newVersion);
1051 				result.append(',');
1052 			}
1053 		}
1054 		return result.toString();
1055 	}
1056 }
1057