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.util.version; 020 021import java.util.Objects; 022 023import org.eclipse.aether.version.InvalidVersionSpecificationException; 024import org.eclipse.aether.version.Version; 025import org.eclipse.aether.version.VersionRange; 026import org.eclipse.aether.version.VersionScheme; 027 028import static java.util.Objects.requireNonNull; 029 030/** 031 * A version range inspired by mathematical range syntax. For example, "[1.0,2.0)", "[1.0,)" or "[1.0]". 032 * <p> 033 * Despite its name, this class is generic in a sense it works with any {@link Version} 034 */ 035public final class GenericVersionRange implements VersionRange { 036 private final VersionScheme versionScheme; 037 038 private final Bound lowerBound; 039 040 private final Bound upperBound; 041 042 /** 043 * Creates a version range from the specified range specification. 044 * 045 * @param range the range specification to parse, must not be {@code null} 046 * @throws InvalidVersionSpecificationException if the range could not be parsed 047 */ 048 GenericVersionRange(VersionScheme versionScheme, String range) throws InvalidVersionSpecificationException { 049 this.versionScheme = requireNonNull(versionScheme, "versionScheme cannot be null"); 050 String process = requireNonNull(range, "version range cannot be null"); 051 052 boolean lowerBoundInclusive, upperBoundInclusive; 053 Version lowerBound, upperBound; 054 055 if (range.startsWith("[")) { 056 lowerBoundInclusive = true; 057 } else if (range.startsWith("(")) { 058 lowerBoundInclusive = false; 059 } else { 060 throw new InvalidVersionSpecificationException( 061 range, "Invalid version range " + range + ", a range must start with either [ or ("); 062 } 063 064 if (range.endsWith("]")) { 065 upperBoundInclusive = true; 066 } else if (range.endsWith(")")) { 067 upperBoundInclusive = false; 068 } else { 069 throw new InvalidVersionSpecificationException( 070 range, "Invalid version range " + range + ", a range must end with either [ or ("); 071 } 072 073 process = process.substring(1, process.length() - 1); 074 075 int index = process.indexOf(","); 076 077 if (index < 0) { 078 if (!lowerBoundInclusive || !upperBoundInclusive) { 079 throw new InvalidVersionSpecificationException( 080 range, "Invalid version range " + range + ", single version must be surrounded by []"); 081 } 082 083 String version = process.trim(); 084 if (version.endsWith(".*")) { 085 String prefix = version.substring(0, version.length() - 1); 086 lowerBound = parse(prefix + "min"); 087 upperBound = parse(prefix + "max"); 088 } else { 089 lowerBound = parse(version); 090 upperBound = lowerBound; 091 } 092 } else { 093 String parsedLowerBound = process.substring(0, index).trim(); 094 String parsedUpperBound = process.substring(index + 1).trim(); 095 096 // more than two bounds, e.g. (1,2,3) 097 if (parsedUpperBound.contains(",")) { 098 throw new InvalidVersionSpecificationException( 099 range, "Invalid version range " + range + ", bounds may not contain additional ','"); 100 } 101 102 lowerBound = !parsedLowerBound.isEmpty() ? parse(parsedLowerBound) : null; 103 upperBound = !parsedUpperBound.isEmpty() ? parse(parsedUpperBound) : null; 104 105 if (upperBound != null && lowerBound != null) { 106 if (upperBound.compareTo(lowerBound) < 0) { 107 throw new InvalidVersionSpecificationException( 108 range, 109 "Invalid version range " + range + ", lower bound must not be greater than upper bound"); 110 } 111 } 112 } 113 114 this.lowerBound = (lowerBound != null) ? new Bound(lowerBound, lowerBoundInclusive) : null; 115 this.upperBound = (upperBound != null) ? new Bound(upperBound, upperBoundInclusive) : null; 116 } 117 118 private Version parse(String version) throws InvalidVersionSpecificationException { 119 return versionScheme.parseVersion(version); 120 } 121 122 @Override 123 public Bound getLowerBound() { 124 return lowerBound; 125 } 126 127 @Override 128 public Bound getUpperBound() { 129 return upperBound; 130 } 131 132 @Override 133 public boolean containsVersion(Version version) { 134 if (lowerBound != null) { 135 int comparison = lowerBound.getVersion().compareTo(version); 136 137 if (comparison == 0 && !lowerBound.isInclusive()) { 138 return false; 139 } 140 if (comparison > 0) { 141 return false; 142 } 143 } 144 145 if (upperBound != null) { 146 int comparison = upperBound.getVersion().compareTo(version); 147 148 if (comparison == 0 && !upperBound.isInclusive()) { 149 return false; 150 } 151 if (comparison < 0) { 152 return false; 153 } 154 } 155 156 return true; 157 } 158 159 @Override 160 public boolean equals(Object obj) { 161 if (obj == this) { 162 return true; 163 } else if (obj == null || !getClass().equals(obj.getClass())) { 164 return false; 165 } 166 167 VersionRange that = (VersionRange) obj; 168 169 return Objects.equals(upperBound, that.getUpperBound()) && Objects.equals(lowerBound, that.getLowerBound()); 170 } 171 172 @Override 173 public int hashCode() { 174 int hash = 17; 175 hash = hash * 31 + hash(upperBound); 176 hash = hash * 31 + hash(lowerBound); 177 return hash; 178 } 179 180 private static int hash(Object obj) { 181 return obj != null ? obj.hashCode() : 0; 182 } 183 184 @Override 185 public String toString() { 186 StringBuilder buffer = new StringBuilder(64); 187 if (lowerBound != null) { 188 buffer.append(lowerBound.isInclusive() ? '[' : '('); 189 buffer.append(lowerBound.getVersion()); 190 } else { 191 buffer.append('('); 192 } 193 buffer.append(','); 194 if (upperBound != null) { 195 buffer.append(upperBound.getVersion()); 196 buffer.append(upperBound.isInclusive() ? ']' : ')'); 197 } else { 198 buffer.append(')'); 199 } 200 return buffer.toString(); 201 } 202}