/*******************************************************************************
* Copyright (c) 2008 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
*******************************************************************************/
package at.bestsolution.efxclipse.runtime.osgi.patch;
import java.io.File;
import java.io.IOException;
import java.util.*;
import org.eclipse.osgi.baseadaptor.BaseData;
import org.eclipse.osgi.baseadaptor.bundlefile.BundleEntry;
import org.eclipse.osgi.baseadaptor.bundlefile.BundleFile;
import org.eclipse.osgi.framework.internal.core.AbstractBundle;
import org.eclipse.osgi.internal.baseadaptor.DevClassPathHelper;
import org.osgi.framework.Bundle;
import org.osgi.service.packageadmin.PackageAdmin;
/**
* A bundle file that wraps the content of another bundle file.
* A list of bundle files is used to patch the content of the
* wrapped bundle file. The list of patches is searched
* before the wrapped bundle file. This allows the
* content of the patches to override (or patch) the
* content of the wrapped bundle file.
*/
public class PFBundleFile extends BundleFile {
/**
* The wrapped bundle file that is being patched
*/
private final BundleFile wrapped;
/**
* The BaseData for the wrapped bundle file
*/
private final BaseData patchedData;
/**
* The adaptor hook
*/
private final PFAdaptorHook pfAdaptorHook;
/**
* Indicates that the list of patches is current and ready to use
*/
private boolean processed = false;
/**
* The list of patch bundle files that are associated with this bundle file.
*/
private BundleFile[] patches;
public PFBundleFile(BundleFile wrapped, BaseData patchedData, PFAdaptorHook pfAdaptorHook) {
// use the base file from the wrapped bundle file
super(wrapped.getBaseFile());
this.wrapped = wrapped;
this.patchedData = patchedData;
this.pfAdaptorHook = pfAdaptorHook;
}
public void close() throws IOException {
wrapped.close();
}
public boolean containsDir(String dir) {
return wrapped.containsDir(dir);
}
public BundleEntry getEntry(String path) {
// see if there are any patches available
BundleFile[] patchFiles = getPatches();
if (patchFiles == null) // none available just use the wrapped content
return wrapped.getEntry(path);
if ("META-INF/MANIFEST.MF".equals(path)) //$NON-NLS-1$
return wrapped.getEntry(path); // don't patch manifest
for (int i = 0; i < patchFiles.length; i++) {
BundleEntry entry = patchFiles[i].getEntry(path);
if (entry != null) { // found patched content; return it
if (PFConfigurator.DEBUG)
System.out.println("Found patch for \"" + path + "\" in \"" + patchFiles[i] + "\""); //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$
return entry;
}
}
// no patched content found for the path; use the wrapped content
return wrapped.getEntry(path);
}
public Enumeration getEntryPaths(String path) {
// we simply call the wrapped bundle file here because
// we do not want to return more entries than what the original content has
return wrapped.getEntryPaths(path);
}
public File getFile(String path, boolean nativeCode) {
// see if there are any patches available
BundleFile[] patchFiles = getPatches();
if (patchFiles == null) // none available just use the wrapped content
return wrapped.getFile(path, nativeCode);
for (int i = 0; i < patchFiles.length; i++) {
File file = patchFiles[i].getFile(path, nativeCode);
if (file != null) // found patched content; return it
return file;
}
// no patched content found for the path; use the wrapped content
return wrapped.getFile(path, nativeCode);
}
public void open() throws IOException {
wrapped.open();
}
private synchronized BundleFile[] getPatches() {
if (processed) // the patches list is current; return it
return patches;
Bundle bundle = patchedData.getBundle();
if (bundle == null)
// BundleFile objects are created before the Bundle object
return null; // we don't know yet
// bundle is not resolved; we can only patch after the bundle is resolved
if (((Bundle.INSTALLED | Bundle.UNINSTALLED) & bundle.getState()) != 0)
return null; // we can only patch if resolved;
// bundle is resolved; now check package admin for patch fragments
PackageAdmin pa = pfAdaptorHook.getPackageAdmin();
if (pa == null)
return null; // we cannot know without PA
// collect a list of bundles we need to listen for UNRESOLVED/UNINSTALLED events
Collection bundlesToListen = new ArrayList();
try {
if ((pa.getBundleType(bundle) & PackageAdmin.BUNDLE_TYPE_FRAGMENT) == 0)
bundlesToListen.add(bundle); // Always listen to the host bundle
else
return null; // we don't patch fragments; no need to listen
Bundle[] fragments = pa.getFragments(bundle);
if (fragments == null)
return null; // no fragments
// search the fragments for patch fragments
ArrayList patchList = new ArrayList(fragments.length);
for (int i = 0; i < fragments.length; i++) {
AbstractBundle fragment = (AbstractBundle) fragments[i];
BaseData fragmentData = (BaseData) fragment.getBundleData();
// The PFStorageHook knows if this is a patch fragment
PFStorageHook storageHook = (PFStorageHook) fragmentData.getStorageHook(PFStorageHook.KEY);
if (storageHook.isPatchFragment()) {
if (PFConfigurator.DEBUG)
System.out.println("Found patch fragment: " + fragmentData.toString()); //$NON-NLS-1$
patchList.add(fragmentData.getBundleFile());
// need to listen to this fragment
bundlesToListen.add(fragment);
// add in dev classpaths
if (DevClassPathHelper.inDevelopmentMode()) {
String[] devPath = DevClassPathHelper.getDevClassPath(fragmentData.getSymbolicName());
if (devPath != null) {
for (int j = 0; j < devPath.length; j++) {
File devFile = fragmentData.getBundleFile().getFile(devPath[i], false);
patchList.add(pfAdaptorHook.createDevClasspathBundleFile(devFile, fragmentData));
}
}
}
}
patches = (BundleFile[]) patchList.toArray(new BundleFile[patchList.size()]);
}
} finally {
// tell the listener about the list to listen
pfAdaptorHook.listenToPatches(bundlesToListen, this);
// mark the patches as processed
processed = true;
}
return patches;
}
synchronized void resetPatches() {
// reset the patches list so it will be re-computed.
processed = false;
patches = null;
}
public String toString() {
return patchedData.toString();
}
}