ThemisMaven.java
/*
* Themis: Java Project Framework
* Copyright 2012-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.proj;
import io.github.tonywasher.joceanus.oceanus.base.OceanusException;
import io.github.tonywasher.joceanus.oceanus.base.OceanusSystem;
import io.github.tonywasher.joceanus.themis.exc.ThemisDataException;
import io.github.tonywasher.joceanus.themis.exc.ThemisIOException;
import io.github.tonywasher.joceanus.themis.parser.proj.ThemisMavenId.ThemisElementParser;
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.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* Maven pom.xml parser.
*/
public class ThemisMaven
implements ThemisElementParser {
/**
* Project filename.
*/
public static final String POM = "pom.xml";
/**
* Document name.
*/
private static final String DOC_NAME = "project";
/**
* Properties XPath.
*/
private static final String XPATH_PROPERTIES = "/project/properties";
/**
* Parent XPath.
*/
private static final String XPATH_PARENT = "/project/parent";
/**
* Modules XPath.
*/
private static final String XPATH_MODULES = "/project/modules";
/**
* Dependencies XPath.
*/
private static final String XPATH_DEPENDENCIES = "/project/dependencies";
/**
* XtraDirs XPath.
*/
private static final String XPATH_XTRADIRS = "/project/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.project.groupId}";
/**
* Parent version indication.
*/
private static final String PARENT_VERSION = "${parent.parent.version}";
/**
* Project groupId indication.
*/
private static final String PROJECT_GROUP = "${project.groupId}";
/**
* Project version indication.
*/
private static final String PROJECT_VERSION = "${project.version}";
/**
* The XPath.
*/
private final XPath theXPath;
/**
* The Document.
*/
private final Document theDoc;
/**
* The Id.
*/
private final ThemisMavenId theId;
/**
* The modules.
*/
private final List<String> theModules;
/**
* The dependencies.
*/
private final List<ThemisMavenId> theDependencies;
/**
* The xtraDirs.
*/
private final List<String> theXtraDirs;
/**
* The parent.
*/
private final ThemisMaven theParent;
/**
* The properties.
*/
private final Map<String, String> theProperties;
/**
* Constructor.
*
* @param pParent the parent pom
* @param pInputStream the input stream to read
* @throws OceanusException on error
*/
ThemisMaven(final ThemisMaven pParent,
final InputStream pInputStream) throws OceanusException {
/* Store the parent */
theParent = pParent;
/* Create the module list */
theModules = new ArrayList<>();
theDependencies = new ArrayList<>();
theXtraDirs = new ArrayList<>();
theProperties = new LinkedHashMap<>();
theProperties.put("${javafx.platform}", OceanusSystem.determineSystem().getClassifier());
/* Protect against exceptions */
try (BufferedInputStream myInBuffer = new BufferedInputStream(pInputStream)) {
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);
theId = parseProjectFile();
/* Handle exceptions */
} catch (IOException
| ParserConfigurationException
| SAXException e) {
throw new ThemisIOException("Exception accessing Pom file", e);
}
}
@Override
public String toString() {
return theId.toString();
}
/**
* Obtain the list of modules.
*
* @return the list
*/
public ThemisMavenId getMavenId() {
return theId;
}
/**
* Obtain the list of modules.
*
* @return the modules
*/
public List<String> getModules() {
return theModules;
}
/**
* Obtain the list of dependencies.
*
* @return the dependencies
*/
public List<ThemisMavenId> getDependencies() {
return theDependencies;
}
/**
* Obtain the list of extra directories.
*
* @return the modules
*/
public List<String> getXtraDirs() {
return theXtraDirs;
}
/**
* Parse the project file.
*
* @return the MavenId
* @throws OceanusException on error
*/
public ThemisMavenId parseProjectFile() throws OceanusException {
/* 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");
}
/* Process any properties */
processProperties();
/* Obtain parent definition if any */
final Element myParentEl = (Element) findNode(XPATH_PARENT);
final ThemisMavenId myParent = myParentEl == null
? null
: new ThemisMavenId(this, myParentEl);
storeParentProperties(myParent);
/* Obtain our mavenId */
final ThemisMavenId myId = new ThemisMavenId(this, myDoc, myParent);
storeProjectProperties(myId);
/* Process modules */
processModules();
/* Process dependencies */
processDependencies();
/* Process extra directories */
processXtraDirs();
/* Return the Id */
return myId;
}
@Override
public String getElementValue(final Element pElement,
final String pValue) {
/* Return null if no element */
if (pElement == null) {
return null;
}
/* Loop through the children */
for (Node myChild = pElement.getFirstChild();
myChild != null;
myChild = myChild.getNextSibling()) {
/* Return result if we have a match */
if (myChild instanceof Element
&& pValue.equals(myChild.getNodeName())) {
return replaceProperty(myChild.getTextContent());
}
}
/* Not found */
return null;
}
/**
* 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);
}
}
/**
* Process properties.
*
* @throws OceanusException on error
*/
private void processProperties() throws OceanusException {
/* Process 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.put("${" + myElement.getNodeName() + "}", myElement.getTextContent());
}
}
}
}
/**
* Store parent properties.
*
* @param pParent the parent
*/
private void storeParentProperties(final ThemisMavenId pParent) {
/* Store parent groupId */
theProperties.put(PARENT_GROUP, pParent == null ? null : pParent.getGroupId());
theProperties.put(PARENT_VERSION, pParent == null ? null : pParent.getVersion());
}
/**
* Store parent properties.
*
* @param pProject the project
*/
private void storeProjectProperties(final ThemisMavenId pProject) {
/* Determine project groupId */
String myGroupId = pProject.getGroupId();
myGroupId = myGroupId != null ? myGroupId : theProperties.get(PARENT_GROUP);
/* Determine project version */
String myVersion = pProject.getVersion();
myVersion = myVersion != null ? myVersion : theProperties.get(PARENT_VERSION);
/* Store project details */
theProperties.put(PROJECT_GROUP, myGroupId);
theProperties.put(PROJECT_VERSION, myVersion);
}
/**
* Process modules.
*
* @throws OceanusException on error
*/
private void processModules() throws OceanusException {
/* Process any modules */
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())) {
theModules.add(myChild.getTextContent());
}
}
}
}
/**
* Process dependencies.
*
* @throws OceanusException on error
*/
private void processDependencies() throws OceanusException {
/* Process any dependencies */
final Node myDependencies = findNode(XPATH_DEPENDENCIES);
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(this, myElement);
if (!myId.isSkippable()) {
theDependencies.add(myId);
}
}
}
}
}
/**
* Process extra directories.
*
* @throws OceanusException on error
*/
private void processXtraDirs() throws OceanusException {
/* Process any modules */
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())) {
theXtraDirs.add(myChild.getTextContent());
}
}
}
}
/**
* Replace property.
*
* @param pValue the value
* @return the value or the replaced property
*/
private String replaceProperty(final String pValue) {
String myResult = pValue;
for (Map.Entry<String, String> myEntry : theProperties.entrySet()) {
if (myResult.contains(myEntry.getKey())) {
myResult = myResult.replace(myEntry.getKey(), myEntry.getValue());
}
}
return theParent != null ? theParent.replaceProperty(myResult) : myResult;
}
}