<commit_before>/*
 * Copyright 2001-2005 The Apache Software Foundation.
 *
 * Licensed 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 top.marchand.xml.maven.catalog;

import top.marchand.xml.maven.catalog.model.CatalogModel;
import top.marchand.xml.maven.catalog.model.RewriteSystemModel;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javanet.staxutils.IndentingXMLStreamWriter;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.DependencyResolutionRequiredException;
import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
import org.apache.maven.artifact.versioning.OverConstrainedVersionException;
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.plugins.annotations.ResolutionScope;
import org.apache.maven.project.MavenProject;
import org.apache.maven.shared.dependency.graph.DependencyGraphBuilder;
import org.apache.maven.shared.dependency.graph.DependencyGraphBuilderException;
import org.apache.maven.shared.dependency.graph.DependencyNode;
import org.apache.maven.shared.dependency.graph.traversal.DependencyNodeVisitor;
import com.google.common.base.Joiner;
import java.util.HashMap;
import net.sf.saxon.Configuration;
import net.sf.saxon.s9api.DocumentBuilder;
import net.sf.saxon.s9api.Processor;
import net.sf.saxon.s9api.SaxonApiException;
import net.sf.saxon.s9api.XPathCompiler;
import net.sf.saxon.s9api.XPathSelector;
import net.sf.saxon.s9api.XdmNode;
import net.sf.saxon.s9api.XdmSequenceIterator;
import net.sf.saxon.s9api.XdmValue;

/**
 * Generates a catalog, based on dependency tree, where each dependency is re-written to the jar URL.
 */
@Mojo(
        name = "catalog", 
        defaultPhase = LifecyclePhase.PROCESS_RESOURCES, 
        requiresDependencyResolution = ResolutionScope.COMPILE)
public class Catalog extends AbstractMojo {
    
    @Parameter( defaultValue = "${project}", readonly = true, required = true )
    private MavenProject project;
    
    @Parameter( defaultValue = "catalog.xml")
    private String catalogFileName;
    
    @Parameter (defaultValue = "artifactId:/")
    private String patternUrl;
    
    @Parameter()
    private String rewriteToProtocol;
    
    @Component( hint = "default" )
    private DependencyGraphBuilder dependencyGraphBuilder;
    
    private DependencyNode rootNode;
    
    private HashMap<File,MyArtifact> dependencyDirs;
    private DocumentBuilder builder;
    private XPathCompiler compiler;
    
    @Override
    public void execute() throws MojoExecutionException {
        dependencyDirs = new HashMap<>();
        Processor proc = new Processor(Configuration.newConfiguration());
        builder = proc.newDocumentBuilder();
        compiler = proc.newXPathCompiler();
        if(rewriteToProtocol!=null && !rewriteToProtocol.endsWith(":/")) {
            rewriteToProtocol=rewriteToProtocol.concat(":/");
        }
        if(!patternUrl.endsWith(":/")) {
            throw new MojoExecutionException("Illegal patternUrl value. patternUrl must end with :/");
        }
        
        final List<String> classpaths;
        try {
            classpaths = new ArrayList<>(project.getCompileClasspathElements().size());
            for(Object i:project.getCompileClasspathElements()) {
                classpaths.add(i.toString());
            }
            getLog().debug(LOG_PREFIX+"classpaths="+classpaths);
            try {
                rootNode = dependencyGraphBuilder.buildDependencyGraph( project, buildArtifactFilter() );
                final CatalogModel catalog = new CatalogModel();
                DependencyNodeVisitor visitor = new DependencyNodeVisitor() {
                    @Override
                    public boolean visit(DependencyNode dn) {
                        getLog().debug(LOG_PREFIX+"Visiting "+dn.toNodeString());
                        if(!dn.getArtifact().equals(project.getArtifact())) {
                            processDependency(dn, classpaths, catalog);
                        }
                        return true;
                    }
                    @Override
                    public boolean endVisit(DependencyNode dn) {
                        return true;
                    }
                };
                rootNode.accept(visitor);
                writeCatalog(catalog);
                getLog().debug(LOG_PREFIX+catalog.toString());
            } catch (XMLStreamException | IOException | DependencyGraphBuilderException ex) {
                getLog().error(LOG_PREFIX+ex.getMessage(),ex);
            }
        } catch(DependencyResolutionRequiredException ex) {
            getLog().error(LOG_PREFIX+ex.getMessage(),ex);
        }
    }
    
    private void processDependency(DependencyNode dn, List<String> classpaths, CatalogModel catalog) {
        String artifactId = dn.getArtifact().getArtifactId();
        String groupId = dn.getArtifact().getGroupId();
        String version = dn.getArtifact().getVersion();
        if(rewriteToProtocol!=null && rewriteToProtocol.length()>1) {
            RewriteSystemModel rsm = new RewriteSystemModel(buildPattern(groupId,artifactId,version), rewriteToProtocol);
            if(!catalog.containsUriStartPrefix(rsm.getUriStartPrefix())) {
                catalog.getEntries().add(rsm);
            }
        } else {
            try {
                String jarFileName = null;
                if(isInJarWithDependencies(dn)) {
                    getLog().debug(LOG_PREFIX+artifactId+" is in a jar-with-dependencies");
                    jarFileName = getJarFileForJarWithDependency(dn, classpaths);
                } else {
                    getLog().debug(LOG_PREFIX+artifactId+" is in a jar");
                    String artifactPath = constructArtifactPath(dn.getArtifact());
                    getLog().debug(LOG_PREFIX+"artifactPath= "+artifactPath);
                    for(String s:classpaths) {
                        if(s.contains(artifactPath)) {
                            jarFileName = s;
                        } else if(s.endsWith("target/classes") || s.matches(".*[/\\\\]target[/\\\\][^/\\\\]+\\.jar")) {
                            // issue #2
                            getLog().debug("found classpath : "+s);
                            // dir should be the project basedir
                            File dir = new File(s).getParentFile().getParentFile();
                            if(isPathMatchesDependency(dir,groupId, artifactId, version)) {
                                jarFileName = s;
                            } else {
                                getLog().debug(LOG_PREFIX+s+" does not match ("+dir.getAbsolutePath()+","+groupId+","+artifactId+","+version+")");
                                getLog().debug(LOG_PREFIX+artifactId+":/ won't be bind to a classpath element");
                            }
                        }
                    }
                }
                getLog().debug(LOG_PREFIX+artifactId+" -> "+jarFileName);
                if(jarFileName!=null) {
                    RewriteSystemModel rsm = (jarFileName.endsWith(".jar")) ?
                            new RewriteSystemModel(buildPattern(groupId,artifactId,version), "jar:file:"+jarFileName+"!/") :
                            new RewriteSystemModel(buildPattern(groupId,artifactId,version), new File(jarFileName).toURI().toString());
                    if(!catalog.containsUriStartPrefix(rsm.getUriStartPrefix())) {
                        catalog.getEntries().add(rsm);
                    }
                }
            } catch (OverConstrainedVersionException ex) {
                getLog().error(LOG_PREFIX+ex.getMessage(), ex);
            }
        }
    }
    boolean isPathMatchesDependency(final File dir, final String groupId, final String artifactId, final String version) {
        MyArtifact art = dependencyDirs.get(dir);
        if(art==null) {
            art=loadArtifactFromDir(dir);
            if(art!=null) {
                dependencyDirs.put(dir,art);
                return groupId.equals(art.getGroupId()) && artifactId.equals(art.getArtifactId()) && version.equals(art.getVersion());
            }
        }
        return false;
    }
    MyArtifact loadArtifactFromDir(final File dir) {
        try {
            XdmNode pom = builder.build(new File(dir,"pom.xml"));
            compiler.declareNamespace("mvn", "http://maven.apache.org/POM/4.0.0");
            XPathSelector selector = compiler.compile("/mvn:project/(mvn:groupId | mvn:artifactId | mvn:version)").load();
            selector.setContextItem(pom);
            XdmValue ret = selector.evaluate();
            String groupId=null, artifactId=null, version=null;
            for(XdmSequenceIterator it=ret.iterator();it.hasNext();) {
                XdmNode node=(XdmNode)it.next();
                switch(node.getNodeName().getLocalName()) {
                    case "groupId": groupId = node.getStringValue(); break;
                    case "artifactId": artifactId = node.getStringValue(); break;
                    case "version": version = node.getStringValue();
                }
            }
            if(version==null) {
                String relativePath = "../pom.xml";
                // case where version is in parent pom. Look for it...
                XPathSelector selector2 = compiler.compile("/mvn:project/mvn:parent/mvn:relativePath").load();
                selector2.setContextItem(pom);<commit_msg>corrected case where art exists<commit_after>/*
 * Copyright 2001-2005 The Apache Software Foundation.
 *
 * Licensed 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 top.marchand.xml.maven.catalog;

import top.marchand.xml.maven.catalog.model.CatalogModel;
import top.marchand.xml.maven.catalog.model.RewriteSystemModel;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javanet.staxutils.IndentingXMLStreamWriter;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.DependencyResolutionRequiredException;
import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
import org.apache.maven.artifact.versioning.OverConstrainedVersionException;
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.plugins.annotations.ResolutionScope;
import org.apache.maven.project.MavenProject;
import org.apache.maven.shared.dependency.graph.DependencyGraphBuilder;
import org.apache.maven.shared.dependency.graph.DependencyGraphBuilderException;
import org.apache.maven.shared.dependency.graph.DependencyNode;
import org.apache.maven.shared.dependency.graph.traversal.DependencyNodeVisitor;
import com.google.common.base.Joiner;
import java.util.HashMap;
import net.sf.saxon.Configuration;
import net.sf.saxon.s9api.DocumentBuilder;
import net.sf.saxon.s9api.Processor;
import net.sf.saxon.s9api.SaxonApiException;
import net.sf.saxon.s9api.XPathCompiler;
import net.sf.saxon.s9api.XPathSelector;
import net.sf.saxon.s9api.XdmNode;
import net.sf.saxon.s9api.XdmSequenceIterator;
import net.sf.saxon.s9api.XdmValue;

/**
 * Generates a catalog, based on dependency tree, where each dependency is re-written to the jar URL.
 */
@Mojo(
        name = "catalog", 
        defaultPhase = LifecyclePhase.PROCESS_RESOURCES, 
        requiresDependencyResolution = ResolutionScope.COMPILE)
public class Catalog extends AbstractMojo {
    
    @Parameter( defaultValue = "${project}", readonly = true, required = true )
    private MavenProject project;
    
    @Parameter( defaultValue = "catalog.xml")
    private String catalogFileName;
    
    @Parameter (defaultValue = "artifactId:/")
    private String patternUrl;
    
    @Parameter()
    private String rewriteToProtocol;
    
    @Component( hint = "default" )
    private DependencyGraphBuilder dependencyGraphBuilder;
    
    private DependencyNode rootNode;
    
    private HashMap<File,MyArtifact> dependencyDirs;
    private DocumentBuilder builder;
    private XPathCompiler compiler;
    
    @Override
    public void execute() throws MojoExecutionException {
        dependencyDirs = new HashMap<>();
        Processor proc = new Processor(Configuration.newConfiguration());
        builder = proc.newDocumentBuilder();
        compiler = proc.newXPathCompiler();
        if(rewriteToProtocol!=null && !rewriteToProtocol.endsWith(":/")) {
            rewriteToProtocol=rewriteToProtocol.concat(":/");
        }
        if(!patternUrl.endsWith(":/")) {
            throw new MojoExecutionException("Illegal patternUrl value. patternUrl must end with :/");
        }
        
        final List<String> classpaths;
        try {
            classpaths = new ArrayList<>(project.getCompileClasspathElements().size());
            for(Object i:project.getCompileClasspathElements()) {
                classpaths.add(i.toString());
            }
            getLog().debug(LOG_PREFIX+"classpaths="+classpaths);
            try {
                rootNode = dependencyGraphBuilder.buildDependencyGraph( project, buildArtifactFilter() );
                final CatalogModel catalog = new CatalogModel();
                DependencyNodeVisitor visitor = new DependencyNodeVisitor() {
                    @Override
                    public boolean visit(DependencyNode dn) {
                        getLog().debug(LOG_PREFIX+"Visiting "+dn.toNodeString());
                        if(!dn.getArtifact().equals(project.getArtifact())) {
                            processDependency(dn, classpaths, catalog);
                        }
                        return true;
                    }
                    @Override
                    public boolean endVisit(DependencyNode dn) {
                        return true;
                    }
                };
                rootNode.accept(visitor);
                writeCatalog(catalog);
                getLog().debug(LOG_PREFIX+catalog.toString());
            } catch (XMLStreamException | IOException | DependencyGraphBuilderException ex) {
                getLog().error(LOG_PREFIX+ex.getMessage(),ex);
            }
        } catch(DependencyResolutionRequiredException ex) {
            getLog().error(LOG_PREFIX+ex.getMessage(),ex);
        }
    }
    
    private void processDependency(DependencyNode dn, List<String> classpaths, CatalogModel catalog) {
        String artifactId = dn.getArtifact().getArtifactId();
        String groupId = dn.getArtifact().getGroupId();
        String version = dn.getArtifact().getVersion();
        if(rewriteToProtocol!=null && rewriteToProtocol.length()>1) {
            RewriteSystemModel rsm = new RewriteSystemModel(buildPattern(groupId,artifactId,version), rewriteToProtocol);
            if(!catalog.containsUriStartPrefix(rsm.getUriStartPrefix())) {
                catalog.getEntries().add(rsm);
            }
        } else {
            try {
                String jarFileName = null;
                if(isInJarWithDependencies(dn)) {
                    getLog().debug(LOG_PREFIX+artifactId+" is in a jar-with-dependencies");
                    jarFileName = getJarFileForJarWithDependency(dn, classpaths);
                } else {
                    getLog().debug(LOG_PREFIX+artifactId+" is in a jar");
                    String artifactPath = constructArtifactPath(dn.getArtifact());
                    getLog().debug(LOG_PREFIX+"artifactPath= "+artifactPath);
                    for(String s:classpaths) {
                        if(s.contains(artifactPath)) {
                            jarFileName = s;
                        } else if(s.endsWith("target/classes") || s.matches(".*[/\\\\]target[/\\\\][^/\\\\]+\\.jar")) {
                            // issue #2
                            getLog().debug("found classpath : "+s);
                            // dir should be the project basedir
                            File dir = new File(s).getParentFile().getParentFile();
                            if(isPathMatchesDependency(dir,groupId, artifactId, version)) {
                                jarFileName = s;
                            } else {
                                getLog().debug(LOG_PREFIX+s+" does not match ("+dir.getAbsolutePath()+","+groupId+","+artifactId+","+version+")");
                                getLog().debug(LOG_PREFIX+artifactId+":/ won't be bind to a classpath element");
                            }
                        }
                    }
                }
                getLog().debug(LOG_PREFIX+artifactId+" -> "+jarFileName);
                if(jarFileName!=null) {
                    RewriteSystemModel rsm = (jarFileName.endsWith(".jar")) ?
                            new RewriteSystemModel(buildPattern(groupId,artifactId,version), "jar:file:"+jarFileName+"!/") :
                            new RewriteSystemModel(buildPattern(groupId,artifactId,version), new File(jarFileName).toURI().toString());
                    if(!catalog.containsUriStartPrefix(rsm.getUriStartPrefix())) {
                        catalog.getEntries().add(rsm);
                    }
                }
            } catch (OverConstrainedVersionException ex) {
                getLog().error(LOG_PREFIX+ex.getMessage(), ex);
            }
        }
    }
    boolean isPathMatchesDependency(final File dir, final String groupId, final String artifactId, final String version) {
        MyArtifact art = dependencyDirs.get(dir);
        if(art==null) {
            art=loadArtifactFromDir(dir);
            if(art!=null) {
                dependencyDirs.put(dir,art);
                return groupId.equals(art.getGroupId()) && artifactId.equals(art.getArtifactId()) && version.equals(art.getVersion());
            } else {
                return false;
            }
        } else {
            return groupId.equals(art.getGroupId()) && artifactId.equals(art.getArtifactId()) && version.equals(art.getVersion());
        }
    }
    MyArtifact loadArtifactFromDir(final File dir) {
        try {
            XdmNode pom = builder.build(new File(dir,"pom.xml"));
            compiler.declareNamespace("mvn", "http://maven.apache.org/POM/4.0.0");
            XPathSelector selector = compiler.compile("/mvn:project/(mvn:groupId | mvn:artifactId | mvn:version)").load();
            selector.setContextItem(pom);
            XdmValue ret = selector.evaluate();
            String groupId=null, artifactId=null, version=null;
            for(XdmSequenceIterator it=ret.iterator();it.hasNext();) {
                XdmNode node=(XdmNode)it.next();
                switch(node.getNodeName().getLocalName()) {
                    case "groupId": groupId = node.getStringValue(); break;
                    case "artifactId": artifactId = node.getStringValue(); break;
                    case "version": version = node.getStringValue();
                }
            }
            if(version==null) {
                String relativePath = "../pom.xml";
                // case where version is in parent pom. Look for it...
                XPathSelector selector2 = compiler.compile("/mvn:project/mvn:parent/mvn:relativePath").load();
                selector2.setContextItem(pom);
{'fraction_non_alphanumeric': 0.10889968400698015, 'fraction_numerical': 0.0025468094137622035, 'mean_word_length': 2.3418439716312056, 'pattern_counts': {'":': 14, '<': 13, '<?xml version=': 0, '>': 17, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 18}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3629971', 'n_tokens_mistral': 5714, 'n_tokens_neox': 5341, 'n_words': 1061}