com.googlecode.ipv6.IPv6AddressRange Maven / Gradle / Ivy
/*
* Copyright 2013 Jan Van Besien
*
* 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
*
* 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 com.googlecode.ipv6;
import java.io.Serializable;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
/**
* Immutable representation of a continuous range of IPv6 addresses (bounds included).
*
* @author Jan Van Besien
*/
public class IPv6AddressRange implements Comparable, Iterable, Serializable
{
private final IPv6Address first;
private final IPv6Address last;
IPv6AddressRange(IPv6Address first, IPv6Address last)
{
if (first.compareTo(last) > 0)
throw new IllegalArgumentException("Cannot create ip address range with last address < first address");
this.first = first;
this.last = last;
}
public static IPv6AddressRange fromFirstAndLast(IPv6Address first, IPv6Address last)
{
return new IPv6AddressRange(first, last);
}
public boolean contains(IPv6Address address)
{
return first.compareTo(address) <= 0 && last.compareTo(address) >= 0;
}
public boolean contains(IPv6AddressRange range)
{
return contains(range.first) && contains(range.last);
}
public boolean overlaps(IPv6AddressRange range)
{
return contains(range.first) || contains(range.last) || range.contains(first) || range.contains(last);
}
/**
* @return an iterator which iterates all addresses in this range, in order.
*/
@Override
public Iterator iterator()
{
return new IPv6AddressRangeIterator();
}
/**
* @return number of addresses in the range
*/
public BigInteger size()
{
BigInteger firstAsBigInteger = new BigInteger(1, first.toByteArray());
BigInteger lastAsBigInteger = new BigInteger(1, last.toByteArray());
// note that first and last are included in the range.
return lastAsBigInteger.subtract(firstAsBigInteger).add(BigInteger.ONE);
}
/**
* Deaggregate a range of IPv6 addresses (which is not necessarily aligned with a single IPv6 network) into a minimal set of non
* overlapping consecutive subnets.
*
* @return iterator of IPv6 networks that all together define the minimal set of subnets by which the range can be represented.
*/
public Iterator toSubnets()
{
return new IPv6AddressRangeAsSubnetsIterator();
}
/**
* Remove an address from the range, resulting in one, none or two new ranges. If an address outside the range is removed, this has no
* effect. If the first or last address is removed, a single new range is returned (potentially empty if the range only contained a
* single address). If an address somewhere else in the range is removed, two new ranges are returned.
*
* @param address adddress to remove from the range
* @return list of resulting ranges
*/
public List remove(IPv6Address address)
{
if (address == null)
throw new IllegalArgumentException("invalid address [null]");
if (!contains(address))
return Collections.singletonList(this);
else if (address.equals(first) && address.equals(last))
return Collections.emptyList();
else if (address.equals(first))
return Collections.singletonList(fromFirstAndLast(first.add(1), last));
else if (address.equals(last))
return Collections.singletonList(fromFirstAndLast(first, last.subtract(1)));
else
return Arrays.asList(fromFirstAndLast(first, address.subtract(1)),
fromFirstAndLast(address.add(1), last));
}
/**
* Extend the range just enough at its head or tail such that the given address is included.
*
* @param address address to extend the range to
* @return new (bigger) range
*/
public IPv6AddressRange extend(IPv6Address address)
{
if (address.compareTo(first) < 0)
return fromFirstAndLast(address, last);
else if (address.compareTo(last) > 0)
return fromFirstAndLast(first, address);
else
return this;
}
/**
* Remove a network from the range, resulting in one, none or two new ranges. If a network outside (or partially outside) the range is
* removed, this has no effect. If the network which is removed is aligned with the beginning or end of the range, a single new ranges
* is returned (potentially empty if the range was equal to the network which is removed from it). If a network somewhere else in the
* range is removed, two new ranges are returned.
*
* @param network network to remove from the range
* @return list of resulting ranges
*/
public List remove(IPv6Network network)
{
if (network == null)
throw new IllegalArgumentException("invalid network [null]");
if (!contains(network))
return Collections.singletonList(this);
else if (this.equals(network))
return Collections.emptyList();
else if (first.equals(network.getFirst()))
return Collections.singletonList(fromFirstAndLast(network.getLast().add(1), last));
else if (last.equals(network.getLast()))
return Collections.singletonList(fromFirstAndLast(first, network.getFirst().subtract(1)));
else
return Arrays.asList(fromFirstAndLast(first, network.getFirst().subtract(1)),
fromFirstAndLast(network.getLast().add(1), last));
}
@Override
public String toString()
{
return first.toString() + " - " + last.toString();
}
/**
* @return like toString
but without using shorthand notations for addresses
*/
public String toLongString()
{
return first.toLongString() + " - " + last.toLongString();
}
/**
* The natural order of {@link com.googlecode.ipv6.IPv6AddressRange}s orders them on increasing first addresses, and on increasing last
* address if the first address would be equal.
*
* Note that the natural order does thus not compare sizes of ranges.
*
* @param that range to compare with
* @return negative, zero or positive depending on whether this is smaller, equal or greater than that
*/
@Override
public int compareTo(IPv6AddressRange that)
{
if (!this.first.equals(that.first))
return this.first.compareTo(that.first);
else
return this.last.compareTo(that.last);
}
@Override
public boolean equals(Object o)
{
if (this == o) return true;
if (!(o instanceof IPv6AddressRange)) return false;
IPv6AddressRange that = (IPv6AddressRange) o;
if (first != null ? !first.equals(that.first) : that.first != null) return false;
if (last != null ? !last.equals(that.last) : that.last != null) return false;
return true;
}
@Override
public int hashCode()
{
int result = first != null ? first.hashCode() : 0;
result = 31 * result + (last != null ? last.hashCode() : 0);
return result;
}
public IPv6Address getFirst()
{
return first;
}
public IPv6Address getLast()
{
return last;
}
/**
* @see IPv6AddressRange#iterator()
*/
private final class IPv6AddressRangeIterator implements Iterator
{
private IPv6Address current = first;
@Override
public boolean hasNext()
{
return current.compareTo(last) <= 0;
}
@Override
public IPv6Address next()
{
if (hasNext())
{
IPv6Address result = current;
current = current.add(1);
return result;
}
else
{
throw new NoSuchElementException();
}
}
@Override
public void remove()
{
throw new UnsupportedOperationException("This iterator provides read only access");
}
}
private class IPv6AddressRangeAsSubnetsIterator implements Iterator
{
private IPv6Address base = first;
private IPv6Network next;
@Override
public IPv6Network next()
{
int step;
if (hasNext())
{
step = 0;
// try setting the step-th bit until we reach a bit that is already set
while (step < 128 && !(base.setBit(step)).equals(base))
{
// if the max address in this subnet is beyond the end of the range, we went too far
if ((base.maximumAddressWithNetworkMask(IPv6NetworkMask.fromPrefixLength(127 - step)).compareTo(last) > 0))
{
break;
}
step++;
}
// the next subnet is found
next = IPv6Network.fromAddressAndMask(base, IPv6NetworkMask.fromPrefixLength(128 - step));
// start the next loop after the end of the subnet just found
if (next.getLast().compareTo(last) < 0)
{
base = next.getLast().add(1);
}
else
{
base = null; // to signal we reached the end
}
}
else
{
throw new NoSuchElementException();
}
return next;
}
@Override
public boolean hasNext()
{
// there is a next subnet as long as we didn't reach the end of the range
return base != null && (base.compareTo(last) <= 0);
}
@Override
public void remove()
{
throw new UnsupportedOperationException("This iterator provides read only access");
}
}
}