package net.sourceforge.javautil.classloader.resolver.impl;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import net.sourceforge.javautil.classloader.resolver.IClassArtifactReference;
import net.sourceforge.javautil.classloader.resolver.IClassDependency;
import net.sourceforge.javautil.classloader.resolver.IClassPackage;
import net.sourceforge.javautil.classloader.resolver.IClassDependencyPool;
import net.sourceforge.javautil.classloader.resolver.IClassDependencyPool.PoolScope;
import net.sourceforge.javautil.classloader.resolver.IClassPackage.IVersion;
import net.sourceforge.javautil.classloader.source.ClassSource;
import net.sourceforge.javautil.classloader.source.CompositeClassSource;
import net.sourceforge.javautil.common.StringUtil;
import net.sourceforge.javautil.common.logging.ILogger;
import net.sourceforge.javautil.common.logging.LoggingContext;
/**
* A pool of dependencies, which can have a parent, is a collection of unique {@link IClassPackage}'s.<br/><br/>
*
* The heiarchy of {@link ClassDependencyPoolImpl}'s must only have a single version of a particular
* {@link IClassPackage} in an entire pool heiarchy branch.
*
* TODO: Make sure dependency conflict is stable at some point
*
* @author elponderador
* @author $Author: ponderator $
* @version $Id: ClassDependencyPoolImpl.java 2709 2010-12-31 00:46:45Z ponderator $
*/
public class ClassDependencyPoolImpl implements IClassDependencyPool {
protected static final ILogger log = LoggingContext.getContextualLogger(ClassDependencyPoolImpl.class);
protected final ClassDependencyPoolImpl parent;
protected final PoolScope scope;
protected final String name;
protected List<IClassPackage> primary = new ArrayList<IClassPackage>();
protected Map<String, IClassPackage> packages = new HashMap<String, IClassPackage>();
protected List<IClassPackage> conflicts = new ArrayList<IClassPackage>();
protected List<String> ignore = new ArrayList<String>();
protected List<IClassDependencyPool> children = new ArrayList<IClassDependencyPool>();
protected long lastModified = System.currentTimeMillis();
public ClassDependencyPoolImpl(String name) { this(name, null, PoolScope.Root); }
protected ClassDependencyPoolImpl(String name, ClassDependencyPoolImpl parent, PoolScope scope) {
this.parent = parent;
this.scope = scope;
this.name = name;
if (this.parent != null) this.parent.link(this);
}
/**
* @return The parent pool, or null if this is the top most pool
*/
public IClassDependencyPool getParent() { return parent; }
public void removeFromScope(IClassArtifactReference reference, IClassDependencyPool caller) {
String id = this.getUniqueId(reference.getGroupId(), reference.getArtifactId());
this.packages.remove(id);
if (scope == PoolScope.Component && caller != parent) { this.parent.removeFromScope(reference, this); }
for (IClassDependencyPool pool : this.children) {
if (pool == caller) continue;
if (pool.getScope() == PoolScope.Component) pool.removeFromScope(reference, this);
}
}
/**
* This will update the last modified for this pool and its parent if this is not a root pool
*/
protected void touch () {
this.lastModified = System.currentTimeMillis();
if (this.scope == PoolScope.Component) this.parent.touch();
}
public long getLastModified() { return this.lastModified; }
public boolean isPrimaryPackage(IClassArtifactReference reference, IClassDependencyPool caller) {
for (IClassPackage pkg : this.primary) {
if (pkg.compareTo(reference) == 0) return true;
}
for (IClassDependencyPool pool : this.children) {
if (pool == caller) continue;
if (pool.getScope() == PoolScope.Component && pool.isPrimaryPackage(reference, this)) return true;
}
return scope == PoolScope.Component && caller != parent ? this.parent.isPrimaryPackage(reference, this) : false;
}
public void link(IClassDependencyPool pool) {
if (!this.children.contains(pool)) this.children.add(pool);
}
public void remove(IClassDependencyPool child) {
this.children.remove(child);
}
public List<IClassDependencyPool> getChildPools() {
return new ArrayList<IClassDependencyPool>(this.children);
}
public ClassDependencyPoolImpl createChild(String name, PoolScope scope) {
return new ClassDependencyPoolImpl(name, this, scope);
}
public String getName() {
return this.name;
}
public PoolScope getScope() { return scope; }
/**
* This will remove any package with the specified
* id's and will make sure they are not added to this
* pool in the future.
*
* @param groupId The group id of the package to ignore
* @param artifactId The artifact of the package to ignore
*/
public void ignore (String groupId, String artifactId) {
String id = this.getUniqueId(groupId, artifactId);
if (this.packages.containsKey(id)) {
this.primary.remove(this.packages.remove(id));
}
this.ignore.add( id );
}
public void addWithDependencies (IClassPackage pkg) {
this.addWithDependencies(pkg, true);
}
public void addWithDependencies (IClassPackage pkg, boolean primary) {
if (this.add(primary, pkg).size() == 1)
return;
for (IClassDependency cp : pkg.getDependencies()) {
if (conflicts.contains(cp.getPackage())) continue;
this.addWithDependencies(cp.getPackage(), false);
}
this.touch();
}
/**
* This will verify there is not already a package in the pool or parent pool.
* If there is and it is the same version it will simply ignore the addition.
* If there is a version conflict the version that would have been added will be
* aggregated to a list that will be returned.
*
* @param packages The packages to add to the pool
* @return A list (possibly empty) of packages that had version conflicts with versions already in the pool
*/
protected List<IClassPackage> add (boolean primary, IClassPackage... packages) {
List<IClassPackage> conflicts = new ArrayList<IClassPackage>();
for (IClassPackage pkg : packages) {
if (ClassPackageImpl.isSelfPackage(pkg)) continue;
String id = this.getUniqueId(pkg);
if (this.ignore.contains(id)) continue;
if (primary) {
this.packages.put(id, pkg);
this.primary.add(pkg);
continue;
}
IVersion version = this.getVersion(pkg.getGroupId(), pkg.getArtifactId(), id, null);
if (version == null) {
this.packages.put(id, pkg);
} else if (version.compareTo( pkg.getVersion() ) < 0) {
IClassPackage old = this.packages.get(id);
if (this.isPrimaryPackage(pkg, this)) {
conflicts.add(pkg);
log.info("Canceling upgrade for primary " + old + " [new: " + pkg + "]");
} else {
log.info("Upgrading from " + old + " to " + pkg);
this.removeFromScope(pkg, this);
this.packages.put(id, pkg);
}
} else if (version.compareTo( pkg.getVersion() ) > 0) {
conflicts.add(pkg); this.conflicts.add(pkg);
}
}
return conflicts;
}
public List<IClassPackage> getPackages (boolean recursive) {
List<IClassPackage> pkgs = new ArrayList<IClassPackage>(this.packages.values());
if (recursive)
for (IClassDependencyPool pool : new ArrayList<IClassDependencyPool>(this.children)) {
if (pool.getScope() == PoolScope.Component)
pkgs.addAll(pool.getPackages(recursive));
}
return pkgs;
}
public IVersion getVersion (String groupId, String artifactId, IClassDependencyPool caller) {
return this.getVersion(groupId, artifactId, this.getUniqueId(groupId, artifactId), caller);
}
protected IVersion getVersion (String groupId, String artifactId, String uniqueId, IClassDependencyPool caller) {
IVersion version = null;
if (parent != null && caller != parent) version = parent.getVersion(groupId, artifactId, this);
if (version == null) {
for (IClassDependencyPool pool : new ArrayList<IClassDependencyPool>(this.children)) {
if (pool.getScope() == PoolScope.Root || caller == pool) continue;
version = pool.getVersion(groupId, artifactId, this);
if (version != null) break;
}
}
return version == null ? this.getVersion( uniqueId ) : version;
}
public DependencyPoolSource getCompositeClassSource () {
DependencyPoolSource ccs = new DependencyPoolSource();
if (log.isDebug())
log.debug("Composite source for pool:");
List<String> packageList = new ArrayList<String>();
List<IClassPackage> pkgs = new ArrayList<IClassPackage>(this.packages.values());
Collections.sort(pkgs);
for (IClassPackage pkg : pkgs) {
if (log.isDebug())
log.debug(" ---->" + pkg);
packageList.add(this.getUniqueId(pkg));
ccs.add( pkg.getMainJarSource() );
}
for (IClassDependencyPool pool : new ArrayList<IClassDependencyPool>(this.children)) {
if (pool.getScope() == PoolScope.Component) {
ccs.add(pool.getCompositeClassSource());
}
}
return ccs;
}
public void printHeiarchy(PrintStream out) {
if (this.parent != null) {
this.parent.printHeiarchy(out);
} else {
this.printHeiarchyInternal(this, out, 0);
}
}
protected void printHeiarchyInternal(IClassDependencyPool pool, PrintStream out, int level) {
String prefix = StringUtil.repeat('\t', level);
out.println(prefix + "Pool: " + pool.getName());
for (IClassPackage pkg : pool.getPackages(false)) {
out.println(prefix + " -> " + pkg);
}
for (IClassDependencyPool child : pool.getChildPools()) {
this.printHeiarchyInternal(child, out, level + 1);
}
}
/**
* @param packageId The unique package id
* @return The version of the package found in the pool (or pools parent), or null if it could not be located
*/
protected IVersion getVersion (String packageId) {
return packages.containsKey(packageId) ? packages.get(packageId).getVersion() : null;
}
/**
* @see #getUniqueId(String, String)
*/
protected String getUniqueId (IClassPackage pkg) {
return this.getUniqueId(pkg.getGroupId(), pkg.getArtifactId());
}
/**
* @param groupId The group id of the package
* @param artifactId The artifact id of the package
* @return A unique package id
*/
protected String getUniqueId (String groupId, String artifactId) {
return groupId + ":" + artifactId;
}
/**
* This will allow the pool to be accessible via the returned class source.
*
* @author elponderador
* @author $Author: ponderator $
* @version $Id: ClassDependencyPoolImpl.java 2709 2010-12-31 00:46:45Z ponderator $
*/
public class DependencyPoolSource extends CompositeClassSource {
public DependencyPoolSource () {
super(ClassDependencyPoolImpl.this.getName());
}
/**
* @return The pool this source was generated from
*/
public ClassDependencyPoolImpl getPool () { return ClassDependencyPoolImpl.this; }
}
}