com.redhat.ceylon.cmr.api.Overrides Maven / Gradle / Ivy
/*
* Copyright 2011 Red Hat inc. and third party contributors as noted
* by the author tags.
* 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 com.redhat.ceylon.cmr.api;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import java.util.logging.Logger;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.transform.TransformerException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.Attributes;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import com.redhat.ceylon.cmr.api.DependencyOverride.Type;
/**
* FIXME: we still need to define how add/remove/set works with replace or recursive replacements.
*
* @author Ales Justin
* @author Stef Epardaud
*/
public class Overrides {
private static final Logger log = Logger.getLogger(Overrides.class.getName());
public static class InvalidOverrideException extends IllegalArgumentException {
private static final long serialVersionUID = 1L;
public int line = -1;
public int column = -1;
public InvalidOverrideException(String message, Element element) {
super(message);
Object data = null;
data = element.getUserData(LINE_NUMBER_KEY_NAME);
this.line = data == null ? -1 : Integer.parseInt((String) data);
data = element.getUserData(COLUMN_NUMBER_KEY_NAME);
this.column = data == null ? -1 : Integer.parseInt((String) data);
}
}
private Map overrides = new HashMap<>();
private Map overridesNoVersion = new HashMap<>();
private Set removed = new HashSet();
private Map replaced = new HashMap<>();
private Map replacedNoVersion = new HashMap<>();
private Map setVersions = new HashMap<>();
private String source = null;
private Overrides() {}
public static Overrides getDistOverrides() {
return getDistOverrides(true);
}
public static Overrides getDistOverrides(boolean upgradeDist) {
try {
return parseDistOverrides(upgradeDist);
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public Overrides append(String overridesFileName) throws FileNotFoundException, Exception {
return parse(overridesFileName, this);
}
public String toString() {
return this.source == null ? super.toString() : "Overrides("+source+")";
}
public void addArtifactOverride(ArtifactOverrides ao) {
overrides.put(ao.getOwner(), ao);
if(ao.getOwner().getVersion() == null)
overridesNoVersion.put(ao.getOwner().getName(), ao);
}
public ArtifactOverrides getArtifactOverrides(ArtifactContext mc) {
ArtifactOverrides ao = overrides.get(mc);
if(ao == null){
// fall-back to overrides with no version specified
return overridesNoVersion.get(mc.getName());
}
return ao;
}
private void addRemovedArtifact(DependencyOverride context) {
removed.add(context);
}
public boolean isRemoved(ArtifactContext context){
for(DependencyOverride ro : removed){
if(ro.matches(context))
return true;
}
return false;
}
public ArtifactContext getReplacement(ArtifactContext mc) {
ArtifactContext ao = replaced.get(mc);
if(ao == null){
// fall-back to overrides with no version specified
ao = replacedNoVersion.get(mc.getName());
}
return ao;
}
public ArtifactContext replace(ArtifactContext context) {
ArtifactOverrides artifactOverrides = getArtifactOverrides(context);
if(artifactOverrides != null && artifactOverrides.getReplace() != null){
ArtifactContext replacingContext = artifactOverrides.getReplace().getArtifactContext();
return replace(context, replacingContext);
}
ArtifactContext replacingContext = getReplacement(context);
if(replacingContext != null){
return replace(context, replacingContext);
}
return null;
}
private ArtifactContext replace(ArtifactContext context, ArtifactContext replacingContext) {
// FIXME: perhaps something smarter? I don't want to use replace.getContext() as it will be missing
// query info such as what type of artifact we're looking for
ArtifactContext ret = context.copy();
ret.setName(replacingContext.getName());
// inherit version from the artifact we replace
if(replacingContext.getVersion() != null)
ret.setVersion(replacingContext.getVersion());
else
ret.setVersion(context.getVersion());
// even if we replace, respect the set version
ret.setVersion(getVersionOverride(ret));
return ret;
}
private void addReplacedArtifact(ArtifactContext context, ArtifactContext withContext) {
if(context.getVersion() == null)
replacedNoVersion.put(context.getName(), withContext);
else
replaced.put(context, withContext);
}
private void addSetArtifact(ArtifactContext context) {
setVersions.put(context.getName(), context.getVersion());
}
public String getVersionOverride(ArtifactContext context){
String overriddenVersion = setVersions.get(context.getName());
if(overriddenVersion != null)
return overriddenVersion;
return context.getVersion();
}
public boolean isVersionOverridden(ArtifactContext context){
return setVersions.containsKey(context.getName());
}
public ModuleInfo applyOverrides(String module, String version, ModuleInfo source) {
ArtifactOverrides artifactOverrides = getArtifactOverrides(new ArtifactContext(module, version));
Set result = new HashSet();
for (ModuleDependencyInfo dep : source.getDependencies()) {
String depName = dep.getName();
String depVersion = dep.getVersion();
boolean optional = dep.isOptional();
boolean export = dep.isExport();
ArtifactContext ctx = new ArtifactContext(depName, depVersion);
if((artifactOverrides != null && artifactOverrides.isRemoved(ctx))
|| isRemoved(ctx))
continue;
if(artifactOverrides != null && artifactOverrides.isAddedOrUpdated(ctx))
continue;
ArtifactContext replacement = replace(ctx);
if(replacement != null){
depName = replacement.getName();
depVersion = replacement.getVersion();
ctx = replacement;
}
if(isVersionOverridden(ctx))
depVersion = getVersionOverride(ctx);
if(artifactOverrides != null){
if(artifactOverrides.isShareOverridden(ctx))
export = artifactOverrides.isShared(ctx);
if(artifactOverrides.isOptionalOverridden(ctx))
optional = artifactOverrides.isOptional(ctx);
}
result.add(new ModuleDependencyInfo(depName, depVersion, optional, export));
}
String filter = source.getFilter();
if(artifactOverrides != null){
if(artifactOverrides.getFilter() != null)
filter = artifactOverrides.getFilter();
for(DependencyOverride add : artifactOverrides.getAdd()){
result.add(new ModuleDependencyInfo(add.getArtifactContext().getName(), add.getArtifactContext().getVersion(),
add.isOptional(),
add.isShared()));
}
}
return new ModuleInfo(filter, result);
}
private static Overrides parse(String overridesFileName, Overrides overrides) throws FileNotFoundException, Exception{
File overridesFile = new File(overridesFileName);
if (overridesFile.exists() == false) {
throw new IllegalArgumentException("No such overrides file: " + overridesFile);
}
try(InputStream is = new FileInputStream(overridesFile)){
Overrides.parse(is, overrides);
overrides.source = overridesFile.getAbsolutePath();
return overrides;
}
}
public static Overrides parseDistOverrides(boolean upgradeDist) throws FileNotFoundException, Exception{
URL resource = Overrides.class.getResource("/com/redhat/ceylon/cmr/api/dist-overrides.xml");
try(InputStream is = resource.openStream()){
try {
Document document = parseXml(is);
String elementName = upgradeDist ? "dist-upgrade" : "dist-downgrade";
List e = getChildren(document.getDocumentElement(), elementName);
if (e.size() == 1) {
Overrides overrides = new Overrides();
parseOverrides(overrides, e.get(0));
overrides.source = resource.toString();
return overrides;
} else {
throw new InvalidOverrideException(String.format("Expected exactly one '%s' element in dist-overrides.xml, but found %d", elementName, e.size()), document.getDocumentElement());
}
} finally {
try {
is.close();
} catch (IOException ignored) {
}
}
}
}
static void parse(InputStream is, Overrides result) throws Exception {
try {
Document document = parseXml(is);
Element rootElement = document.getDocumentElement();
parseOverrides(result, rootElement);
} finally {
try {
is.close();
} catch (IOException ignored) {
}
}
}
/**
* Parse the overrides in the given root element (usually called
* {@code }, but the name doesn't actually matter)
* updating the given result.
* @param result
* @param rootElement
* @return
* @throws TransformerException
*/
protected static void parseOverrides(final Overrides result, Element rootElement) throws TransformerException {
Map interpolation = new HashMap<>();
List defines = getChildren(rootElement, "define");
for (Element define : defines) {
// do not interpolate while we're defining things
String name = getRequiredAttribute(define, "name", null);
String value = getRequiredAttribute(define, "value", null);
interpolation.put(name, value);
}
// old name
List artifacts = getChildren(rootElement, "artifact");
parseArtifacts(artifacts, result, interpolation);
// new name
List modules = getChildren(rootElement, "module");
parseArtifacts(modules, result, interpolation);
List removedArtifacts = getChildren(rootElement, "remove");
for (Element artifact : removedArtifacts) {
ArtifactContext context = getArtifactContext(artifact, true, interpolation);
DependencyOverride doo = new DependencyOverride(context, Type.REMOVE, false, false);
result.addRemovedArtifact(doo);
}
List replacedArtifacts = getChildren(rootElement, "replace");
for (Element artifact : replacedArtifacts) {
ArtifactContext context = getArtifactContext(artifact, true, interpolation);
List withs = getChildren(artifact, "with");
for (Element with : withs) {
ArtifactContext withContext = getArtifactContext(with, true, interpolation);
result.addReplacedArtifact(context, withContext);
}
}
List setArtifacts = getChildren(rootElement, "set");
for (Element artifact : setArtifacts) {
ArtifactContext context = getArtifactContext(artifact, true, interpolation);
result.addSetArtifact(context);
}
}
private static void parseArtifacts(List artifacts, Overrides result, Map interpolation) throws TransformerException {
for (Element artifact : artifacts) {
ArtifactContext mc = getArtifactContext(artifact, true, interpolation); // version is optional
ArtifactOverrides ao = new ArtifactOverrides(mc);
result.addArtifactOverride(ao);
addOverrides(ao, artifact, DependencyOverride.Type.ADD, interpolation);
addOverrides(ao, artifact, DependencyOverride.Type.REMOVE, interpolation);
addOverrides(ao, artifact, DependencyOverride.Type.REPLACE, interpolation);
// filter
NodeList filterNode = artifact.getElementsByTagName("filter");
if (filterNode != null && filterNode.getLength() > 0) {
Node node = filterNode.item(0);
ao.setFilter(interpolate(PathFilterParser.convertNodeToString(node), interpolation));
}
List shareArtifacts = getChildren(artifact, "share");
for (Element share : shareArtifacts) {
ArtifactContext context = getArtifactContext(share, true, interpolation);
ao.addShareOverride(context, true);
}
List unshareArtifacts = getChildren(artifact, "unshare");
for (Element unshare : unshareArtifacts) {
ArtifactContext context = getArtifactContext(unshare, true, interpolation);
ao.addShareOverride(context, false);
}
List optionalArtifacts = getChildren(artifact, "optional");
for (Element optional : optionalArtifacts) {
ArtifactContext context = getArtifactContext(optional, true, interpolation);
ao.addOptionalOverride(context, true);
}
List requireArtifacts = getChildren(artifact, "require");
for (Element require : requireArtifacts) {
ArtifactContext context = getArtifactContext(require, true, interpolation);
ao.addOptionalOverride(context, false);
}
}
}
public static String interpolate(String string, Map interpolation) {
if(interpolation == null || string == null || string.isEmpty())
return string;
int firstReplacement = string.indexOf("${");
if(firstReplacement == -1)
return string;
StringBuffer strbuf = new StringBuffer(string.length());
int start = 0;
int[] end = new int[1];
while(firstReplacement != -1){
// put the start
strbuf.append(string, start, firstReplacement);
String part = replace(string, firstReplacement+2, end, interpolation);
strbuf.append(part);
// move to the end of replacement
start = Math.min(end[0]+1, string.length());
firstReplacement = string.indexOf("${", start);
}
// now put whatever remains
strbuf.append(string, start, string.length());
return strbuf.toString();
}
private static String replace(String string, int start, int[] end, Map interpolation) {
StringBuffer strbufName = new StringBuffer(string.length());
// now find the end
boolean seenDollar = false;
for(int i=start;i interpolation) {
String groupId = getAttribute(element, "groupId", interpolation);
if(groupId != null){
String artifactId = getRequiredAttribute(element, "artifactId", interpolation);
String version = optionalVersion ? getAttribute(element, "version", interpolation) : getRequiredAttribute(element, "version", interpolation);
String packaging = getAttribute(element, "packaging", interpolation);
String classifier = getAttribute(element, "classifier", interpolation);
return createMavenArtifactContext(groupId, artifactId, version, packaging, classifier);
}else{
String module = getRequiredAttribute(element, "module", interpolation);
String version = optionalVersion ? getAttribute(element, "version", interpolation) : getRequiredAttribute(element, "version", interpolation);
return new ArtifactContext(module, version);
}
}
protected static void addOverrides(ArtifactOverrides ao, Element artifact, DependencyOverride.Type type, Map interpolation) {
List overrides = getChildren(artifact, type.name().toLowerCase());
for (Element override : overrides) {
ArtifactContext dep = getArtifactContext(override, type == Type.REMOVE, interpolation);
boolean shared = getBooleanAttribute(override, "shared", interpolation);
boolean optional = getBooleanAttribute(override, "optional", interpolation);
DependencyOverride doo = new DependencyOverride(dep, type, shared, optional);
ao.addOverride(doo);
}
}
protected static boolean getBooleanAttribute(Element element, String name, Map interpolation) {
String val = getAttribute(element, name, interpolation);
return val != null && val.toLowerCase().equals("true");
}
protected static String getAttribute(Element element, String name, Map interpolation) {
String value = interpolate(element.getAttribute(name), interpolation);
return (value == null || value.length() == 0) ? null : value;
}
protected static String getRequiredAttribute(Element element, String name, Map interpolation) {
String value = getAttribute(element, name, interpolation);
if (value == null) {
throw new InvalidOverrideException(String.format("Missing '%s' attribute in element %s.", name, element), element);
}
return value;
}
final static String LINE_NUMBER_KEY_NAME = "lineNumber";
final static String COLUMN_NUMBER_KEY_NAME = "columnNumber";
/**
* Parse the given input stream to a Document
*/
protected static Document parseXml(InputStream inputStream) throws ParserConfigurationException, SAXException, IOException {
// We parse using SAX so that we can add location info to the DOM
// for error reporting
final Document doc;
SAXParser parser;
final SAXParserFactory factory = SAXParserFactory.newInstance();
parser = factory.newSAXParser();
final DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
final DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
doc = docBuilder.newDocument();
final Stack elementStack = new Stack();
final StringBuilder textBuffer = new StringBuilder();
final DefaultHandler handler = new DefaultHandler() {
private Locator locator;
@Override
public void setDocumentLocator(final Locator locator) {
this.locator = locator; // Save the locator, so that it can be used later for line tracking when traversing nodes.
}
@Override
public void startElement(final String uri, final String localName, final String qName, final Attributes attributes)
throws SAXException {
addTextIfNeeded();
final Element el = doc.createElement(qName);
for (int i = 0; i < attributes.getLength(); i++) {
el.setAttribute(attributes.getQName(i), attributes.getValue(i));
}
el.setUserData(LINE_NUMBER_KEY_NAME, String.valueOf(this.locator.getLineNumber()), null);
el.setUserData(COLUMN_NUMBER_KEY_NAME, String.valueOf(this.locator.getColumnNumber()), null);
elementStack.push(el);
}
@Override
public void endElement(final String uri, final String localName, final String qName) {
addTextIfNeeded();
final Element closedEl = elementStack.pop();
if (elementStack.isEmpty()) { // Is this the root element?
doc.appendChild(closedEl);
} else {
final Element parentEl = elementStack.peek();
parentEl.appendChild(closedEl);
}
}
@Override
public void characters(final char ch[], final int start, final int length) throws SAXException {
textBuffer.append(ch, start, length);
}
// Outputs text accumulated under the current node
private void addTextIfNeeded() {
if (textBuffer.length() > 0) {
final Element el = elementStack.peek();
final Node textNode = doc.createTextNode(textBuffer.toString());
el.appendChild(textNode);
textBuffer.delete(0, textBuffer.length());
}
}
};
parser.parse(inputStream, handler);
doc.getDocumentElement().normalize();
return doc;
}
protected static List getChildren(Element element, String tagName) {
NodeList nodes = element.getElementsByTagName(tagName);
List elements = new ArrayList<>(nodes.getLength());
for (int i = 0; i < nodes.getLength(); i++) {
elements.add((Element) nodes.item(i));
}
return elements;
}
public static ArtifactContext createMavenArtifactContext(String groupId, String artifactId, String version,
String packaging, String classifier) {
return new MavenArtifactContext(groupId, artifactId, version, packaging, classifier);
}
public ArtifactContext applyOverrides(final ArtifactContext sought) {
ArtifactContext replacedContext = this.replace(sought);
if(replacedContext == null) {
replacedContext = sought;
} else {
log.fine(this + ": " + sought + " -> " + replacedContext);
}
String versionOverride = this.getVersionOverride(replacedContext);
if (versionOverride != null // only possible to default module, and we can't override that
&& !versionOverride.equals(replacedContext.getVersion())) {
log.fine(this + ": " + replacedContext + " -> version " + versionOverride);
replacedContext.setVersion(versionOverride);
}
return replacedContext;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy