blob: e5ceced3bd2794ba1fe8eadfd823627205014705 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007, 2015 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.parseBoolean(tok.nextToken());
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;
}
}