Skip to main content
summaryrefslogtreecommitdiffstats
blob: ab69b885657391eca82682c60ac006126bbe9280 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
/*******************************************************************************
 * Copyright (c) 2007, 2013 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: 
 * 		IBM Corporation - initial API and implementation
 * 		Red Hat, Inc (Krzysztof Daniel) - Bug 421935: Extend simpleconfigurator to
 * read .info files from many locations
 ******************************************************************************/
package org.eclipse.equinox.internal.simpleconfigurator.utils;

import java.io.*;
import java.net.*;
import java.util.*;
import org.eclipse.equinox.internal.simpleconfigurator.Activator;
import org.osgi.framework.Version;

public class SimpleConfiguratorUtils {

	private static final String LINK_KEY = "link";
	private static final String LINK_FILE_EXTENSION = ".link";
	private static final String UNC_PREFIX = "//";
	private static final String VERSION_PREFIX = "#version=";
	public static final String ENCODING_UTF8 = "#encoding=UTF-8";
	public static final Version COMPATIBLE_VERSION = new Version(1, 0, 0);

	private static final String FILE_SCHEME = "file";
	private static final String REFERENCE_PREFIX = "reference:";
	private static final String FILE_PREFIX = "file:";
	private static final String COMMA = ",";
	private static final String ENCODED_COMMA = "%2C";

	private static final Set<File> reportedExtensions = Collections.synchronizedSet(new HashSet<File>(0));

	public static List<BundleInfo> readConfiguration(URL url, URI base) throws IOException {
		List<BundleInfo> result = new ArrayList<BundleInfo>();

		//old behaviour
		result.addAll(readConfigurationFromFile(url, base));

		if (!Activator.EXTENDED) {
			return result;
		}
		readExtendedConfigurationFiles(result);
		//dedup - some bundles may be listed more than once
		removeDuplicates(result);
		return result;
	}

	public static void removeDuplicates(List<BundleInfo> result) {
		if (result.size() > 1) {
			int index = 0;
			while (index < result.size()) {
				String aSymbolicName = result.get(index).getSymbolicName();
				String aVersion = result.get(index).getVersion();

				for (int i = index + 1; i < result.size();) {
					String bSymbolicName = result.get(i).getSymbolicName();
					String bVersion = result.get(i).getVersion();
					if (aSymbolicName.equals(bSymbolicName) && aVersion.equals(bVersion)) {
						result.remove(i);
					} else {
						i++;
					}
				}

				index++;
			}
		}
	}

	public static void readExtendedConfigurationFiles(List<BundleInfo> result) throws IOException, FileNotFoundException, MalformedURLException {
		//extended behaviour
		List<File> files;
		try {
			files = getInfoFiles();
			for (File info : files) {
				List<BundleInfo> list = readConfigurationFromFile(info.toURL(), info.getParentFile().toURI());
				// extensions are relative to extension root, not to the framework
				// it is necessary to replace relative locations with absolute ones
				for (int i = 0; i < list.size(); i++) {
					BundleInfo singleInfo = list.get(i);
					if (singleInfo.getBaseLocation() != null) {
						singleInfo = new BundleInfo(singleInfo.getSymbolicName(), singleInfo.getVersion(), singleInfo.getBaseLocation().resolve(singleInfo.getLocation()), singleInfo.getStartLevel(), singleInfo.isMarkedAsStarted());
						list.remove(i);
						list.add(i, singleInfo);
					}
				}
				if (Activator.DEBUG) {
					System.out.println("List of bundles to be loaded from " + info.toURL());
					for (BundleInfo b : list) {
						System.out.println(b.getSymbolicName() + "_" + b.getVersion());
					}
				}
				result.addAll(list);
			}
		} catch (URISyntaxException e) {
			throw new IllegalArgumentException("Couldn't parse simpleconfigurator extensions", e);
		}
	}

	public static ArrayList<File> getInfoFiles() throws IOException, FileNotFoundException, URISyntaxException {
		ArrayList<File> files = new ArrayList<File>(1);

		if (Activator.EXTENSIONS != null) {
			//configured simpleconfigurator extensions location
			String stringExtenionLocation = Activator.EXTENSIONS;
			String[] locationToCheck = stringExtenionLocation.split(",");
			for (String location : locationToCheck) {
				files.addAll(getInfoFilesFromLocation(location));
			}
		}
		return files;
	}

	private static ArrayList<File> getInfoFilesFromLocation(String locationToCheck) throws IOException, FileNotFoundException, URISyntaxException {
		ArrayList<File> result = new ArrayList<File>(1);

		File extensionsLocation = new File(locationToCheck);

		if (extensionsLocation.exists() && extensionsLocation.isDirectory()) {
			//extension location contains extensions
			File[] extensions = extensionsLocation.listFiles();
			for (File extension : extensions) {
				if (extension.isFile() && extension.getName().endsWith(LINK_FILE_EXTENSION)) {
					Properties link = new Properties();
					link.load(new FileInputStream(extension));
					String newInfoName = link.getProperty(LINK_KEY);
					URI newInfoURI = new URI(newInfoName);
					File newInfoFile = null;
					if (newInfoURI.isAbsolute()) {
						newInfoFile = new File(newInfoName);
					} else {
						newInfoFile = new File(extension.getParentFile(), newInfoName);
					}
					if (newInfoFile.exists()) {
						extension = newInfoFile.getParentFile();
					}
				}

				if (extension.isDirectory()) {
					if (extension.canWrite()) {
						synchronized (reportedExtensions) {
							if (!reportedExtensions.contains(extension)) {
								reportedExtensions.add(extension);
								System.err.println("Fragment directory should be read only " + extension);
							}
						}
						continue;
					}
					File[] listFiles = extension.listFiles();
					// new magic - multiple info files, f.e.
					//   egit.info (git feature)
					//   cdt.link (properties file containing link=path) to other info file
					for (File file : listFiles) {
						//if it is a info file - load it
						if (file.getName().endsWith(".info")) {
							result.add(file);
						}
						// if it is a link - dereference it
					}
				} else if (Activator.DEBUG) {
					synchronized (reportedExtensions) {
						if (!reportedExtensions.contains(extension)) {
							reportedExtensions.add(extension);
							System.out.println("Unrecognized fragment " + extension);
						}
					}
				}
			}
		}
		return result;
	}

	private static List<BundleInfo> readConfigurationFromFile(URL url, URI base) throws IOException {
		InputStream stream = null;
		try {
			stream = url.openStream();
		} catch (IOException e) {
			// if the exception is a FNF we return an empty bundle list
			if (e instanceof FileNotFoundException)
				return Collections.emptyList();
			throw e;
		}

		try {
			return readConfiguration(stream, base);
		} finally {
			stream.close();
		}
	}

	/**
	 * Read the configuration from the given InputStream
	 * 
	 * @param stream - the stream is always closed 
	 * @param base
	 * @return List of {@link BundleInfo}
	 * @throws IOException
	 */
	public static List<BundleInfo> readConfiguration(InputStream stream, URI base) throws IOException {
		List<BundleInfo> bundles = new ArrayList<BundleInfo>();

		BufferedInputStream bufferedStream = new BufferedInputStream(stream);
		String encoding = determineEncoding(bufferedStream);
		BufferedReader r = new BufferedReader(encoding == null ? new InputStreamReader(bufferedStream) : new InputStreamReader(bufferedStream, encoding));

		String line;
		try {
			while ((line = r.readLine()) != null) {
				line = line.trim();
				//ignore any comment or empty lines
				if (line.length() == 0)
					continue;

				if (line.startsWith("#")) {//$NON-NLS-1$
					parseCommentLine(line);
					continue;
				}

				BundleInfo bundleInfo = parseBundleInfoLine(line, base);
				if (bundleInfo != null)
					bundles.add(bundleInfo);
			}
		} finally {
			try {
				r.close();
			} catch (IOException ex) {
				// ignore
			}
		}
		return bundles;
	}

	/*
	 * We expect the first line of the bundles.info to be 
	 *    #encoding=UTF-8
	 * if it isn't, then it is an older bundles.info and should be 
	 * read with the default encoding
	 */
	private static String determineEncoding(BufferedInputStream stream) {
		byte[] utfBytes = ENCODING_UTF8.getBytes();
		byte[] buffer = new byte[utfBytes.length];

		int bytesRead = -1;
		stream.mark(utfBytes.length + 1);
		try {
			bytesRead = stream.read(buffer);
		} catch (IOException e) {
			//do nothing
		}

		if (bytesRead == utfBytes.length && Arrays.equals(utfBytes, buffer))
			return "UTF-8";

		//if the first bytes weren't the encoding, need to reset
		try {
			stream.reset();
		} catch (IOException e) {
			// nothing
		}
		return null;
	}

	public static void parseCommentLine(String line) {
		// version
		if (line.startsWith(VERSION_PREFIX)) {
			String version = line.substring(VERSION_PREFIX.length()).trim();
			if (!COMPATIBLE_VERSION.equals(new Version(version)))
				throw new IllegalArgumentException("Invalid version: " + version);
		}
	}

	public static BundleInfo parseBundleInfoLine(String line, URI base) {
		// symbolicName,version,location,startLevel,markedAsStarted
		StringTokenizer tok = new StringTokenizer(line, COMMA);
		int numberOfTokens = tok.countTokens();
		if (numberOfTokens < 5)
			throw new IllegalArgumentException("Line does not contain at least 5 tokens: " + line);

		String symbolicName = tok.nextToken().trim();
		String version = tok.nextToken().trim();
		URI location = parseLocation(tok.nextToken().trim());
		int startLevel = Integer.parseInt(tok.nextToken().trim());
		boolean markedAsStarted = Boolean.valueOf(tok.nextToken()).booleanValue();
		BundleInfo result = new BundleInfo(symbolicName, version, location, startLevel, markedAsStarted);
		if (!location.isAbsolute())
			result.setBaseLocation(base);
		return result;
	}

	public static URI parseLocation(String location) {
		// decode any commas we previously encoded when writing this line
		int encodedCommaIndex = location.indexOf(ENCODED_COMMA);
		while (encodedCommaIndex != -1) {
			location = location.substring(0, encodedCommaIndex) + COMMA + location.substring(encodedCommaIndex + 3);
			encodedCommaIndex = location.indexOf(ENCODED_COMMA);
		}

		if (File.separatorChar != '/') {
			int colon = location.indexOf(':');
			String scheme = colon < 0 ? null : location.substring(0, colon);
			if (scheme == null || scheme.equals(FILE_SCHEME))
				location = location.replace(File.separatorChar, '/');
			//if the file is a UNC path, insert extra leading // if needed to make a valid URI (see bug 207103)
			if (scheme == null) {
				if (location.startsWith(UNC_PREFIX) && !location.startsWith(UNC_PREFIX, 2))
					location = UNC_PREFIX + location;
			} else {
				//insert UNC prefix after the scheme
				if (location.startsWith(UNC_PREFIX, colon + 1) && !location.startsWith(UNC_PREFIX, colon + 3))
					location = location.substring(0, colon + 3) + location.substring(colon + 1);
			}
		}

		try {
			URI uri = new URI(location);
			if (!uri.isOpaque())
				return uri;
		} catch (URISyntaxException e1) {
			// this will catch the use of invalid URI characters (e.g. spaces, etc.)
			// ignore and fall through
		}

		try {
			return URIUtil.fromString(location);
		} catch (URISyntaxException e) {
			throw new IllegalArgumentException("Invalid location: " + location);
		}
	}

	public static void transferStreams(List<InputStream> sources, OutputStream destination) throws IOException {
		destination = new BufferedOutputStream(destination);
		try {
			for (int i = 0; i < sources.size(); i++) {
				InputStream source = new BufferedInputStream(sources.get(i));
				try {
					byte[] buffer = new byte[8192];
					while (true) {
						int bytesRead = -1;
						if ((bytesRead = source.read(buffer)) == -1)
							break;
						destination.write(buffer, 0, bytesRead);
					}
				} finally {
					try {
						source.close();
					} catch (IOException e) {
						// ignore
					}
				}
			}
		} finally {
			try {
				destination.close();
			} catch (IOException e) {
				// ignore
			}
		}
	}

	// This will produce an unencoded URL string
	public static String getBundleLocation(BundleInfo bundle, boolean useReference) {
		URI location = bundle.getLocation();
		String scheme = location.getScheme();
		String host = location.getHost();
		String path = location.getPath();

		if (location.getScheme() == null) {
			URI baseLocation = bundle.getBaseLocation();
			if (baseLocation != null && baseLocation.getScheme() != null) {
				scheme = baseLocation.getScheme();
				host = baseLocation.getHost();
			}
		}

		String bundleLocation = null;
		try {
			URL bundleLocationURL = new URL(scheme, host, path);
			bundleLocation = bundleLocationURL.toExternalForm();

		} catch (MalformedURLException e1) {
			bundleLocation = location.toString();
		}

		if (useReference && bundleLocation.startsWith(FILE_PREFIX))
			bundleLocation = REFERENCE_PREFIX + bundleLocation;
		return bundleLocation;
	}

	public static long getExtendedTimeStamp() {
		long regularTimestamp = -1;
		if (Activator.EXTENDED) {
			try {
				ArrayList<File> infoFiles = SimpleConfiguratorUtils.getInfoFiles();
				for (File f : infoFiles) {
					long infoFileLastModified = f.lastModified();
					// pick latest modified always
					if (infoFileLastModified > regularTimestamp) {
						regularTimestamp = infoFileLastModified;
					}
				}
			} catch (FileNotFoundException e) {
				if (Activator.DEBUG) {
					e.printStackTrace();
				}
			} catch (IOException e) {
				if (Activator.DEBUG) {
					e.printStackTrace();
				}
			} catch (URISyntaxException e) {
				if (Activator.DEBUG) {
					e.printStackTrace();
				}
			}
			if (Activator.DEBUG) {
				System.out.println("Fragments timestamp: " + regularTimestamp);
			}
		}
		return regularTimestamp;
	}
}

Back to the top