001/* 002 * JDrupes Builder 003 * Copyright (C) 2026 Michael N. Lipp 004 * 005 * This program is free software: you can redistribute it and/or modify 006 * it under the terms of the GNU Affero General Public License as 007 * published by the Free Software Foundation, either version 3 of the 008 * License, or (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 013 * GNU Affero General Public License for more details. 014 * 015 * You should have received a copy of the GNU Affero General Public License 016 * along with this program. If not, see <https://www.gnu.org/licenses/>. 017 */ 018 019package org.jdrupes.builder.bnd; 020 021import aQute.bnd.osgi.Analyzer; 022import io.vavr.control.Try; 023 024import static org.jdrupes.builder.api.Intent.Supply; 025 026import java.util.Map; 027import java.util.jar.Attributes; 028import java.util.stream.Stream; 029import org.jdrupes.builder.api.Project; 030import org.jdrupes.builder.api.Resource; 031import org.jdrupes.builder.api.ResourceRequest; 032import org.jdrupes.builder.api.ResourceType; 033import org.jdrupes.builder.api.Resources; 034import org.jdrupes.builder.core.AbstractGenerator; 035import org.jdrupes.builder.core.AbstractProvider; 036import org.jdrupes.builder.java.ClassTree; 037import org.jdrupes.builder.java.ClasspathElement; 038import static org.jdrupes.builder.java.JavaTypes.*; 039import org.jdrupes.builder.java.LibraryBuilder; 040import org.jdrupes.builder.java.LibraryJarFile; 041 042/// A provider that computes OSGi attributes. It does not provide any 043/// resources. Rather it analyzes the resources of the project and computes 044/// OSGi attributes for the bundle to be eventually built by 045/// [LibraryBuilder]. 046/// 047/// The analyzer first requests the resources of type [ClassTree] supplied 048/// to the project. The classes obtained are assumed to be the content of the 049/// bundle. It then requests the resources of type [LibraryJarFile] 050/// from the project's dependencies with intent `Consume` and `Expose`. 051/// These are considered the external dependencies of the bundle. 052/// 053/// The data is analyzed and provides the requested attributes. 054/// 055public class OsgiAnalyzer extends AbstractGenerator { 056 057 /// Initializes a new osgi analyzer. 058 /// 059 /// @param project the project 060 /// 061 public OsgiAnalyzer(Project project) { 062 super(project); 063 } 064 065 /// Returns the attributes computed by the analyzer. 066 /// 067 /// @return the stream 068 /// 069 public Stream<Map.Entry<Attributes.Name, String>> attributes() { 070 try (var analyzer = new Analyzer()) { 071 // Bundle content 072 var content = newResource(ClassTreesType) 073 .addAll(project().providers() 074 .resources(of(ClassTree.class).using(Supply))); 075 // A bnd ("better never document") jar can be "expanded", i.e. 076 // a directory structure representing the jar content. 077 vavrStream(content).find(_ -> true) 078 .peek(t -> Try.of(() -> analyzer.setJar(t.root().toFile()))); 079 analyzer.setProperty("Export-Package", "*"); 080 081 // .map(t -> t.root()); 082// .forEach(path -> analyzer.setJar(path.toFile())); 083 084// // 2. Identify "External" content (the dependencies) 085// // Filter by Intent as per your doc: Consume and Expose 086// project().dependencies().stream() 087// .flatMap(dep -> dep 088// .resources(ResourceRequest.of(ClasspathElement.class)) 089// .stream()) 090// .map(ClasspathElement::path) 091// .forEach(path -> { 092// try { 093// analyzer.addClasspath(path.toFile()); 094// } catch (Exception e) { 095// // Handle or log classpath resolution errors 096// } 097// }); 098 099 // 3. Compute and transform 100 var manifest = analyzer.calcManifest(); 101 var mainAttrs = manifest.getMainAttributes(); 102 103 // Convert java.util.jar.Attributes to the requested Stream format 104 return mainAttrs.entrySet().stream() 105 .map(e -> Map.entry((Attributes.Name) e.getKey(), 106 (String) e.getValue())); 107 108 } catch (Exception e) { 109 throw new RuntimeException("Failed to analyze OSGi metadata", e); 110 } 111 } 112 113 @Override 114 protected <T extends Resource> Stream<T> 115 doProvide(ResourceRequest<T> requested) { 116 return Stream.empty(); 117 } 118}