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 - Initial API and implementation
13  *******************************************************************************/
14 package org.eclipse.pde.internal.build;
15 
16 import java.io.*;
17 import java.net.URI;
18 import java.net.URISyntaxException;
19 import java.util.*;
20 import org.eclipse.core.runtime.*;
21 import org.eclipse.equinox.internal.p2.engine.SimpleProfileRegistry;
22 import org.eclipse.equinox.internal.p2.publisher.eclipse.ProductFile;
23 import org.eclipse.equinox.p2.core.IAgentLocation;
24 import org.eclipse.equinox.p2.core.IProvisioningAgent;
25 import org.eclipse.equinox.p2.engine.IProfile;
26 import org.eclipse.equinox.p2.engine.IProfileRegistry;
27 import org.eclipse.equinox.p2.publisher.eclipse.FeatureEntry;
28 import org.eclipse.osgi.service.resolver.BundleDescription;
29 import org.eclipse.osgi.service.resolver.State;
30 import org.eclipse.osgi.util.NLS;
31 import org.eclipse.pde.internal.build.ant.AntScript;
32 import org.eclipse.pde.internal.build.builder.BuildDirector;
33 import org.eclipse.pde.internal.build.site.*;
34 import org.eclipse.pde.internal.build.site.compatibility.SiteManager;
35 import org.osgi.framework.Version;
36 
37 /**
38  * Generic super-class for all script generator classes.
39  * It contains basic informations like the script, the configurations, and a location
40  */
41 public abstract class AbstractScriptGenerator implements IXMLConstants, IPDEBuildConstants, IBuildPropertiesConstants {
42 	private static final FilenameFilter METADATA_REPO_FILTER = (dir, name) -> name.startsWith("content.") || name.startsWith("compositeContent.") || //$NON-NLS-1$ //$NON-NLS-2$
43 			name.endsWith(".profile") || name.endsWith(".profile.gz"); //$NON-NLS-1$//$NON-NLS-2$
44 
45 	private static final FilenameFilter ARTIFACT_REPO_FILTER = (dir, name) -> name.startsWith("artifacts.") || name.startsWith("compositeArtifacts."); //$NON-NLS-1$ //$NON-NLS-2$
46 
47 	private static Properties immutableAntProperties = null;
48 	protected static boolean embeddedSource = false;
49 	protected static boolean forceUpdateJarFormat = false;
50 	private static List<Config> configInfos;
51 	protected static String workingDirectory;
52 	protected URI[] contextMetadata = null;
53 	protected URI[] contextArtifacts = null;
54 	protected AntScript script;
55 	protected Properties platformProperties;
56 	protected String productQualifier;
57 
58 	private static PDEUIStateWrapper pdeUIState;
59 
60 	/** Location of the plug-ins and fragments. */
61 	protected String[] sitePaths;
62 	protected String[] pluginPath;
63 	protected BuildTimeSiteFactory siteFactory;
64 
65 	/**
66 	 * Indicate whether the content of the pdestate should only contain the plugins that are in the transitive closure of the features being built
67 	 */
68 	protected boolean filterState = false;
69 	protected List<String> featuresForFilterRoots = new ArrayList<>();
70 	protected List<String> pluginsForFilterRoots = new ArrayList<>();
71 	protected boolean filterP2Base = false;
72 
73 	protected boolean reportResolutionErrors;
74 
75 	static {
76 		// By default, a generic configuration is set
77 		configInfos = new ArrayList<>(1);
Config.genericConfig()78 		configInfos.add(Config.genericConfig());
79 	}
80 
getConfigInfos()81 	public static List<Config> getConfigInfos() {
82 		return configInfos;
83 	}
84 
85 	/**
86 	 * Starting point for script generation. See subclass implementations for
87 	 * individual comments.
88 	 *
89 	 * @throws CoreException
90 	 */
generate()91 	public abstract void generate() throws CoreException;
92 
setStaticAntProperties(Properties properties)93 	protected static void setStaticAntProperties(Properties properties) {
94 		if (properties == null) {
95 			immutableAntProperties = new Properties();
96 			BuildDirector.p2Gathering = false;
97 		} else
98 			immutableAntProperties = properties;
99 		if (getImmutableAntProperty(IBuildPropertiesConstants.PROPERTY_PACKAGER_MODE) == null) {
100 			immutableAntProperties.setProperty(IBuildPropertiesConstants.PROPERTY_PACKAGER_MODE, "false"); //$NON-NLS-1$
101 		}
102 		//When we are generating build scripts, the normalization needs to be set, and when doing packaging the default is to set normalization to true for backward compatibility
103 		if (!getPropertyAsBoolean(IBuildPropertiesConstants.PROPERTY_PACKAGER_MODE) || getImmutableAntProperty(IBuildPropertiesConstants.PROPERTY_PACKAGER_AS_NORMALIZER) == null) {
104 			immutableAntProperties.setProperty(IBuildPropertiesConstants.PROPERTY_PACKAGER_AS_NORMALIZER, "true"); //$NON-NLS-1$
105 		}
106 
107 		if (getPropertyAsBoolean(IBuildPropertiesConstants.PROPERTY_P2_GATHERING))
108 			BuildDirector.p2Gathering = true;
109 	}
110 
getImmutableAntProperty(String key)111 	public static String getImmutableAntProperty(String key) {
112 		return getImmutableAntProperty(key, null);
113 	}
114 
getPropertyAsBoolean(String key)115 	public static boolean getPropertyAsBoolean(String key) {
116 		String booleanValue = getImmutableAntProperty(key, null);
117 		if ("true".equalsIgnoreCase(booleanValue)) //$NON-NLS-1$
118 			return true;
119 		return false;
120 	}
121 
getImmutableAntProperty(String key, String defaultValue)122 	public static String getImmutableAntProperty(String key, String defaultValue) {
123 		if (immutableAntProperties == null || !immutableAntProperties.containsKey(key))
124 			return defaultValue;
125 		Object obj = immutableAntProperties.get(key);
126 		return (obj instanceof String) ? (String) obj : null;
127 	}
128 
setConfigInfo(String spec)129 	public static void setConfigInfo(String spec) throws CoreException {
130 		String[] configs = Utils.getArrayFromStringWithBlank(spec, "&"); //$NON-NLS-1$
131 		List<Config> infos = new ArrayList<>(configs.length);
132 		String[] os = new String[configs.length];
133 		String[] ws = new String[configs.length];
134 		String[] archs = new String[configs.length];
135 		for (int i = 0; i < configs.length; i++) {
136 			String[] configElements = Utils.getArrayFromStringWithBlank(configs[i], ","); //$NON-NLS-1$
137 			if (configElements.length != 3) {
138 				IStatus error = new Status(IStatus.ERROR, IPDEBuildConstants.PI_PDEBUILD, IPDEBuildConstants.EXCEPTION_CONFIG_FORMAT, NLS.bind(Messages.error_configWrongFormat, configs[i]), null);
139 				throw new CoreException(error);
140 			}
141 			Config aConfig = new Config(configs[i]);
142 			if (aConfig.equals(Config.genericConfig())) {
143 				infos.add(Config.genericConfig());
144 			} else {
145 				infos.add(aConfig);
146 			}
147 
148 			// create a list of all ws, os and arch to feed the SiteManager
149 			os[i] = aConfig.getOs();
150 			ws[i] = aConfig.getWs();
151 			archs[i] = aConfig.getArch();
152 		}
153 		SiteManager.setOS(Utils.getStringFromArray(os, ",")); //$NON-NLS-1$
154 		SiteManager.setWS(Utils.getStringFromArray(ws, ",")); //$NON-NLS-1$
155 		SiteManager.setArch(Utils.getStringFromArray(archs, ",")); //$NON-NLS-1$
156 		configInfos = infos;
157 	}
158 
setWorkingDirectory(String location)159 	public void setWorkingDirectory(String location) {
160 		workingDirectory = location;
161 	}
162 
163 	/**
164 	 * Return the file system location for the given plug-in model object.
165 	 *
166 	 * @param model the plug-in
167 	 * @return String
168 	 */
getLocation(BundleDescription model)169 	public String getLocation(BundleDescription model) {
170 		return model.getLocation();
171 	}
172 
173 	static public class MissingProperties extends Properties {
174 		private static final long serialVersionUID = 3546924667060303927L;
175 		private static MissingProperties singleton;
176 
MissingProperties()177 		private MissingProperties() {
178 			//nothing to do;
179 		}
180 
181 		@Override
setProperty(String key, String value)182 		public synchronized Object setProperty(String key, String value) {
183 			throw new UnsupportedOperationException();
184 		}
185 
186 		@Override
put(Object key, Object value)187 		public synchronized Object put(Object key, Object value) {
188 			throw new UnsupportedOperationException();
189 		}
190 
getInstance()191 		public static MissingProperties getInstance() {
192 			if (singleton == null)
193 				singleton = new MissingProperties();
194 			return singleton;
195 		}
196 	}
197 
readProperties(String location, String fileName, int errorLevel)198 	public static Properties readProperties(String location, String fileName, int errorLevel) throws CoreException {
199 		if (location == null) {
200 			if (errorLevel != IStatus.INFO && errorLevel != IStatus.OK) {
201 				String message = NLS.bind(Messages.exception_missingFile, fileName);
202 				BundleHelper.getDefault().getLog().log(new Status(errorLevel, PI_PDEBUILD, EXCEPTION_READING_FILE, message, null));
203 			}
204 			return MissingProperties.getInstance();
205 		}
206 
207 		Properties result = new Properties();
208 		File file = new File(location, fileName);
209 		try (InputStream input = new BufferedInputStream(new FileInputStream(file))) {
210 			result.load(input);
211 		} catch (FileNotFoundException e) {
212 			if (errorLevel != IStatus.INFO && errorLevel != IStatus.OK) {
213 				String message = NLS.bind(Messages.exception_missingFile, file);
214 				BundleHelper.getDefault().getLog().log(new Status(errorLevel, PI_PDEBUILD, EXCEPTION_READING_FILE, message, null));
215 			}
216 			result = MissingProperties.getInstance();
217 		} catch (IOException e) {
218 			String message = NLS.bind(Messages.exception_readingFile, file);
219 			throw new CoreException(new Status(IStatus.ERROR, PI_PDEBUILD, EXCEPTION_READING_FILE, message, e));
220 		}
221 		return result;
222 	}
223 
openScript(String scriptLocation, String scriptName)224 	public void openScript(String scriptLocation, String scriptName) throws CoreException {
225 		if (script != null)
226 			return;
227 		script = newAntScript(scriptLocation, scriptName);
228 	}
229 
newAntScript(String scriptLocation, String scriptName)230 	protected static AntScript newAntScript(String scriptLocation, String scriptName) throws CoreException {
231 		AntScript result = null;
232 		try {
233 			OutputStream scriptStream = new BufferedOutputStream(new FileOutputStream(scriptLocation + '/' + scriptName));
234 			try {
235 				result = new AntScript(scriptStream);
236 			} catch (IOException e) {
237 				try {
238 					scriptStream.close();
239 					String message = NLS.bind(Messages.exception_writingFile, scriptLocation + '/' + scriptName);
240 					throw new CoreException(new Status(IStatus.ERROR, PI_PDEBUILD, EXCEPTION_WRITING_FILE, message, e));
241 				} catch (IOException e1) {
242 					// Ignored
243 				}
244 			}
245 		} catch (FileNotFoundException e) {
246 			String message = NLS.bind(Messages.exception_writingFile, scriptLocation + '/' + scriptName);
247 			throw new CoreException(new Status(IStatus.ERROR, PI_PDEBUILD, EXCEPTION_WRITING_FILE, message, e));
248 		}
249 		return result;
250 	}
251 
closeScript()252 	public void closeScript() {
253 		script.close();
254 	}
255 
getWorkingDirectory()256 	public static String getWorkingDirectory() {
257 		return workingDirectory;
258 	}
259 
getDefaultOutputFormat()260 	public static String getDefaultOutputFormat() {
261 		return "zip"; //$NON-NLS-1$
262 	}
263 
getDefaultEmbeddedSource()264 	public static boolean getDefaultEmbeddedSource() {
265 		return false;
266 	}
267 
setEmbeddedSource(boolean embed)268 	public static void setEmbeddedSource(boolean embed) {
269 		embeddedSource = embed;
270 	}
271 
getForceUpdateJarFormat()272 	public static boolean getForceUpdateJarFormat() {
273 		return false;
274 	}
275 
setForceUpdateJar(boolean force)276 	public static void setForceUpdateJar(boolean force) {
277 		forceUpdateJarFormat = force;
278 	}
279 
getDefaultConfigInfos()280 	public static String getDefaultConfigInfos() {
281 		return "*, *, *"; //$NON-NLS-1$
282 	}
283 
loadP2Class()284 	protected static boolean loadP2Class() {
285 		try {
286 			BundleHelper.getDefault().getClass().getClassLoader().loadClass("org.eclipse.equinox.p2.publisher.Publisher"); //$NON-NLS-1$
287 			return true;
288 		} catch (Throwable e) {
289 			return false;
290 		}
291 	}
292 
293 	/**
294 	 * Return a build time site referencing things to be built.
295 	 * @param refresh : indicate if a refresh must be performed. Although this flag is set to true, a new site is not rebuild if the urls of the site did not changed
296 	 * @return BuildTimeSite
297 	 * @throws CoreException
298 	 */
getSite(boolean refresh)299 	public BuildTimeSite getSite(boolean refresh) throws CoreException {
300 		if (siteFactory != null && refresh == false)
301 			return siteFactory.createSite();
302 
303 		//If there is an exception from createSite(), we will discard the factory
304 		BuildTimeSiteFactory factory = new BuildTimeSiteFactory();
305 		factory.setFilterState(filterState);
306 		factory.setFilterRoots(featuresForFilterRoots, pluginsForFilterRoots);
307 		factory.setReportResolutionErrors(reportResolutionErrors);
308 		factory.setFilterP2Base(filterP2Base);
309 		factory.setSitePaths(getPaths());
310 		factory.setEESources(getEESources());
311 		factory.setInitialState(pdeUIState);
312 
313 		BuildTimeSite result = factory.createSite();
314 		siteFactory = factory;
315 
316 		if (platformProperties != null)
317 			result.setPlatformPropeties(platformProperties);
318 
319 		File baseProfile = result.getSiteContentProvider().getBaseProfile();
320 		if (baseProfile != null) {
321 			List<URI> repos = getAssociatedRepositories(baseProfile);
322 			if (repos.size() > 0) {
323 				addContextRepos(repos.toArray(new URI[repos.size()]));
324 			}
325 		}
326 
327 		return result;
328 	}
329 
330 	/**
331 	 * Method getPaths.  These are the paths used for the BuildTimeSite
332 	 * @return URL[]
333 	 */
getPaths()334 	private String[] getPaths() {
335 		if (sitePaths == null) {
336 			if (pluginPath != null) {
337 				sitePaths = new String[pluginPath.length + 1];
338 				System.arraycopy(pluginPath, 0, sitePaths, 0, pluginPath.length);
339 				sitePaths[sitePaths.length - 1] = workingDirectory;
340 			} else {
341 				sitePaths = new String[] {workingDirectory};
342 			}
343 		}
344 
345 		return sitePaths;
346 	}
347 
getEESources()348 	protected String[] getEESources() {
349 		return null;
350 	}
351 
setBuildSiteFactory(BuildTimeSiteFactory siteFactory)352 	public void setBuildSiteFactory(BuildTimeSiteFactory siteFactory) {
353 		this.siteFactory = siteFactory;
354 	}
355 
356 	/**
357 	 * Return the path of the plugins		//TODO Do we need to add support for features, or do we simply consider one list of URL? It is just a matter of style/
358 	 * @return URL[]
359 	 */
getPluginPath()360 	public String[] getPluginPath() {
361 		return pluginPath;
362 	}
363 
364 	/**
365 	 * Sets the pluginPath.
366 	 *
367 	 * @param path
368 	 */
setPluginPath(String[] path)369 	public void setPluginPath(String[] path) {
370 		pluginPath = path;
371 	}
372 
setPDEState(State state)373 	public void setPDEState(State state) {
374 		ensurePDEUIStateNotNull();
375 		pdeUIState.setState(state);
376 	}
377 
setStateExtraData(HashMap<Long, String[]> classpath, Map<Long, String> patchData)378 	public void setStateExtraData(HashMap<Long, String[]> classpath, Map<Long, String> patchData) {
379 		setStateExtraData(classpath, patchData, null);
380 	}
381 
setStateExtraData(HashMap<Long, String[]> classpath, Map<Long, String> patchData, Map<String, Map<String, Set<IPath>>> outputFolders)382 	public void setStateExtraData(HashMap<Long, String[]> classpath, Map<Long, String> patchData, Map<String, Map<String, Set<IPath>>> outputFolders) {
383 		ensurePDEUIStateNotNull();
384 		pdeUIState.setExtraData(classpath, patchData, outputFolders);
385 	}
386 
setNextId(long nextId)387 	public void setNextId(long nextId) {
388 		ensurePDEUIStateNotNull();
389 		pdeUIState.setNextId(nextId);
390 	}
391 
flushState()392 	protected void flushState() {
393 		pdeUIState = null;
394 	}
395 
ensurePDEUIStateNotNull()396 	private void ensurePDEUIStateNotNull() {
397 		if (pdeUIState == null)
398 			pdeUIState = new PDEUIStateWrapper();
399 	}
400 
havePDEUIState()401 	protected boolean havePDEUIState() {
402 		return pdeUIState != null;
403 	}
404 
loadProduct(String product)405 	public ProductFile loadProduct(String product) throws CoreException {
406 		//the ProductFile uses the OS to determine which icons to return, we don't care so can use null
407 		//this is better since this generator may be used for multiple OS's
408 		return loadProduct(product, null);
409 	}
410 
loadProduct(String product, String os)411 	public ProductFile loadProduct(String product, String os) throws CoreException {
412 		if (product == null || product.startsWith("${") || product.length() == 0) { //$NON-NLS-1$
413 			return null;
414 		}
415 		String productPath = findFile(product, false);
416 		File f = null;
417 		if (productPath != null) {
418 			f = new File(productPath);
419 		} else {
420 			// couldn't find productFile, try it as a path directly
421 			f = new File(product);
422 			if (!f.exists() || !f.isFile()) {
423 				// doesn't exist, try it as a path relative to the working directory
424 				f = new File(getWorkingDirectory(), product);
425 				if (!f.exists() || !f.isFile()) {
426 					f = new File(getWorkingDirectory() + "/" + DEFAULT_PLUGIN_LOCATION, product); //$NON-NLS-1$
427 					if (!f.exists() || !f.isFile()) {
428 						f = new File(getWorkingDirectory() + '/' + DEFAULT_FEATURE_LOCATION, product);
429 					}
430 				}
431 			}
432 		}
433 		return new ProductFile(f.getAbsolutePath(), os);
434 	}
435 
436 	//Find a file in a bundle or a feature.
437 	//location is assumed to be structured like : /<featureId | pluginId>/path.to.the.file
findFile(String location, boolean makeRelative)438 	protected String findFile(String location, boolean makeRelative) {
439 		if (location == null || location.length() == 0)
440 			return null;
441 
442 		//shortcut building the site if we don't need to
443 		if (new File(location).exists())
444 			return location;
445 
446 		PDEState state;
447 		try {
448 			state = getSite(false).getRegistry();
449 		} catch (CoreException e) {
450 			BundleHelper.getDefault().getLog().log(e.getStatus());
451 			return null;
452 		}
453 		Path path = new Path(location);
454 		String id = path.segment(0);
455 		BundleDescription[] matches = state.getState().getBundles(id);
456 		if (matches != null && matches.length != 0) {
457 			BundleDescription bundle = matches[0];
458 			if (bundle != null) {
459 				String result = checkFile(new Path(bundle.getLocation()), path, makeRelative);
460 				if (result != null)
461 					return result;
462 			}
463 		}
464 		// Couldn't find the file in any of the plugins, try in a feature.
465 		BuildTimeFeature feature = null;
466 		try {
467 			feature = getSite(false).findFeature(id, null, false);
468 		} catch (CoreException e) {
469 			BundleHelper.getDefault().getLog().log(e.getStatus());
470 		}
471 		if (feature == null)
472 			return null;
473 
474 		String featureRoot = feature.getRootLocation();
475 		if (featureRoot != null)
476 			return checkFile(new Path(featureRoot), path, makeRelative);
477 		return null;
478 	}
479 
findConfigFile(ProductFile productFile, String os)480 	protected String findConfigFile(ProductFile productFile, String os) {
481 		String path = productFile.getConfigIniPath(os);
482 		if (path == null)
483 			return null;
484 
485 		String result = findFile(path, false);
486 		if (result != null)
487 			return result;
488 
489 		// couldn't find productFile, try it as a path directly
490 		File f = new File(path);
491 		if (f.exists() && f.isFile())
492 			return f.getAbsolutePath();
493 
494 		// relative to the working directory
495 		f = new File(getWorkingDirectory(), path);
496 		if (f.exists() && f.isFile())
497 			return f.getAbsolutePath();
498 
499 		// relative to the working directory/plugins
500 		f = new File(getWorkingDirectory() + "/" + DEFAULT_PLUGIN_LOCATION, path); //$NON-NLS-1$
501 		if (f.exists() && f.isFile())
502 			return f.getAbsolutePath();
503 
504 		//relative to .product file
505 		f = new File(productFile.getLocation().getParent(), path);
506 		if (f.exists() && f.isFile())
507 			return f.getAbsolutePath();
508 
509 		return null;
510 	}
511 
checkFile(IPath base, Path target, boolean makeRelative)512 	private String checkFile(IPath base, Path target, boolean makeRelative) {
513 		IPath path = base.append(target.removeFirstSegments(1));
514 		String result = path.toOSString();
515 		if (!new File(result).exists())
516 			return null;
517 		if (makeRelative)
518 			return Utils.makeRelative(path, new Path(workingDirectory)).toOSString();
519 		return result;
520 	}
521 
setFilterState(boolean filter)522 	public void setFilterState(boolean filter) {
523 		filterState = filter;
524 	}
525 
setFilterP2Base(boolean filter)526 	public void setFilterP2Base(boolean filter) {
527 		filterP2Base = filter;
528 	}
529 
getDownloadCacheLocation(IProvisioningAgent agent)530 	static private URI getDownloadCacheLocation(IProvisioningAgent agent) {
531 		IAgentLocation location = (IAgentLocation) agent.getService(IAgentLocation.SERVICE_NAME);
532 		if (location == null)
533 			return null;
534 		return URIUtil.append(location.getDataArea("org.eclipse.equinox.p2.core"), "cache/"); //$NON-NLS-1$ //$NON-NLS-2$
535 	}
536 
setContextArtifacts(URI[] uris)537 	protected void setContextArtifacts(URI[] uris) {
538 		contextArtifacts = uris;
539 	}
540 
setContextMetadata(URI[] uris)541 	protected void setContextMetadata(URI[] uris) {
542 		contextMetadata = uris;
543 	}
544 
setContextMetadataRepositories(URI[] uris)545 	public void setContextMetadataRepositories(URI[] uris) {
546 		Set<URI> uriSet = new HashSet<>();
547 		uriSet.addAll(Arrays.asList(uris));
548 
549 		for (int i = 0; i < uris.length; i++) {
550 			//try and find additional repos associated with a profile
551 			File uriFile = URIUtil.toFile(uris[i]);
552 			uriSet.addAll(getAssociatedRepositories(uriFile));
553 		}
554 
555 		addContextRepos(uriSet.toArray(new URI[uriSet.size()]));
556 	}
557 
addContextRepos(URI[] repos)558 	protected void addContextRepos(URI[] repos) {
559 		List<URI> metadata = filterRepos(repos, METADATA_REPO_FILTER);
560 		List<URI> artifacts = filterRepos(repos, ARTIFACT_REPO_FILTER);
561 
562 		if (contextMetadata != null) {
563 			Set<URI> uriSet = new HashSet<>();
564 			uriSet.addAll(Arrays.asList(contextMetadata));
565 			uriSet.addAll(metadata);
566 			contextMetadata = uriSet.toArray(new URI[uriSet.size()]);
567 		} else {
568 			contextMetadata = metadata.toArray(new URI[metadata.size()]);
569 		}
570 
571 		if (contextArtifacts != null) {
572 			Set<URI> uriSet = new HashSet<>();
573 			uriSet.addAll(Arrays.asList(contextArtifacts));
574 			uriSet.addAll(artifacts);
575 			contextArtifacts = uriSet.toArray(new URI[uriSet.size()]);
576 		} else {
577 			contextArtifacts = artifacts.toArray(new URI[artifacts.size()]);
578 		}
579 	}
580 
581 	//return only the metadata repos, and also the ones we aren't sure about
filterRepos(URI[] contexts, FilenameFilter repoFilter)582 	private List<URI> filterRepos(URI[] contexts, FilenameFilter repoFilter) {
583 		if (contexts == null)
584 			return null;
585 		ArrayList<URI> result = new ArrayList<>();
586 		for (int i = 0; i < contexts.length; i++) {
587 			File repo = URIUtil.toFile(contexts[i]);
588 			if (repo == null) {
589 				//remote, not sure, just use it
590 				result.add(contexts[i]);
591 			} else {
592 				String[] list = repo.list(repoFilter);
593 				if (list != null && list.length > 0)
594 					result.add(contexts[i]);
595 			}
596 		}
597 		return result;
598 	}
599 
getAssociatedRepositories(File profileFile)600 	private List<URI> getAssociatedRepositories(File profileFile) {
601 		if (profileFile == null || !profileFile.exists() || !profileFile.getName().endsWith(".profile")) //$NON-NLS-1$
602 			return Collections.emptyList();
603 
604 		ArrayList<URI> result = new ArrayList<>();
605 		URI profileURI = profileFile.toURI();
606 		result.add(profileURI);
607 
608 		Map<String, Object> profileInfo = extractProfileInformation(profileFile);
609 		if (profileInfo == null)
610 			return result;
611 
612 		File areaFile = new File((String) profileInfo.get(PROFILE_DATA_AREA));
613 		if (areaFile.exists()) {
614 			IProvisioningAgent agent = BundleHelper.getDefault().getProvisioningAgent(areaFile.toURI());
615 			if (agent != null) {
616 				IProfileRegistry registry = new SimpleProfileRegistry(agent, (File) profileInfo.get(PROFILE_REGISTRY), null, false);
617 				try {
618 					long timestamp = ((Long) profileInfo.get(PROFILE_TIMESTAMP)).longValue();
619 					String profileId = (String) profileInfo.get(PROFILE_ID);
620 					if (timestamp == -1L) {
621 						long[] timestamps = registry.listProfileTimestamps(profileId);
622 						if (timestamps.length > 0)
623 							timestamp = timestamps[timestamps.length - 1];
624 					}
625 
626 					//specifying the timestamp avoids attempting to lock the profile registry
627 					//which could be a problem if it is read only.
628 					if (timestamp > 0) {
629 						IProfile profile = registry.getProfile(profileId, timestamp);
630 						if (profile != null) {
631 							String cache = profile.getProperty(IProfile.PROP_CACHE);
632 							if (cache != null) {
633 								File cacheFolder = new File(cache);
634 								if (cacheFolder.exists()) {
635 									result.add(cacheFolder.toURI());
636 								} else {
637 									//if cache does not exist, this could be a roaming profile that has not
638 									//been run yet, lets guess and use the parent of the p2 data area
639 									result.add(areaFile.getParentFile().toURI());
640 								}
641 							}
642 							String sharedCache = profile.getProperty(IProfile.PROP_SHARED_CACHE);
643 							if (sharedCache != null)
644 								result.add(new File(cache).toURI());
645 							String dropinRepositories = profile.getProperty("org.eclipse.equinox.p2.cache.extensions"); //$NON-NLS-1$
646 							if (dropinRepositories != null) {
647 								// #filterRepos will remove any dropin folders that require synchronization
648 								StringTokenizer tokenizer = new StringTokenizer(dropinRepositories, "|"); //$NON-NLS-1$
649 								while (tokenizer.hasMoreTokens()) {
650 									try {
651 										result.add(new URI(tokenizer.nextToken()));
652 									} catch (URISyntaxException e) {
653 										//skip
654 									}
655 								}
656 							}
657 						}
658 					}
659 				} catch (IllegalStateException e) {
660 					//unable to read profile, may be read only
661 					result.add(areaFile.getParentFile().toURI());
662 				}
663 
664 				//download cache
665 				URI download = getDownloadCacheLocation(agent);
666 				if (URIUtil.toFile(download).exists())
667 					result.add(download);
668 			}
669 		}
670 		return result;
671 	}
672 
673 	private static String PROFILE_TIMESTAMP = "timestamp"; //$NON-NLS-1$
674 	private static String PROFILE_ID = "profileId"; //$NON-NLS-1$
675 	private static String PROFILE_DATA_AREA = "dataArea"; //$NON-NLS-1$
676 	private static String PROFILE_REGISTRY = "registry"; //$NON-NLS-1$
677 
extractProfileInformation(File target)678 	private static Map<String, Object> extractProfileInformation(File target) {
679 		if (target == null || !target.exists())
680 			return null;
681 
682 		IPath path = new Path(target.getAbsolutePath());
683 		if (!path.lastSegment().endsWith(PROFILE) && !path.lastSegment().endsWith(PROFILE_GZ))
684 			return null;
685 
686 		//expect at least "p2/org.eclipse.equinox.p2.engine/profileRegistry/Profile.profile"
687 		if (path.segmentCount() < 4)
688 			return null;
689 
690 		Map<String, Object> results = new HashMap<>();
691 		results.put(PROFILE_TIMESTAMP, Long.valueOf(-1));
692 
693 		String profileId = null;
694 		if (target.isFile()) {
695 			//p2/org.eclipse.equinox.p2.engine/profileRegistry/Profile.profile/123456.profile.gz
696 			if (path.segmentCount() < 5)
697 				return null;
698 
699 			String timestamp = path.lastSegment();
700 			int idx = timestamp.indexOf('.');
701 			if (idx > 0) {
702 				timestamp = timestamp.substring(0, idx);
703 				try {
704 					results.put(PROFILE_TIMESTAMP, Long.valueOf(timestamp));
705 				} catch (NumberFormatException e) {
706 					//not a timestamp?
707 				}
708 			}
709 
710 			path = path.removeLastSegments(1);
711 			profileId = path.removeFileExtension().lastSegment();
712 		} else {
713 			//target is the profile folder
714 			profileId = path.removeFileExtension().lastSegment();
715 		}
716 
717 		profileId = SimpleProfileRegistry.unescape(profileId);
718 		results.put(PROFILE_ID, profileId);
719 
720 		//remove Profile.profile to get the registry folder
721 		path = path.removeLastSegments(1);
722 		results.put(PROFILE_REGISTRY, path.toFile());
723 
724 		//removing "org.eclipse.equinox.p2.engine/profileRegistry"
725 		path = path.removeLastSegments(2);
726 		results.put(PROFILE_DATA_AREA, path.toOSString());
727 
728 		return results;
729 	}
730 
getContextMetadata()731 	public URI[] getContextMetadata() {
732 		return contextMetadata;
733 	}
734 
getContextArtifacts()735 	public URI[] getContextArtifacts() {
736 		return contextArtifacts;
737 	}
738 
setProductQualifier(String value)739 	public void setProductQualifier(String value) {
740 		productQualifier = value;
741 	}
742 
743 	/*
744 	 * If the user has specified a platform properties then load it.
745 	 */
setPlatformProperties(String filename)746 	public void setPlatformProperties(String filename) {
747 		if (filename == null || filename.trim().length() == 0)
748 			return;
749 		File file = new File(filename);
750 		if (!file.exists())
751 			return;
752 		platformProperties = new Properties();
753 		InputStream input = null;
754 		try {
755 			input = new BufferedInputStream(new FileInputStream(file));
756 			platformProperties.load(input);
757 		} catch (IOException e) {
758 			platformProperties = null;
759 			String message = NLS.bind(Messages.error_loading_platform_properties, filename);
760 			IStatus status = new Status(IStatus.WARNING, IPDEBuildConstants.PI_PDEBUILD, message, e);
761 			BundleHelper.getDefault().getLog().log(status);
762 		} finally {
763 			if (input != null)
764 				try {
765 					input.close();
766 				} catch (IOException e) {
767 					// ignore
768 				}
769 		}
770 	}
771 
generateProductReplaceTask(ProductFile product, String productFilePath, AssemblyInformation assemblyInfo)772 	protected void generateProductReplaceTask(ProductFile product, String productFilePath, AssemblyInformation assemblyInfo) {
773 		if (product == null)
774 			return;
775 
776 		BuildTimeSite site = null;
777 		try {
778 			site = getSite(false);
779 		} catch (CoreException e1) {
780 			return;
781 		}
782 
783 		String version = product.getVersion();
784 		if (version.endsWith(PROPERTY_QUALIFIER)) {
785 			Version oldVersion = new Version(version);
786 			version = oldVersion.getMajor() + "." + oldVersion.getMinor() + "." + oldVersion.getMicro() + "." + Utils.getPropertyFormat(PROPERTY_P2_PRODUCT_QUALIFIER); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
787 		}
788 
789 		List<FeatureEntry> productEntries = product.getProductEntries();
790 		String mappings = Utils.getEntryVersionMappings(productEntries.toArray(new FeatureEntry[productEntries.size()]), site, assemblyInfo);
791 
792 		script.println("<eclipse.idReplacer productFilePath=\"" + AntScript.getEscaped(productFilePath) + "\""); //$NON-NLS-1$ //$NON-NLS-2$
793 		script.println("                    selfVersion=\"" + version + "\" "); //$NON-NLS-1$ //$NON-NLS-2$
794 		if (product.useFeatures())
795 			script.println("                    featureIds=\"" + mappings + "\"/>"); //$NON-NLS-1$ //$NON-NLS-2$
796 		else
797 			script.println("                    pluginIds=\"" + mappings + "\"/>"); //$NON-NLS-1$ //$NON-NLS-2$
798 
799 		return;
800 	}
801 }
802