001/*
002 * JPPF.
003 * Copyright (C) 2005-2018 JPPF Team.
004 * http://www.jppf.org
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License");
007 * you may not use this file except in compliance with the License.
008 * 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, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018
019package org.jppf.net;
020
021import static org.jppf.utils.StringUtils.build;
022
023import java.util.*;
024
025import org.jppf.utils.*;
026
027/**
028 * Represents a pattern used for IP addresses inclusion or exclusion lists.<br/>
029 * A pattern represents a single value or a range of values for each component of an IP address.<br/>
030 * @author Laurent Cohen
031 */
032public class RangePattern {
033  /**
034   * The list of ranges constituting this address pattern.
035   */
036  protected List<Range<Integer>> ranges = new ArrayList<>();
037  /**
038   * The configuration used for this pattern.
039   */
040  protected PatternConfiguration config = null;
041
042  /**
043   * Initialize this object with the specified string pattern.
044   * @param source the source pattern as a string.
045   * @param config the configuration used for this pattern.
046   * @throws IllegalArgumentException if the pattern is null or invalid.
047   */
048  public RangePattern(final String source, final PatternConfiguration config) throws IllegalArgumentException {
049    this.config = config;
050    convertSource(source);
051  }
052
053  /**
054   * Convert the specified source into an IP pattern.
055   * @param source the source pattern as a string.
056   * @throws IllegalArgumentException if the pattern is null or invalid.
057   */
058  protected void convertSource(final String source) throws IllegalArgumentException {
059    if (source == null) throw new IllegalArgumentException("pattern cannot be null");
060    String src = preProcess(source);
061    src = RegexUtils.SPACES_PATTERN.matcher(src).replaceAll("");
062    src = postProcess(src);
063    final String[] rangeArray = config.compSeparatorPattern.split(src);
064    if ((rangeArray == null) || (rangeArray.length == 0)) throw new IllegalArgumentException("invalid empty pattern");
065    if (rangeArray.length > config.nbComponents) throw new IllegalArgumentException("pattern describes more than " + config.nbComponents + " components : \"" + source + '\"');
066    try {
067      for (final String s : rangeArray) ranges.add(parseRangePattern(s));
068      if (rangeArray.length < config.nbComponents) {
069        for (int i = rangeArray.length; i < config.nbComponents; i++) ranges.add(config.fullRange);
070      }
071    } catch (final IllegalArgumentException e) {
072      throw new IllegalArgumentException(build("error in pattern \"", source, "\" : ", e.getMessage()));
073    }
074  }
075
076  /**
077   * Perform pre-processing of the source string before applying common transformations.
078   * @param source the pattern source to process.
079   * @return a new processed string.
080   */
081  protected String preProcess(final String source) {
082    return source;
083  }
084
085  /**
086   * Perform post-processing of the source string before applying common transformations.
087   * @param source the pattern source to process.
088   * @return a new processed string.
089   */
090  protected String postProcess(final String source) {
091    return source;
092  }
093
094  /**
095   * Determine whether the specified IP address matches this pattern.
096   * No check is made to verify that the IP address is valid.
097   * @param values the ip address to match as a array of values representing its components.
098   * @return true if the address matches this pattern, false otherwise.
099   */
100  public boolean matches(final int... values) {
101    try {
102      if ((values == null) || (values.length != ranges.size())) return false;
103      for (int i = 0; i < values.length; i++) {
104        if (!ranges.get(i).isValueInRange(values[i])) return false;
105      }
106    } catch (@SuppressWarnings("unused") final Exception e) {
107      return false;
108    }
109    return true;
110  }
111
112  /**
113   * Parse the specified string into a <code>Range</code> object.
114   * @param src the range pattern string to parse.
115   * @return a <code>Range</code> instance, or null if the pattern is invalid.
116   * @throws IllegalArgumentException if the pattern is invalid.
117   */
118  private Range<Integer> parseRangePattern(final String src) throws IllegalArgumentException {
119    if ((src == null) || "".equals(src)) return config.fullRange;
120    if (src.indexOf('-') < 0) return new Range<>(parseValue(src));
121    final String[] vals = RegexUtils.MINUS_PATTERN.split(src);
122    if ((vals == null) || vals.length == 0) return config.fullRange;
123    if (vals.length > 2) throw new IllegalArgumentException(build("invalid range pattern (pattern: ", src, ")"));
124    int lower = 0;
125    int upper = 0;
126    if (vals.length == 1) {
127      if (src.startsWith("-")) {
128        lower = config.minValue;
129        upper = parseValue(vals[0]);
130      } else {
131        lower = parseValue(vals[0]);
132        upper = config.maxValue;
133      }
134    } else {
135      lower = "".equals(vals[0]) ? config.minValue : parseValue(vals[0]);
136      upper = "".equals(vals[1]) ? config.maxValue : parseValue(vals[1]);
137    }
138    if (upper < lower) throw new IllegalArgumentException(build("lower bound must be <= upper bound (pattern: ", src, ")"));
139    return new Range<>(lower, upper);
140  }
141
142  /**
143   * Parse the specified string into an int value.
144   * @param src the string to parse.
145   * @return the value as an int
146   * @throws IllegalArgumentException if the string is not a valid number format or the value is out of allowed bounds.
147   */
148  private int parseValue(final String src) throws IllegalArgumentException {
149    try {
150      final int value = Integer.decode(config.valuePrefix + src.toLowerCase());
151      if ((value < config.minValue) || (value > config.maxValue))
152        throw new IllegalArgumentException(build("value must be in [", config.minValue, " ... ", config.maxValue, "] range (value: ", src, ")"));
153      return value;
154    } catch (@SuppressWarnings("unused") final NumberFormatException e) {
155      throw new IllegalArgumentException(build("invalid value format (value: ", src, ")"));
156    }
157  }
158
159  @Override
160  public String toString() {
161    final StringBuilder sb = new StringBuilder();
162    for (int i = 0; i < ranges.size(); i++) {
163      if (i > 0) sb.append(config.getCompSeparator());
164      sb.append(ranges.get(i));
165    }
166    return sb.toString();
167  }
168}