ThemisMavenPomParser.java
/*
* Themis: Java Project Framework
* Copyright 2026. Tony Washer
*
* 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 io.github.tonywasher.joceanus.themis.parser.maven;
import io.github.tonywasher.joceanus.oceanus.base.OceanusException;
import io.github.tonywasher.joceanus.themis.exc.ThemisDataException;
import io.github.tonywasher.joceanus.themis.exc.ThemisIOException;
import io.github.tonywasher.joceanus.themis.parser.base.ThemisChar;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.SAXException;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* Pom Parser.
*/
public class ThemisMavenPomParser {
/**
* Document name.
*/
private static final String DOC_NAME = "project";
/**
* Parent name.
*/
private static final String PARENT_NAME = "parent";
/**
* GroupId name.
*/
private static final String GROUP_NAME = "groupId";
/**
* Version name.
*/
private static final String VERSION_NAME = "version";
/**
* Document path prefix.
*/
private static final String DOC_PREFIX = "/" + DOC_NAME;
/**
* Properties XPath.
*/
private static final String XPATH_PROPERTIES = DOC_PREFIX + "/properties";
/**
* Parent XPath.
*/
private static final String XPATH_PARENT = DOC_PREFIX + ThemisChar.COMMENT + PARENT_NAME;
/**
* Modules XPath.
*/
private static final String XPATH_MODULES = DOC_PREFIX + "/modules";
/**
* DependencyManagement XPath.
*/
private static final String XPATH_DEPENDENCYMGMT = DOC_PREFIX + "/dependencyManagement/dependencies";
/**
* Dependencies XPath.
*/
private static final String XPATH_DEPENDENCIES = DOC_PREFIX + "/dependencies";
/**
* Packaging XPath.
*/
private static final String XPATH_PACKAGING = DOC_PREFIX + "/packaging";
/**
* XtraDirs XPath.
*/
private static final String XPATH_XTRADIRS = DOC_PREFIX
+ "/build/plugins/plugin[artifactId='build-helper-maven-plugin']"
+ "/executions/execution/configuration/sources";
/**
* Module element.
*/
private static final String EL_MODULE = "module";
/**
* Dependency element.
*/
private static final String EL_DEPENDENCY = "dependency";
/**
* Source element.
*/
private static final String EL_SOURCE = "source";
/**
* Parent groupId indication.
*/
private static final String PARENT_GROUP = PARENT_NAME + ThemisChar.PERIOD + DOC_NAME + ThemisChar.PERIOD + GROUP_NAME;
/**
* Parent version indication.
*/
private static final String PARENT_VERSION = PARENT_NAME + ThemisChar.PERIOD + DOC_NAME + ThemisChar.PERIOD + VERSION_NAME;
/**
* Project groupId indication.
*/
private static final String PROJECT_GROUP = DOC_NAME + ThemisChar.PERIOD + GROUP_NAME;
/**
* Project version indication.
*/
private static final String PROJECT_VERSION = DOC_NAME + ThemisChar.PERIOD + VERSION_NAME;
/**
* The parsed document.
*/
private final Document theDoc;
/**
* The XPath.
*/
private final XPath theXPath;
/**
* The PropertyCache.
*/
private ThemisMavenPropertyCache theProperties;
/**
* Constructor.
*
* @param pLocation the pomFile location the parent pom
* @param pProperties the properties cache
* @throws OceanusException on error
*/
public ThemisMavenPomParser(final File pLocation,
final ThemisMavenPropertyCache pProperties) throws OceanusException {
/* Record the cache */
theProperties = pProperties;
/* Protect against exceptions */
try (InputStream myInputStream = new FileInputStream(pLocation);
BufferedInputStream myInBuffer = new BufferedInputStream(myInputStream)) {
final DocumentBuilderFactory myFactory = DocumentBuilderFactory.newInstance();
myFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
myFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
myFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
final DocumentBuilder myBuilder = myFactory.newDocumentBuilder();
/* Create the XPath */
theXPath = XPathFactory.newInstance().newXPath();
/* Build the document from the input stream */
theDoc = myBuilder.parse(myInBuffer);
/* Access the document element */
final Element myDoc = theDoc.getDocumentElement();
/* Check that the document name is correct */
if (!Objects.equals(myDoc.getNodeName(), DOC_NAME)) {
throw new ThemisDataException("Invalid document type");
}
/* Handle exceptions */
} catch (IOException
| ParserConfigurationException
| SAXException e) {
throw new ThemisIOException("Exception accessing Pom file", e);
}
}
/**
* Read the properties into the cache.
*
* @throws OceanusException on error
*/
void readProperties() throws OceanusException {
/* Store any properties */
final Node myProps = findNode(XPATH_PROPERTIES);
if (myProps != null) {
for (Node myNode = myProps.getFirstChild(); myNode != null; myNode = myNode.getNextSibling()) {
if (myNode instanceof Element myElement) {
theProperties.setProperty(myElement.getNodeName(),
myElement.getTextContent());
}
}
}
}
/**
* Is this jar packaging?
*
* @return true/false
* @throws OceanusException on error
*/
boolean isJarPackaging() throws OceanusException {
/* Obtain packaging definition if any */
final Element myPackageEl = (Element) findNode(XPATH_PACKAGING);
return myPackageEl == null
|| "jar".equals(myPackageEl.getTextContent())
|| "bundle".equals(myPackageEl.getTextContent());
}
/**
* Obtain the parentId.
*
* @return the parentId (or null)
* @throws OceanusException on error
*/
ThemisMavenId getParent() throws OceanusException {
/* Obtain parent definition if any */
final Element myParentEl = (Element) findNode(XPATH_PARENT);
final ThemisMavenId myParent = myParentEl == null
? null
: new ThemisMavenId(theProperties, myParentEl);
/* Store parent properties if we have a parent */
if (myParent != null) {
storeParentProperties(myParent);
}
/* Return the parent */
return myParent;
}
/**
* Store parent properties.
*
* @param pParent the parent
*/
private void storeParentProperties(final ThemisMavenId pParent) {
/* Store parent groupId and version */
theProperties.setProperty(PARENT_GROUP, pParent.getGroupId());
theProperties.setProperty(PARENT_VERSION, pParent.getVersion());
}
/**
* Obtain the Id.
*
* @param pParent the parent id
* @return the Id (or null)
* @throws OceanusException on error
*/
ThemisMavenId getId(final ThemisMavenId pParent) throws OceanusException {
/* Obtain definition */
final ThemisMavenId myProject = new ThemisMavenId(theProperties, theDoc.getDocumentElement(), pParent);
/* Store self properties */
storeSelfProperties(myProject);
/* Return the id */
return myProject;
}
/**
* Store self properties.
*
* @param pProject the project
*/
private void storeSelfProperties(final ThemisMavenId pProject) {
/* Determine project groupId */
String myGroupId = pProject.getGroupId();
myGroupId = myGroupId != null ? myGroupId : theProperties.getProperty(PARENT_GROUP);
/* Determine project version */
String myVersion = pProject.getVersion();
myVersion = myVersion != null ? myVersion : theProperties.getProperty(PARENT_VERSION);
/* Store project details */
theProperties.setProperty(PROJECT_GROUP, myGroupId);
theProperties.setProperty(PROJECT_VERSION, myVersion);
}
/**
* Obtain modules.
*
* @return the list of modules
* @throws OceanusException on error
*/
List<String> getModules() throws OceanusException {
/* Process any modules */
final List<String> myList = new ArrayList<>();
final Node myModules = findNode(XPATH_MODULES);
if (myModules != null) {
/* Loop through the children */
for (Node myChild = myModules.getFirstChild();
myChild != null;
myChild = myChild.getNextSibling()) {
/* Return result if we have a match */
if (myChild instanceof Element
&& EL_MODULE.equals(myChild.getNodeName())) {
myList.add(myChild.getTextContent());
}
}
}
/* Return the list */
return myList;
}
/**
* Get dependencyManagement.
*
* @return the list
* @throws OceanusException on error
*/
List<ThemisMavenId> getDependencyManagement() throws OceanusException {
return getDependencies(XPATH_DEPENDENCYMGMT);
}
/**
* Get dependencies.
*
* @return the list
* @throws OceanusException on error
*/
List<ThemisMavenId> getDependencies() throws OceanusException {
return getDependencies(XPATH_DEPENDENCIES);
}
/**
* Get dependencies.
*
* @param pPath XPath to dependencies
* @return the list
* @throws OceanusException on error
*/
private List<ThemisMavenId> getDependencies(final String pPath) throws OceanusException {
/* Process any dependencies */
final List<ThemisMavenId> myList = new ArrayList<>();
final Node myDependencies = findNode(pPath);
if (myDependencies != null) {
/* Loop through the children */
for (Node myChild = myDependencies.getFirstChild();
myChild != null;
myChild = myChild.getNextSibling()) {
/* Return result if we have a match */
if (myChild instanceof Element myElement
&& EL_DEPENDENCY.equals(myChild.getNodeName())) {
final ThemisMavenId myId = new ThemisMavenId(theProperties, myElement);
if (!myId.isSkippable()) {
myList.add(myId);
}
}
}
}
/* Return the list */
return myList;
}
/**
* Get extra directories.
*
* @return the list
* @throws OceanusException on error
*/
List<String> getXtraDirs() throws OceanusException {
/* Process any modules */
final List<String> myList = new ArrayList<>();
final Node myXtraDirs = findNode(XPATH_XTRADIRS);
if (myXtraDirs != null) {
/* Loop through the children */
for (Node myChild = myXtraDirs.getFirstChild();
myChild != null;
myChild = myChild.getNextSibling()) {
/* Return result if we have a match */
if (myChild instanceof Element
&& EL_SOURCE.equals(myChild.getNodeName())) {
myList.add(myChild.getTextContent());
}
}
}
/* Return the list */
return myList;
}
/**
* Obtain the XPath node.
*
* @param pPath the Path
* @return the Node (or null if not found)
* @throws OceanusException on error
*/
private Node findNode(final String pPath) throws OceanusException {
/* Protect against exceptions */
try {
return (Node) theXPath.compile(pPath).evaluate(theDoc, XPathConstants.NODE);
} catch (XPathExpressionException e) {
throw new ThemisDataException("Exception locating XPath: " + pPath, e);
}
}
}