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}