blob: 57d859a35d58c85e90cb7d327384e56bbada6bbc [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.cauldron.bld.core.repository;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.cauldron.bld.core.BldCore;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.SubMonitor;
import org.cauldron.sigil.model.ICompoundModelElement;
import org.cauldron.sigil.model.IModelElement;
import org.cauldron.sigil.model.eclipse.ILibrary;
import org.cauldron.sigil.model.eclipse.ILibraryImport;
import org.cauldron.sigil.model.eclipse.ISigilBundle;
import org.cauldron.sigil.model.osgi.IPackageExport;
import org.cauldron.sigil.model.osgi.IPackageImport;
import org.cauldron.sigil.model.osgi.IRequiredBundle;
import org.cauldron.sigil.repository.IBundleRepository;
import org.cauldron.sigil.repository.IBundleResolver;
import org.cauldron.sigil.repository.IRepositoryManager;
import org.cauldron.sigil.repository.IResolution;
import org.cauldron.sigil.repository.IResolutionMonitor;
import org.cauldron.sigil.repository.ResolutionConfig;
import org.cauldron.sigil.repository.ResolutionException;
import org.osgi.framework.Version;
public class BundleResolver implements IBundleResolver {
private class BundleOrderComparator implements Comparator<ISigilBundle> {
private IModelElement requirement;
public BundleOrderComparator(IModelElement requirement) {
this.requirement = requirement;
}
public int compare(ISigilBundle o1, ISigilBundle o2) {
int c = compareVersions(o1, o2);
if ( c == 0 ) {
c = compareImports(o1, o2);
}
return c;
}
private int compareImports(ISigilBundle o1, ISigilBundle o2) {
int c1 = o1.getBundleInfo().getImports().size();
int c2 = o2.getBundleInfo().getImports().size();
if ( c1 < c2 ) {
return -1;
}
else if ( c2 > c1 ) {
return 1;
}
else {
return 0;
}
}
private int compareVersions(ISigilBundle o1, ISigilBundle o2) {
Version v1 = null;
Version v2 = null;
if ( requirement instanceof IPackageImport ) {
v1 = findExportVersion( (IPackageImport) requirement, o1 );
v2 = findExportVersion( (IPackageImport) requirement, o2 );
}
else if ( requirement instanceof IRequiredBundle ) {
v1 = o1.getBundleInfo().getVersion();
v2 = o1.getBundleInfo().getVersion();
}
if ( v1 == null ) {
if ( v2 == null ) {
return 0;
}
else {
return 1;
}
}
else {
if ( v2 == null ) {
return -1;
}
else {
return v2.compareTo(v1);
}
}
}
private Version findExportVersion(IPackageImport pi, ISigilBundle o1) {
for ( IPackageExport pe : o1.getBundleInfo().getExports() ) {
if ( pi.getPackageName().equals( pi.getPackageName() ) ) {
return pe.getVersion();
}
}
return null;
}
}
private class ResolutionContext {
private final IModelElement root;
private final ResolutionConfig config;
private final IResolutionMonitor monitor;
private final Resolution resolution = new Resolution();
private final Set<IModelElement> parsed = new HashSet<IModelElement>();
private final LinkedList<IModelElement> requirements = new LinkedList<IModelElement>();
public ResolutionContext(IModelElement root, ResolutionConfig config, IResolutionMonitor monitor) {
this.root = root;
this.config = config;
this.monitor = monitor;
}
public void enterModelElement(IModelElement element) {
parsed.add(element);
}
public void exitModelElement(IModelElement element) {
parsed.remove(element);
}
public boolean isNewModelElement(IModelElement element) {
return !parsed.contains(element);
}
public boolean isValid() {
return resolution.isSuccess();
}
public void setValid(boolean valid) {
resolution.setSuccess(valid);
}
public ResolutionException newResolutionException() {
return new ResolutionException(root, requirements.toArray( new IModelElement[requirements.size()]) );
}
public void startRequirement(IModelElement element) {
requirements.add(element);
monitor.startResolution(element);
}
public void endRequirement(IModelElement element) {
ISigilBundle provider = resolution.getProvider(element);
setValid( provider != null || isOptional(element) || config.isIgnoreErrors() );
if ( isValid() ) {
// only clear stack if valid
// else use it as an aid to trace errors
requirements.remove(element);
}
monitor.endResolution( element, provider );
}
}
private class Resolution implements IResolution {
private Map<ISigilBundle, List<IModelElement>> providees = new HashMap<ISigilBundle, List<IModelElement>>();
private Map<IModelElement, ISigilBundle> providers = new HashMap<IModelElement, ISigilBundle>();
private boolean success = true; // assume success
boolean addProvider(IModelElement element, ISigilBundle provider) {
providers.put( element, provider );
List<IModelElement> requirements = providees.get( provider );
boolean isNewProvider = requirements == null;
if ( isNewProvider ) {
requirements = new ArrayList<IModelElement>();
providees.put( provider, requirements );
}
requirements.add( element );
return isNewProvider;
}
void removeProvider(IModelElement element, ISigilBundle provider) {
providers.remove(element);
List<IModelElement> e = providees.get(provider);
e.remove(element);
if ( e.isEmpty() ) {
providees.remove(provider);
}
}
void setSuccess(boolean success) {
this.success = success;
}
public boolean isSuccess() {
return success;
}
public ISigilBundle getProvider(IModelElement requirement) {
return providers.get(requirement);
}
public Set<ISigilBundle> getBundles() {
return providees.keySet();
}
public List<IModelElement> getMatchedRequirements(ISigilBundle bundle) {
return providees.get(bundle);
}
public boolean isSynchronized() {
for ( ISigilBundle b : getBundles() ) {
if ( !b.isSynchronized() ) {
return false;
}
}
return true;
}
public void synchronize(IProgressMonitor monitor) {
Set<ISigilBundle> bundles = getBundles();
SubMonitor progress = SubMonitor.convert(monitor, bundles.size());
for ( ISigilBundle b : bundles ) {
if ( monitor.isCanceled() ) {
break;
}
try {
b.synchronize(progress.newChild(1));
} catch (IOException e) {
BldCore.error( "Failed to synchronize " + b, e );
}
}
}
}
private static final IResolutionMonitor NULL_MONITOR = new IResolutionMonitor() {
public void endResolution(IModelElement requirement,
ISigilBundle sigilBundle) {
}
public boolean isCanceled() {
return false;
}
public void startResolution(IModelElement requirement) {
}
};
private IRepositoryManager repositoryManager;
public BundleResolver(IRepositoryManager repositoryManager) {
this.repositoryManager = repositoryManager;
}
public IResolution resolve(IModelElement element, ResolutionConfig config, IResolutionMonitor monitor) throws ResolutionException {
if ( monitor == null ) {
monitor = NULL_MONITOR;
}
ResolutionContext ctx = new ResolutionContext(element, config, monitor);
resolveElement(element, ctx);
if ( !ctx.isValid() ) {
throw ctx.newResolutionException();
}
return ctx.resolution;
}
private void resolveElement(IModelElement element, ResolutionContext ctx) throws ResolutionException {
if ( isRequirement(element) ) {
resolveRequirement(element, ctx);
}
if ( ctx.isValid() && element instanceof ICompoundModelElement ) {
resolveCompound((ICompoundModelElement) element, ctx);
}
}
private void resolveCompound(ICompoundModelElement compound, ResolutionContext ctx) throws ResolutionException {
for ( IModelElement element : compound.children() ) {
if ( ctx.isNewModelElement(element) ) {
if ( isRequirement(element) ) {
resolveRequirement(element, ctx);
}
else if ( element instanceof ICompoundModelElement ) {
if ( !ctx.monitor.isCanceled() ) {
ctx.enterModelElement( element );
resolveElement((ICompoundModelElement) element, ctx);
ctx.exitModelElement(element);
}
}
if ( !ctx.isValid() ) {
break;
}
}
}
}
private void resolveRequirement(IModelElement requirement, ResolutionContext ctx) throws ResolutionException {
if ( ctx.config.isOptional() || !isOptional(requirement) ) {
ctx.startRequirement(requirement );
try {
int[] priorities = repositoryManager.getPriorityLevels();
outer: for ( int i = 0; i< priorities.length; i++ ) {
List<ISigilBundle> providers = findProvidersAtPriority(priorities[i], requirement, ctx);
if ( !providers.isEmpty() && !ctx.monitor.isCanceled() ) {
if ( providers.size() > 1 ) {
Collections.sort(providers, new BundleOrderComparator(requirement));
}
for ( ISigilBundle provider : providers ) {
// reset validity - if there's another provider it can still be solved
ctx.setValid(true);
if ( ctx.resolution.addProvider(requirement, provider) ) {
if ( ctx.config.isDependents() ) {
resolveElement(provider, ctx);
}
if ( ctx.isValid() ) {
break outer;
}
else {
ctx.resolution.removeProvider(requirement, provider);
}
}
else {
break outer;
}
}
}
}
}
finally {
ctx.endRequirement(requirement);
}
}
}
private List<ISigilBundle> findProvidersAtPriority(int i, IModelElement requirement, ResolutionContext ctx) throws ResolutionException {
ArrayList<ISigilBundle> providers = new ArrayList<ISigilBundle>();
for (IBundleRepository rep : repositoryManager.getRepositories(i)) {
if ( ctx.monitor.isCanceled() ) {
break;
}
providers.addAll( findProviders( requirement, ctx.config, rep ) );
}
return providers;
}
private Collection<ISigilBundle> findProviders(IModelElement requirement, ResolutionConfig config, IBundleRepository rep) throws ResolutionException {
ArrayList<ISigilBundle> found = new ArrayList<ISigilBundle>();
if ( requirement instanceof IPackageImport ) {
IPackageImport pi = (IPackageImport) requirement;
found.addAll( rep.findAllProviders( pi, config.getOptions() ) );
}
else if ( requirement instanceof IRequiredBundle ) {
IRequiredBundle rb = (IRequiredBundle) requirement;
found.addAll( rep.findAllProviders( rb, config.getOptions() ) );
}
else if ( requirement instanceof ILibraryImport ) {
ILibrary lib = repositoryManager.resolveLibrary((ILibraryImport) requirement);
if (lib != null) {
found.addAll( rep.findProviders(lib, config.getOptions()) );
}
}
else {
// shouldn't get here - developer error if do
// use isRequirement before getting anywhere near this logic...
throw new IllegalStateException( "Invalid requirement type " + requirement );
}
return found;
}
private boolean isOptional(IModelElement element) {
if ( element instanceof IPackageImport ) {
return ((IPackageImport) element).isOptional();
}
else if ( element instanceof IRequiredBundle ) {
return ((IRequiredBundle) element).isOptional();
}
else if ( element instanceof ILibraryImport ) {
ILibrary lib = repositoryManager.resolveLibrary((ILibraryImport) element);
for ( IPackageImport pi : lib.getImports() ) {
if ( !isOptional(pi) ) {
return false;
}
}
return true;
}
else {
// should never get this due to isRequirement test prior to calling this
// developer error if found
throw new IllegalStateException( "Invalid optional element test for " + element);
}
}
private boolean isRequirement(IModelElement element) {
return element instanceof IPackageImport || element instanceof IRequiredBundle || element instanceof ILibraryImport;
}
}