All Downloads are FREE. Search and download functionalities are using the official Maven repository.

inet.ipaddr.IPAddressString Maven / Gradle / Ivy

There is a newer version: 5.5.1
Show newest version
/*
 * Copyright 2016-2018 Sean C Foley
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *     or at
 *     https://github.com/seancfoley/IPAddress/blob/master/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package inet.ipaddr;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;

import inet.ipaddr.IPAddress.IPVersion;
import inet.ipaddr.format.validate.HostIdentifierStringValidator;
import inet.ipaddr.format.validate.IPAddressProvider;
import inet.ipaddr.format.validate.Validator;
import inet.ipaddr.ipv4.IPv4Address;
import inet.ipaddr.ipv6.IPv6Address;
import inet.ipaddr.mac.MACAddress;

/**
 * Parses the string representation of an IP address.  Such a string can represent just a single address like 1.2.3.4 or 1:2:3:4:6:7:8, or a subnet like 1.2.0.0/16 or 1.*.1-3.1-4 or 1111:222::/64.
 * 

* This supports a much wider range of address string formats than InetAddress.getByName. It supports subnet formats, provides specific error messages, and allows more specific configuration. *

* You can control all of the supported formats using {@link IPAddressStringParameters.Builder} to build a parameters instance of {@link IPAddressStringParameters}. * When not using the constructor that takes a {@link IPAddressStringParameters}, a default instance of {@link IPAddressStringParameters} is used that is generally permissive. *

*

Supported formats

* Both IPv4 and IPv6 are supported. *

* Subnets are supported: *

    *
  • wildcards '*' and ranges '-' (for example 1.*.2-3.4), useful for working with subnets
  • *
  • SQL wildcards '%' and '_', although '%' is considered an SQL wildcard only when it is not considered an IPv6 zone indicator
  • *
  • CIDR network prefix length addresses, like 1.2.3.4/16, which is equivalent to 1.2.*.*
  • *
  • address/mask pairs, in which the mask is applied to the address, like 1.2.3.4/255.255.0.0, which is also equivalent to 1.2.*.*
  • *
*

* You can combine these variations, such as 1.*.2-3.4/255.255.255.0 *

* IPv6 is fully supported: *

    *
  • IPv6 addresses like ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff
  • *
  • IPv6 zones or scope ids, like ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff%zone
  • *
  • IPv6 mixed addresses are supported, which are addresses for which the last two IPv6 segments are represented as IPv4, like ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255
  • *
  • IPv6 compressed addresses like ::1
  • *
  • A single value of 32 hex digits like 00aa00bb00cc00dd00ee00ff00aa00bb with or without a preceding hex delimiter 0x
  • *
  • A base 85 address comprising 20 base 85 digits like 4)+k&C#VzJ4br>0wv%Yp as in rfc 1924 https://tools.ietf.org/html/rfc1924
  • *
*

* All of the above subnet variations work for IPv6, whether network prefixes, masks, ranges or wildcards. * Similarly, all the the above subnet variations work for any supported IPv4 format, such as the standard dotted-decimal IPv4 format as well as the inet_aton formats listed below. *

* This class support all address formats of the C routine inet_pton and the Java method java.net.InetAddress.getByName. * This class supports all IPv4 address formats of the C routine inet_aton as follows: *

    *
  • IPv4 hex: 0x1.0x2.0x3.0x4 (0x prefix)
  • *
  • IPv4 octal: 01.02.03.0234. Note this clashes with the same address interpreted as dotted decimal
  • *
  • 3-part IPv4: 1.2.3 (which is interpreted as 1.2.0.3 (ie the third part covers the last two)
  • *
  • 2-part IPv4: 1.2 (which is interpreted as 1.0.0.2 (ie the 2nd part covers the last 3)
  • *
  • 1-part IPv4: 1 (which is interpreted as 0.0.0.1 (ie the number represents all 4 segments, and can be any number of digits less than the 32 digits which would be interpreted as IPv6)
  • *
  • hex or octal variants of 1, 2, and 3 part, such as 0xffffffff (which is interpreted as 255.255.255.255)
  • *

* inet_aton (and this class) allows mixing octal, hex and decimal (e.g. 0xa.11.013.11 which is equivalent to 11.11.11.11). String variations using prefixes, masks, ranges, and wildcards also work for inet_aton style. *

* Note that there is ambiguity when supporting both inet_aton octal and dotted-decimal leading zeros, like 010.010.010.010 which can * be interpreted as octal or decimal, thus it can be either 8.8.8.8 or 10.10.10.10, with the default behaviour using the former interpretation

* This behaviour can be controlled by {@link IPAddressStringParameters.Builder#getIPv4AddressParametersBuilder()} and * {@link inet.ipaddr.ipv4.IPv4AddressStringParameters.Builder#allowLeadingZeros(boolean)} *

* Some additional formats: *

    *
  • null or empty strings are interpreted as the loopback, in the same way as InetAddress.getByName interprets null or empty strings
  • *
  • the single wildcard address "*" which represents all addresses both ipv4 and ipv6, * although you need to give it some help when converting to IPAddress by specifying the IP version in {@link #getAddress(IPVersion)} or {@link #toAddress(IPVersion)}
  • *
  • specifying CIDR prefix lengths with no corresponding addresses are interpreted as the corresponding network mask. For instance, * /64 is interpreted as the 64 bit network mask (ie 64 ones followed by 64 zeros)
  • *
*

* If you have an address in which segments have been delimited with commas, such as "1,2.3.4,5.6", you can parse this with {@link #parseDelimitedSegments(String)} * which gives an iterator of strings. For "1,2.3.4,5.6" you will iterate through "1.3.4.6", "1.3.5.6", "2.3.4.6" and "2.3.5.6". * You can count the number of elements in such an iterator with {@link #countDelimitedAddresses(String)}. * Each string can then be used to construct an IPAddressString. *

*

Usage

* Once you have constructed an IPAddressString object, you can convert it to an IPAddress object with various methods. * It is as simple as:
*

 * {@link IPAddress} address = new {@link IPAddressString}("1.2.3.4").{@link #getAddress()};
 * 
*

* If your application takes user input IP addresses, you can validate with: *


 * try {
 *  {@link IPAddress} address = new IPAddressString("1.2.3.4").{@link #toAddress()};
 * } catch({@link AddressStringException} e) {
 *	//e.getMessage() provides description of validation failure
 * }
 * 
* Most address strings can be converted to an IPAddress object using {@link #getAddress()} or {@link #toAddress()}. In most cases the IP version is determined by the string itself. *

* There are a few exceptions, cases in which the version is unknown or ambiguous, for which {@link #getAddress()} returns null: *

    *
  • strings which do not represent valid addresses (eg "bla")
  • *
  • ambiguous address strings (eg "/32" is a prefix that could be IPv4 or IPv6). For such strings you can provide the IPv4/IPv6 version to {@link #getAddress(IPVersion)} to get an address.
  • *
  • the "all" address "*" which represents all IPv4 and IPv6 addresses. For this string you can provide the IPv4/IPv6 version to {@link #getAddress(IPVersion)} to get an address representing either all IPv4 or all IPv6 addresses.
  • *
  • empty string "" is interpreted as the default loopback address. You can provide the ipv4/ipv6 version to{@link #getAddress(IPVersion)}to get the loopback version of your choice.
  • *
*

* The other exception is a subnet in which the range of values in a segment of the subnet are not sequential, for which {@link #getAddress()} throws {@link IncompatibleAddressException} because there is no single IPAddress value, there would be many. * An IPAddress instance requires that all segments can be represented as a range of values. * There are only two unusual circumstances when this can occur: *

    *
  • using masks on subnets specified with wildcard or range characters causing non-sequential segments such as the final IPv4 segment of 0.0.0.* with mask 0.0.0.128, * this example translating to the two addresses 0.0.0.0 and 0.0.0.128, so the last IPv4 segment cannot be represented as a sequential range of values.
  • *
  • using wildcards or range characters in the IPv4 section of an IPv6 mixed address causing non-sequential segments such as the last IPv6 segment of ::ffff:0.0.*.0, * this example translating to the addresses ::ffff:0:100, ::ffff:0:200, , ::ffff:0:300, ..., so the last IPv6 segment cannot be represented as a sequential range of values.
  • *
* These exceptions do not occur with non-subnets, nor can they occur with standard CIDR prefix-based subnets. *

* This class is thread-safe. In fact, IPAddressString objects are immutable. * An IPAddressString object represents a single IP address representation that cannot be changed after construction. * Some of the derived state is created upon demand and cached, such as the derived IPAddress instances. *

* * @custom.core * @author sfoley * */ /* * The test class IPAddressTest and other test classes can be used to validate any changes to this class and others. * * A nice summary exists at http://www.gestioip.net/docu/ipv6_address_examples.html * * Some discussion of formats is https://tools.ietf.org/html/draft-main-ipaddr-text-rep-00 * Discussion of theses formats: http://tools.ietf.org/html/draft-main-ipaddr-text-rep-02 * RFCs of interest are 2732, 2373, 3986, 4291, 5952, 2765, 1918, 3513 (IPv4 rfcs 1123 0953) 1883 1884 (original spec of 3 string representations of IPv6), 4007 6874 for IPv6 zone identifier or scope id * Early ones: 2460, 2553, 1122, 1812 * * Nice cheat sheet for IPv6: http://www.roesen.org/files/ipv6_cheat_sheet.pdf * * Nice summary on zones and parsing http://veithen.github.io/2013/12/30/how-to-correctly-parse-ipv6-addresses.html * * Nice resource on IPv6 vs IPv4 and lots of stuff including MAC: * https://communities.bmc.com/docs/DOC-19235 * Another: https://www.midnightfreddie.com/ipv6-ipv4-similar.html * * Some parsing code for various languages: https://rosettacode.org/wiki/Parse_an_IP_Address * http://www.cisco.com/c/en/us/support/docs/ip/routing-information-protocol-rip/13788-3.html */ public class IPAddressString implements HostIdentifierString, Comparable { private static final long serialVersionUID = 4L; /* * Generally permissive, settings are the default constants in IPAddressStringParameters. * % denotes a zone, not an SQL wildcard (allowZone is true), * and leading zeros are considered decimal, not octal (allow_inet_aton_octal is false). */ public static final IPAddressStringParameters DEFAULT_VALIDATION_OPTIONS = new IPAddressStringParameters.Builder().toParams(); private static final AddressStringException IS_IPV6_EXCEPTION = new AddressStringException("ipaddress.error.address.is.ipv6"); private static final AddressStringException IS_IPV4_EXCEPTION = new AddressStringException("ipaddress.error.address.is.ipv4"); final IPAddressStringParameters validationOptions; /* the full original string address */ final String fullAddr; // fields for validation state /* exceptions and booleans for validation - for type INVALID both of ipv6Exception and ipv4Exception are non-null */ private AddressStringException ipv6Exception, ipv4Exception; // an object created by parsing that will provide the associated IPAddress(es) private IPAddressProvider addressProvider = IPAddressProvider.NO_TYPE_PROVIDER; /** * Constructs an IPAddressString instance using the given String instance. * * @param addr the address in string format, either IPv4 like a.b.c.d or IPv6 like a:b:c:d:e:f:g:h or a:b:c:d:e:f:h.i.j.k or a::b or some other valid IPv4 or IPv6 form. * IPv6 addresses are allowed to terminate with a scope id which starts with a % symbol. * Both types of addresses can terminate with a network prefix value like a.b.c.d/24 or ::/24 * Optionally, you can specify just a network prefix value like /24, which represents the associated masks 255.255.255.0/24 or ffff:ff00::/24. *

* Both IPv4 and IPv6 addresses can terminate with a mask instead of a prefix length, like a.b.c.d/255.0.0.0 or ::/ffff:: * If a terminating mask is equivalent to a network prefix, then it will be the same as specifying the prefix, so a.b.c.d/16 is the same as a.b.c.d/255.255.0.0 * If a terminating mask is not equivalent to a network prefix, then the mask will simply be applied to the address to produce a single address. *

* You can also alter the addresses to include ranges using the wildcards * and -, such as 1.*.1-2.3. */ public IPAddressString(String addr) { this(addr, DEFAULT_VALIDATION_OPTIONS); } /** * @param addr the address in string format * * This constructor allows you to alter the default validation options. */ public IPAddressString(String addr, IPAddressStringParameters valOptions) { if(addr == null) { fullAddr = addr = ""; } else { addr = addr.trim(); fullAddr = addr; } this.validationOptions = valOptions; } /** * Provides an address string instance for an existing address. Not all valid address strings can be converted to address objects, * such as the all address"*", or empty strings "", or prefix only addresses like "/32", * so it can be useful to maintain a set of address strings instances. *

* Even though the address exists already, the options provide the networks in use by the address, as well as options for creating new addresses from this address. * * @param address * @param network */ IPAddressString(IPAddress address, IPAddressStringParameters valOptions) { validationOptions = valOptions; fullAddr = address.toCanonicalString(); initByAddress(address); } void cacheAddress(IPAddress address) { if(addressProvider == IPAddressProvider.NO_TYPE_PROVIDER) { initByAddress(address); } } void initByAddress(IPAddress address) { IPAddressProvider provider = address.getProvider(); if(provider.isProvidingIPv4()) { ipv6Exception = IS_IPV4_EXCEPTION; } else if(provider.isProvidingIPv6()) { ipv4Exception = IS_IPV6_EXCEPTION; } addressProvider = provider; } public IPAddressStringParameters getValidationOptions() { return validationOptions; } /** * @return whether this address has an associated prefix length */ public boolean isPrefixed() { return getNetworkPrefixLength() != null; } /** * @return if this address is a valid address with a network prefix then this returns that prefix, otherwise returns null */ public Integer getNetworkPrefixLength() { if(isValid()) { return addressProvider.getProviderNetworkPrefixLength(); } return null; } /** * @return whether the address represents a valid specific IP address, * as opposed to an empty string, the address representing all addresses of all types, a prefix length, or an invalid format. */ public boolean isIPAddress() { return isValid() && addressProvider.isProvidingIPAddress(); } /** * @return whether the address represents the set all all valid IP addresses (as opposed to an empty string, a specific address, a prefix length, or an invalid format). */ public boolean isAllAddresses() { return isValid() && addressProvider.isProvidingAllAddresses(); } /** * @return whether the address represents a valid IP address network prefix (as opposed to an empty string, an address with or without a prefix, or an invalid format). */ public boolean isPrefixOnly() { return isValid() && addressProvider.isProvidingPrefixOnly(); } /** * Returns true if the address is empty (zero-length). * @return */ public boolean isEmpty() { return isValid() && addressProvider.isProvidingEmpty(); } /** * Returns true if the address is IPv4 (with or without a network prefix, with or without wildcard segments). * @return */ public boolean isIPv4() { return isValid() && addressProvider.isProvidingIPv4(); } /** * Returns true if the address is IPv6 (with or without a network prefix, with or without wildcard segments). * @return */ public boolean isIPv6() { return isValid() && addressProvider.isProvidingIPv6(); } /** * If this address string represents an IPv6 address, returns whether the lower 4 bytes were represented as IPv4 * @return */ public boolean isMixedIPv6() { return isIPv6() && addressProvider.isProvidingMixedIPv6(); } /** * If this address string represents an IPv6 address, returns whether the string was base 85 * @return */ public boolean isBase85IPv6() { return isIPv6() && addressProvider.isProvidingBase85IPv6(); } public IPVersion getIPVersion() { if(isValid()) { return addressProvider.getProviderIPVersion(); } return null; } /** * @see java.net.InetAddress#isLoopbackAddress() */ public boolean isLoopback() { IPAddress val = getAddress(); return val != null && val.isLoopback(); } public boolean isZero() { IPAddress value = getAddress(); return value != null && value.isZero(); } /** * @return whether the address represents one of the accepted IP address types, which are: * an IPv4 address, an IPv6 address, a network prefix, the address representing all addresses of all types, or an empty string. * If it does not, and you want more details, call validate() and examine the thrown exception. */ public boolean isValid() { if(addressProvider.isUninitialized()) { try { validate(); return true; } catch(AddressStringException e) { return false; } } return !addressProvider.isInvalid(); } /** * Validates that this string is a valid IPv4 address, and if not, throws an exception with a descriptive message indicating why it is not. * @throws AddressStringException */ public void validateIPv4() throws AddressStringException { validate(IPVersion.IPV4); checkIPv4Exception(); } /** * Validates that this string is a valid IPv6 address, and if not, throws an exception with a descriptive message indicating why it is not. * @throws AddressStringException */ public void validateIPv6() throws AddressStringException { validate(IPVersion.IPV6); checkIPv6Exception(); } /** * Validates that this string is a valid address, and if not, throws an exception with a descriptive message indicating why it is not. * @throws AddressStringException */ @Override public void validate() throws AddressStringException { validate(null); } private void checkIPv4Exception() throws AddressStringException { if(ipv4Exception != null) { if(ipv4Exception == IS_IPV6_EXCEPTION) { ipv4Exception = new AddressStringException("ipaddress.error.address.is.ipv6"); // uses proper stack trace rather than IS_IPV6_EXCEPTION } throw ipv4Exception; } } private void checkIPv6Exception() throws AddressStringException { if(ipv6Exception != null) { if(ipv6Exception == IS_IPV4_EXCEPTION) { ipv6Exception = new AddressStringException("ipaddress.error.address.is.ipv4"); // uses proper stack trace rather than IS_IPV4_EXCEPTION } throw ipv6Exception; } } private boolean isValidated(IPVersion version) throws AddressStringException { if(addressProvider != IPAddressProvider.NO_TYPE_PROVIDER) { if(version == null) { if(ipv6Exception != null && ipv4Exception != null) { throw ipv4Exception; // the two exceptions are the same, so no need to choose } } else if(version.isIPv4()) { checkIPv4Exception(); } else if(version.isIPv6()) { checkIPv6Exception(); } return true; } return false; } protected HostIdentifierStringValidator getValidator() { return Validator.VALIDATOR; } private void validate(IPVersion version) throws AddressStringException { if(isValidated(version)) { return; } synchronized(this) { if(isValidated(version)) { return; } //we know nothing about this address. See what it is. try { IPAddressProvider valueCreator = getValidator().validateAddress(this); //either the address is ipv4, ipv6, or indeterminate, and we set the cached validation exception appropriately IPVersion createdVersion = valueCreator.getProviderIPVersion(); if(createdVersion != null) { if(createdVersion.isIPv4()) { ipv6Exception = IS_IPV4_EXCEPTION; } else if(createdVersion.isIPv6()) { ipv4Exception = IS_IPV6_EXCEPTION; } } addressProvider = valueCreator; } catch(AddressStringException e) { ipv6Exception = ipv4Exception = e; addressProvider = IPAddressProvider.INVALID_PROVIDER; throw e; } } } /** * Validates that the string has the format "/x" for a valid prefix length x. * @param ipVersion IPv4, IPv6, or null if you do not know in which case it will be assumed that it can be either * @param networkPrefixLength the network prefix length integer as a string, eg "24" * @return the network prefix length * @throws IncompatibleAddressException if invalid with an appropriate message */ public static int validateNetworkPrefixLength(IPVersion ipVersion, CharSequence networkPrefixLength) throws PrefixLenException { try { return Validator.VALIDATOR.validatePrefix(networkPrefixLength, ipVersion); } catch(AddressStringException e) { throw new PrefixLenException(networkPrefixLength, ipVersion, e); } } public static void validateNetworkPrefix(IPVersion ipVersion, int networkPrefixLength, boolean allowPrefixesBeyondAddressSize) throws PrefixLenException { boolean asIPv4 = (ipVersion != null && ipVersion.isIPv4()); if(networkPrefixLength > (asIPv4 ? IPv4Address.BIT_COUNT : IPv6Address.BIT_COUNT)) { throw new PrefixLenException(networkPrefixLength, ipVersion); } } @Override public int hashCode() { if(isValid()) { return addressProvider.providerHashCode(); } return toString().hashCode(); } @Override public int compareTo(IPAddressString other) { if(this == other) { return 0; } boolean isValid = isValid(); boolean otherIsValid = other.isValid(); if(!isValid && !otherIsValid) { return toString().compareTo(other.toString()); } return addressProvider.providerCompare(other.addressProvider); } /** * Similar to {@link #equals(Object)}, but instead returns whether the prefix of this address matches the same of the given address, * using the prefix length of this address. *

* In other words, determines if the other address is in the same prefix subnet using the prefix length of this address. *

* It this address has no prefix length, returns false. The other address need not have an associated prefix length for this method to return true. *

* If this address string or the given address string is invalid, returns false. * * @param other * @return */ public boolean prefixEquals(IPAddressString other) { // getting the prefix Integer prefixLength = getNetworkPrefixLength(); if(prefixLength == null) { return false; } if(other == this && !isPrefixOnly()) { return true; } if(other.addressProvider == IPAddressProvider.NO_TYPE_PROVIDER) { // other not yet validated - if other is validated no need for this quick contains // do the quick check that uses only the String of the other, matching til the end of the prefix length, for performance Boolean directResult = addressProvider.prefixEquals(other.fullAddr); if(directResult != null) { return directResult.booleanValue(); } } if(other.isValid()) { Boolean directResult = addressProvider.prefixEquals(other.addressProvider); if(directResult != null) { return directResult.booleanValue(); } IPAddress thisAddress = getAddress(); IPAddress otherAddress = other.getAddress(); if(thisAddress != null) { return otherAddress != null && prefixLength <= otherAddress.getBitCount() && thisAddress.prefixEquals(otherAddress); } // one or both addresses are null, so there is no prefix to speak of } return false; } /** * Two IPAddressString objects are equal if they represent the same set of addresses. * Whether one or the other has an associated network prefix length is not considered. * * If an IPAddressString is invalid, it is equal to another address only if the other address was constructed from the same string. * */ @Override public boolean equals(Object o) { if(o == this) { return true; } if(o instanceof IPAddressString) { IPAddressString other = (IPAddressString) o; // if they have the same string, they must be the same, // but the converse is not true, if they have different strings, they can // still be the same because IPv6 addresses have many representations // and additional things like leading zeros can have an effect for IPv4 // Also note that we do not call equals() on the validation options, this is intended as an optimization, // and probably better to avoid going through all the validation objects here boolean stringsMatch = toString().equals(other.toString()); if(stringsMatch && validationOptions == other.validationOptions) { return true; } if(isValid()) { if(other.isValid()) { Boolean directResult = addressProvider.parsedEquals(other.addressProvider); if(directResult != null) { return directResult.booleanValue(); } try { // When a value provider produces no value, equality and comparison are based on the enum IPType, // which can be null. return addressProvider.equalsProvider(other.addressProvider); } catch(IncompatibleAddressException e) { return stringsMatch; } } } else if(!other.isValid()) { return stringsMatch; // Two invalid addresses are not equal unless strings match, regardless of validation options } } return false; } /** * Returns whether the address subnet identified by this address string contains the address identified by the given string. *

* If this address string or the given address string is invalid then returns false. * * @param other * @return */ public boolean contains(IPAddressString other) { if(isValid()) { if(other == this) { return true; } if(other.addressProvider == IPAddressProvider.NO_TYPE_PROVIDER) { // other not yet validated - if other is validated no need for this quick contains //do the quick check that uses only the String of the other Boolean directResult = addressProvider.contains(other.fullAddr); if(directResult != null) { return directResult.booleanValue(); } } if(other.isValid()) { // note the quick result also handles the case of "all addresses" Boolean directResult = addressProvider.contains(other.addressProvider); if(directResult != null) { return directResult.booleanValue(); } IPAddress addr = getAddress(); if(addr != null) { IPAddress otherAddress = other.getAddress(); if(otherAddress != null) { return addr.contains(otherAddress); } } } } return false; } /** * If this address string was constructed from a host address with prefix length, * then this provides just the host address, rather than the address * provided by {@link #getAddress()} that incorporates the prefix. *

* Otherwise this returns the same object as {@link #getAddress()}. *

* This method returns null for invalid formats, the equivalent method {@link #toHostAddress()} throws exceptions for invalid formats. * * @return */ public IPAddress getHostAddress() throws IncompatibleAddressException { if(!addressProvider.isInvalid()) { //Avoid the exception the second time with this check try { return toHostAddress(); } catch(AddressStringException e) { /* note that this exception is cached, it is not lost forever */ } } return null; } /** * Similar to {@link #toAddress(IPVersion)}, but returns null rather than throwing an exception with the address is invalid or does not match the supplied version. * */ public IPAddress getAddress(IPVersion version) throws IncompatibleAddressException { if(!addressProvider.isInvalid()) { //Avoid the exception the second time with this check try { return toAddress(version); } catch(AddressStringException e) { /* note that this exception is cached, it is not lost forever */ } } return null; } /** * If this represents an ip address, returns that address. Otherwise, returns null. *

* This method will return null for invalid formats. Use {@link #toAddress()} for an equivalent method that throws exceptions for invalid formats. *

* If you have a prefix address and you wish to get only the host without the prefix, use {@link #getHostAddress()} * * @return the address */ @Override public IPAddress getAddress() throws IncompatibleAddressException { if(!addressProvider.isInvalid()) { //Avoid the exception the second time with this check try { return toAddress(); } catch(AddressStringException e) { /* note that this exception is cached, it is not lost forever */ } } return null; } /** * If this address string was constructed from a host address with prefix, * then this provides just the host address, rather than the address with the prefix * provided by {@link #toAddress()} that incorporates the prefix. * * Otherwise this returns the same object as {@link #toAddress()} * * This method throws exceptions for invalid formats, the equivalent method {@link #getHostAddress()} will simply return null in such cases. * * @return */ public IPAddress toHostAddress() throws AddressStringException, IncompatibleAddressException { validate(); //call validate so that we throw consistently, cover type == INVALID, and ensure the addressProvider exists return addressProvider.getProviderHostAddress(); } /** * Produces the {@link IPAddress} of the specified address version corresponding to this IPAddressString. *

* In most cases the string indicates the address version and calling {@link #toAddress()} is sufficient, with a few exceptions. *

* When this object represents only a network prefix length, * specifying the address version allows the conversion to take place to the associated mask for that prefix length. *

* When this object represents all addresses, specifying the address version allows the conversion to take place * to the associated representation of all IPv4 or all IPv6 addresses. *

* When this object represents the empty string and that string is interpreted as a loopback, then it returns * the corresponding loopback address. If empty strings are not interpreted as loopback, null is returned. *

* When this object represents an ipv4 or ipv6 address, it returns that address if and only if that address matches the provided version. *

* If the string used to construct this object is an invalid format, * or a format that does not match the provided version, then this method throws {@link AddressStringException}. *

* @param version the address version that this address should represent. * @return * @throws AddressStringException * @throws IncompatibleAddressException address in proper format cannot be converted to an address: for masks inconsistent with associated address range, or ipv4 mixed segments that cannot be joined into ipv6 segments */ public IPAddress toAddress(IPVersion version) throws AddressStringException, IncompatibleAddressException { validate(); //call validate so that we throw consistently, cover type == INVALID, and ensure the addressProvider exists return addressProvider.getProviderAddress(version); } /** * Produces the {@link IPAddress} corresponding to this IPAddressString. *

* If this object does not represent a specific IPAddress or a ranged IPAddress, null is returned, * which may be the case if this object represents only a network prefix or if it represents the empty address string. *

* If the string used to construct this object is not a known format (empty string, address, range of addresses, or prefix) then this method throws {@link AddressStringException}. *

* An equivalent method that does not throw exception for invalid formats is {@link #getAddress()} *

* If you have a prefixed address and you wish to get only the host rather than the address with the prefix, use {@link #toHostAddress()} *

* * As long as this object represents a valid address (but not necessarily a specific address), this method does not throw. *

* @throws AddressStringException if the address format is invalid * @throws IncompatibleAddressException if a valid address string representing multiple addresses cannot be represented * This happens only for masks inconsistent with the associated address ranges, or ranges in ipv4 mixed segments that cannot be joined into ipv6 segments * */ @Override public IPAddress toAddress() throws AddressStringException, IncompatibleAddressException { validate(); //call validate so that we throw consistently, cover type == INVALID, and ensure the addressProvider exists return addressProvider.getProviderAddress(); } public IPAddressString adjustPrefixBySegment(boolean nextSegment) { if(isPrefixOnly()) { //Use IPv4 segment boundaries int bitsPerSegment = IPv4Address.BITS_PER_SEGMENT; int existingPrefixLength = getNetworkPrefixLength(); int newBits; if(nextSegment) { int adjustment = existingPrefixLength % bitsPerSegment; newBits = Math.min(IPv6Address.BIT_COUNT, existingPrefixLength + bitsPerSegment - adjustment); } else { int adjustment = ((existingPrefixLength - 1) % bitsPerSegment) + 1; newBits = Math.max(0, existingPrefixLength - adjustment); } return new IPAddressString(IPAddressNetwork.getPrefixString(newBits), validationOptions); } IPAddress address = getAddress(); if(address == null) { return null; } Integer prefix = address.getNetworkPrefixLength(); if(!nextSegment && prefix != null && prefix == 0 && address.isMultiple() && address.isPrefixBlock()) { return new IPAddressString(IPAddress.SEGMENT_WILDCARD_STR, validationOptions); } return address.adjustPrefixBySegment(nextSegment).toAddressString(); } public IPAddressString adjustPrefixLength(int adjustment) { if(isPrefixOnly()) { int newBits = adjustment > 0 ? Math.min(IPv6Address.BIT_COUNT, getNetworkPrefixLength() + adjustment) : Math.max(0, getNetworkPrefixLength() + adjustment); return new IPAddressString(IPAddressNetwork.getPrefixString(newBits), validationOptions); } IPAddress address = getAddress(); if(address == null) { return null; } if(adjustment == 0) { return this; } Integer prefix = address.getNetworkPrefixLength(); if(prefix != null && prefix + adjustment < 0 && address.isPrefixBlock()) { return new IPAddressString(IPAddress.SEGMENT_WILDCARD_STR, validationOptions); } return address.adjustPrefixLength(adjustment).toAddressString(); } /** * Given a string with comma delimiters to denote segment elements, this method will count the possible combinations. *

* For example, given "1,2.3.4,5.6" this method will return 4 for the possible combinations: "1.3.4.6", "1.3.5.6", "2.3.4.6" and "2.3.5.6" *

* @param str * @return */ public static int countDelimitedAddresses(String str) { int segDelimitedCount = 0; int result = 1; for(int i = 0; i < str.length(); i++) { char c = str.charAt(i); if(isDelimitedBoundary(c)) { if(segDelimitedCount > 0) { result *= segDelimitedCount + 1; segDelimitedCount = 0; } } else if(c == SEGMENT_VALUE_DELIMITER) { segDelimitedCount++; } } if(segDelimitedCount > 0) { result *= segDelimitedCount + 1; } return result; } private static boolean isDelimitedBoundary(char c) { return c == IPv4Address.SEGMENT_SEPARATOR || c == IPv6Address.SEGMENT_SEPARATOR || c == Address.RANGE_SEPARATOR || c == MACAddress.DASHED_SEGMENT_RANGE_SEPARATOR; } /** * Given a string with comma delimiters to denote segment elements, this method will provide an iterator to iterate through the possible combinations. *

* For example, given "1,2.3.4,5.6" this will iterate through "1.3.4.6", "1.3.5.6", "2.3.4.6" and "2.3.5.6" *

* Another example: "1-2,3.4.5.6" will iterate through "1-2.4.5.6" and "1-3.4.5.6". *

* This method will not validate strings. Each string produced can be validated using an instance of IPAddressString. * * @param str * @return */ public static Iterator parseDelimitedSegments(String str) { List> parts = null; int lastSegmentStartIndex = 0; int lastPartIndex = 0; int lastDelimiterIndex = 0; boolean anyDelimited = false; List delimitedList = null; for(int i = 0; i < str.length(); i++) { char c = str.charAt(i); if(isDelimitedBoundary(c)) { if(delimitedList != null) { if(parts == null) { parts = new ArrayList>(8); } addParts(str, parts, lastSegmentStartIndex, lastPartIndex, lastDelimiterIndex, delimitedList, i); lastPartIndex = i; delimitedList = null; } lastSegmentStartIndex = lastDelimiterIndex = i + 1; } else if(c == SEGMENT_VALUE_DELIMITER) { anyDelimited = true; if(delimitedList == null) { delimitedList = new ArrayList(); } String sub = str.substring(lastDelimiterIndex, i); delimitedList.add(sub); lastDelimiterIndex = i + 1; } } if(anyDelimited) { if(delimitedList != null) { if(parts == null) { parts = new ArrayList>(8); } addParts(str, parts, lastSegmentStartIndex, lastPartIndex, lastDelimiterIndex, delimitedList, str.length()); } else { parts.add(Arrays.asList(new String[] {str.substring(lastPartIndex, str.length())})); } return iterator(parts); } return new Iterator() { boolean done; @Override public boolean hasNext() { return !done; } @Override public String next() { if(done) { throw new NoSuchElementException(); } done = true; return str; } @Override public void remove() { throw new UnsupportedOperationException(); } }; } private static Iterator iterator(List> parts) { return new Iterator() { private boolean done; final int partCount = parts.size(); @SuppressWarnings("unchecked") private final Iterator variations[] = new Iterator[partCount]; private String nextSet[] = new String[partCount]; { updateVariations(0); } private void updateVariations(int start) { for(int i = start; i < partCount; i++) { variations[i] = parts.get(i).iterator(); nextSet[i] = variations[i].next(); } } @Override public boolean hasNext() { return !done; } @Override public String next() { if(done) { throw new NoSuchElementException(); } StringBuilder result = new StringBuilder(); for(int i = 0; i < partCount; i++) { result.append(nextSet[i]); } increment(); return result.toString(); } private void increment() { for(int j = partCount - 1; j >= 0; j--) { if(variations[j].hasNext()) { nextSet[j] = variations[j].next(); updateVariations(j + 1); return; } } done = true; } @Override public void remove() { throw new UnsupportedOperationException(); } }; } private static void addParts(String str, List> parts, int lastSegmentStartIndex, int lastPartIndex, int lastDelimiterIndex, List delimitedList, int i) { String sub = str.substring(lastDelimiterIndex, i); delimitedList.add(sub); if(lastPartIndex != lastSegmentStartIndex) { parts.add(Arrays.asList(new String[] {str.substring(lastPartIndex, lastSegmentStartIndex)})); } parts.add(delimitedList); } /** * Converts this address to a prefix length * * @return the prefix of the indicated IP type represented by this address or null if this address is valid but cannot be represented by a network prefix length * @throws AddressStringException if the address is invalid */ public String convertToPrefixLength() throws AddressStringException { IPAddress address = toAddress(); Integer prefix; if(address == null) { if(isPrefixOnly()) { prefix = getNetworkPrefixLength(); } else { return null; } } else { prefix = address.getBlockMaskPrefixLength(true); if(prefix == null) { return null; } } return IPAddressSegment.toUnsignedString(prefix, 10, new StringBuilder(IPAddressSegment.toUnsignedStringLength(prefix, 10) + 1).append(IPAddress.PREFIX_LEN_SEPARATOR)).toString(); } private static String toNormalizedString(IPAddressProvider addressProvider) { String result; if(addressProvider.isProvidingAllAddresses()) { result = IPAddress.SEGMENT_WILDCARD_STR; } else if(addressProvider.isProvidingEmpty()) { result = ""; } else if(addressProvider.isProvidingPrefixOnly()) { result = IPAddressNetwork.getPrefixString(addressProvider.getProviderNetworkPrefixLength()); } else if(addressProvider.isProvidingIPAddress()) { result = addressProvider.getProviderAddress().toNormalizedString(); } else { result = null; } return result; } @Override public String toNormalizedString() { String result; if(isValid()) { result = toNormalizedString(addressProvider); } else { result = toString(); } return result; } /** * Gives us the original string provided to the constructor. For variations, call {@link #getAddress()}/{@link #toAddress()} and then use string methods on the address object. */ @Override public String toString() { return fullAddr; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy