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

inet.ipaddr.ipv6.IPv6Address Maven / Gradle / Ivy

There is a newer version: 5.5.1
Show newest version
package inet.ipaddr.ipv6;

import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Iterator;

import inet.ipaddr.IPAddress;
import inet.ipaddr.IPAddressConverter;
import inet.ipaddr.IPAddressSection.IPStringBuilderOptions;
import inet.ipaddr.IPAddressSection.StringOptions;
import inet.ipaddr.IPAddressTypeException;
import inet.ipaddr.format.IPAddressPart;
import inet.ipaddr.format.util.IPAddressPartStringCollection;
import inet.ipaddr.ipv4.IPv4Address;
import inet.ipaddr.ipv4.IPv4Address.IPv4AddressConverter;
import inet.ipaddr.ipv4.IPv4AddressNetwork.IPv4AddressCreator;
import inet.ipaddr.ipv4.IPv4AddressSection;
import inet.ipaddr.ipv6.IPv6AddressNetwork.IPv6AddressCreator;
import inet.ipaddr.ipv6.IPv6AddressSection.IPv6StringBuilderOptions;
import inet.ipaddr.ipv6.IPv6AddressSection.IPv6StringCache;
import inet.ipaddr.ipv6.IPv6AddressSection.IPv6StringCollection;
import inet.ipaddr.ipv6.IPv6AddressSection.IPv6StringOptions;

/**
 * An IPv6 address, or a subnet of multiple IPv6 addresses.
 * 
 * @custom.core
 * @author sfoley
 */
/*
 * rfc 6890 and the earlier 5156 has details on some of the special addresses
 * 
 * For some of the various pre-specified IPv6 address formats (IPv4 mapped, IPv4 translated, IPv4 compatible, etc), 
 * see gestioip.net/docu/ipv6_address_examples.html
 * 
 * A nice summary of IPV6 formats at https://technet.microsoft.com/en-us/library/cc757359(v=ws.10).aspx
 * https://technet.microsoft.com/en-us/library/dd379548(v=ws.10).aspx
 */
public class IPv6Address extends IPAddress implements Iterable {

	private static final long serialVersionUID = 1L;
	
	public static final char SEGMENT_SEPARATOR = ':';
	public static final char ZONE_SEPARATOR = '%';
	
	public static final char UNC_SEGMENT_SEPARATOR = '-';
	public static final char UNC_ZONE_SEPARATOR = 's';
	public static final char UNC_RANGE_SEPARATOR = '?';
	public static final String UNC_RANGE_SEPARATOR_STR = String.valueOf(UNC_RANGE_SEPARATOR);
	
	public static final int BITS_PER_SEGMENT = 16;
	public static final int BYTES_PER_SEGMENT = 2;
	public static final int SEGMENT_COUNT = 8;
	public static final int MIXED_REPLACED_SEGMENT_COUNT = 2; //IPv4Address.BYTE_COUNT / BYTES_PER_SEGMENT;
	public static final int MIXED_ORIGINAL_SEGMENT_COUNT = 6; //SEGMENT_COUNT - MIXED_REPLACED_SEGMENT_COUNT
	public static final int BYTE_COUNT = 16;
	public static final int BIT_COUNT = 128;
	public static final int DEFAULT_TEXTUAL_RADIX = 16;
	public static final int MAX_STRING_LEN = 50;
	public static final int MAX_VALUE_PER_SEGMENT = 0xffff;
	
	private static IPv6AddressNetwork network = new IPv6AddressNetwork();
	
	/* An IPv6 zone distinguishes two IPv6 addresses that are the same.
	 * They are used with link-local addresses fe80::/10 and distinguishes two interfaces to the link-local network, this is known as the zone id.
	 * They are used with site-local addresses to distinguish sites, using the site id, also known as the scope id.
	 * 
	 * A zone that consists of a scope id is called a scoped zone.
	 */
	private final String zone;

	private transient IPv6StringCache stringCache;
	
	/**
	 * Constructs an IPv6 address or subnet.
	 * @param segments the address segments
	 */
	public IPv6Address(IPv6AddressSegment[] segments) {
		this(network.getAddressCreator().createSection(segments));
	}

	/**
	 * Constructs an IPv6 address or a set of addresses.
	 * When networkPrefixLength is non-null, this object represents a network prefix or the set of addresses with the same network prefix (a network or subnet, in other words).
	 * @param segments the address segments
	 * @param networkPrefixLength
	 */
	public IPv6Address(IPv6AddressSegment[] segments, Integer networkPrefixLength) {
		this(network.getAddressCreator().createSection(segments, networkPrefixLength));
	}
	
	/**
	 * Constructs an IPv6 address or a set of addresses.
	 * @param segments the address segments
	 * @param zone the zone
	 */
	public IPv6Address(IPv6AddressSegment[] segments, CharSequence zone) {
		this(network.getAddressCreator().createSection(segments), zone);
	}
	
	public IPv6Address(IPv6AddressSection section, CharSequence zone) {
		super(section);
		if(section.getSegmentCount() != SEGMENT_COUNT) {
			throw new IllegalArgumentException();
		}
		this.zone = (zone == null) ? "" : zone.toString();
	}
	
	public IPv6Address(IPv6AddressSection section) throws IPAddressTypeException {
		super(section);
		if(section.getSegmentCount() != SEGMENT_COUNT) {
			throw new IllegalArgumentException();
		}
		this.zone = "";
	}
	
	/**
	 * Constructs an IPv6 address.
	 *
	 * @param bytes must be a 16 byte IPv6 address
	 */
	public IPv6Address(byte[] bytes, CharSequence zone) {
		super(network.getAddressCreator().createSection(bytes, null));
		if(bytes.length != BYTE_COUNT) {
			throw new IllegalArgumentException();
		}
		this.zone = (zone == null) ? "" : zone.toString();
	}
	
	/**
	 * Constructs an IPv6 address.
	 *
	 * @param bytes must be a 16 byte IPv6 address
	 */
	public IPv6Address(byte[] bytes) {
		this(bytes, (Integer) null);
	}
	
	/**
	 * Constructs an IPv6 address or subnet.
	 * When networkPrefixLength is non-null, this object represents a network prefix or the set of addresses with the same network prefix (a network or subnet, in other words).
	 * 
	 * @param bytes must be a 16 byte IPv6 address
	 * @param networkPrefixLength the CIDR prefix, which can be null for no prefix length
	 * @throws IllegalArgumentException if bytes is not length 16
	 */
	public IPv6Address(byte[] bytes, Integer networkPrefixLength) {
		super(network.getAddressCreator().createSection(bytes, networkPrefixLength));
		if(bytes.length != BYTE_COUNT) {
			throw new IllegalArgumentException();
		}
		zone = "";
	}
	
	public static IPv6AddressNetwork network() {
		return network;
	}
	
	@Override
	public IPv6AddressNetwork getNetwork() {
		return network;
	}
	
	public static IPv6Address getLoopback() {
		return network.getLoopback();
	}
	
	public static String[] getStandardLoopbackStrings() {
		return network.getStandardLoopbackStrings();
	}
	
	@Override
	public IPv6AddressSection getSection() {
		return (IPv6AddressSection) super.getSection();
	}
	
	@Override
	public IPv6AddressSegment getSegment(int index) {
		return getSection().getSegment(index);
	}

	@Override
	public IPAddressPart[] getParts(IPStringBuilderOptions options) {
		return getParts(IPv6StringBuilderOptions.from(options));
	}
	
	public IPAddressPart[] getParts(IPv6StringBuilderOptions options) {
		IPAddressPart parts[] = getSection().getParts(options);
		IPv4Address ipv4Addr = getConverted(options);
		if(ipv4Addr != null) {
			IPAddressPart ipv4Parts[] = ipv4Addr.getParts(options.ipv4ConverterOptions);
			IPAddressPart tmp[] = parts;
			parts = new IPAddressPart[tmp.length + ipv4Parts.length];
			System.arraycopy(tmp, 0, parts, 0, tmp.length);
			System.arraycopy(ipv4Parts,  0, parts, tmp.length, ipv4Parts.length);
		}
		return parts;
	}
	
	private static IPv6AddressSection createSection(IPv6AddressSegment nonMixedSection[], IPv4Address mixedSection) throws IPAddressTypeException {
		IPv4AddressSection ipv4Section = mixedSection.getSection();
		IPv6AddressCreator creator = network.getAddressCreator();
		IPv6AddressSegment newSegs[] = creator.createSegmentArray(SEGMENT_COUNT);
		newSegs[0] = nonMixedSection[0];
		newSegs[1] = nonMixedSection[1];
		newSegs[2] = nonMixedSection[2];
		newSegs[3] = nonMixedSection[3];
		newSegs[4] = nonMixedSection[4];
		newSegs[5] = nonMixedSection[5];
		newSegs[6] = IPv6AddressSegment.join(ipv4Section.getSegment(0), ipv4Section.getSegment(1));
		newSegs[7] = IPv6AddressSegment.join(ipv4Section.getSegment(2), ipv4Section.getSegment(3));
		IPv6AddressSection result = creator.createSectionInternal(newSegs);
		result.embeddedIPv4Section = ipv4Section;
		return result;
	}
	
	@Override
	public int getSegmentCount() {
		return SEGMENT_COUNT;
	}
	
	@Override
	public int getByteCount() {
		return BYTE_COUNT;
	}
	
	@Override
	public int getBitCount() {
		return BIT_COUNT;
	}
	
	private IPv6Address getLowestOrHighest(boolean lowest) {
		return getSingle(this, () -> {
			IPv6AddressSection section = getSection();
			IPv6AddressSegment[] segs = createSingle(section, network.getAddressCreator(), i -> {
				IPv6AddressSegment seg = getSegment(i);
				return lowest ? seg.getLower() : seg.getUpper();
			});
			return network.getAddressCreator().createAddressInternal(segs, zone);
		});
	}

	@Override
	public IPv6Address getLower() {
		return getLowestOrHighest(true);
	}
	
	@Override
	public IPv6Address getUpper() {
		return getLowestOrHighest(false);
	}
	
	@Override
	public Iterator iterator() {
		return iterator(
			this,
			hasZone() ? network.getAddressCreator() : new IPv6AddressCreator() {//using a lambda for this one results in a big performance hit
				@Override
				protected IPv6Address createAddressInternal(IPv6AddressSegment segments[]) {
					IPv6AddressCreator creator = network.getAddressCreator();
					return creator.createAddressInternal(segments, zone); /* address creation */
				}
			},
			() -> getSection().getLowerSegments(),
			index -> getSegment(index).iterator());
	}
	
	@Override
	public Iterable getAddresses() {
		return this;
	}
	
	public static IPv6Address from(byte bytes[], byte bytes2[], Integer prefix, String zone) {
		return (IPv6Address) IPAddress.from(bytes, bytes2, prefix, zone);
	}
	
	public static IPv6Address from(byte bytes[], String zone) {
		return from(bytes, null, null, zone);
	}

	/**
	 * If this address is IPv4 convertible, returns that address.
	 * Otherwise, returns null.
	 * 
	 * This uses {@link #isIPv4Convertible()} to determine convertibility, and that uses an instance of {@link IPAddressConverter.DefaultAddressConverter} which uses IPv4-mapped address mappings from rfc 4038.
	 * 
	 * Override this method and {@link IPv6Address#isIPv4Convertible()} if you wish to map IPv6 to IPv4 according to the mappings defined by
	 * in {@link IPv6Address#isIPv4Compatible()}, {@link IPv6Address#isIPv4Mapped()}, {@link IPv6Address#is6To4()} or by some other mapping.
	 * 
	 * For the reverse mapping, see {@link IPv4Address#toIPv6()} 
	 */
	@Override
	public IPv4Address toIPv4() {
		IPAddressConverter conv = addressConverter;
		if(conv != null && conv.isIPv4Convertible(this)) {
			return conv.toIPv4(this);
		}
		return null;
	}
	
	@Override
	public IPv6Address toIPv6() {
		return this;
	}
	
	/**
	 * Determines whether this address can be converted to IPv4. 
	 * Override this method to convert in your own way, or call setAddressConverter with your own converter object.
	 * The default behaviour is to use isIPv4Mapped()
	 * 
	 * You should also override {@link #toIPv4()} to match the conversion.
	 * 
	 * @return
	 */
	@Override
	public boolean isIPv4Convertible() {
		IPAddressConverter conv = addressConverter;
		return conv != null && conv.isIPv4Convertible(this);
	}
	
	@Override
	public boolean isIPv6Convertible() {
		return true;
	}

	/**
	 * 
	 * @param addr
	 * @return
	 * @throws IPAddressTypeException if the IPv4 address segments cannot be converted to IPv6 segments because of one or more incompatible segment ranges.
	 */
	public static IPv6Address toIPv4Mapped(IPv4Address addr) throws IPAddressTypeException {
		IPv6AddressSegment zero = IPv6AddressSegment.ZERO_SEGMENT;
		IPv6AddressCreator creator = network.getAddressCreator();
		IPv6AddressSegment segs[] = creator.createSegmentArray(MIXED_ORIGINAL_SEGMENT_COUNT);
		segs[0] = segs[1] = segs[2] = segs[3] = segs[4] = zero;
		segs[5] = IPv6AddressSegment.ALL_SEGMENT;
		return creator.createAddress(createSection(segs, addr)); /* address creation */
	}
	
	/**
	 * ::ffff:x:x/96 indicates IPv6 address mapped to IPv4
	 */
	public IPv4AddressSection toMappedIPv4Segments() {
		if(isIPv4Mapped()) {
			return getSection().getEmbeddedIPv4AddressSection();
		}
		return null;
	}
	
	/**
	 * Returns the second and third bytes as an {@link IPv4Address}.
	 * 
	 * This can be used for IPv4 or for IPv6 6to4 addresses convertible to IPv4.
	 * 
	 * @return the address
	 */
	public IPv4Address get6to4IPv4Address() {
		return getEmbeddedIPv4Address(2);
	}
	
	/**
	 * Returns the embedded {@link IPv4Address} in the lowest (least-significant) two segments.
	 * This is used by IPv4-mapped, IPv4-compatible, ISATAP addresses and 6over4 addresses
	 * 
	 * @return the embedded {@link IPv4Address}
	 */
	public IPv4Address getEmbeddedIPv4Address() {
		IPv4AddressCreator creator = IPv4Address.network().getAddressCreator();
		return creator.createAddress(getSection().getEmbeddedIPv4AddressSection()); /* address creation */
	}
	
	/**
	 * Produces an IPv4 address from any sequence of 4 bytes in this IPv6 address.
	 * 
	 * @param byteIndex the byte index to start
	 * @throws IndexOutOfBoundsException if the index is less than zero or bigger than 7
	 * @return
	 */
	public IPv4Address getEmbeddedIPv4Address(int byteIndex) {
		if(byteIndex == IPv6Address.MIXED_ORIGINAL_SEGMENT_COUNT * IPv6Address.BYTES_PER_SEGMENT) {
			return getEmbeddedIPv4Address();
		}
		IPv4AddressCreator creator = IPv4Address.network().getAddressCreator();
		return creator.createAddress(getSection().getEmbeddedIPv4AddressSection(byteIndex, byteIndex + IPv4Address.BYTE_COUNT)); /* address creation */
	}
	
	/**
	 * @see java.net.InetAddress#isLinkLocalAddress()
	 */
	@Override
	public boolean isLinkLocal() {
		//1111 1110 10 .... fe8x currently only in use
		return getSegment(0).matchesWithPrefix(0xfe80, 10);
	}
	
	/**
	 * @see java.net.InetAddress#isSiteLocalAddress()
	 */
	@Override
	public boolean isSiteLocal() {
		//1111 1110 11 ...
		return getSegment(0).matchesWithPrefix(0xfec0, 10);
	}
	
	public boolean isUniqueLocal() {
		return getSegment(0).matchesWithPrefix(0xfc00, 7);
	}
	
	/**
	 * Whether the address is IPv4-mapped
	 * 
	 * ::ffff:x:x/96 indicates IPv6 address mapped to IPv4
	 */
	public boolean isIPv4Mapped() {
		//::ffff:x:x/96 indicates IPv6 address mapped to IPv4
		if(getSegment(5).matches(IPv6AddressSegment.ALL_SEGMENT.getLowerSegmentValue())) {
			for(int i = 0; i < 5; i++) {
				if(!getSegment(i).isZero()) {
					return false;
				}
			}
			return true;
		}
		return false;
	}
	
	/**
	 * Whether the address is IPv4-compatible
	 * 
	 * @see java.net.Inet6Address#isIPv4CompatibleAddress()
	 */
	public boolean isIPv4Compatible() {
		return getSegment(0).isZero() && getSegment(1).isZero() && getSegment(2).isZero() &&
				getSegment(3).isZero() && getSegment(4).isZero() && getSegment(5).isZero();
	}
	
	/**
	 * Whether the address is IPv6 to IPv4 relay
	 * @see #get6to4IPv4Address()
	 */
	public boolean is6To4() {
		//2002::/16
		return getSegment(0).matches(0x2002);
	}
	
	/**
	 * Whether the address is 6over4
	 */
	public boolean is6Over4() {
		return getSegment(4).isZero() && getSegment(5).isZero();
	}
	
	/**
	 * Whether the address is Teredo
	 */
	public boolean isTeredo() {
		//2001::/32
		return getSegment(0).matches(0x2001) && getSegment(1).isZero();
	}

	/**
	 * Whether the address is ISATAP
	 */
	public boolean isIsatap() {
		return getSegment(4).isZero() && getSegment(5).matches(0x5efe);
	}
	
	/**
	 * 
	 * @return Whether the address is IPv4 translatable as in rfc 2765
	 */
	public boolean isIPv4Translatable() { //rfc 2765  
		//::ffff:0:x:x/96 indicates IPv6 addresses translated from IPv4
		if(getSegment(4).matches(0xffff) && getSegment(5).isZero()) {
			for(int i = 0; i < 3; i++) {
				if(!getSegment(i).isZero()) {
					return false;
				}
			}
			return true;
		}
		return false;
	}
	
	/**
	 * Whether the address has the well-known prefix for IPv4 translatable addresses as in rfc 6052 and 6144
	 * @return
	 */
	public boolean isWellKnownIPv4Translatable() { //rfc 6052 rfc 6144
		//64:ff9b::/96 prefix for auto ipv4/ipv6 translation
		if(getSegment(0).matches(0x64) && getSegment(1).matches(0xff9b)) {
			for(int i=2; i<=5; i++) {
				if(!getSegment(i).isZero()) {
					return false;
				}
			}
			return true;
		}
		return false;
	}
	
	@Override
	public boolean isMulticast() {
		// 11111111...
		return getSegment(0).matchesWithPrefix(0xff, 8);
	}

	/**
	 * @see java.net.InetAddress#isLoopbackAddress()
	 */
	@Override
	public boolean isLoopback() {
		//::1
		int i=0;
		for(; i < getSegmentCount() - 1; i++) {
			if(!getSegment(i).isZero()) {
				return false;
			}
		}
		return getSegment(i).matches(1);
	}
	
	@Override
	public IPv6Address[] subtract(IPAddress other) {
		IPv6AddressSection thisSection = getSection();
		IPv6AddressSection sections[] = thisSection.subtract(other.getSection());
		if(sections == null) {
			return null;
		}
		IPv6Address result[] = new IPv6Address[sections.length];
		for(int i = 0; i < result.length; i++) {
			result[i] = network.getAddressCreator().createAddress(sections[i], zone); /* address creation */
		}
		return result;
	}

	@Override
	public IPv6Address toSubnet(int networkPrefixLength) throws IPAddressTypeException {
		IPv6AddressSection thisSection = getSection();
		IPv6AddressSection subnetSection = thisSection.toSubnet(networkPrefixLength);
		if(thisSection == subnetSection) {
			return this;
		}
		IPv6AddressCreator creator = network.getAddressCreator();
		return creator.createAddress(subnetSection); /* address creation */
	}

	/**
	 * Creates a subnet address using the given mask.
	 * The mask can be a subnet itself, in which case the lowest value of the mask's range is used.
	 */
	@Override
	public IPv6Address toSubnet(IPAddress mask) throws IPAddressTypeException {
		return toSubnet(mask, null);
	}
	
	/**
	 * Creates a subnet address using the given mask.  If networkPrefixLength is non-null, applies the prefix length as well.
	 * The mask can be a subnet itself, in which case the lowest value of the mask's range is used.
	 */
	@Override
	public IPv6Address toSubnet(IPAddress mask, Integer networkPrefixLength) throws IPAddressTypeException {
		IPv6AddressSection thisSection = getSection();
		IPv6AddressSection subnetSection = thisSection.toSubnet(mask.getSection(), networkPrefixLength);
		if(thisSection == subnetSection) {
			return this;
		}
		IPv6AddressCreator creator = network.getAddressCreator();
		return creator.createAddress(subnetSection); /* address creation */
	}

	@Override
	public IPv6AddressSection getNetworkSection(int networkPrefixLength, boolean withPrefixLength) {
		return getSection().getNetworkSection(networkPrefixLength, withPrefixLength);
	}
	
	@Override
	public IPv6AddressSection getNetworkSection() {
		if(isPrefixed()) {
			return getNetworkSection(getNetworkPrefixLength(), true);
		}
		return getNetworkSection(getBitCount(), true);
	}
	
	@Override
	public IPv6AddressSection getHostSection(int networkPrefixLength) {
		return getSection().getHostSection(networkPrefixLength);
	}
	
	@Override
	public IPv6AddressSection getHostSection() {
		if(isPrefixed()) {
			return getHostSection(getNetworkPrefixLength());
		}
		return getHostSection(0);
	}

	public boolean hasZone() {
		return zone.length() > 0;
	}
	
	public String getZone() {
		return zone;
	}
	
	public IPv6Address removeZone() {
		return network.getAddressCreator().createAddress(getSection()); /* address creation */
	}

	@Override
	public Inet6Address toInetAddress() {
		Inet6Address result = (Inet6Address) inetAddress;
		if(result == null) {
			synchronized(this) {
				result = (Inet6Address) inetAddress;
				if(result == null) {
					byte bytes[] = getBytes();
					try {
						if(hasZone()) {
							try {
								int scopeId = Integer.valueOf(zone);
								result = Inet6Address.getByAddress(null, bytes, scopeId);
							} catch(NumberFormatException e) {
								//there is no related function that takes a string as third arg. Only other one takes a NetworkInterface.  we don't want to be looking up network interface objects.
								//public static Inet6Address getByAddress(String host, byte[] addr, NetworkInterface nif) 
							
								//so we must go back to a string, even though we have the bytes available to us.  There appears to be no other alternative.
								result = (Inet6Address) InetAddress.getByName(toNormalizedString());
							}
						} else {
							result = (Inet6Address) InetAddress.getByAddress(bytes);
						}
					} catch(UnknownHostException e) {
						result = null;
					}
					inetAddress = result;
				}
			}
		}
		return result;
	}
	
	@Override
	public int hashCode() {
		int result = super.hashCode();
		if(hasZone()) {
			result *= zone.hashCode();
		}
		return result;
	}
	
	@Override
	public boolean isSameAddress(IPAddress other) {
		if(super.isSameAddress(other)) {
			//must check the zone too
			IPv6Address otherIPv6Address = other.toIPv6();
			String otherZone = otherIPv6Address.zone;
			return zone.equals(otherZone);
		}
		return false;
	}
	
	/**
	 * 
	 * @param other
	 * @return whether this subnet contains the given address
	 */
	@Override
	public boolean contains(IPAddress other) {
		if(super.contains(other)) {
			//must check the zone too
			if(other != this) {
				IPv6Address otherIPv6Address = other.toIPv6();
				String otherZone = otherIPv6Address.zone;
				return zone.equals(otherZone);
			}
			return true;
		}
		return false;
	}
	
	//////////////// string creation below ///////////////////////////////////////////////////////////////////////////////////////////
	
	private boolean hasNoStringCache() {
		if(stringCache == null) {
			synchronized(this) {
				if(stringCache == null) {
					if(hasZone()) {
						stringCache = new IPv6StringCache();
					} else {
						//when there is no zone, the section and address strings are the same, so we use the same cache
						IPv6AddressSection section = getSection();
						boolean result = section.hasNoStringCache();
						stringCache = section.stringCache;
						return result;
					}
					return true;
				}
			}
		}
		return false;
	}
	
	public String toMixedString() {
		String result;
		if(hasNoStringCache() || (result = stringCache.mixedString) == null) {
			if(hasZone()) {
				stringCache.mixedString = result = toNormalizedString(IPv6StringCache.mixedParams);
			} else {
				result = getSection().toMixedString();//the cache is shared so no need to update it here
			}
		}
		return result;
	}
	
	@Override
	public String toCanonicalString() {
		String result;
		if(hasNoStringCache() || (result = stringCache.canonicalString) == null) {
			if(hasZone()) {
				stringCache.canonicalString = result = toNormalizedString(IPv6StringCache.canonicalParams);
			} else {
				result = getSection().toCanonicalString();//the cache is shared so no need to update it here
			}
		}
		return result;
	}

	@Override
	public String toFullString() {
		String result;
		if(hasNoStringCache() || (result = stringCache.fullString) == null) {
			if(hasZone()) {
				stringCache.fullString = result = toNormalizedString(IPv6StringCache.fullParams);
			} else {
				result = getSection().toFullString();//the cache is shared so no need to update it here
			}
		}
		return result;
	}
	
	@Override
	public String toNormalizedString() {
		String result;
		if(hasNoStringCache() || (result = stringCache.normalizedString) == null) {
			if(hasZone()) {
				stringCache.normalizedString = result = toNormalizedString(IPv6StringCache.normalizedParams);
			} else {
				result = getSection().toNormalizedString();//the cache is shared so no need to update it here
			}
		}
		return result;
	}
	
	@Override
	public String toCompressedString() {
		String result;
		if(hasNoStringCache() || (result = stringCache.compressedString) == null) {
			if(hasZone()) {
				stringCache.compressedString = result = toNormalizedString(IPv6StringCache.compressedParams);
			} else {
				result = getSection().toCompressedString();//the cache is shared so no need to update it here
			}
		}
		return result;
	}
	
	@Override
	public String toSubnetString() {
		return toNetworkPrefixLengthString();
	}
	
	//note this string is used by hashCode
	@Override
	public String toNormalizedWildcardString() {
		String result;
		if(hasNoStringCache() || (result = stringCache.normalizedWildcardString) == null) {
			if(hasZone()) {
				stringCache.normalizedWildcardString = result = toNormalizedString(IPv6StringCache.wildcardNormalizedParams);
			} else {
				result = getSection().toNormalizedWildcardString();//the cache is shared so no need to update it here
			}
		}
		return result;
	}
	
	@Override
	public String toCanonicalWildcardString() {
		String result;
		if(hasNoStringCache() || (result = stringCache.canonicalWildcardString) == null) {
			if(hasZone()) {
				stringCache.canonicalWildcardString = result = toNormalizedString(IPv6StringCache.wildcardCanonicalParams);
			} else {
				result = getSection().toCanonicalWildcardString();//the cache is shared so no need to update it here
			}
		}
		return result;
	}
	
	@Override
	public String toCompressedWildcardString() {
		String result;
		if(hasNoStringCache() || (result = stringCache.compressedWildcardString) == null) {
			if(hasZone()) {
				stringCache.compressedWildcardString = result = toNormalizedString(IPv6StringCache.wildcardCompressedParams);
			} else {
				result = getSection().toCompressedWildcardString();//the cache is shared with the section, so no need to update it here
			}
		}
		return result;
	}
	
	@Override
	public String toSQLWildcardString() {
		String result;
		if(hasNoStringCache() || (result = stringCache.sqlWildcardString) == null) {
			if(hasZone()) {
				stringCache.sqlWildcardString = result = toNormalizedString(IPv6StringCache.sqlWildcardParams);
			} else {
				result = getSection().toSQLWildcardString();//the cache is shared so no need to update it here
			}
		}
		return result;
	}
	
	@Override
	public String toHexString(boolean withPrefix) {
		String result;
		if(hasNoStringCache() || (result = (withPrefix ? stringCache.hexStringPrefixed : stringCache.hexString)) == null) {
			if(hasZone()) {
				result = getSection().toHexString(withPrefix, zone);
				if(withPrefix) {
					stringCache.hexStringPrefixed = result;
				} else {
					stringCache.hexString = result;
				}
			} else {
				result = getSection().toHexString(withPrefix);//the cache is shared so no need to update it here
			}
		}
		return result;
	}

	@Override
	public String toNetworkPrefixLengthString() {
		String result;
		if(hasNoStringCache() || (result = stringCache.networkPrefixLengthString) == null) {
			if(hasZone()) {
				stringCache.networkPrefixLengthString = result = toNormalizedString(IPv6StringCache.networkPrefixLengthParams);
			} else {
				result = getSection().toNetworkPrefixLengthString();//the cache is shared so no need to update it here
			}
		}
		return result;
	}
	
	@Override
	public String toConvertedString() {
		if(isIPv4Convertible()) {
			return toMixedString();
		}
		return toNormalizedString();
	}
	
	@Override
	public String toNormalizedString(StringOptions params) {
		return getSection().toNormalizedString(params, zone);
	}
	
	public String toNormalizedString(IPv6StringOptions params) {
		return getSection().toNormalizedString(params, zone);
	}

	/**
	 * Constructs a string representing this address according to the given parameters
	 * 
	 * @param keepMixed if this address was constructed from a string with mixed representation (a:b:c:d:e:f:1.2.3.4), whether to keep it that way (ignored if makeMixed is true in the params argument)
	 * @param params the parameters for the address string
	 */
	public String toNormalizedString(boolean keepMixed, IPv6StringOptions params) {
		if(keepMixed && fromString != null && fromString.isMixedIPv6() && !params.makeMixed()) {
			params = new IPv6StringOptions(
					params.base,
					params.expandSegments,
					params.wildcardOptions,
					params.segmentStrPrefix,
					true,
					params.ipv4Opts,
					params.compressOptions,
					params.separator,
					params.zoneSeparator,
					params.addrPrefix,
					params.addrSuffix,
					params.reverse,
					params.splitDigits,
					params.uppercase);
		}
		return toNormalizedString(params);
	}
	
	@Override
	public String toUNCHostName() {
		String result;
		if(hasNoStringCache() || (result = stringCache.uncString) == null) {
			stringCache.uncString = result = getSection().toNormalizedString(IPv6StringCache.uncParams, zone.replace(IPv6Address.ZONE_SEPARATOR, IPv6Address.UNC_ZONE_SEPARATOR).replace(IPv6Address.SEGMENT_SEPARATOR, IPv6Address.UNC_SEGMENT_SEPARATOR));
		}
		return result;
	}
	
	@Override
	public String toReverseDNSLookupString() {
		String result;
		if(hasNoStringCache() || (result = stringCache.reverseDNSString) == null) {
			stringCache.reverseDNSString = result = getSection().toNormalizedString(IPv6StringCache.reverseDNSParams, "");//the zone is dropped
		}
		return result;
	}
	
	@Override
	public IPAddressPartStringCollection toStandardStringCollection() {
		return toStringCollection(IPv6StringBuilderOptions.STANDARD_OPTS);
	}

	@Override
	public IPAddressPartStringCollection toAllStringCollection() {
		return toStringCollection(IPv6StringBuilderOptions.ALL_OPTS);
	}
	
	@Override
	public IPAddressPartStringCollection toStringCollection(IPStringBuilderOptions opts) {
		return toStringCollection(IPv6StringBuilderOptions.from(opts));
	}

	private IPv4Address getConverted(IPv6StringBuilderOptions opts) {
		if(!hasZone() && opts.includes(IPv6StringBuilderOptions.IPV4_CONVERSIONS)) {//we cannot convert to ipv4 if there is a zone
			IPv4AddressConverter converter = opts.converter;
			return converter.toIPv4(this);
		}
		return null;
	}
	
	public IPAddressPartStringCollection toStringCollection(IPv6StringBuilderOptions opts) {
		IPv6StringCollection coll = getSection().toStringCollection(opts, zone);
		IPv4Address ipv4Addr = getConverted(opts);
		if(ipv4Addr != null) {
			IPAddressPartStringCollection ipv4StringCollection = ipv4Addr.toStringCollection(opts.ipv4ConverterOptions);
			coll.addAll(ipv4StringCollection);
		}
		return coll;
	}
	
	/**
	 * @custom.core
	 * @author sfoley
	 *
	 */
	public interface IPv6AddressConverter {
		/**
		 * If the given address is IPv6, or can be converted to IPv6, returns that {@link IPv6Address}.  Otherwise, returns null.
		 */
		IPv6Address toIPv6(IPAddress address);
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy