View Javadoc
1   /**
2    * This file Copyright (c) 2003-2018 Magnolia International
3    * Ltd.  (http://www.magnolia-cms.com). All rights reserved.
4    *
5    *
6    * This file is dual-licensed under both the Magnolia
7    * Network Agreement and the GNU General Public License.
8    * You may elect to use one or the other of these licenses.
9    *
10   * This file is distributed in the hope that it will be
11   * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12   * implied warranty of MERCHANTABILITY or FITNESS FOR A
13   * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14   * Redistribution, except as permitted by whichever of the GPL
15   * or MNA you select, is prohibited.
16   *
17   * 1. For the GPL license (GPL), you can redistribute and/or
18   * modify this file under the terms of the GNU General
19   * Public License, Version 3, as published by the Free Software
20   * Foundation.  You should have received a copy of the GNU
21   * General Public License, Version 3 along with this program;
22   * if not, write to the Free Software Foundation, Inc., 51
23   * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24   *
25   * 2. For the Magnolia Network Agreement (MNA), this file
26   * and the accompanying materials are made available under the
27   * terms of the MNA which accompanies this distribution, and
28   * is available at http://www.magnolia-cms.com/mna.html
29   *
30   * Any modifications to this file must keep this entire header
31   * intact.
32   *
33   */
34  package info.magnolia.module.model;
35  
36  import java.util.regex.Matcher;
37  import java.util.regex.Pattern;
38  
39  /**
40   * Module dependencies can use a range syntax for versions.
41   * Supports the following syntaxes
42   * <ul>
43   * <li>* - includes all versions</li>
44   * <li>1.2 - matches only 1.2 exactly </li>
45   * <li>1.2/* - matches all versions including and above 1.2</li>
46   * <li>1.2/1.2.9 - matches all versions including and between 1.2 and 1.2.9</li>
47   * <li>[1.2,1.2.9] - inward square brackets mean inclusion</li>
48   * <li>[1.2,1.2.9[ - outward square brackets mean exclusion</li>
49   * <li>[1.2,1.2.9) - parenthesis mean exclusion</li>
50   * </ul>
51   *
52   * @see info.magnolia.module.model.VersionRangeTest for examples.
53   */
54  public class VersionRange {
55      // a bracket, a version, a comma, a version, a bracket
56      private final static Pattern boundedRangeSyntax = Pattern.compile("^([(\\[\\]])(.*),(.*)([)\\[\\]])$");
57      // make sure we start and end with characters that are not brackets
58      private final static Pattern notBoundedByBrackets = Pattern.compile("^([^(\\[\\]])(.*)([^)\\[\\]])$", Pattern.DOTALL);
59      private final Version from;
60      private final Version to;
61      private final Boundary leftBoundary;
62      private final Boundary rightBoundary;
63  
64      public static VersionRange parse(String input) {
65          return new VersionRange(input);
66      }
67  
68      /**
69       * @deprecated since 5.0.2 use {@link VersionRange#parse(String)} instead - should not be public.
70       */
71      @Deprecated
72      public VersionRange(String input) {
73          final String rangeDef = input == null ? "*" : input;
74          final int slashIdx = rangeDef.indexOf('/');
75          final int commaIdx = rangeDef.indexOf(',');
76          final Matcher boundedRangeMatcher = input != null ? boundedRangeSyntax.matcher(input) : null;
77          final Matcher notBoundedByBracketsMatcher = input != null ? notBoundedByBrackets.matcher(input) : null;
78          if (slashIdx < 0 && commaIdx < 0) {
79              this.from = newVersionOrStar(rangeDef, Version.UNDEFINED_FROM);
80              this.to = newVersionOrStar(rangeDef, Version.UNDEFINED_TO);
81              this.leftBoundary = Boundary.leftInclSq;
82              this.rightBoundary = Boundary.rightInclSq;
83          } else if (slashIdx >= 0 && notBoundedByBracketsMatcher != null && notBoundedByBracketsMatcher.matches()) {
84              this.from = newVersionOrStar(rangeDef.substring(0, slashIdx), Version.UNDEFINED_FROM);
85              this.to = newVersionOrStar(rangeDef.substring(slashIdx + 1), Version.UNDEFINED_TO);
86              this.leftBoundary = Boundary.leftInclSq;
87              this.rightBoundary = Boundary.rightInclSq;
88          } else if (boundedRangeMatcher != null && boundedRangeMatcher.matches()) {
89              this.leftBoundary = Boundary.bySymbol(boundedRangeMatcher.group(1).charAt(0), Hand.left);
90              this.from = newVersionOrStarOrEmpty(boundedRangeMatcher.group(2), Version.UNDEFINED_FROM);
91              this.to = newVersionOrStarOrEmpty(boundedRangeMatcher.group(3), Version.UNDEFINED_TO);
92              this.rightBoundary = Boundary.bySymbol(boundedRangeMatcher.group(4).charAt(0), Hand.right);
93          } else {
94              throw new IllegalArgumentException("Can't parse version range: " + input);
95          }
96          validate(input);
97      }
98  
99      /**
100      * @deprecated should not be public - since 5.0.2
101      */
102     @Deprecated
103     public VersionRange(Versionf="../../../../info/magnolia/module/model/Version.html#Version">Version from, Version to) {
104         this(from, Boundary.leftInclSq, to, Boundary.rightInclSq);
105     }
106 
107     private VersionRange(Versionnolia/module/model/Version.html#Version">Version from, Boundary leftBoundary, Version to, Boundary rightBoundary) {
108         this.from = from;
109         this.to = to;
110         this.leftBoundary = leftBoundary;
111         this.rightBoundary = rightBoundary;
112         validate("from:" + from + ",to:" + to + ",leftBoundary:" + leftBoundary + ",rightBoundary:" + rightBoundary);
113     }
114 
115     private Version/model/Version.html#Version">Version newVersionOrStarOrEmpty(String rangeDef, Version ifUndefined) {
116         if ("".equals(rangeDef.trim())) {
117             return ifUndefined;
118         }
119         return newVersionOrStar(rangeDef, ifUndefined);
120     }
121 
122     private Version/module/model/Version.html#Version">Version newVersionOrStar(String rangeDef, Version ifUndefined) {
123         if ("*".equals(rangeDef.trim())) {
124             return ifUndefined;
125         }
126         return Version.parseVersion(rangeDef);
127     }
128 
129     /**
130      * @param inputSpec the range as specified by the caller. Should not be used for actual validation, but can be used for error messages.
131      */
132     private void validate(String inputSpec) {
133         if (from.isStrictlyAfter(to)) {
134             throw new IllegalArgumentException("Invalid range: " + from + " is not after " + to + " (specified as \"" + inputSpec + "\")");
135         }
136     }
137 
138     /**
139      * @deprecated should not be public - since 5.0.2
140      */
141     @Deprecated
142     public Version getFrom() {
143         return from;
144     }
145 
146     /**
147      * @deprecated should not be public - since 5.0.2
148      */
149     @Deprecated
150     public Version getTo() {
151         return to;
152     }
153 
154     public boolean contains(Version other) {
155         if (other.isEquivalent(from) && leftBoundary.include) {
156             return true;
157         }
158         if (other.isEquivalent(to) && rightBoundary.include) {
159             return true;
160         }
161         return other.isStrictlyAfter(from) && to.isStrictlyAfter(other);
162     }
163 
164     @Override
165     public String toString() {
166         final StringBuilder sb = new StringBuilder();
167         if (from.equals(Version.UNDEFINED_FROM)) {
168             sb.append(Boundary.leftParen.symbol);
169         } else {
170             sb.append(leftBoundary.symbol);
171             sb.append(from);
172         }
173         sb.append(',');
174         if (to.equals(Version.UNDEFINED_TO)) {
175             sb.append(Boundary.rightParen.symbol);
176         } else {
177             sb.append(to);
178             sb.append(rightBoundary.symbol);
179         }
180         return sb.toString();
181     }
182 
183     private static enum Hand {left, right}
184 
185     private static enum Boundary {
186         leftExclSq(Hand.left, ']', false), leftInclSq(Hand.left, '[', true), leftParen(Hand.left, '(', false),
187         rightExclSq(Hand.right, '[', false), rightInclSq(Hand.right, ']', true), rightParen(Hand.right, ')', false);
188 
189         private final Hand hand;
190         private final char symbol;
191         private final boolean include;
192 
193         private Boundary(Hand hand, char symbol, boolean include) {
194             this.hand = hand;
195             this.symbol = symbol;
196             this.include = include;
197         }
198 
199         static Boundary bySymbol(char c, Hand h) {
200             for (Boundary boundary : values()) {
201                 if (boundary.hand == h && boundary.symbol == c) {
202                     return boundary;
203                 }
204             }
205             throw new IllegalArgumentException("No " + Boundary.class.getSimpleName() + " found by symbol " + c + " for " + h.name() + " boundary. ");
206         }
207     }
208 
209 }