001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019package org.eclipse.aether.internal.impl.collect; 020 021import javax.inject.Named; 022import javax.inject.Singleton; 023 024import java.util.ArrayList; 025import java.util.Arrays; 026import java.util.List; 027import java.util.Optional; 028import java.util.function.Function; 029import java.util.function.Predicate; 030import java.util.stream.Collectors; 031 032import org.eclipse.aether.artifact.Artifact; 033import org.eclipse.aether.collection.VersionFilter; 034import org.eclipse.aether.collection.VersionFilterBuilder; 035import org.eclipse.aether.util.ConfigUtils; 036import org.eclipse.aether.util.graph.version.ChainedVersionFilter; 037import org.eclipse.aether.util.graph.version.ContextPredicateDelegatingVersionFilter; 038import org.eclipse.aether.util.graph.version.ContextualSnapshotVersionFilter; 039import org.eclipse.aether.util.graph.version.GenericQualifiersVersionFilter; 040import org.eclipse.aether.util.graph.version.HighestVersionFilter; 041import org.eclipse.aether.util.graph.version.LowestVersionFilter; 042import org.eclipse.aether.util.graph.version.ReleaseVersionFilter; 043import org.eclipse.aether.util.graph.version.SnapshotVersionFilter; 044import org.eclipse.aether.util.graph.version.VersionPredicateVersionFilter; 045import org.eclipse.aether.version.VersionConstraint; 046 047import static java.util.Objects.requireNonNull; 048 049/** 050 * Builds {@link VersionFilter} instances out of input expression string. 051 * 052 * Expression is a semicolon separated list of filters to apply. By default, no version filter is applied (like in Maven 3). 053 * <br/> 054 * Supported filters: 055 * <ul> 056 * <li>{@code "s"} - contextual snapshot filter (project version decides are snapshots allowed or not)</li> 057 * <li>{@code "nosnapshot"} - unconditional snapshot filter (no snapshot versions selected from ranges)</li> 058 * <li>{@code "norelease"} - unconditional release filter (no release versions selected from ranges)</li> 059 * <li>{@code "nopreview"} - unconditional preview filter (no preview versions selected from ranges)</li> 060 * <li>{@code "noprerelease"} - unconditional pre-release filter (no preview and rc/cr versions selected from ranges)</li> 061 * <li>{@code "noqualifier"} - unconditional any-qualifier filter (no version with any qualifier selected from ranges)</li> 062 * <li>{@code "h"} (shorthand of {@code h(1)}) or {@code "h(num)"} - highest N version (based on version ordering)</li> 063 * <li>{@code "l"} (shorthand of {@code l(1)}) or {@code "l(num)"} - lowest N version (based on version ordering)</li> 064 * <li>{@code "e(V)"} - exclusion filter (excludes versions matching V version constraint)</li> 065 * <li>{@code "i(V)"} - inclusion filter (includes versions matching V version constraint)</li> 066 * </ul> 067 * Every filter expression may have "scope" applied, in form of {@code @G[:A]}. Presence of "scope" narrows the 068 * application of filter to given G or G:A. 069 * <p> 070 * In case of multiple "similar" rule scopes, user should enlist rules from "most specific" to "least specific". 071 * <p> 072 * Example filter expression: <code>"h(5);s;e(1)@org.foo:bar</code> will cause: 073 * <ul> 074 * <li>ranges are filtered for "top 5" (instead of full range)</li> 075 * <li>snapshots are banned if root project is not a snapshot</li> 076 * <li>if range for <code>org.foo:bar</code> is being processed, version 1 is omitted</li> 077 * </ul> 078 * Values in this property builds <code>org.eclipse.aether.collection.VersionFilter</code> instance. 079 * 080 * @since 2.0.18 081 */ 082@Singleton 083@Named 084public class DefaultVersionFilterBuilder implements VersionFilterBuilder { 085 /** 086 * Builds a version filter based on the given filter expression. 087 * 088 * @param filterExpression a string containing filter expressions, may be {@code null}. 089 * @param versionConstraintParser version constraint parts to be used during parsing, must not be {@code null}. 090 * @return optional version filter, never {@code null}. 091 */ 092 @Override 093 public Optional<VersionFilter> buildVersionFilter( 094 String filterExpression, Function<String, VersionConstraint> versionConstraintParser) { 095 requireNonNull(versionConstraintParser); 096 ArrayList<VersionFilter> filters = new ArrayList<>(); 097 if (filterExpression != null) { 098 List<String> expressions = Arrays.stream(filterExpression.split(";")) 099 .filter(s -> !s.trim().isEmpty()) 100 .collect(Collectors.toList()); 101 for (String expression : expressions) { 102 Predicate<Artifact> scopePredicate; 103 VersionFilter filter; 104 if (expression.contains("@")) { 105 String remainder = expression.substring(expression.indexOf('@') + 1); 106 if (remainder.contains(":")) { 107 String g = remainder.substring(0, remainder.indexOf(':')); 108 String a = remainder.substring(remainder.indexOf(':') + 1); 109 scopePredicate = 110 artifact -> g.equals(artifact.getGroupId()) && a.equals(artifact.getArtifactId()); 111 } else { 112 scopePredicate = artifact -> remainder.equals(artifact.getGroupId()); 113 } 114 expression = expression.substring(0, expression.indexOf('@')); 115 } else { 116 scopePredicate = null; 117 } 118 if ("s".equals(expression)) { 119 filter = new ContextualSnapshotVersionFilter(); 120 } else if ("nosnapshot".equals(expression)) { 121 filter = new SnapshotVersionFilter(); 122 } else if ("norelease".equals(expression)) { 123 filter = new ReleaseVersionFilter(); 124 } else if ("nopreview".equals(expression)) { 125 filter = GenericQualifiersVersionFilter.previewVersionFilter(); 126 } else if ("noprerelease".equals(expression)) { 127 filter = GenericQualifiersVersionFilter.preReleaseVersionFilter(); 128 } else if ("noqualifier".equals(expression)) { 129 filter = GenericQualifiersVersionFilter.anyQualifierVersionFilter(); 130 } else if ("h".equals(expression)) { 131 filter = new HighestVersionFilter(); 132 } else if ("l".equals(expression)) { 133 filter = new LowestVersionFilter(); 134 } else if ((expression.startsWith("h(") || expression.startsWith("l(")) && expression.endsWith(")")) { 135 int num = Integer.parseInt(expression.substring(2, expression.length() - 1)); 136 if (expression.startsWith("h(")) { 137 filter = new HighestVersionFilter(num); 138 } else { 139 filter = new LowestVersionFilter(num); 140 } 141 } else if ((expression.startsWith("e(") || (expression.startsWith("i("))) && expression.endsWith(")")) { 142 VersionConstraint versionConstraint = 143 versionConstraintParser.apply(expression.substring(2, expression.length() - 1)); 144 if (expression.startsWith("e(")) { 145 // exclude 146 filter = new VersionPredicateVersionFilter(v -> !versionConstraint.containsVersion(v)); 147 } else { 148 // include 149 filter = new VersionPredicateVersionFilter(versionConstraint::containsVersion); 150 } 151 } else { 152 throw new IllegalArgumentException("Unsupported filter expression: " + expression); 153 } 154 155 filters.add(contextPredicate(scopePredicate, filter)); 156 } 157 } 158 if (filters.isEmpty()) { 159 return Optional.empty(); 160 } else if (filters.size() == 1) { 161 return Optional.of(filters.get(0)); 162 } else { 163 return Optional.of(ChainedVersionFilter.newInstance(filters)); 164 } 165 } 166 167 private VersionFilter contextPredicate(Predicate<Artifact> artifactPredicate, VersionFilter filter) { 168 Predicate<VersionFilter.VersionFilterContext> contextPredicate = 169 c -> !ConfigUtils.getBoolean(c.getSession(), false, VERSION_FILTER_SUPPRESSED); 170 if (artifactPredicate != null) { 171 contextPredicate = contextPredicate.and( 172 c -> artifactPredicate.test(c.getDependency().getArtifact())); 173 } 174 return new ContextPredicateDelegatingVersionFilter(contextPredicate, filter); 175 } 176}