001/*
002 * JPPF.
003 * Copyright (C) 2005-2015 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  /**
035   * The list of ranges constituting this address pattern.
036   */
037  protected List<Range<Integer>> ranges = new ArrayList<>();
038  /**
039   * The configuration used for this pattern.
040   */
041  protected PatternConfiguration config = null;
042
043  /**
044   * Initialize this object with the specified string pattern.
045   * @param source the source pattern as a string.
046   * @param config the configuration used for this pattern.
047   * @throws IllegalArgumentException if the pattern is null or invalid.
048   */
049  public RangePattern(final String source, final PatternConfiguration config) throws IllegalArgumentException
050  {
051    this.config = config;
052    convertSource(source);
053  }
054
055  /**
056   * Convert the specified source into an IP pattern.
057   * @param source the source pattern as a string.
058   * @throws IllegalArgumentException if the pattern is null or invalid.
059   */
060  protected void convertSource(final String source) throws IllegalArgumentException
061  {
062    if (source == null) throw new IllegalArgumentException("pattern cannot be null");
063    String src = preProcess(source);
064    src = RegexUtils.SPACES_PATTERN.matcher(src).replaceAll("");
065    src = postProcess(src);
066    String[] rangeArray = config.compSeparatorPattern.split(src);
067    if ((rangeArray == null) || (rangeArray.length == 0)) throw new IllegalArgumentException("invalid empty pattern");
068    if (rangeArray.length > config.nbComponents) throw new IllegalArgumentException("pattern describes more than " + config.nbComponents + " components : \"" + source + '\"');
069    try
070    {
071      for (String s: rangeArray) ranges.add(parseRangePattern(s));
072      if (rangeArray.length < config.nbComponents)
073      {
074        for (int i=rangeArray.length; i<config.nbComponents; i++) ranges.add(config.fullRange);
075      }
076    }
077    catch(IllegalArgumentException e)
078    {
079      throw new IllegalArgumentException(build("error in pattern \"", source, "\" : ", e.getMessage()));
080    }
081  }
082
083  /**
084   * Perform pre-processing of the source string before applying common transformations.
085   * @param source the pattern source to process.
086   * @return a new processed string.
087   */
088  protected String preProcess(final String source)
089  {
090    return source;
091  }
092
093  /**
094   * Perform post-processing of the source string before applying common transformations.
095   * @param source the pattern source to process.
096   * @return a new processed string.
097   */
098  protected String postProcess(final String source)
099  {
100    return source;
101  }
102
103  /**
104   * Determine whether the specified IP address matches this pattern.
105   * No check is made to verify that the IP address is valid.
106   * @param values the ip address to match as a array of values representing its components.
107   * @return true if the address matches this pattern, false otherwise.
108   */
109  public boolean matches(final int...values)
110  {
111    try
112    {
113      if ((values == null) || (values.length != ranges.size())) return false;
114      for (int i=0; i<values.length; i++) if (!ranges.get(i).isValueInRange(values[i])) return false;
115    }
116    catch (Exception e)
117    {
118      return false;
119    }
120    return true;
121  }
122
123  /**
124   * Parse the specified string into a <code>Range</code> object.
125   * @param src the range pattern string to parse.
126   * @return a <code>Range</code> instance, or null if the pattern is invalid.
127   * @throws IllegalArgumentException if the pattern is invalid.
128   */
129  private Range<Integer> parseRangePattern(final String src) throws IllegalArgumentException
130  {
131    if ((src == null) || "".equals(src)) return config.fullRange;
132    if (src.indexOf('-') < 0) return new Range<>(parseValue(src));
133    String[] vals = RegexUtils.MINUS_PATTERN.split(src);
134    if ((vals == null) || vals.length == 0) return config.fullRange;
135    if (vals.length > 2) throw new IllegalArgumentException(build("invalid range pattern (pattern: ", src, ")"));
136    int lower = 0;
137    int upper = 0;
138    if (vals.length == 1)
139    {
140      if (src.startsWith("-"))
141      {
142        lower = config.minValue;
143        upper = parseValue(vals[0]);
144      }
145      else
146      {
147        lower = parseValue(vals[0]);
148        upper = config.maxValue;
149      }
150    }
151    else
152    {
153      lower = "".equals(vals[0]) ? config.minValue : parseValue(vals[0]);
154      upper = "".equals(vals[1]) ? config.maxValue : parseValue(vals[1]);
155    }
156    if (upper < lower) throw new IllegalArgumentException(build("lower bound must be <= upper bound (pattern: ", src, ")"));
157    return new Range<>(lower, upper);
158  }
159
160  /**
161   * Parse the specified string into an int value.
162   * @param src the string to parse.
163   * @return the value as an int
164   * @throws IllegalArgumentException if the string is not a valid number format or the value is out of allowed bounds.
165   */
166  private int parseValue(final String src) throws IllegalArgumentException
167  {
168    try
169    {
170      int value = Integer.decode(config.valuePrefix + src.toLowerCase());
171      if ((value < config.minValue) || (value > config.maxValue))
172        throw new IllegalArgumentException(build("value must be in [" , config.minValue, " ... ", config.maxValue, "] range (value: ", src, ")"));
173      return value;
174    }
175    catch(NumberFormatException e)
176    {
177      throw new IllegalArgumentException(build("invalid value format (value: ", src, ")"));
178    }
179  }
180
181  @Override
182  public String toString()
183  {
184    StringBuilder sb = new StringBuilder();
185    for (int i=0; i<ranges.size(); i++)
186    {
187      if (i > 0) sb.append(config.getCompSeparator());
188      sb.append(ranges.get(i));
189    }
190    return sb.toString();
191  }
192}