/******************************************************************************* | |
* Copyright (c) 2011, 2015 Soft-Maint, and Mia-Software. | |
* All rights reserved. This program and the accompanying materials | |
* are made available under the terms of the Eclipse Public License v2.0 | |
* which accompanies this distribution, and is available at | |
* http://www.eclipse.org/legal/epl-v20.html | |
* | |
* Contributors: | |
* Fabien Treguer (Soft-Maint) - Bug 418565 - [Unit Test Failure] Missing dependencies during tests, target platform creation and load | |
* Grégoire Dupé (Mia-Software) - Bug 468648 - TargetPlatformUtils.loadTargetPlatform: wrong number of arguments | |
******************************************************************************/ | |
package org.eclipse.modisco.facet.util.pde.core.internal.exported; | |
import java.io.File; | |
import java.io.FileOutputStream; | |
import java.io.IOException; | |
import java.io.InputStream; | |
import java.lang.reflect.Array; | |
import java.lang.reflect.Constructor; | |
import java.lang.reflect.Method; | |
import java.util.Arrays; | |
import java.util.Enumeration; | |
import java.util.Iterator; | |
import java.util.LinkedList; | |
import java.util.List; | |
import java.util.jar.JarEntry; | |
import java.util.jar.JarFile; | |
import java.util.jar.JarOutputStream; | |
import org.eclipse.core.runtime.NullProgressMonitor; | |
import org.eclipse.core.runtime.Platform; | |
import org.eclipse.modisco.facet.util.pde.core.internal.Activator; | |
import org.eclipse.modisco.facet.util.pde.core.internal.exported.exception.PdeCoreUtilsException; | |
import org.eclipse.modisco.facet.util.pde.core.internal.exported.exception.ReflexiveDiscouragedAccessException; | |
import org.osgi.framework.Bundle; | |
import org.osgi.framework.Version; | |
public final class TargetPlatformUtils { | |
private static final String BUNDLE_SEPARATOR = ","; //$NON-NLS-1$ | |
private static final String JAR_EXT = ".jar"; //$NON-NLS-1$ | |
private static final String JAR_DIRECTORY = "jarFiles"; //$NON-NLS-1$ | |
private static final int BUFFER_SIZE = 4096; | |
private static final String BUNDLE_PREFIX = "reference:file:"; //$NON-NLS-1$ | |
private static final String FRAMEWORK_PREFIX = "file:"; //$NON-NLS-1$ | |
private static final String OSGI_PROP = "osgi.bundles"; //$NON-NLS-1$ | |
private static final String OSGI_PROP_FRK = "osgi.framework"; //$NON-NLS-1$ | |
private static final String ARRAY_PREFIX = "[L"; //$NON-NLS-1$ | |
private TargetPlatformUtils() { | |
//Must not be used. | |
} | |
/** | |
* Creates and loads a target platform with all needed bundles. | |
* @throws PdeCoreUtilsException | |
* @throws IOException | |
*/ | |
public static void loadTargetPlatform() throws PdeCoreUtilsException { | |
String copyDirectoryPath; | |
try { | |
/*ITargetPlatformService*/ | |
final Object targetPlServ = reflexiveCall( | |
true, | |
"org.eclipse.pde.internal.core.target.TargetPlatformService", //$NON-NLS-1$ | |
"getDefault", //$NON-NLS-1$ | |
new Object[]{}); | |
/*ITargetDefinition*/ | |
final Object targetDefinition = reflexiveCall( | |
false, | |
targetPlServ, | |
"newTarget", //$NON-NLS-1$ | |
new Object[]{}); | |
copyDirectoryPath = Activator.getDefault().getStateLocation() | |
+ File.separator + JAR_DIRECTORY; | |
final File copyDirectory = new File(copyDirectoryPath); | |
copyDirectory.mkdirs(); | |
final List<String> dirPaths = parseBundlesList(); | |
/*IBundleContainer*/ | |
final Object[] dirContainers = | |
copyJarsAndGetContainers(dirPaths, copyDirectory); | |
reflexiveCall( | |
false, | |
targetDefinition, | |
getContainerSetterName(), | |
new Object[]{dirContainers}); | |
reflexiveCall( | |
false, | |
targetDefinition, | |
"resolve", //$NON-NLS-1$ | |
new Object[]{new NullProgressMonitor()}); | |
reflexiveCall( | |
true, | |
getLoadTargetDefinitionJobQualifiedName(), | |
"load", //$NON-NLS-1$ | |
new Object[]{targetDefinition}); | |
reflexiveCall( | |
false, | |
targetPlServ, | |
"saveTargetDefinition", //$NON-NLS-1$ | |
new Object[]{targetDefinition}); | |
} catch (Exception e) { | |
throw new PdeCoreUtilsException(e); | |
} | |
new File(copyDirectoryPath).delete(); | |
} | |
private static boolean isHigherVersion() { | |
final Version version = new Version(3, 7, 1); | |
final Bundle bundle = Platform.getBundle("org.eclipse.pde.core"); //$NON-NLS-1$ | |
final Version currentVersion = bundle.getVersion(); | |
return currentVersion.compareTo(version) > 0; | |
} | |
private static String getLoadTargetDefinitionJobQualifiedName() { | |
String lTDefJobName; | |
if (isHigherVersion()) { | |
lTDefJobName = | |
"org.eclipse.pde.core.target.LoadTargetDefinitionJob"; //$NON-NLS-1$ | |
} else { | |
lTDefJobName = | |
"org.eclipse.pde.internal.core.target.provisional.LoadTargetDefinitionJob"; //$NON-NLS-1$ | |
} | |
return lTDefJobName; | |
} | |
private static String getContainerSetterName() { | |
String setterName; | |
if (isHigherVersion()) { | |
setterName = | |
"setTargetLocations"; //$NON-NLS-1$ | |
} else { | |
setterName = | |
"setBundleContainers"; //$NON-NLS-1$ | |
} | |
return setterName; | |
} | |
/** | |
* Parses osgi.bundles property value to load all required bundles directories. | |
* @return List<String> array | |
* @throws PdeCoreUtilsException | |
* @throws IOException | |
*/ | |
private static List<String> parseBundlesList() | |
throws PdeCoreUtilsException, IOException { | |
final LinkedList<String> result = new LinkedList<String>(); | |
final String bundlesFromConfig = System.getProperty(OSGI_PROP); | |
final String frwkFromConfig = System.getProperty(OSGI_PROP_FRK); | |
if (bundlesFromConfig != null && !"".equals(bundlesFromConfig)) { //$NON-NLS-1$ | |
final String[] bundlesStr = | |
bundlesFromConfig.split(BUNDLE_SEPARATOR); | |
result.addAll(trimPaths(bundlesStr, BUNDLE_PREFIX)); | |
} | |
if (frwkFromConfig != null && !"".equals(frwkFromConfig)) { //$NON-NLS-1$ | |
final String[] frameworkStr = | |
frwkFromConfig.split(BUNDLE_SEPARATOR); | |
result.addAll(trimPaths(frameworkStr, FRAMEWORK_PREFIX)); | |
} | |
return result; | |
} | |
/** | |
* Returns list of trimmed paths; | |
* @param paths String[] | |
* @param prefix String | |
* @return List<String> | |
*/ | |
private static List<String> trimPaths(final String[] paths, | |
final String prefix) { | |
final LinkedList<String> result = new LinkedList<String>(); | |
for (String path : paths) { | |
int indexSuffix = path.indexOf("@"); //$NON-NLS-1$ | |
if (indexSuffix < 0) { | |
indexSuffix = path.length(); | |
} | |
result.add(path.substring(prefix.length(), | |
indexSuffix)); | |
} | |
return result; | |
} | |
/** | |
* Reflexive call of DirectoryBundleContainer constructor. | |
* @param containerPath String | |
* @return Object | |
* @throws PdeCoreUtilsException | |
*/ | |
private static Object newDirectoryBundleContainer( | |
final String containerPath) | |
throws PdeCoreUtilsException { | |
Object container = null; | |
try { | |
final Bundle bundle = Activator.getDefault().getBundle(); | |
final Class<?> classs = | |
bundle.loadClass("org.eclipse.pde.internal.core.target.DirectoryBundleContainer"); //$NON-NLS-1$ | |
final Class<?>[] paramTypes = new Class[] {containerPath.getClass()}; | |
final Constructor<?> constructor = classs.getConstructor(paramTypes); | |
final Object[] initargs = new Object[] { containerPath }; | |
container = constructor.newInstance(initargs); | |
} catch (Exception e) { | |
throw new PdeCoreUtilsException(e); | |
} | |
return container; | |
} | |
/** | |
* Copies all needed jar files in a directory and returns set of containers. | |
* @param bundlesStr List<String> | |
* @param copyDirectory File | |
* @return Object[] | |
* @throws PdeCoreUtilsException | |
* @throws IOException | |
*/ | |
private static Object[] copyJarsAndGetContainers( | |
final List<String> bundlesStr, final File copyDirectory) | |
throws PdeCoreUtilsException, IOException { | |
final List<Object> bundleContainers = new LinkedList<Object>(); | |
final Iterator<String> bundlesStrIter = bundlesStr.iterator(); | |
while (bundlesStrIter.hasNext()) { | |
final File bundleFile = new File(bundlesStrIter.next()); // NOPMD | |
// NOPMD: gdupe> No other way to write this code | |
if (bundleFile.exists()) { | |
if (bundleFile.isDirectory()) { | |
copyJarFileOrAddContainerDirectory(bundleFile, | |
bundleContainers, copyDirectory); | |
} else { | |
try { | |
//FIXME gdupe> is a call to close() required ? | |
final JarFile jarFile = new JarFile(bundleFile); // NOPMD | |
// NOPMD: gdupe> No other way to write this code | |
copyJarFile(jarFile, copyDirectory); | |
} catch (IOException e) { | |
throw new PdeCoreUtilsException(e); | |
} | |
} | |
} | |
} | |
final Object copyDirContainer = | |
newDirectoryBundleContainer(copyDirectory.getAbsolutePath()); | |
bundleContainers.add(copyDirContainer); | |
return bundleContainers.toArray(); | |
} | |
/** | |
* Copies jarFile to the specified directory or creates a container | |
* if bundleFile is a directory. | |
* @param bundleFile File | |
* @param bundleContainers List<Object> | |
* @param copyDirectory File | |
* @throws PdeCoreUtilsException | |
* @throws IOException | |
*/ | |
private static void copyJarFileOrAddContainerDirectory( | |
final File bundleFile, final List<Object> bundleContainers, | |
final File copyDirectory) | |
throws PdeCoreUtilsException, IOException { | |
Object container; | |
//FIXME gdupe> is a call to close() required ? | |
final JarFile jarFile = getFirstMatchingJarFile(bundleFile); | |
if (jarFile == null) { | |
container = newDirectoryBundleContainer(bundleFile.getAbsolutePath()); | |
bundleContainers.add(container); | |
} else { | |
copyJarFile(jarFile, copyDirectory); | |
} | |
} | |
/** | |
* Return first jar file that match with the bundle directory. | |
* @param bundleFile File | |
* @return JarFile | |
* @throws IOException | |
*/ | |
private static JarFile getFirstMatchingJarFile(final File bundleFile) | |
throws IOException { | |
JarFile result = null; | |
if (bundleFile.exists() && bundleFile.isDirectory()) { | |
final String bundleFileName = bundleFile.getName(); | |
final File jar = findJarFile(bundleFile, bundleFileName); | |
if (jar != null) { | |
result = new JarFile(jar); | |
} | |
} | |
return result; | |
} | |
/** | |
* Search jar file in directory and sub-directories. | |
* @param directory File | |
* @param fileName String | |
* @return File | |
*/ | |
private static File findJarFile(final File directory, | |
final String fileName) { | |
final File[] children = directory.listFiles(); | |
final LinkedList<File> toVisit = new LinkedList<File>(); | |
int cpt = 0; | |
File foundFile = null; | |
String childName; | |
while (foundFile != null && cpt < children.length) { | |
final File child = children[cpt]; | |
if (child.isFile()) { | |
childName = child.getName(); | |
if (childName.startsWith(fileName) | |
&& childName.endsWith(JAR_EXT)) { | |
foundFile = child; | |
} | |
} else { | |
toVisit.add(child); | |
} | |
cpt++; | |
} | |
if (foundFile == null) { | |
foundFile = searchInSubDirectories(fileName, toVisit); | |
} | |
return foundFile; | |
} | |
/** | |
* Search jar file and sub-directories. | |
* @param fileName String | |
* @param toVisit List<File> | |
* @return File | |
*/ | |
private static File searchInSubDirectories(final String fileName, | |
final List<File> toVisit) { | |
File result = null; | |
final Iterator<File> childrenIterator = toVisit.iterator(); | |
while (childrenIterator.hasNext() && result != null) { | |
final File child = childrenIterator.next(); | |
result = findJarFile(child.getAbsoluteFile(), fileName); | |
} | |
return result; | |
} | |
/** | |
* Copies a jarFile into specified directory. | |
* @param jarFile JarFile | |
* @param copyTo File | |
* @throws PdeCoreUtilsException | |
*/ | |
private static void copyJarFile(final JarFile jarFile, final File copyTo) | |
throws PdeCoreUtilsException { | |
final String fileName = jarFile.getName(); | |
final String bundleName = fileName.substring(fileName | |
.lastIndexOf(File.separator)); | |
final File copyToDirectory = new File(copyTo, bundleName); | |
JarOutputStream jarOutputStream = null; | |
try { | |
jarOutputStream = new JarOutputStream( | |
new FileOutputStream(copyToDirectory)); | |
final Enumeration<JarEntry> jarFileEntries = jarFile.entries(); | |
final byte[] buffer = new byte[BUFFER_SIZE]; | |
while (jarFileEntries.hasMoreElements()) { | |
final JarEntry sourceEntry = jarFileEntries.nextElement(); | |
final InputStream inputStream = | |
jarFile.getInputStream(sourceEntry); | |
final JarEntry targetEntry = | |
new JarEntry(sourceEntry.getName()); // NOPMD | |
// ftreguer> NOPMD This loop has to create one jar entry for | |
// each loop iteration | |
jarOutputStream.putNextEntry(targetEntry); | |
int bytesRead = inputStream.read(buffer); | |
while (bytesRead != -1) { | |
jarOutputStream.write(buffer, 0, bytesRead); | |
bytesRead = inputStream.read(buffer); | |
} | |
inputStream.close(); | |
jarOutputStream.flush(); | |
jarOutputStream.closeEntry(); | |
} | |
} catch (IOException e) { | |
throw new PdeCoreUtilsException(e); | |
} finally { // NOPMD gdupe> No other choice if I want to managed the | |
// stream closing | |
if (jarOutputStream != null) { | |
try { | |
jarOutputStream.close(); | |
} catch (IOException e) { | |
throw new PdeCoreUtilsException(e); | |
} | |
} | |
} | |
} | |
/** | |
* Calls a method reflexively on an object or a class (static call) with | |
* specified arguments. | |
* @param staticCall boolean | |
* @param callOn Object | |
* @param calledMethodName String | |
* @param args Object[] | |
* @throws ExpException | |
*/ | |
private static Object reflexiveCall(final boolean staticCall, | |
final Object callOn, final String calledMethodName, | |
final Object[] args) throws ReflexiveDiscouragedAccessException { | |
Object result = null; | |
Method aMethod = null; | |
final Class<?>[] classes = getClassesFromArgs(args); | |
try { | |
if (staticCall) { | |
// ftreguer> Static Call, the second parameter should be a | |
// String representing Class name. | |
if (callOn instanceof String) { | |
final Class<?> clazz = Activator.getDefault() | |
.getBundle().loadClass((String) callOn); | |
aMethod = getMethod(clazz, calledMethodName, classes); | |
} else { | |
throw new ReflexiveDiscouragedAccessException( | |
"Invalid parameter, callOn parameter should be a String. (Static Call)"); //$NON-NLS-1$ | |
} | |
} else { | |
aMethod = getMethod(callOn.getClass(), | |
calledMethodName, classes); | |
} | |
if (aMethod == null) { | |
String staticStr = ""; //$NON-NLS-1$ | |
if (staticCall) { | |
staticStr = "static"; //$NON-NLS-1$ | |
} | |
final String message = String.format( | |
"No method with specified arguments found. API break ? Loking for : %s %s.%s(%s)", //$NON-NLS-1$ | |
staticStr, | |
callOn.toString(), | |
calledMethodName, | |
Arrays.toString(args) | |
); | |
throw new ReflexiveDiscouragedAccessException(message); | |
} | |
result = aMethod.invoke(callOn, manageArguments(args)); | |
} catch (Exception e) { | |
String message = calledMethodName; | |
if (aMethod != null) { | |
message = String.format("Failed to call %s", aMethod.toString()); //$NON-NLS-1$ | |
} | |
throw new ReflexiveDiscouragedAccessException(message, e); | |
} | |
return result; | |
} | |
/** | |
* Transforms Object arrays to specific arrays. | |
* @param arguments Object[] | |
* @return Object[] | |
*/ | |
private static Object[] manageArguments(final Object[] arguments) { | |
Object[] result; | |
if (arguments != null && arguments.length > 0) { | |
result = new Object[arguments.length]; | |
Object arg; | |
for (int i = 0; i < arguments.length; i++) { | |
arg = arguments[i]; | |
if (isArray(arg)) { | |
final Object argElement = ((Object[]) arg)[0]; | |
final int arraySize = Array.getLength(arg); | |
Object[] argTmp = (Object[]) Array.newInstance( | |
argElement.getClass(), arraySize); | |
for (int y = 0; y < arraySize; y++) { | |
argTmp[y] = ((Object[]) arg)[y]; | |
} | |
result[i] = argTmp; | |
} else { | |
result[i] = arg; | |
} | |
} | |
} else { | |
result = arguments; | |
} | |
return result; | |
} | |
/** | |
* Returns true if the parameter represents an array. | |
* @param obj Object | |
* @return boolean | |
*/ | |
private static boolean isArray(final Object obj) { | |
return obj.getClass().getName().startsWith(ARRAY_PREFIX); | |
} | |
/** | |
* Returns array of Class that represents types of each arguments. | |
* @param args Object[] | |
* @return Class<?>[] | |
*/ | |
private static Class<?>[] getClassesFromArgs(final Object[] args) { | |
Class<?>[] classes = null; | |
if (args != null) { | |
classes = new Class[args.length]; | |
Object arg = null; | |
for (int i = 0; i < args.length; i++) { | |
arg = args[i]; | |
if (arg != null) { | |
if (isArray(arg)) { | |
arg = ((Object[]) arg)[0]; | |
arg = Array.newInstance(arg.getClass(), 0); | |
} | |
classes[i] = arg.getClass(); | |
} | |
} | |
} | |
return classes; | |
} | |
/** | |
* Returns a Method object that correspond to the specified name | |
* and parameters. | |
* @param clazz Class<?> | |
* @param methodName String | |
* @param parameterTypes Class<?>[] | |
* @return Method | |
* @throws ClassNotFoundException | |
*/ | |
private static Method getMethod(final Class<?> clazz, | |
final String methodName, final Class<?>... parameterTypes) | |
throws ClassNotFoundException { | |
Method result = null; | |
final Method[] methods = clazz.getMethods(); | |
for (Method method : methods) { | |
if (methodName.equals(method.getName())) { | |
final Class<?>[] parameters = method.getParameterTypes(); | |
if (checkTypes(parameters, parameterTypes)) { | |
result = method; | |
break; | |
} | |
} | |
} | |
return result; | |
} | |
/** | |
* Checks if all parameters are the same in both arrays. | |
* @param refParams | |
* @param parameterToTest | |
* @return boolean | |
* @throws ClassNotFoundException | |
*/ | |
private static boolean checkTypes(final Class<?>[] refParams, | |
final Class<?>[] parameterToTest) throws ClassNotFoundException { | |
boolean result = false; | |
if (refParams.length == parameterToTest.length) { | |
if (refParams.length == 0) { | |
result = true; | |
} | |
String refClazzName = null; | |
String clazzName = null; | |
for (int i = 0; i < parameterToTest.length; i++) { | |
refClazzName = refParams[i].getName(); | |
clazzName = parameterToTest[i].getName(); | |
if (!clazzName.equals(refClazzName)) { | |
final boolean isRefArray = refClazzName | |
.startsWith(ARRAY_PREFIX); | |
final boolean isParamArray = clazzName | |
.startsWith(ARRAY_PREFIX); | |
if (isRefArray == isParamArray) { | |
if (isRefArray) { | |
refClazzName = refClazzName | |
.substring(ARRAY_PREFIX.length(), | |
refClazzName.length() - 1); | |
clazzName = clazzName | |
.substring(ARRAY_PREFIX.length(), | |
clazzName.length() - 1); | |
} | |
result = paramExtendsRef(refClazzName, clazzName); | |
} | |
} | |
} | |
} | |
return result; | |
} | |
/** | |
* Returns true if className is a SubType of refClassName. | |
* @param refClassName String | |
* @param className String | |
* @return boolean | |
* @throws ClassNotFoundException | |
*/ | |
private static boolean paramExtendsRef(final String refClassName, | |
final String className) throws ClassNotFoundException { | |
final Bundle bundle = Activator.getDefault().getBundle(); | |
final Class<?> refClazz = bundle.loadClass(refClassName); | |
final Class<?> clazz = bundle.loadClass(className); | |
return refClazz.isAssignableFrom(clazz); | |
} | |
} |