diff --git a/tycho-baseline-plugin/pom.xml b/tycho-baseline-plugin/pom.xml
index 5acee8e8ba..f76e211228 100644
--- a/tycho-baseline-plugin/pom.xml
+++ b/tycho-baseline-plugin/pom.xml
@@ -53,6 +53,16 @@
asciitable
0.3.2
+
+ org.ow2.asm
+ asm
+ 9.7.1
+
+
+ org.eclipse.emf
+ org.eclipse.emf.ecore
+ 2.38.0
+
@@ -60,6 +70,10 @@
org.codehaus.plexus
plexus-component-metadata
+
+ org.eclipse.sisu
+ sisu-maven-plugin
+
org.apache.maven.plugins
maven-plugin-plugin
diff --git a/tycho-baseline-plugin/src/main/java/org/eclipse/tycho/baseline/DependencyCheckMojo.java b/tycho-baseline-plugin/src/main/java/org/eclipse/tycho/baseline/DependencyCheckMojo.java
new file mode 100644
index 0000000000..388e485224
--- /dev/null
+++ b/tycho-baseline-plugin/src/main/java/org/eclipse/tycho/baseline/DependencyCheckMojo.java
@@ -0,0 +1,377 @@
+/*******************************************************************************
+ * Copyright (c) 2025 Christoph Läubrich and others.
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Christoph Läubrich - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tycho.baseline;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.stream.Collectors;
+
+import org.apache.maven.execution.MavenSession;
+import org.apache.maven.plugin.AbstractMojo;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.MojoFailureException;
+import org.apache.maven.plugin.logging.Log;
+import org.apache.maven.plugins.annotations.Component;
+import org.apache.maven.plugins.annotations.LifecyclePhase;
+import org.apache.maven.plugins.annotations.Mojo;
+import org.apache.maven.plugins.annotations.Parameter;
+import org.apache.maven.project.MavenProject;
+import org.eclipse.equinox.p2.metadata.IInstallableUnit;
+import org.eclipse.osgi.container.ModuleRevisionBuilder;
+import org.eclipse.osgi.container.ModuleRevisionBuilder.GenericInfo;
+import org.eclipse.osgi.container.builders.OSGiManifestBuilderFactory;
+import org.eclipse.osgi.internal.framework.FilterImpl;
+import org.eclipse.tycho.ArtifactDescriptor;
+import org.eclipse.tycho.ArtifactType;
+import org.eclipse.tycho.DependencyArtifacts;
+import org.eclipse.tycho.PackagingType;
+import org.eclipse.tycho.artifacts.ArtifactVersion;
+import org.eclipse.tycho.artifacts.ArtifactVersionProvider;
+import org.eclipse.tycho.baseline.analyze.ClassCollection;
+import org.eclipse.tycho.baseline.analyze.ClassMethods;
+import org.eclipse.tycho.baseline.analyze.ClassUsage;
+import org.eclipse.tycho.baseline.analyze.JrtClasses;
+import org.eclipse.tycho.baseline.analyze.MethodSignature;
+import org.eclipse.tycho.core.TychoProjectManager;
+import org.eclipse.tycho.core.maven.OSGiJavaToolchain;
+import org.eclipse.tycho.core.maven.ToolchainProvider;
+import org.eclipse.tycho.core.osgitools.BundleReader;
+import org.eclipse.tycho.core.osgitools.OsgiManifest;
+import org.eclipse.tycho.core.resolver.target.ArtifactMatcher;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.Version;
+import org.osgi.framework.VersionRange;
+import org.osgi.framework.namespace.PackageNamespace;
+import org.osgi.resource.Namespace;
+
+/**
+ * This mojos performs deep inspections of dependencies to find out if a version
+ * range is actually valid. For this the following steps are performed:
+ *
+ * - The current project artifact is analyzed for method signatures it
+ * calls
+ * - Then it is checked what of these match to a given dependency
+ * - All dependency versions matching the range are fetched and inspected
+ * using {@link ArtifactVersionProvider}s
+ * - Then it checks if there are any missing signatures or inconsistencies and
+ * possibly failing the build
+ *
+ */
+@Mojo(defaultPhase = LifecyclePhase.VERIFY, name = "check-dependencies", threadSafe = true, requiresProject = true)
+public class DependencyCheckMojo extends AbstractMojo {
+
+ private static final String CLASS_SUFFIX = ".class";
+
+ @Parameter(property = "project", readonly = true)
+ private MavenProject project;
+
+ @Parameter(property = "session", readonly = true)
+ private MavenSession session;
+
+ @Component
+ private TychoProjectManager projectManager;
+
+ @Component
+ private List versionProvider;
+
+ @Component
+ private BundleReader bundleReader;
+
+ @Component
+ ToolchainProvider toolchainProvider;
+
+ @Override
+ public void execute() throws MojoExecutionException, MojoFailureException {
+ if (!"jar".equals(project.getPackaging())
+ && !PackagingType.TYPE_ECLIPSE_PLUGIN.equals(project.getPackaging())) {
+ return;
+ }
+ DependencyArtifacts artifacts = projectManager.getDependencyArtifacts(project).orElse(null);
+ File file = project.getArtifact().getFile();
+ if (file == null || !file.isFile()) {
+ throw new MojoFailureException("Project artifact is not a valid file");
+ }
+ JrtClasses jrtClassResolver = getJRTClassResolver();
+ List usages = analyzeUsage(file, jrtClassResolver);
+ if (usages.isEmpty()) {
+ return;
+ }
+ Collection units = artifacts.getInstallableUnits();
+ ModuleRevisionBuilder builder = readOSGiInfo(file);
+ List requirements = builder.getRequirements();
+ List dependencyProblems = new ArrayList<>();
+ Map analyzeCache = new HashMap<>();
+ Log log = getLog();
+ Map lowestPackageVersion = new HashMap<>();
+ Set packageWithError = new HashSet<>();
+ Function> classResolver = createDependencyClassResolver(jrtClassResolver,
+ artifacts);
+ for (GenericInfo genericInfo : requirements) {
+ if (PackageNamespace.PACKAGE_NAMESPACE.equals(genericInfo.getNamespace())) {
+ Map pkgInfo = getVersionInfo(genericInfo,
+ PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE);
+ String packageVersion = pkgInfo.getOrDefault(PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE, "0.0.0");
+ String packageName = pkgInfo.get(PackageNamespace.PACKAGE_NAMESPACE);
+ Optional packageProvidingUnit = ArtifactMatcher.findPackage(packageName, units);
+ if (packageProvidingUnit.isEmpty()) {
+ continue;
+ }
+ if (packageName.contains(".internal.")) {
+ // TODO configurable, but also internal packages should be properly versioned!
+ continue;
+ }
+ IInstallableUnit unit = packageProvidingUnit.get();
+ ArtifactMatcher.getPackageVersion(unit, packageName).filter(v -> v.isOSGiCompatible())
+ .ifPresent(v -> lowestPackageVersion.put(packageName, new Version(v.toString())));
+ VersionRange versionRange = VersionRange.valueOf(packageVersion);
+ List list = versionProvider.stream()
+ .flatMap(avp -> avp.getPackageVersions(unit, packageName, versionRange, project)).toList();
+ if (log.isDebugEnabled()) {
+ log.debug("== " + packageName + " " + packageVersion + " is provided by " + unit
+ + " with version range " + versionRange + ", matching versions: " + list.stream()
+ .map(av -> av.getVersion()).map(String::valueOf).collect(Collectors.joining(", ")));
+ }
+ Set packageMethods = new TreeSet<>();
+ Map> references = new HashMap<>();
+ for (ClassUsage usage : usages) {
+ usage.signatures().filter(ms -> packageName.equals(ms.packageName())).forEach(sig -> {
+ packageMethods.add(sig);
+ references.computeIfAbsent(sig, nil -> new TreeSet<>()).addAll(usage.classRef(sig));
+ });
+ }
+ if (packageMethods.isEmpty()) {
+ // it could be that actually no methods referenced (e.g. interface is only
+ // referencing a type)
+ // TODO we need to check that the types used are present in all versions as
+ // otherwise we will get CNF exception!
+ // TODO a class can also reference fields!
+ continue;
+ }
+ if (log.isDebugEnabled()) {
+ for (MethodSignature signature : packageMethods) {
+ log.debug("Referenced: " + signature.id());
+ }
+ }
+ // now we need to inspect all jars
+ Set checked = new HashSet<>();
+ for (ArtifactVersion v : list) {
+ Version version = v.getVersion();
+ if (version == null || !checked.add(version)) {
+ continue;
+ }
+ Path artifact = v.getArtifact();
+ log.debug(v + "=" + artifact);
+ if (artifact == null) {
+ // Retrieval of artifacts might be lazy and we can't get this one --> error?
+ continue;
+ }
+ ClassCollection collection = analyzeCache.get(artifact);
+ if (collection == null) {
+ collection = analyzeProvides(artifact.toFile(), classResolver, null);
+ analyzeCache.put(artifact, collection);
+ }
+ boolean ok = true;
+ Set set = collection.provides().collect(Collectors.toSet());
+ for (MethodSignature mthd : packageMethods) {
+ if (!set.contains(mthd)) {
+ List provided = collection.get(mthd.className());
+ if (provided != null) {
+ provided = provided.stream().filter(ms -> packageName.equals(ms.packageName()))
+ .toList();
+ }
+ if (log.isDebugEnabled()) {
+ log.debug("Not found: " + mthd);
+ if (provided != null) {
+ for (MethodSignature s : provided) {
+ log.debug("Provided: " + s);
+ }
+ }
+ }
+ dependencyProblems.add(new DependencyVersionProblem(String.format(
+ "Import-Package '%s %s (compiled against %s %s) includes %s (provided by %s) but this version is missing the method %s",
+ packageName, packageVersion, unit.getId(), unit.getVersion(), v.getVersion(),
+ v.getProvider(), mthd.id()), references.get(mthd), provided));
+ ok = false;
+ packageWithError.add(packageName);
+ }
+ }
+ if (ok) {
+ lowestPackageVersion.merge(packageName, version, (v1,v2)->{
+ if (v1.compareTo(v2)>0) {
+ return v2;
+ }
+ return v1;
+ });
+ }
+ }
+ // TODO we should emit a warning if the lower bound is not part of the
+ // discovered versions (or even fail?)
+
+ }
+ }
+ if (dependencyProblems.isEmpty()) {
+ return;
+ }
+ List results = new ArrayList<>();
+ for (DependencyVersionProblem problem : dependencyProblems) {
+ Collection references = problem.references();
+ String message;
+ if (references == null || references.isEmpty()) {
+ message = problem.message();
+ } else {
+ message = String.format("%s, referenced by:%s%s", problem.message(), System.lineSeparator(),
+ problem.references().stream().collect(Collectors.joining(System.lineSeparator())));
+ }
+ log.error(message);
+ results.add(message);
+ List provided = problem.provided();
+ if (provided != null && !provided.isEmpty()) {
+ results.add("Provided Methods in this version are:");
+ for (MethodSignature sig : provided) {
+ results.add("\t" + sig);
+ }
+ }
+ }
+ for (String pkg : packageWithError) {
+ String suggestion = "Suggested lower version for package " + pkg + " : " + lowestPackageVersion.get(pkg);
+ log.info(suggestion);
+ results.add(suggestion);
+ }
+ if (results.isEmpty()) {
+ return;
+ }
+ Path path = project.getBasedir().toPath().resolve("versionProblems.txt");
+ try {
+ Files.writeString(path, results.stream().collect(Collectors.joining(System.lineSeparator())));
+ } catch (IOException e) {
+ }
+ }
+
+ private Function> createDependencyClassResolver(JrtClasses jrtClassResolver,
+ DependencyArtifacts artifacts) throws MojoFailureException {
+ ClassCollection allClassMethods = new ClassCollection();
+ Function> function = allClassMethods.chain(jrtClassResolver);
+ List list = artifacts.getArtifacts(ArtifactType.TYPE_ECLIPSE_PLUGIN);
+ for (ArtifactDescriptor descriptor : list) {
+ File file = descriptor.fetchArtifact().join();
+ analyzeProvides(file, function, allClassMethods);
+ }
+ return function;
+ }
+
+ private Map getVersionInfo(GenericInfo genericInfo, String versionAttribute) {
+ Map directives = new HashMap<>(genericInfo.getDirectives());
+ String filter = directives.remove(Namespace.REQUIREMENT_FILTER_DIRECTIVE);
+ FilterImpl filterImpl;
+ try {
+ filterImpl = FilterImpl.newInstance(filter);
+ } catch (InvalidSyntaxException e) {
+ throw new IllegalArgumentException("Invalid filter directive", e); //$NON-NLS-1$
+ }
+ return filterImpl.getStandardOSGiAttributes(versionAttribute);
+ }
+
+ private ModuleRevisionBuilder readOSGiInfo(File file) throws MojoFailureException {
+ OsgiManifest manifest = bundleReader.loadManifest(file);
+ ModuleRevisionBuilder builder;
+ try {
+ builder = OSGiManifestBuilderFactory.createBuilder(manifest.getHeaders());
+ } catch (BundleException e) {
+ throw new MojoFailureException(e);
+ }
+ return builder;
+ }
+
+ private List analyzeUsage(File file, JrtClasses jre) throws MojoFailureException {
+ List usages = new ArrayList<>();
+ try {
+ try (JarFile jar = new JarFile(file)) {
+ Enumeration entries = jar.entries();
+ while (entries.hasMoreElements()) {
+ JarEntry jarEntry = entries.nextElement();
+ String name = jarEntry.getName();
+ if (name.endsWith(CLASS_SUFFIX)) {
+ InputStream stream = jar.getInputStream(jarEntry);
+ usages.add(new ClassUsage(stream.readAllBytes(), jre));
+ }
+ }
+ }
+ return usages;
+ } catch (IOException e) {
+ throw new MojoFailureException(e);
+ }
+ }
+
+ private ClassCollection analyzeProvides(File file, Function> classResolver,
+ Consumer consumer)
+ throws MojoFailureException {
+ try {
+ ClassCollection local = new ClassCollection();
+ Function> resolver = local.chain(classResolver);
+ try (JarFile jar = new JarFile(file)) {
+ Enumeration entries = jar.entries();
+ while (entries.hasMoreElements()) {
+ JarEntry jarEntry = entries.nextElement();
+ String name = jarEntry.getName();
+ if (name.endsWith(CLASS_SUFFIX)) {
+ InputStream stream = jar.getInputStream(jarEntry);
+ ClassMethods methods = new ClassMethods(stream.readAllBytes(), resolver);
+ if (consumer != null) {
+ consumer.accept(methods);
+ }
+ local.accept(methods);
+ }
+ }
+ }
+ return local;
+ } catch (IOException e) {
+ throw new MojoFailureException(e);
+ }
+ }
+
+ private JrtClasses getJRTClassResolver() {
+ String profileName = projectManager.getExecutionEnvironments(project, session).findFirst()
+ .map(ee -> ee.getProfileName()).orElse(null);
+ if (profileName != null) {
+ OSGiJavaToolchain osgiToolchain = toolchainProvider.getToolchain(profileName).orElse(null);
+ if (osgiToolchain != null) {
+ return new JrtClasses(osgiToolchain.getJavaHome());
+ }
+ }
+ // use running jvm
+ return new JrtClasses(null);
+ }
+
+ private static record DependencyVersionProblem(String message, Collection references,
+ List provided) {
+
+ }
+}
diff --git a/tycho-baseline-plugin/src/main/java/org/eclipse/tycho/baseline/analyze/ClassCollection.java b/tycho-baseline-plugin/src/main/java/org/eclipse/tycho/baseline/analyze/ClassCollection.java
new file mode 100644
index 0000000000..2acf030db4
--- /dev/null
+++ b/tycho-baseline-plugin/src/main/java/org/eclipse/tycho/baseline/analyze/ClassCollection.java
@@ -0,0 +1,54 @@
+/*******************************************************************************
+ * Copyright (c) 2025 Christoph Läubrich and others.
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Christoph Läubrich - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tycho.baseline.analyze;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.stream.Stream;
+
+public class ClassCollection implements Function>, Consumer {
+
+ private Map classLookupMap = new HashMap<>();
+
+ @Override
+ public Optional apply(String className) {
+ return Optional.ofNullable(classLookupMap.get(className));
+ }
+
+ public Stream provides() {
+
+ return classLookupMap.values().stream().distinct().flatMap(cm -> cm.provides());
+ }
+
+ public List get(String className) {
+ return apply(className).stream().flatMap(cm -> cm.provides()).toList();
+ }
+
+ public Function> chain(Function> chained) {
+ return cls -> {
+ return apply(cls).or(() -> chained.apply(cls));
+ };
+ }
+
+ @Override
+ public void accept(ClassMethods methods) {
+ methods.definitions().forEach(def -> {
+ classLookupMap.put(def.name(), methods);
+ });
+ }
+
+}
diff --git a/tycho-baseline-plugin/src/main/java/org/eclipse/tycho/baseline/analyze/ClassDef.java b/tycho-baseline-plugin/src/main/java/org/eclipse/tycho/baseline/analyze/ClassDef.java
new file mode 100644
index 0000000000..1b9d9359de
--- /dev/null
+++ b/tycho-baseline-plugin/src/main/java/org/eclipse/tycho/baseline/analyze/ClassDef.java
@@ -0,0 +1,17 @@
+/*******************************************************************************
+ * Copyright (c) 2025 Christoph Läubrich and others.
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Christoph Läubrich - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tycho.baseline.analyze;
+
+record ClassDef(int access, String name, String signature, String superName, String[] interfaces) {
+
+}
diff --git a/tycho-baseline-plugin/src/main/java/org/eclipse/tycho/baseline/analyze/ClassMethodSignature.java b/tycho-baseline-plugin/src/main/java/org/eclipse/tycho/baseline/analyze/ClassMethodSignature.java
new file mode 100644
index 0000000000..850965370b
--- /dev/null
+++ b/tycho-baseline-plugin/src/main/java/org/eclipse/tycho/baseline/analyze/ClassMethodSignature.java
@@ -0,0 +1,17 @@
+/*******************************************************************************
+ * Copyright (c) 2025 Christoph Läubrich and others.
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Christoph Läubrich - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tycho.baseline.analyze;
+
+record ClassMethodSignature(ClassDef clazz, int access, String name, String descriptor, String signature) {
+
+}
diff --git a/tycho-baseline-plugin/src/main/java/org/eclipse/tycho/baseline/analyze/ClassMethods.java b/tycho-baseline-plugin/src/main/java/org/eclipse/tycho/baseline/analyze/ClassMethods.java
new file mode 100644
index 0000000000..01d097dc28
--- /dev/null
+++ b/tycho-baseline-plugin/src/main/java/org/eclipse/tycho/baseline/analyze/ClassMethods.java
@@ -0,0 +1,100 @@
+/*******************************************************************************
+ * Copyright (c) 2025 Christoph Läubrich and others.
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Christoph Läubrich - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tycho.baseline.analyze;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Stream;
+
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+/**
+ * Analyze a class about methods it possibly provides to callers
+ */
+public class ClassMethods {
+
+ private List classDefs = new ArrayList<>();
+ private List signatures = new ArrayList<>();
+ private Function> supplier;
+ private Set collect;
+
+ public ClassMethods(byte[] classbytes, Function> supplier) {
+ this.supplier = supplier;
+ ClassReader reader = new ClassReader(classbytes);
+ reader.accept(new ClassVisitor(DependencyAnalyzer.ASM_API) {
+
+ private ClassDef classDef;
+
+ @Override
+ public void visit(int version, int access, String name, String signature, String superName,
+ String[] interfaces) {
+ if ((access & Opcodes.ACC_PRIVATE) != 0) {
+ // private methods can not be called
+ return;
+ }
+ classDef = new ClassDef(access, name, signature, superName, interfaces);
+ classDefs.add(classDef);
+ }
+
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String descriptor, String signature,
+ String[] exceptions) {
+ signatures.add(new ClassMethodSignature(classDef, access, name, descriptor, signature));
+ return null;
+ }
+ }, ClassReader.SKIP_FRAMES);
+ }
+
+ Stream definitions() {
+ return classDefs.stream();
+ }
+
+ /**
+ * @return a stream of all method signatures this class can provide
+ */
+ Stream provides() {
+ // all methods declared by our class are provided
+ Stream declared = signatures.stream().map(cms -> {
+ return new MethodSignature(cms.clazz().name(), cms.name(), cms.descriptor());
+ });
+ // and from the super class, transformed with our class as the classname
+ Stream supermethods = classDefs.stream().flatMap(cd -> {
+ return Optional.ofNullable(cd.superName()).flatMap(cn -> findRef(cn)).stream().flatMap(cm -> cm.provides())
+ .map(ms -> new MethodSignature(cd.name(), ms.methodName(), ms.signature()));
+ });
+ // and possibly from interfaces
+ Stream interfaces = classDefs.stream().flatMap(cd -> {
+ return Optional.ofNullable(cd.interfaces()).stream().flatMap(Arrays::stream)
+ .flatMap(cn -> findRef(cn).stream()).flatMap(cm -> cm.provides())
+ .map(ms -> new MethodSignature(cd.name(), ms.methodName(), ms.signature()));
+ });
+ Stream inherited = Stream.concat(supermethods, interfaces);
+ return Stream.concat(declared, inherited).distinct();
+ }
+
+ private Optional findRef(String cn) {
+ Optional ref = supplier.apply(cn);
+ if (ref.isEmpty()) {
+ System.out.println("Referenced class " + cn + " was not found by the supplier!");
+ }
+ return ref;
+ }
+
+}
diff --git a/tycho-baseline-plugin/src/main/java/org/eclipse/tycho/baseline/analyze/ClassUsage.java b/tycho-baseline-plugin/src/main/java/org/eclipse/tycho/baseline/analyze/ClassUsage.java
new file mode 100644
index 0000000000..bc42aba0e0
--- /dev/null
+++ b/tycho-baseline-plugin/src/main/java/org/eclipse/tycho/baseline/analyze/ClassUsage.java
@@ -0,0 +1,76 @@
+/*******************************************************************************
+ * Copyright (c) 2025 Christoph Läubrich and others.
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Christoph Läubrich - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tycho.baseline.analyze;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.stream.Stream;
+
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+/**
+ * Analyze code that is used by the class itself
+ */
+public class ClassUsage {
+
+ private Set usedMethodSignatures = new HashSet<>();
+ private Map> classRef = new HashMap<>();
+
+ public ClassUsage(byte[] classbytes, JrtClasses jrt) {
+ ClassReader reader = new ClassReader(classbytes);
+ reader.accept(new ClassVisitor(Opcodes.ASM9) {
+
+ private String className;
+
+ @Override
+ public void visit(int version, int access, String name, String signature, String superName,
+ String[] interfaces) {
+ this.className = name.replace('/', '.');
+ }
+
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String descriptor, String signature,
+ String[] exceptions) {
+ return new MethodVisitor(Opcodes.ASM9) {
+ @Override
+ public void visitMethodInsn(int opcode, String owner, String name, String descriptor,
+ boolean isInterface) {
+ if (jrt.apply(owner).isPresent()) {
+ // ignore references to java provided classes
+ return;
+ }
+ MethodSignature sig = new MethodSignature(owner, name, descriptor);
+ classRef.computeIfAbsent(sig, nil -> new TreeSet<>()).add(className);
+ usedMethodSignatures.add(sig);
+ }
+ };
+ }
+ }, ClassReader.SKIP_FRAMES);
+ }
+
+ public Stream signatures() {
+ return usedMethodSignatures.stream();
+ }
+
+ public Collection classRef(MethodSignature mthd) {
+ return classRef.getOrDefault(mthd, List.of());
+ }
+}
diff --git a/tycho-baseline-plugin/src/main/java/org/eclipse/tycho/baseline/analyze/DependencyAnalyzer.java b/tycho-baseline-plugin/src/main/java/org/eclipse/tycho/baseline/analyze/DependencyAnalyzer.java
new file mode 100644
index 0000000000..0933881c19
--- /dev/null
+++ b/tycho-baseline-plugin/src/main/java/org/eclipse/tycho/baseline/analyze/DependencyAnalyzer.java
@@ -0,0 +1,29 @@
+/*******************************************************************************
+ * Copyright (c) 2025 Christoph Läubrich and others.
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Christoph Läubrich - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tycho.baseline.analyze;
+
+import org.objectweb.asm.Opcodes;
+
+public class DependencyAnalyzer {
+
+ static final int ASM_API = Opcodes.ASM9;
+
+ public static String getPackageName(String className) {
+ className = className.replace('/', '.');
+ int idx = className.lastIndexOf('.');
+ if (idx > 0) {
+ return className.substring(0, idx);
+ }
+ return className;
+ }
+}
diff --git a/tycho-baseline-plugin/src/main/java/org/eclipse/tycho/baseline/analyze/JrtClasses.java b/tycho-baseline-plugin/src/main/java/org/eclipse/tycho/baseline/analyze/JrtClasses.java
new file mode 100644
index 0000000000..6626724893
--- /dev/null
+++ b/tycho-baseline-plugin/src/main/java/org/eclipse/tycho/baseline/analyze/JrtClasses.java
@@ -0,0 +1,98 @@
+/*******************************************************************************
+ * Copyright (c) 2025 Christoph Läubrich and others.
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Christoph Läubrich - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tycho.baseline.analyze;
+
+import java.io.IOException;
+import java.net.URI;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Function;
+import java.util.stream.Stream;
+
+
+/**
+ * Lookup of all classes provided by the java runtime
+ */
+public class JrtClasses implements Function> {
+
+ private Path rootPath;
+ private Map> cache = new ConcurrentHashMap<>();
+
+ public JrtClasses(String javaHome) {
+ try {
+ Map map;
+ if (javaHome != null) {
+ map = Map.of("java.home", javaHome);
+ } else {
+ map = Map.of();
+ }
+ FileSystem fs = FileSystems.newFileSystem(URI.create("jrt:/"), map);
+ rootPath = fs.getPath("/packages");
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public Optional apply(String className) {
+ if (rootPath == null) {
+ return Optional.empty();
+ }
+ return cache.computeIfAbsent(className.replace('.', '/'), path -> lookupJreClass(className));
+ }
+
+ private Optional lookupJreClass(String classPath) {
+// Paths in the "jrt:/" NIO filesystem are of this form:
+//
+// /modules/$MODULE/$PATH
+// /packages/$PACKAGE/$MODULE
+//
+// where $PACKAGE is a package name (e.g., "java.lang"). A path of the
+// second form names a symbolic link which, in turn, points to the
+// directory under /modules that contains a module that defines that
+// package. Example:
+//
+// /packages/java.lang/java.base -> /modules/java.base
+//
+// To find java/sql/Array.class without knowing its module you look up
+// /packages/java.sql, which is a directory, and enumerate its entries.
+// In this case there will be just one entry, a symbolic link named
+// "java.sql", which will point to /modules/java.sql, which will contain
+// java/sql/Array.class.
+//
+ String packageName = DependencyAnalyzer.getPackageName(classPath);
+ Path modulesPath = rootPath.resolve(packageName);
+ if (Files.isDirectory(modulesPath)) {
+ try (Stream module = Files.list(modulesPath)) {
+ Iterator iterator = module.iterator();
+ while (iterator.hasNext()) {
+ Path modulePath = iterator.next();
+ Path classFile = modulePath.resolve(classPath + ".class");
+ if (Files.isRegularFile(classFile)) {
+ return Optional.of(new ClassMethods(Files.readAllBytes(classFile), JrtClasses.this));
+ }
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ return Optional.empty();
+ }
+
+}
diff --git a/tycho-baseline-plugin/src/main/java/org/eclipse/tycho/baseline/analyze/MethodSignature.java b/tycho-baseline-plugin/src/main/java/org/eclipse/tycho/baseline/analyze/MethodSignature.java
new file mode 100644
index 0000000000..5bbaa9973b
--- /dev/null
+++ b/tycho-baseline-plugin/src/main/java/org/eclipse/tycho/baseline/analyze/MethodSignature.java
@@ -0,0 +1,31 @@
+/*******************************************************************************
+ * Copyright (c) 2025 Christoph Läubrich and others.
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Christoph Läubrich - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tycho.baseline.analyze;
+
+public record MethodSignature(String className, String methodName, String signature)
+ implements Comparable {
+
+ public String packageName() {
+ String cn = className();
+ return DependencyAnalyzer.getPackageName(cn);
+ }
+
+ public String id() {
+ return className() + "#" + methodName() + signature();
+ }
+
+ @Override
+ public int compareTo(MethodSignature o) {
+ return id().compareTo(o.id());
+ }
+}
\ No newline at end of file
diff --git a/tycho-baseline-plugin/src/main/java/org/eclipse/tycho/baseline/provider/EclipseIndexArtifactVersionProvider.java b/tycho-baseline-plugin/src/main/java/org/eclipse/tycho/baseline/provider/EclipseIndexArtifactVersionProvider.java
new file mode 100644
index 0000000000..2640062396
--- /dev/null
+++ b/tycho-baseline-plugin/src/main/java/org/eclipse/tycho/baseline/provider/EclipseIndexArtifactVersionProvider.java
@@ -0,0 +1,171 @@
+/*******************************************************************************
+ * Copyright (c) 2024 Christoph Läubrich and others.
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Christoph Läubrich - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tycho.baseline.provider;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Stream;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+import org.apache.maven.project.MavenProject;
+import org.eclipse.equinox.p2.metadata.IInstallableUnit;
+import org.eclipse.equinox.p2.metadata.Version;
+import org.eclipse.equinox.p2.query.QueryUtil;
+import org.eclipse.equinox.p2.repository.metadata.IMetadataRepository;
+import org.eclipse.equinox.spi.p2.publisher.PublisherHelper;
+import org.eclipse.tycho.artifacts.ArtifactVersion;
+import org.eclipse.tycho.artifacts.ArtifactVersionProvider;
+import org.eclipse.tycho.copyfrom.oomph.P2Index;
+import org.eclipse.tycho.copyfrom.oomph.P2Index.Repository;
+import org.eclipse.tycho.copyfrom.oomph.P2IndexImpl;
+import org.eclipse.tycho.core.resolver.target.ArtifactMatcher;
+import org.eclipse.tycho.p2maven.repository.P2RepositoryManager;
+import org.eclipse.tycho.p2maven.transport.TransportCacheConfig;
+import org.osgi.framework.VersionRange;
+
+/**
+ * {@link ArtifactVersionProvider} using eclipse index
+ */
+@Named
+public class EclipseIndexArtifactVersionProvider implements ArtifactVersionProvider {
+
+ private P2Index p2Index;
+ private P2RepositoryManager repositoryManager;
+
+ @Inject
+ public EclipseIndexArtifactVersionProvider(TransportCacheConfig cacheConfig,
+ P2RepositoryManager repositoryManager) {
+ this.repositoryManager = repositoryManager;
+ p2Index = new P2IndexImpl(new File(cacheConfig.getCacheLocation(), "index"));
+ }
+
+ @Override
+ public Stream getPackageVersions(IInstallableUnit unit, String packageName,
+ VersionRange versionRange, MavenProject mavenProject) {
+ Map> map = p2Index.lookupCapabilities(PublisherHelper.CAPABILITY_NS_JAVA_PACKAGE,
+ packageName);
+ Map> found = new HashMap<>();
+ map.entrySet().forEach(entry -> {
+ entry.getValue().stream().filter(v -> v.isOSGiCompatible()).forEach(v -> {
+ found.computeIfAbsent(v, x -> new ArrayList<>()).add(entry.getKey());
+ });
+ });
+ String id = unit.getId();
+ return found.entrySet().stream().map(entry -> {
+ return new EclipseIndexArtifactVersion(entry.getValue(), id, packageName, entry.getKey());
+ }).filter(eia -> versionRange.includes(eia.getVersion()))
+ .sorted(Comparator.comparing(EclipseIndexArtifactVersion::getVersion).reversed())
+ .map(ArtifactVersion.class::cast);
+ }
+
+ private class EclipseIndexArtifactVersion implements ArtifactVersion {
+
+ private Version version;
+// private org.apache.maven.model.Repository repository;
+ private String packageName;
+ private Path tempFile;
+ private String unitId;
+ private List repositories;
+ private org.osgi.framework.Version osgiVersion;
+ private Optional unit;
+
+ public EclipseIndexArtifactVersion(List repositories, String unitId, String packageName,
+ Version version) {
+ osgiVersion = org.osgi.framework.Version.parseVersion(version.getOriginal());
+ this.repositories = repositories;
+
+ this.unitId = unitId;
+ this.packageName = packageName;
+ this.version = version;
+ }
+
+ @Override
+ public Path getArtifact() {
+ if (tempFile == null) {
+ IInstallableUnit unit = getUnit().orElse(null);
+ if (unit != null) {
+ Path file;
+ try {
+ file = Files.createTempFile(unit.getId(), ".jar");
+ } catch (IOException e) {
+ return null;
+ }
+ file.toFile().deleteOnExit();
+ for (Repository repository : repositories) {
+ org.apache.maven.model.Repository r = new org.apache.maven.model.Repository();
+ r.setUrl(repository.getLocation().toString());
+ try {
+ try (OutputStream stream = Files.newOutputStream(file)) {
+ repositoryManager.downloadArtifact(unit, repositoryManager.getArtifactRepository(r),
+ stream);
+ return tempFile = file;
+ }
+ } catch (Exception e) {
+ }
+ }
+ file.toFile().delete();
+ }
+ }
+ return tempFile;
+ }
+
+ private Optional getUnit() {
+ if (unit == null) {
+ for (Repository repository : repositories) {
+ org.apache.maven.model.Repository r = new org.apache.maven.model.Repository();
+ r.setUrl(repository.getLocation().toString());
+ try {
+ IMetadataRepository metadataRepository = repositoryManager.getMetadataRepository(r);
+ return unit = ArtifactMatcher.findPackage(packageName,
+ metadataRepository.query(QueryUtil.createIUQuery(unitId), null), version);
+ } catch (Exception e) {
+ }
+ }
+ }
+ return Objects.requireNonNullElse(unit, Optional.empty());
+ }
+
+ @Override
+ public org.osgi.framework.Version getVersion() {
+ return osgiVersion;
+ }
+
+ @Override
+ public String toString() {
+ if (unit != null && unit.isPresent()) {
+ return getVersion() + " (" + unit.get() + ")";
+ }
+ return getVersion().toString();
+ }
+
+ @Override
+ public String getProvider() {
+ return getUnit().map(unit -> unit.getId() + " " + unit.getVersion()).orElse(null);
+ }
+
+ }
+
+}
diff --git a/tycho-baseline-plugin/src/main/java/org/eclipse/tycho/baseline/provider/MavenArtifactVersionProvider.java b/tycho-baseline-plugin/src/main/java/org/eclipse/tycho/baseline/provider/MavenArtifactVersionProvider.java
new file mode 100644
index 0000000000..285382e105
--- /dev/null
+++ b/tycho-baseline-plugin/src/main/java/org/eclipse/tycho/baseline/provider/MavenArtifactVersionProvider.java
@@ -0,0 +1,177 @@
+/*******************************************************************************
+ * Copyright (c) 2024 Christoph Läubrich and others.
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Christoph Läubrich - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tycho.baseline.provider;
+
+import java.nio.file.Path;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Stream;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+import org.apache.maven.RepositoryUtils;
+import org.apache.maven.SessionScoped;
+import org.apache.maven.execution.MavenSession;
+import org.apache.maven.project.MavenProject;
+import org.eclipse.aether.RepositorySystem;
+import org.eclipse.aether.artifact.Artifact;
+import org.eclipse.aether.artifact.DefaultArtifact;
+import org.eclipse.aether.repository.RemoteRepository;
+import org.eclipse.aether.resolution.ArtifactRequest;
+import org.eclipse.aether.resolution.ArtifactResolutionException;
+import org.eclipse.aether.resolution.ArtifactResult;
+import org.eclipse.aether.resolution.VersionRangeRequest;
+import org.eclipse.aether.resolution.VersionRangeResolutionException;
+import org.eclipse.aether.resolution.VersionRangeResult;
+import org.eclipse.aether.version.Version;
+import org.eclipse.equinox.p2.metadata.IInstallableUnit;
+import org.eclipse.osgi.container.ModuleRevisionBuilder;
+import org.eclipse.osgi.container.ModuleRevisionBuilder.GenericInfo;
+import org.eclipse.osgi.container.builders.OSGiManifestBuilderFactory;
+import org.eclipse.tycho.TychoConstants;
+import org.eclipse.tycho.artifacts.ArtifactVersion;
+import org.eclipse.tycho.artifacts.ArtifactVersionProvider;
+import org.eclipse.tycho.core.osgitools.BundleReader;
+import org.eclipse.tycho.core.osgitools.OsgiManifest;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.VersionRange;
+import org.osgi.framework.namespace.PackageNamespace;
+
+/**
+ * A {@link ArtifactVersionProvider} that checks maven repository for possible
+ * candidates
+ */
+@Named
+@SessionScoped
+public class MavenArtifactVersionProvider implements ArtifactVersionProvider {
+
+ private MavenSession session;
+ private RepositorySystem repoSystem;
+ private BundleReader bundleReader;
+
+ @Inject
+ public MavenArtifactVersionProvider(MavenSession session, RepositorySystem repoSystem, BundleReader bundleReader) {
+ this.session = session;
+ this.repoSystem = repoSystem;
+ this.bundleReader = bundleReader;
+ }
+
+ @Override
+ public Stream getPackageVersions(IInstallableUnit unit, String packageName,
+ VersionRange versionRange, MavenProject mavenProject) {
+ String groupId = unit.getProperty(TychoConstants.PROP_GROUP_ID);
+ String artifactId = unit.getProperty(TychoConstants.PROP_ARTIFACT_ID);
+ String classifier = unit.getProperty(TychoConstants.PROP_CLASSIFIER);
+ if (groupId != null && artifactId != null && !"sources".equals(classifier)) {
+ List repositories = RepositoryUtils.toRepos(mavenProject.getRemoteArtifactRepositories());
+ DefaultArtifact artifact = new DefaultArtifact(groupId, artifactId, classifier, "jar", "[0,)");
+ // as we have no mean for a package version in maven we can only fetch all
+ // versions an check if the match
+ VersionRangeRequest rangeRequest = new VersionRangeRequest(artifact, repositories, "");
+ try {
+ VersionRangeResult range = repoSystem.resolveVersionRange(session.getRepositorySession(), rangeRequest);
+ // now we sort from highest > lowest version
+ List versions = range.getVersions().stream()
+ .sorted(Comparator.naturalOrder().reversed()).toList();
+ return versions.stream()
+ .map(v -> new MavenPackageArtifactVersion(artifact, v, packageName, repositories))
+ .filter(mav -> mav.getVersion() != null)
+ // and drop all until we find a matching version
+ .dropWhile(mav -> !versionRange.includes(mav.getVersion()))
+ // and stop when we find the first non matching version
+ .takeWhile(mav -> versionRange.includes(mav.getVersion()))
+ // cast to make compiler happy
+ .map(ArtifactVersion.class::cast);
+ } catch (VersionRangeResolutionException e) {
+ // can't provide any useful data then...
+ }
+ }
+ return Stream.empty();
+ }
+
+ private class MavenPackageArtifactVersion implements ArtifactVersion {
+
+ private Artifact artifact;
+ private List repositories;
+ private Path path;
+ private String packageName;
+ private org.osgi.framework.Version packageVersion;
+
+ public MavenPackageArtifactVersion(Artifact artifact, Version version, String packageName,
+ List repositories) {
+ this.artifact = new DefaultArtifact(artifact.getGroupId(), artifact.getArtifactId(),
+ artifact.getExtension(), version.toString());
+ this.packageName = packageName;
+ this.repositories = repositories;
+ }
+
+ @Override
+ public Path getArtifact() {
+ try {
+ ArtifactRequest request = new ArtifactRequest(artifact, repositories, "");
+ ArtifactResult result = repoSystem.resolveArtifact(session.getRepositorySession(), request);
+ path = result.getArtifact().getFile().toPath();
+ } catch (ArtifactResolutionException e) {
+ }
+ return path;
+ }
+
+ @Override
+ public org.osgi.framework.Version getVersion() {
+ if (packageVersion == null) {
+ ModuleRevisionBuilder builder = readOSGiInfo(getArtifact());
+ if (builder != null) {
+ List capabilities = builder.getCapabilities(PackageNamespace.PACKAGE_NAMESPACE);
+ for (GenericInfo info : capabilities) {
+ Map attributes = info.getAttributes();
+ if (packageName.equals(attributes.get(PackageNamespace.PACKAGE_NAMESPACE))) {
+ packageVersion = (org.osgi.framework.Version) attributes.getOrDefault(
+ PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE,
+ org.osgi.framework.Version.emptyVersion);
+ }
+ }
+ }
+ }
+ return packageVersion;
+ }
+
+ @Override
+ public String toString() {
+ return getVersion() + " (maven artifact " + artifact + ")";
+ }
+
+ @Override
+ public String getProvider() {
+ ModuleRevisionBuilder info = readOSGiInfo(getArtifact());
+ if (info != null) {
+ return info.getSymbolicName() + " " + info.getVersion();
+ }
+ return null;
+ }
+
+ }
+
+ private ModuleRevisionBuilder readOSGiInfo(Path path) {
+ if (path != null) {
+ OsgiManifest manifest = bundleReader.loadManifest(path.toFile());
+ try {
+ return OSGiManifestBuilderFactory.createBuilder(manifest.getHeaders());
+ } catch (BundleException e) {
+ }
+ }
+ return null;
+ }
+
+}
diff --git a/tycho-baseline-plugin/src/main/java/org/eclipse/tycho/copyfrom/oomph/P2Index.java b/tycho-baseline-plugin/src/main/java/org/eclipse/tycho/copyfrom/oomph/P2Index.java
index 03cc9203fa..7e1db8dbbb 100644
--- a/tycho-baseline-plugin/src/main/java/org/eclipse/tycho/copyfrom/oomph/P2Index.java
+++ b/tycho-baseline-plugin/src/main/java/org/eclipse/tycho/copyfrom/oomph/P2Index.java
@@ -8,15 +8,14 @@
* Contributors:
* Eike Stepper - initial API and implementation
*/
-package org.eclipse.oomph.p2.internal.core;
-
-import org.eclipse.emf.common.util.URI;
-
-import org.eclipse.equinox.p2.metadata.Version;
+package org.eclipse.tycho.copyfrom.oomph;
import java.util.Map;
import java.util.Set;
+import org.eclipse.emf.common.util.URI;
+import org.eclipse.equinox.p2.metadata.Version;
+
/**
* @author Eike Stepper
*/
@@ -26,8 +25,6 @@ public interface P2Index
public static final int COMPOSED_REPOSITORY = 1;
- public static final P2Index INSTANCE = P2IndexImpl.INSTANCE;
-
public Repository[] getRepositories();
public Map> getCapabilities();
diff --git a/tycho-baseline-plugin/src/main/java/org/eclipse/tycho/copyfrom/oomph/P2IndexImpl.java b/tycho-baseline-plugin/src/main/java/org/eclipse/tycho/copyfrom/oomph/P2IndexImpl.java
index b7df4d3433..a728ea9032 100644
--- a/tycho-baseline-plugin/src/main/java/org/eclipse/tycho/copyfrom/oomph/P2IndexImpl.java
+++ b/tycho-baseline-plugin/src/main/java/org/eclipse/tycho/copyfrom/oomph/P2IndexImpl.java
@@ -8,22 +8,10 @@
* Contributors:
* Eike Stepper - initial API and implementation
*/
-package org.eclipse.oomph.p2.internal.core;
-
-import org.eclipse.oomph.util.CollectionUtil;
-import org.eclipse.oomph.util.IOUtil;
-import org.eclipse.oomph.util.StringUtil;
-
-import org.eclipse.emf.common.util.URI;
-import org.eclipse.emf.ecore.resource.impl.BinaryResourceImpl;
-import org.eclipse.emf.ecore.resource.impl.BinaryResourceImpl.EObjectInputStream;
-
-import org.eclipse.core.runtime.IPath;
-import org.eclipse.core.runtime.IStatus;
-import org.eclipse.core.runtime.Path;
-import org.eclipse.equinox.p2.metadata.Version;
+package org.eclipse.tycho.copyfrom.oomph;
import java.io.BufferedReader;
+import java.io.Closeable;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
@@ -38,555 +26,528 @@
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
+import org.eclipse.emf.common.util.URI;
+import org.eclipse.emf.ecore.resource.impl.BinaryResourceImpl;
+import org.eclipse.emf.ecore.resource.impl.BinaryResourceImpl.EObjectInputStream;
+import org.eclipse.equinox.p2.metadata.Version;
+
/**
* @author Eike Stepper
*/
-public class P2IndexImpl implements P2Index
-{
- public static final P2IndexImpl INSTANCE = new P2IndexImpl();
-
- private static final String INDEX_BASE = "https://download.eclipse.org/oomph/index/"; //$NON-NLS-1$
-
- private long timeStamp;
-
- private Map repositories;
-
- private Repository[] repositoriesArray;
-
- private Map> capabilitiesMap;
-
- private File repositoriesCacheFile;
-
- private File capabilitiesCacheFile;
-
- private int capabilitiesRefreshHours = -1;
-
- private int repositoriesRefreshHours = -1;
-
- private P2IndexImpl()
- {
- }
-
- private synchronized void initCapabilities()
- {
- if (capabilitiesMap == null || capabilitiesCacheFile.lastModified() + capabilitiesRefreshHours * 60 * 60 * 1000 < System.currentTimeMillis())
- {
- capabilitiesMap = new LinkedHashMap<>();
-
- ZipFile zipFile = null;
- InputStream inputStream = null;
-
- try
- {
- initCapabilitiesCacheFile();
-
- zipFile = new ZipFile(capabilitiesCacheFile);
- ZipEntry zipEntry = zipFile.getEntry("capabilities"); //$NON-NLS-1$
-
- inputStream = zipFile.getInputStream(zipEntry);
-
- Map