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

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

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

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Iterator;

import inet.ipaddr.IPAddress;
import inet.ipaddr.IPAddress.IPVersion;
import inet.ipaddr.IPAddressConverter.DefaultAddressConverter;
import inet.ipaddr.IPAddressSection;
import inet.ipaddr.IPAddressSection.WildcardOptions.Wildcards;
import inet.ipaddr.IPAddressSegment;
import inet.ipaddr.IPAddressTypeException;
import inet.ipaddr.format.IPAddressDivision;
import inet.ipaddr.format.IPAddressPart;
import inet.ipaddr.format.IPAddressSegmentGrouping;
import inet.ipaddr.format.util.IPAddressPartConfiguredString;
import inet.ipaddr.format.util.IPAddressPartStringCollection;
import inet.ipaddr.format.util.IPAddressPartStringParams;
import inet.ipaddr.format.util.IPAddressPartStringSubCollection;
import inet.ipaddr.format.util.sql.IPAddressSQLTranslator;
import inet.ipaddr.format.util.sql.SQLStringMatcher;
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.ipv4.IPv4AddressSection.IPv4StringBuilderOptions;
import inet.ipaddr.ipv4.IPv4AddressSegment;
import inet.ipaddr.ipv6.IPv6AddressNetwork.IPv6AddressCreator;
import inet.ipaddr.ipv6.IPv6AddressSection.CompressOptions.CompressionChoiceOptions;
import inet.ipaddr.ipv6.IPv6AddressSection.IPv6StringCollection.IPv6StringBuilder;
import inet.ipaddr.ipv6.IPv6AddressSection.IPv6StringCollection.IPv6StringParams;
import inet.ipaddr.ipv6.IPv6AddressSection.IPv6StringCollection.IPv6v4MixedParams;
import inet.ipaddr.ipv6.IPv6AddressSection.IPv6StringCollection.IPv6v4MixedStringBuilder;

/**
 * 
 * @author sfoley
 *
 */
public class IPv6AddressSection extends IPAddressSection {

	private static final long serialVersionUID = 1L;

	private static IPv6AddressCreator creators[] = new IPv6AddressCreator[IPv6Address.SEGMENT_COUNT + 1];

	static class IPv6StringCache extends StringCache {
		//a set of pre-defined string types
		static final IPv6StringOptions mixedParams;
		static final IPv6StringOptions fullParams;

		static final IPv6StringOptions normalizedParams;
		static final IPv6StringOptions canonicalParams;
		static final IPv6StringOptions uncParams;
		static final IPv6StringOptions compressedParams;
		
		static final IPv6StringOptions wildcardNormalizedParams;
		static final IPv6StringOptions wildcardCanonicalParams;
		static final IPv6StringOptions sqlWildcardParams;
		static final IPv6StringOptions wildcardCompressedParams;
		static final IPv6StringOptions networkPrefixLengthParams;
		static final IPv6StringOptions reverseDNSParams;
		
		static {
			CompressOptions 
				compressAll = new CompressOptions(true, CompressOptions.CompressionChoiceOptions.ZEROS_OR_HOST),
				compressMixed = new CompressOptions(true, CompressOptions.CompressionChoiceOptions.MIXED_PREFERRED),
				compressAllNoSingles = new CompressOptions(false, CompressOptions.CompressionChoiceOptions.ZEROS_OR_HOST), 
				compressHostPreferred = new CompressOptions(true, CompressOptions.CompressionChoiceOptions.HOST_PREFERRED),
				compressZeros = new CompressOptions(true, CompressOptions.CompressionChoiceOptions.ZEROS),
				compressZerosNoSingles = new CompressOptions(false, CompressOptions.CompressionChoiceOptions.ZEROS);

			mixedParams = new IPv6StringOptions.Builder().setMakeMixed(true).setCompressOptions(compressMixed).toParams();
			fullParams = new IPv6StringOptions.Builder().setExpandedSegments(true).setWildcardOptions(new WildcardOptions(WildcardOptions.WildcardOption.NETWORK_ONLY, new Wildcards(IPAddress.RANGE_SEPARATOR_STR))).toParams();
			canonicalParams = new IPv6StringOptions.Builder().setCompressOptions(compressAllNoSingles).toParams();
			uncParams = new IPv6StringOptions.Builder().setSeparator('-').setZoneSeparator('s').setAddressSuffix(".ipv6-literal.net").setWildcardOptions(new WildcardOptions(WildcardOptions.WildcardOption.NETWORK_ONLY, new Wildcards(IPv6Address.UNC_RANGE_SEPARATOR_STR, IPAddress.SEGMENT_WILDCARD_STR, null))).toParams();
			compressedParams = new IPv6StringOptions.Builder().setCompressOptions(compressAll).toParams();
			normalizedParams = new IPv6StringOptions.Builder().toParams();
			WildcardOptions 
				allWildcards = new WildcardOptions(WildcardOptions.WildcardOption.ALL),
				allSQLWildcards = new WildcardOptions(WildcardOptions.WildcardOption.ALL, new Wildcards(IPAddress.SEGMENT_SQL_WILDCARD_STR, IPAddress.SEGMENT_SQL_SINGLE_WILDCARD_STR));

			wildcardCanonicalParams = new IPv6StringOptions.Builder().setWildcardOptions(allWildcards).setCompressOptions(compressZerosNoSingles).toParams();
			wildcardNormalizedParams = new IPv6StringOptions.Builder().setWildcardOptions(allWildcards).toParams(); //no compression
			sqlWildcardParams = new IPv6StringOptions.Builder().setWildcardOptions(allSQLWildcards).toParams(); //no compression
			wildcardCompressedParams = new IPv6StringOptions.Builder().setWildcardOptions(allWildcards).setCompressOptions(compressZeros).toParams();
			networkPrefixLengthParams = new IPv6StringOptions.Builder().setCompressOptions(compressHostPreferred).toParams();
			reverseDNSParams = new IPv6StringOptions.Builder().setReverse(true).setAddressSuffix(".ip6.arpa").setSplitDigits(true).setExpandedSegments(true).setSeparator('.').toParams();
		}
		
		public String normalizedString;
		public String compressedString;
		public String mixedString;
		public String compressedWildcardString;									
		public String canonicalWildcardString;
		public String networkPrefixLengthString;
		
		//we piggy-back on the section cache for strings that are full address only
		public String uncString;
	}
	
	transient IPv6StringCache stringCache;

	transient IPv4AddressSection embeddedIPv4Section;//the lowest 4 bytes as IPv4
	transient IPv6v4MixedAddressSection defaultMixedAddressSection;

	/*
	 * Indicates the index of the first segment where this section would be located in a full IPv6 address.  0 for network sections or full addresses
	 */
	public final int startIndex;

	/* also for caching: index of segments that are zero, and the number of consecutive zeros for each. */
	private transient RangeList zeroSegments;
	
	/* also for caching: index of segments that are zero or any value due to CIDR prefix, and the number of consecutive segments for each. */
	private transient RangeList zeroRanges;
	
	/*
	 * Only an address section including leading segment should use this constructor
	 */
	public IPv6AddressSection(IPv6AddressSegment segments[]) {
		this(segments, 0, true);
	}
	
	//TODO construct addresses from multiple sections.  Just need to check if the startIndex values work out.  Maybe we could force the start indices as necessary.
	//the impetus is EUI64 using mac addresses, but in such cases it would just work out, the prefix would start wiht 0 startindex, the othe section would have the correct start index.
	//maybe we allow the 0 start index to be flexible.
	//or why bother?  We can just use the segments to construct.  We will be throwing away the section object anyway.
	//Is there anything to keep?  The mixed or embedded ipv4, in some cases, but none of the strings or zero ranges.
	
	/*
	 * Only an address section including leading segment should use this constructor
	 */
	public IPv6AddressSection(IPv6AddressSegment segments[], Integer networkPrefixLength) {
		this(segments, 0, networkPrefixLength);
	}
	
	public IPv6AddressSection(IPv6AddressSegment[] segments, int startIndex, Integer networkPrefixLength) {
		this(toCIDRSegments(networkPrefixLength, segments, getIPv6SegmentCreator(), IPv6AddressSegment::toNetworkSegment), startIndex, false);
	}
	
	IPv6AddressSection(IPv6AddressSegment[] segments, int startIndex, boolean cloneSegments) {
		super(segments, null, cloneSegments, false);
		if(startIndex < 0) {
			throw new IllegalArgumentException();
		}
		this.startIndex = startIndex;
	}
	
	IPv6AddressSection(byte bytes[], Integer prefix, boolean cloneBytes) {
		super(toSegments(bytes, IPv6Address.SEGMENT_COUNT, IPv6Address.BYTES_PER_SEGMENT, IPv6Address.BITS_PER_SEGMENT, getIPv6SegmentCreator(), prefix), bytes, false, cloneBytes);
		this.startIndex = 0;
	}
	
	public IPv6AddressSection(byte bytes[], Integer prefix) {
		this(bytes, prefix, true);
	}
	
	@Override
	protected void initCachedValues(
			Integer prefixLen,
			boolean network,
			Integer cachedNetworkPrefix,
			Integer cachedMinPrefix,
			Integer cachedEquivalentPrefix,
			BigInteger cachedCount,
			RangeList zeroSegments,
			RangeList zeroRanges) {
		super.initCachedValues(prefixLen, network, cachedNetworkPrefix, cachedMinPrefix, cachedEquivalentPrefix, cachedCount, zeroSegments, zeroRanges);
		this.zeroSegments = zeroSegments;
		this.zeroRanges = zeroRanges;
	}
	
	private IPv6AddressSegment[] getLowestOrHighestSegments(boolean lowest) {
		return getSingle(this, (IPv6AddressSegment[]) divisions, getAddressCreator(), (i) -> {
			IPv6AddressSegment seg = getSegment(i);
			return lowest ? seg.getLower() : seg.getUpper();
		}, false);
	}
	
	@Override
	public IPv6AddressSegment[] getSegments() {
		return (IPv6AddressSegment[]) divisions.clone();
	}
	
	@Override
	public IPv6AddressSegment[] getLowerSegments() {
		return getLowestOrHighestSegments(true);
	}
	
	@Override
	public IPv6AddressSegment[] getUpperSegments() {
		return getLowestOrHighestSegments(false);
	}
	
	private IPv6AddressSection getLowestOrHighestSection(boolean lowest) {
		return getSingle(this, () -> {
			IPAddressSection result;
			if(hasNoSectionCache() || (result = (lowest ? sectionCache.lowerSection : sectionCache.upperSection)) == null) {
				IPv6AddressCreator creator = getAddressCreator();
				IPv6AddressSegment[] segs = createSingle(this, creator, i -> {
					IPv6AddressSegment seg = getSegment(i);
					return lowest ? seg.getLower() : seg.getUpper();
				});
				IPv6AddressSection newSection = creator.createSectionInternal(segs);
				if(lowest) {
					sectionCache.lowerSection = newSection;
				} else {
					sectionCache.upperSection = newSection;
				}
				return newSection;
			}
			return (IPv6AddressSection) result;
		});
	}
	
	@Override
	public IPv6AddressSection getLowerSection() {
		return getLowestOrHighestSection(true);
	}
	
	@Override
	public IPv6AddressSection getUpperSection() {
		return getLowestOrHighestSection(false);
	}
	
	@Override
	public Iterator sectionIterator() {
		return new SectionIterator(this, getAddressCreator(), iterator());
	}
	
	@Override
	public Iterator iterator() {
		return super.iterator(getSegmentCreator(), false, this::getLowerSegments, index -> getSegment(index).iterator());
	}
	
	@Override
	protected IPv6AddressCreator getSegmentCreator() {
		return getIPv6SegmentCreator();
	}
	
	private static IPv6AddressCreator getIPv6SegmentCreator() {
		return IPv6Address.network().getAddressCreator();
	}
	
	@Override
	protected IPv6AddressCreator getAddressCreator() {
		return getAddressCreator(startIndex);
	}
	
	protected static IPv6AddressCreator getAddressCreator(int startIndex) {
		IPv6AddressCreator result = creators[startIndex];
		if(result == null) {
			creators[startIndex] = result = new IPv6AddressCreator() {
				@Override
				protected IPv6AddressSection createSectionInternal(IPv6AddressSegment segments[]) {
					return IPv6Address.network().getAddressCreator().createSectionInternal(segments, startIndex); /* address creation */
				}
			};
		}
		return result;
	}
	
	@Override
	public IPv6AddressSegment getSegment(int index) {
		return (IPv6AddressSegment) super.getSegment(index);
	}

	/**
	 * Produces an IPv4 address section from any sequence of bytes in this IPv6 address section
	 * 
	 * @param startIndex the byte index in this section to start from
	 * @param endIndex the byte index in this section to end at
	 * @throws IndexOutOfBoundsException
	 * @return
	 * 
	 * @see #getEmbeddedIPv4AddressSection()
	 * @see #getMixedAddressSection()
	 */
	public IPv4AddressSection getEmbeddedIPv4AddressSection(int startIndex, int endIndex) {
		if(startIndex == ((IPv6Address.MIXED_ORIGINAL_SEGMENT_COUNT - this.startIndex) << 1) && endIndex == (getSegmentCount() << 1)) {
			return getEmbeddedIPv4AddressSection();
		}
		IPv4AddressCreator creator = IPv4Address.network().getAddressCreator();
		IPv4AddressSegment[] segments = creator.createSegmentArray((endIndex - startIndex) >> 1);
		int i = startIndex, j = 0;
		if(i % IPv6Address.BYTES_PER_SEGMENT == 1) {
			IPv6AddressSegment ipv6Segment = getSegment(i++ / IPv6Address.BYTES_PER_SEGMENT);
			ipv6Segment.getIPv4Segments(segments, j++ - 1);
		}
		for(; i < endIndex; i <<= 1, j <<= 1) {
			IPv6AddressSegment ipv6Segment = getSegment(i / IPv6Address.BYTES_PER_SEGMENT);
			ipv6Segment.getIPv4Segments(segments, j);
		}
		return createSection(creator, segments);
	}
	
	/**
	 * Gets the IPv4 section corresponding to the lowest (least-significant) 4 bytes in the original address,
	 * which will correspond to between 0 and 4 bytes in this address.  Many IPv4 to IPv6 mapping schemes (but not all) use these 4 bytes for a mapped IPv4 address.
	 * 
	 * @see #getEmbeddedIPv4AddressSection(int, int)
	 * @see #getMixedAddressSection()
	 * @return the embedded IPv4 section or null
	 */
	public IPv4AddressSection getEmbeddedIPv4AddressSection() {
		if(embeddedIPv4Section == null) {
			synchronized(this) {
				if(embeddedIPv4Section == null) {
					int mixedCount = getSegmentCount() - Math.max(IPv6Address.MIXED_ORIGINAL_SEGMENT_COUNT - startIndex, 0);
					int lastIndex = getSegmentCount() - 1;
					IPv4AddressCreator creator = IPv4Address.network().getAddressCreator();
					IPv4AddressSegment[] mixed;
					if(mixedCount == 0) {
						mixed = creator.createSegmentArray(0);
					} else {
						mixed = (mixedCount == 1) ? 
							getSegment(lastIndex).split() : 
							IPv6AddressSegment.split(getSegment(lastIndex - 1), getSegment(lastIndex));
					}
					embeddedIPv4Section = createSection(creator, mixed);
				}
			}
		}
		return embeddedIPv4Section;
	}
	
	public IPv6AddressSection createNonMixedSection() {
		int mixedCount = getSegmentCount() - Math.max(IPv6Address.MIXED_ORIGINAL_SEGMENT_COUNT - startIndex, 0);
		if(mixedCount <= 0) {
			return this;
		}
		int nonMixedCount = Math.max(0, getSegmentCount() - mixedCount);
		IPv6AddressCreator creator = IPv6Address.network().getAddressCreator();
		IPv6AddressSegment[] nonMixed = creator.createSegmentArray(nonMixedCount);
		copySegments(0, nonMixedCount, nonMixed, 0);
		return creator.createSectionInternal(nonMixed, startIndex);
	}
	
	public IPv6v4MixedAddressSection getMixedAddressSection() {
		if(defaultMixedAddressSection == null) {
			synchronized(this) {
				if(defaultMixedAddressSection == null) {
					defaultMixedAddressSection = new IPv6v4MixedAddressSection(
							createNonMixedSection(),
							getEmbeddedIPv4AddressSection());
				}
			}
		}
		return defaultMixedAddressSection;
	}
	
	@Override
	public int getBitsPerSegment() {
		return IPv6Address.BITS_PER_SEGMENT;
	}
	
	@Override
	public int getBytesPerSegment() {
		return IPv6Address.BYTES_PER_SEGMENT;
	}
	
	/**
	 * Returns whether this subnet or address has alphabetic digits when printed.
	 * 
	 * Note that this method does not indicate whether any address contained within this subnet has alphabetic digits,
	 * only whether the subnet itself when printed has alphabetic digits.
	 * 
	 * @return whether the section has alphabetic digits when printed.
	 */
	public boolean hasAlphabeticDigits(int base, boolean lowerOnly) {
		if(base > 10) {
			int count = getSegmentCount();
			for(int i = 0; i < count; i++) {
				IPv6AddressSegment seg = getSegment(i);
				if(seg.hasAlphabeticDigits(base, lowerOnly)) {
					return true;
				}
			}
		}
		return false;
	}
	
	@Override
	public boolean isIPv6() {
		return true;
	}
	
	@Override
	public IPVersion getIPVersion() {
		return IPVersion.IPV6;
	}
	
	@Override
	public boolean contains(IPAddressSection other) {
		return other.isIPv6() &&
				startIndex == ((IPv6AddressSection) other).startIndex && 
				super.contains(other);
	}
	
	@Override
	protected boolean isSameGrouping(IPAddressSegmentGrouping other) {
		return other instanceof IPv6AddressSection &&
				startIndex == ((IPv6AddressSection) other).startIndex &&
				super.isSameGrouping(other);
	}
	
	@Override
	public boolean equals(Object o) {
		if(o == this) {
			return true;
		}
		if(o instanceof IPv6AddressSection) {
			IPv6AddressSection other = (IPv6AddressSection) o;
			return startIndex == other.startIndex && super.isSameGrouping(other);
		}
		return false;
	}
	
	@Override
	public IPv6AddressSection[] subtract(IPAddressSection other) {
		if(!(other instanceof IPv6AddressSection)) {
			throw new IPAddressTypeException(this, other, "ipaddress.error.typeMismatch");
		}
		return subtract(this, (IPv6AddressSection) other, getAddressCreator(), this::getSegment, (section, prefix) -> section.toSubnet(prefix));
	}
	
	@Override
	public int getByteIndex(Integer networkPrefixLength) {
		return getByteIndex(networkPrefixLength, IPv6Address.BYTE_COUNT);
	}
	
	@Override
	public int getSegmentIndex(Integer networkPrefixLength) {
		return getSegmentIndex(networkPrefixLength, IPv6Address.BYTE_COUNT, IPv6Address.BYTES_PER_SEGMENT);
	}
	
	@Override
	public IPv6AddressNetwork getNetwork() {
		return IPv6Address.network();
	}
	
	@Override
	public IPv6AddressSection toSubnet(int networkPrefixLength) throws IPAddressTypeException {
		super.checkSubnet(networkPrefixLength);
		if(isPrefixed() && networkPrefixLength >= getNetworkPrefixLength()) {
			return this;
		}
		IPv6Address addressMask = getNetwork().getNetworkMask(networkPrefixLength, false);
		IPv6AddressSection mask = addressMask.getNetworkSection(getBitCount(), false);
		return getSubnetSegments(this, mask, networkPrefixLength, getAddressCreator(), false, this::getSegment, mask::getSegment);
	}
	
	/**
	 * Creates a subnet address using the given mask. 
	 */
	@Override
	public IPv6AddressSection toSubnet(IPAddressSection 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.
	 */
	@Override
	public IPv6AddressSection toSubnet(IPAddressSection mask, Integer networkPrefixLength) throws IPAddressTypeException {
		if(!(mask instanceof IPv6AddressSection)) {
			throw new IPAddressTypeException(this, mask, "ipaddress.error.typeMismatch");
		}
		IPv6AddressSection theMask = (IPv6AddressSection) mask;
		super.checkSubnet(theMask, networkPrefixLength);
		return getSubnetSegments(this, theMask, networkPrefixLength, getAddressCreator(), true, this::getSegment, theMask::getSegment);
	}
	
	@Override
	public IPv6AddressSection getNetworkSection(int networkPrefixLength) {
		return getNetworkSection(networkPrefixLength, true);
	}
	
	@Override
	public IPv6AddressSection getNetworkSection(int networkPrefixLength, boolean withPrefixLength) {
		int cidrSegmentCount = getNetworkSegmentCount(networkPrefixLength);
		return getNetworkSegments(this, networkPrefixLength, cidrSegmentCount, withPrefixLength, getAddressCreator(), (i, prefix) -> getSegment(i).toNetworkSegment(prefix, withPrefixLength));
	}
	
	@Override
	public IPv6AddressSection getHostSection(int networkPrefixLength) {
		int cidrSegmentCount = getHostSegmentCount(networkPrefixLength);
		IPv6AddressCreator creator = getAddressCreator(startIndex + (getSegmentCount() - cidrSegmentCount));
		return getHostSegments(this, networkPrefixLength, cidrSegmentCount, creator, (i, prefix) -> getSegment(i).toHostSegment(prefix));
	}
	
	////////////////string creation below ///////////////////////////////////////////////////////////////////////////////////////////

	@Override
	protected boolean hasNoStringCache() {
		if(stringCache == null) {
			synchronized(this) {
				if(stringCache == null) {
					stringCache = new IPv6StringCache();
					return true;
				}
			}
		}
		return false;
	}
	
	@Override
	protected StringCache getStringCache() {
		return stringCache;
	}
	
	/**
	 * This produces the shortest valid string for the address.
	 */
	@Override
	public String toCompressedString() {
		String result;
		if(hasNoStringCache() || (result = stringCache.compressedString) == null) {
			stringCache.compressedString = result = toNormalizedString(IPv6StringCache.compressedParams);
		}
		return result;
	}

	/**
	 * This produces a canonical string.
	 * 
	 * RFC 5952 describes canonical representations.
	 * http://en.wikipedia.org/wiki/IPv6_address#Recommended_representation_as_text
	 * http://tools.ietf.org/html/rfc5952
	 */
	@Override
	public String toCanonicalString() {
		String result;
		if(hasNoStringCache() || (result = stringCache.canonicalString) == null) {
			stringCache.canonicalString = result = toNormalizedString(IPv6StringCache.canonicalParams);
		}
		return result;
	}
	
	/**
	 * This produces the mixed IPv6/IPv4 string.  It is the shortest such string (ie fully compressed).
	 */
	public String toMixedString() {
		String result;
		if(hasNoStringCache() || (result = stringCache.mixedString) == null) {
			stringCache.mixedString = result = toNormalizedString(IPv6StringCache.mixedParams);
		}
		return result;
	}

	/**
	 * This produces a string with no compressed segments and all segments of full length,
	 * which is 4 characters for IPv6 segments and 3 characters for IPv4 segments.
	 */
	@Override
	public String toFullString() {
		String result;
		if(hasNoStringCache() || (result = stringCache.fullString) == null) {
			stringCache.fullString = result = toNormalizedString(IPv6StringCache.fullParams);
		}
		return result;
	}
	
	@Override
	public String toCompressedWildcardString() {
		String result;
		if(hasNoStringCache() || (result = stringCache.compressedWildcardString) == null) {
			stringCache.compressedWildcardString = result = toNormalizedString(IPv6StringCache.wildcardCompressedParams);
		}
		return result;
	}
	
	@Override
	public String toNetworkPrefixLengthString() {
		String result;
		if(hasNoStringCache() || (result = stringCache.networkPrefixLengthString) == null) {
			stringCache.networkPrefixLengthString = result = toNormalizedString(IPv6StringCache.networkPrefixLengthParams);
		}
		return result;
	}
	
	@Override
	public String toSubnetString() {
		return toNetworkPrefixLengthString();
	}
	
	@Override
	public String toCanonicalWildcardString() {
		String result;
		if(hasNoStringCache() || (result = stringCache.canonicalWildcardString) == null) {
			stringCache.canonicalWildcardString = result = toNormalizedString(IPv6StringCache.wildcardCanonicalParams);
		}
		return result;
	}
	
	@Override
	public String toNormalizedWildcardString() {
		String result;
		if(hasNoStringCache() || (result = stringCache.normalizedWildcardString) == null) {
			stringCache.normalizedWildcardString = result = toNormalizedString(IPv6StringCache.wildcardNormalizedParams);
		}
		return result;
	}
	
	@Override
	public String toSQLWildcardString() {
		String result;
		if(hasNoStringCache() || (result = stringCache.sqlWildcardString) == null) {
			stringCache.sqlWildcardString = result = toNormalizedString(IPv6StringCache.sqlWildcardParams);
		}
		return result;
	}
	
	/**
	 * The normalized string returned by this method is consistent with java.net.Inet6address.
	 * IPs are not compressed nor mixed in this representation.
	 */
	@Override
	public String toNormalizedString() {
		String result;
		if(hasNoStringCache() || (result = stringCache.normalizedString) == null) {
			stringCache.normalizedString = result = toNormalizedString(IPv6StringCache.normalizedParams);
		}
		return result;
	}
	
	@Override
	protected String toHexString(boolean withPrefix, String zone) {
		return super.toHexString(withPrefix, zone);
	}

	@Override
	protected void cacheNormalizedString(String str) {
		if(hasNoStringCache() || stringCache.normalizedString == null) {
			stringCache.normalizedString = str;
		}
	}
	
	@Override
	public String toNormalizedStringRange(StringOptions options, String zone) {
		if(options instanceof IPv6StringOptions) {
			return toNormalizedStringRange((IPv6StringOptions) options, zone);
		}
		if(zone != null) {
			return toNormalizedStringRange(IPv6StringOptions.from(options), zone);
		}
		return super.toNormalizedStringRange(options, null);
	}
	
	public String toNormalizedStringRange(IPv6StringOptions options, String zone) {
		IPv6AddressSection section1 = getLowerSection(), section2 = getUpperSection();
		IPv6StringParams stringParams1 = options.from(section1), stringParams2 = options.from(section2);
		stringParams1.zone = zone;
		stringParams2.zone = zone;
		int length = 0;
		IPv6v4MixedAddressSection mixed1, mixed2;
		mixed1 = mixed2 = null;
		IPv6v4MixedParams mixedParams1, mixedParams2;
		mixedParams1 = mixedParams2 = null;
		if(options.makeMixed()) {
			if(stringParams1.nextUncompressedIndex <= IPv6Address.MIXED_ORIGINAL_SEGMENT_COUNT - startIndex) {
				mixedParams1 = new IPv6v4MixedParams(stringParams1, options.ipv4Opts);
				mixed1 = section1.getMixedAddressSection();
				length += mixedParams1.getStringLength(mixed1);
			} else {
				length += stringParams1.getStringLength(section1);
			}
			if(stringParams2.nextUncompressedIndex <= IPv6Address.MIXED_ORIGINAL_SEGMENT_COUNT - startIndex) {
				mixedParams2 = new IPv6v4MixedParams(stringParams2, options.ipv4Opts);
				mixed2 = section2.getMixedAddressSection();
				length += mixedParams2.getStringLength(mixed2);
			} else {
				length += stringParams2.getStringLength(section2);
			}
		} else {
			length += stringParams1.getStringLength(section1) + stringParams2.getStringLength(section2);
		}
		String separator = stringParams1.getWildcardOption().wildcards.rangeSeparator;
		length += separator.length();
		StringBuilder builder = new StringBuilder(length);
		if(mixed1 == null) {
			stringParams1.append(builder, section1);
		} else {
			mixedParams1.append(builder, mixed1);
		}
		builder.append(separator);
		if(mixed2 == null) {
			stringParams2.append(builder, section2);
		} else {
			mixedParams2.append(builder, mixed2);
		}
		stringParams1.checkLengths(length, builder);
		return builder.toString();
	}

	@Override
	public String toNormalizedString(StringOptions options) {
		if(options instanceof IPv6StringOptions) {
			return toNormalizedString((IPv6StringOptions) options);
		}
		return super.toNormalizedString(options);
	}
		
	@Override
	public String toNormalizedString(StringOptions options, String zone) {
		if(zone == null) {
			return toNormalizedString(options);
		}
		return toNormalizedString(IPv6StringOptions.from(options), zone);
	}
	
	public String toNormalizedString(IPv6StringOptions options) {
		return toNormalizedString(options, (String) null);
	}
	
	private String toNormalizedMixedString(IPv6v4MixedParams mixedParams) {
		IPv6v4MixedAddressSection mixed = getMixedAddressSection();
		String result = mixedParams.toString(mixed);
		return result;
	}
	
	public String toNormalizedString(IPv6StringOptions options, String zone) {
		IPv6StringParams stringParams;
		if(options.compressOptions == null && zone == null) {
			IPAddressPartStringParams cachedParams = getCachedParams(options);
			if(cachedParams == null) {
				stringParams = options.from(this);
				if(options.makeMixed()) {
					IPv6v4MixedParams mixedParams = new IPv6v4MixedParams(stringParams, options.ipv4Opts);
					setCachedParams(options, mixedParams);
					return toNormalizedMixedString(mixedParams);
				} else {
					setCachedParams(options, stringParams);
				}
			} else {
				if(cachedParams instanceof IPv6v4MixedParams) {
					return toNormalizedMixedString((IPv6v4MixedParams) cachedParams);
				}
				stringParams = (IPv6StringParams) cachedParams;
			}
		} else {
			//no caching is possible
			stringParams = options.from(this);
			stringParams.zone = zone;
			if(options.makeMixed() && stringParams.nextUncompressedIndex <= IPv6Address.MIXED_ORIGINAL_SEGMENT_COUNT - startIndex) {//the mixed section is not compressed
				return toNormalizedMixedString(new IPv6v4MixedParams(stringParams, options.ipv4Opts));
			}
		}
		return stringParams.toString(this);
	}

	@Override
	public IPAddressPartStringCollection toStandardStringCollection() {
		return toStringCollection(IPv6StringBuilderOptions.STANDARD_OPTS);
	}
	
	@Override
	public IPAddressPartStringCollection toAllStringCollection() {
		return toStringCollection(IPv6StringBuilderOptions.ALL_OPTS);
	}
	
	@Override
	public IPAddressPartStringCollection toDatabaseSearchStringCollection() {
		return toStringCollection(IPv6StringBuilderOptions.DATABASE_SEARCH_OPTS);
	}
	
	@Override
	public IPAddressPartStringCollection toStringCollection(IPStringBuilderOptions options) {
		return toStringCollection(IPv6StringBuilderOptions.from(options));
	}
	
	public IPAddressPartStringCollection toStringCollection(IPv6StringBuilderOptions opts) {
		return toStringCollection(opts, null);
	}

	IPv6StringCollection toStringCollection(IPv6StringBuilderOptions opts, String zone) {
		IPv6StringCollection collection = new IPv6StringCollection();
		int mixedCount = getSegmentCount() - Math.max(IPv6Address.MIXED_ORIGINAL_SEGMENT_COUNT - startIndex, 0);
		if(mixedCount > 0 && opts.includes(IPv6StringBuilderOptions.MIXED)) {
			IPv6v4MixedAddressSection mixed = getMixedAddressSection();
			IPv6v4MixedStringBuilder mixedBuilder = new IPv6v4MixedStringBuilder(mixed, opts, zone);
			IPv6v4MixedStringCollection mixedCollection = mixedBuilder.getVariations();
			collection.add(mixedCollection);
		}
		if(opts.includes(IPStringBuilderOptions.BASIC)) {
			IPv6StringBuilder ipv6Builder = new IPv6StringBuilder(this, opts, zone);
			IPv6AddressSectionStringCollection ipv6Collection = ipv6Builder.getVariations();
			collection.add(ipv6Collection);
		}
		return collection;
	}
	
	@Override
	public IPAddressPart[] getParts(IPStringBuilderOptions opts) {
		return getParts(IPv6StringBuilderOptions.from(opts));
	}
	
	public IPAddressPart[] getParts(IPv6StringBuilderOptions opts) {
		if(opts.includes(IPv6StringBuilderOptions.MIXED)) {
			if(opts.includes(IPStringBuilderOptions.BASIC)) {
				return new IPAddressPart[] { this, getMixedAddressSection() };
			}
			return new IPAddressPart[] { getMixedAddressSection() };
		}
		return super.getParts(opts);
	}
	
	private static class IPv6StringMatcher extends SQLStringMatcher {
		IPv6StringMatcher(
				IPv6AddressSectionString networkString,
				IPAddressSQLTranslator translator) {
			super(networkString, networkString.addr.isEntireAddress(), translator);
		}
			
		@Override
		public StringBuilder getSQLCondition(StringBuilder builder, String columnName) {
			if(networkString.addr.isEntireAddress()) {
				matchString(builder, columnName, networkString.getString());
			} else if(networkString.endIsCompressed()) { //'::' is at end of networkString
				char sep = networkString.getTrailingSegmentSeparator();
				String searchStr = networkString.getString().substring(0, networkString.getString().length() - 1);
				builder.append('(');
				matchSubString(builder, columnName, sep, networkString.getTrailingSeparatorCount(), searchStr);
				
				//We count the separators to ensure they are below a max count.
				//The :: is expected to match a certain number of segments in the network and possibly more in the host.
				//If the network has y segments then there can be anywhere between 0 and 7 - y additional separators for the host. 
				//eg 1:: matching 7 segments in network means full string has at most an additional 7 - 7 = 0 host separators, so it is either 1:: or 1::x.  It cannot be 1::x:x.
				//eg 1:: matching 6 segments means full string has at most an additional 7 - 6 = 1 separators, so it is either 1::, 1::x or 1::x:x.  It cannot be 1::x:x:x.
				int extraSeparatorCountMax = (IPv6Address.SEGMENT_COUNT - 1) - networkString.addr.getSegmentCount();
				builder.append(") AND (");
				boundSeparatorCount(builder, columnName, sep, extraSeparatorCountMax + networkString.getTrailingSeparatorCount());
				builder.append(')');
			} else if(networkString.isCompressed()) { //'::' is in networkString but not at end of networkString
				char sep = networkString.getTrailingSegmentSeparator();
				builder.append('(');
				matchSubString(builder, columnName, sep, networkString.getTrailingSeparatorCount() + 1, networkString.getString());
				
				//we count the separators to ensure they are an exact count.
				//The :: is expected to match a certain number of segments in the network and there is no compression in the host.
				//If the network has y segments then there is 8 - y additional separators for the host. 
				//eg ::1 matching 7 segments in network means full string has additional 8 - 7 = 1 host separators, so it is ::1:x
				//eg ::1 matching 6 segments means full string has additional 8 - 6 = 2 separators, so it is ::1:x:x
				int extraSeparatorCount = IPv6Address.SEGMENT_COUNT - networkString.addr.getSegmentCount();
				builder.append(") AND (");
				matchSeparatorCount(builder, columnName, sep, extraSeparatorCount + networkString.getTrailingSeparatorCount());
				builder.append(')');
			} else {
				matchSubString(builder, columnName, networkString.getTrailingSegmentSeparator(), networkString.getTrailingSeparatorCount() + 1, networkString.getString());
			}
			return builder;
		}
	}
	
	public static class CompressOptions {
		public enum CompressionChoiceOptions {
			HOST_PREFERRED, //if there is a host section, compress the host along with any adjoining zero segments, otherwise compress a range of zero segments
			MIXED_PREFERRED, //if there is a mixed section that is compressible according to the MixedCompressionOptions, compress the mixed section along with any adjoining zero segments, otherwise compress a range of zero segments
			ZEROS_OR_HOST, //compress the largest range of zero or host segments
			ZEROS; //compress the largest range of zero segments
			
			boolean compressHost() {
				return this != ZEROS;
			}
		}
		
		public enum MixedCompressionOptions {
			NO, //do not allow compression of a mixed section
			NO_HOST, //allow compression of a mixed section when there is no host section
			COVERED_BY_HOST, //allow compression of a mixed section when there is no host section or the host section covers the mixed section
			YES; //allow compression of a mixed section
			
			boolean compressMixed(IPv6AddressSection addressSection) {
				switch(this) {
					default:
					case YES:
						return true;
					case NO:
						return false;
					case NO_HOST:
						return !addressSection.isPrefixed();
					case COVERED_BY_HOST:
						if(addressSection.isPrefixed()) {
							int mixedDistance = IPv6Address.MIXED_ORIGINAL_SEGMENT_COUNT - addressSection.startIndex;
							int mixedCount = addressSection.getSegmentCount() - Math.max(mixedDistance, 0);
							if(mixedCount > 0) {
								return (mixedDistance * addressSection.getBitsPerSegment()) >= addressSection.getNetworkPrefixLength();
							}
						}
						return true;
				}
			}
		}

		public final boolean compressSingle;
		public final CompressionChoiceOptions rangeSelection;
		
		//options for addresses with an ipv4 section
		public final MixedCompressionOptions compressMixedOptions;
				
		public CompressOptions(boolean compressSingle, CompressionChoiceOptions rangeSelection) {
			this(compressSingle, rangeSelection, MixedCompressionOptions.YES);
		}
		
		public CompressOptions(boolean compressSingle, CompressionChoiceOptions rangeSelection, MixedCompressionOptions compressMixedOptions) {
			this.compressSingle = compressSingle;
			this.rangeSelection = rangeSelection;
			this.compressMixedOptions = compressMixedOptions == null ? MixedCompressionOptions.YES : compressMixedOptions;
		}
	}
	
	/**
	 * Provides a clear way to create a specific type of string.
	 * 
	 * @author sfoley
	 *
	 */
	public static class IPv6StringOptions extends StringOptions {
		public final StringOptions ipv4Opts;

		//can be null, which means no compression
		public final CompressOptions compressOptions;
		
		public final char zoneSeparator;
		
		IPv6StringOptions(
				int base,
				boolean expandSegments,
				WildcardOptions wildcardOptions,
				String segmentStrPrefix,
				boolean makeMixed,
				StringOptions ipv4Opts,
				CompressOptions compressOptions,
				Character separator,
				char zoneSeparator,
				String addressPrefix,
				String addressSuffix,
				boolean reverse,
				boolean splitDigits,
				boolean uppercase) {
			super(base, expandSegments, wildcardOptions, segmentStrPrefix, separator, addressPrefix, addressSuffix, reverse, splitDigits, uppercase);
			this.compressOptions = compressOptions;
			this.zoneSeparator = zoneSeparator;
			if(makeMixed) {
				if(ipv4Opts == null) {
					ipv4Opts = new StringOptions.Builder().
							setExpandedSegments(expandSegments).setWildcardOptions(wildcardOptions).toParams();
				}
				this.ipv4Opts = ipv4Opts;
			} else {
				this.ipv4Opts = null;
			}
		}
		
		boolean makeMixed() {
			return ipv4Opts != null;
		}
		
		private IPv6StringParams from(IPv6AddressSection addr) {
			IPv6StringParams result = new IPv6StringParams();
			if(compressOptions != null) {
				boolean makeMixed = makeMixed();
				int vals[] = addr.getCompressIndexAndCount(compressOptions, makeMixed);
				if(vals != null) {
					int maxIndex = vals[0];
					int maxCount = vals[1];
					result.firstCompressedSegmentIndex = maxIndex;
					result.nextUncompressedIndex = maxIndex + maxCount;
					result.hostCompressed = compressOptions.rangeSelection.compressHost() &&
							(result.nextUncompressedIndex > 
								getSegmentIndex(addr.getNetworkPrefixLength(), IPv6Address.BYTE_COUNT, IPv6Address.BYTES_PER_SEGMENT));
				}
			}
			result.expandSegments(expandSegments);
			result.setWildcardOption(wildcardOptions);
			result.setSeparator(separator);
			result.setAddressSuffix(addrSuffix);
			result.setAddressLabel(addrPrefix);
			result.setReverse(reverse);
			result.setSplitDigits(splitDigits);
			result.setZoneSeparator(zoneSeparator);
			result.setUppercase(uppercase);
			result.setRadix(base);
			result.setSegmentStrPrefix(segmentStrPrefix);
			return result;
		}
		
		public static IPv6StringOptions from(StringOptions opts) {
			if(opts instanceof IPv6StringOptions) {
				return (IPv6StringOptions) opts;
			}
			return new IPv6StringOptions(
					opts.base,
					opts.expandSegments,
					opts.wildcardOptions,
					opts.segmentStrPrefix,
					false,
					null,
					null,
					opts.separator,
					IPv6Address.ZONE_SEPARATOR,
					opts.addrPrefix,
					opts.addrSuffix,
					opts.reverse,
					opts.splitDigits,
					opts.uppercase);
		}
		
		public static class Builder extends StringOptions.Builder {
			private boolean makeMixed;
			private StringOptions ipv4Options;
			
			//default is null, which means no compression
			private CompressOptions compressOptions;
			
			private char zoneSeparator = IPv6Address.ZONE_SEPARATOR;
			
			public Builder() {
				super(IPv6Address.DEFAULT_TEXTUAL_RADIX, IPv6Address.SEGMENT_SEPARATOR);
			}
			
			public Builder setCompressOptions(CompressOptions compressOptions) {
				this.compressOptions = compressOptions;
				return this;
			}
			
			public Builder setMakeMixed(boolean makeMixed) {
				this.makeMixed = makeMixed;
				return this;
			}
			
			public Builder setMakeMixed(StringOptions ipv4Options) {
				this.makeMixed = true;
				this.ipv4Options = ipv4Options;
				return this;
			}
			
			@Override
			public Builder setWildcardOptions(WildcardOptions wildcardOptions) {
				return (Builder) super.setWildcardOptions(wildcardOptions);
			}
			
			@Override
			public Builder setExpandedSegments(boolean expandSegments) {
				return (Builder) super.setExpandedSegments(expandSegments);
			}
			
			@Override
			public Builder setRadix(int base) {
				return (Builder) super.setRadix(base);
			}
			
			@Override
			public Builder setSeparator(Character separator) {
				return (Builder) super.setSeparator(separator);
			}
			
			public Builder setZoneSeparator(char separator) {
				this.zoneSeparator = separator;
				return this;
			}
			
			@Override
			public Builder setAddressSuffix(String suffix) {
				return (Builder) super.setAddressSuffix(suffix);
			}
			
			@Override
			public Builder setSegmentStrPrefix(String prefix) {
				return (Builder) super.setSegmentStrPrefix(prefix);
			}
			
			@Override
			public Builder setReverse(boolean reverse) {
				return (Builder) super.setReverse(reverse);
			}
			
			@Override
			public Builder setUppercase(boolean upper) {
				return (Builder) super.setUppercase(upper);
			}
			
			@Override
			public Builder setSplitDigits(boolean splitDigits) {
				return (Builder) super.setSplitDigits(splitDigits);
			}
			
			@Override
			public IPv6StringOptions toParams() {
				return new IPv6StringOptions(base, expandSegments, wildcardOptions, segmentStrPrefix, makeMixed, ipv4Options, compressOptions, separator, zoneSeparator, addrPrefix, addrSuffix, reverse, splitDigits, uppercase);
			}
		}
	}
	
	@Override
	public RangeList getZeroSegments() {
		if(zeroSegments == null) {
			zeroSegments = super.getZeroSegments();
		}
		return zeroSegments;
	}

	@Override
	public RangeList getZeroRangeSegments() {
		if(zeroRanges == null) {
			zeroRanges = super.getZeroRangeSegments();
		}
		return zeroRanges;
	}

	@Override
	public boolean isZero() {
		RangeList ranges = getZeroSegments();
		return ranges.size() == 1 && ranges.getRange(0).length == getSegmentCount();
	}
	
	private int[] getCompressIndexAndCount(CompressOptions options) {
		return getCompressIndexAndCount(options, false);
	}
	
	/**
	 * Chooses a single segment to be compressed, or null if no segment could be chosen.
	 * @param options
	 * @param createMixed
	 * @return
	 */
	private int[] getCompressIndexAndCount(CompressOptions options, boolean createMixed) {
		if(options != null) {
			CompressionChoiceOptions rangeSelection = options.rangeSelection;
			RangeList compressibleSegs = rangeSelection.compressHost() ? getZeroRangeSegments() : getZeroSegments();
			int maxIndex = -1, maxCount = 0;
			int segmentCount = getSegmentCount();
			
			boolean compressMixed = createMixed && options.compressMixedOptions.compressMixed(this);
			boolean preferHost = (rangeSelection == CompressOptions.CompressionChoiceOptions.HOST_PREFERRED);
			boolean preferMixed = createMixed && (rangeSelection == CompressOptions.CompressionChoiceOptions.MIXED_PREFERRED);
			for(int i = compressibleSegs.size() - 1; i >= 0 ; i--) {
				Range range = compressibleSegs.getRange(i);
				int index = range.index;
				int count = range.length;
				if(createMixed) {
					//so here we shorten the range to exclude the mixed part if necessary
					int mixedIndex = IPv6Address.MIXED_ORIGINAL_SEGMENT_COUNT - startIndex;
					if(!compressMixed ||
							index > mixedIndex || index + count < segmentCount) { //range does not include entire mixed part.  We never compress only part of a mixed part.
						//the compressible range must stop at the mixed part
						count = Math.min(count, mixedIndex - index);
					}
				}
				//select this range if is the longest
				if(count > 0 && count >= maxCount && (options.compressSingle || count > 1)) {
					maxIndex = index;
					maxCount = count;
				}
				if(preferHost && isPrefixed() &&
						((index + count) * IPv6Address.BITS_PER_SEGMENT) > getNetworkPrefixLength()) { //this range contains the host
					//Since we are going backwards, this means we select as the maximum any zero segment that includes the host
					break;
				}
				if(preferMixed && index + count >= segmentCount) { //this range contains the mixed section
					//Since we are going backwards, this means we select to compress the mixed segment
					break;
				}
			}
			if(maxIndex >= 0) {
				return new int[] {maxIndex, maxCount};
			}
		}
		return null;
	}

	public static class IPv6v4MixedAddressSection extends IPAddressSegmentGrouping {

		private static final long serialVersionUID = 1L;
		
		private final IPv6AddressSection ipv6Section;
		private final IPv4AddressSection ipv4Section;
		
		private IPv6v4MixedAddressSection(
				IPv6AddressSection ipv6Section,
				IPv4AddressSection ipv4Section) {
			super(createSegments(ipv6Section, ipv4Section));
			this.ipv4Section = ipv4Section;
			this.ipv6Section = ipv6Section;
		}
		
		private static IPAddressDivision[] createSegments(IPv6AddressSection ipv6Section, IPv4AddressSection ipv4Section) {
			int ipv6Len = ipv6Section.getSegmentCount();
			int ipv4Len = ipv4Section.getSegmentCount();
			IPAddressSegment allSegs[] = new IPAddressSegment[ipv6Len + ipv4Len];
			ipv6Section.copySegments(0, ipv6Len, allSegs, 0);
			ipv4Section.copySegments(0, ipv4Len, allSegs, ipv6Len);
			return allSegs;
		}

		@Override
		public int getByteCount() {
			return ipv6Section.getByteCount() + ipv4Section.getByteCount();
		}

		@Override
		public int getBitCount() {
			return ipv6Section.getBitCount() + ipv4Section.getBitCount();
		}
		
		@Override
		public String toString() {
			if(string == null) {
				IPv6StringOptions mixedParams = IPv6StringCache.mixedParams;
				IPv6StringParams ipv6Params = mixedParams.from(ipv6Section);
				StringOptions ipv4Opts = mixedParams.ipv4Opts;
				IPv6v4MixedParams parms = new IPv6v4MixedParams(ipv6Params, ipv4Opts);
				string = parms.toString(this);
			}
			return string;
		}
		
		@Override
		protected boolean isSameGrouping(IPAddressSegmentGrouping o) {
			if(o instanceof IPv6v4MixedAddressSection) {
				IPv6v4MixedAddressSection other = (IPv6v4MixedAddressSection) o;
				return ipv6Section.equals(other.ipv6Section) && ipv4Section.equals(other.ipv4Section);
			}
			return false;
		}
		
		@Override
		public boolean equals(Object o) {
			if(o == this) {
				return true;
			}
			if(o instanceof IPv6v4MixedAddressSection) {
				IPv6v4MixedAddressSection other = (IPv6v4MixedAddressSection) o;
				return ipv6Section.equals(other.ipv6Section) && ipv4Section.equals(other.ipv4Section);
			}
			return false;
		}
	}

	static class IPv6AddressSectionStringCollection extends IPAddressPartStringSubCollection {
		IPv6AddressSectionStringCollection(IPv6AddressSection addr) {
			super(addr);
		}
		
		@Override
		public Iterator iterator() {
			return new IPAddressConfigurableStringIterator() {
				@Override
				public IPv6AddressSectionString next() {
					return new IPv6AddressSectionString(part, iterator.next()); 
				}
			};
		}
	}
	
	static class IPv6v4MixedStringCollection
		extends IPAddressPartStringSubCollection> {
	
		public IPv6v4MixedStringCollection(IPv6v4MixedAddressSection part) {
			super(part);
		}
		
		@Override
		public Iterator> iterator() {
			return new IPAddressConfigurableStringIterator() {
				@Override
				public IPAddressPartConfiguredString next() {
					return new IPAddressPartConfiguredString(part, iterator.next()); 
				}
			};
		}
	}
	
	static class IPv6StringCollection extends IPAddressPartStringCollection {
		
		@Override
		protected void add(IPAddressPartStringSubCollection collection) {
			super.add(collection);
		}
		
		@Override
		protected void addAll(IPAddressPartStringCollection collections) {
			super.addAll(collections);
		}

		static class IPv6v4MixedParams extends IPAddressPartStringParams {
			private StringParams ipv4Params; //params for the IPv4 part of a mixed IPv6/IPv4 address a:b:c:d:e:f:1.2.3.4
			private IPv6StringParams ipv6Params;
			
			@SuppressWarnings("unchecked")
			IPv6v4MixedParams(IPv6AddressSectionString ipv6Variation, IPAddressPartConfiguredString ipv4Variation) {
				this.ipv4Params = (StringParams) ipv4Variation.stringParams;
				this.ipv6Params = ipv6Variation.stringParams;
			}
			
			IPv6v4MixedParams(IPv6StringParams ipv6Params, StringOptions ipv4Opts) {
				this.ipv4Params = IPAddressSection.toParams(ipv4Opts);
				this.ipv6Params = ipv6Params;
			}
			
			@Override
			public char getTrailingSegmentSeparator() {
				return ipv4Params.getTrailingSegmentSeparator();
			}
			
			@Override
			public int getTrailingSeparatorCount(IPv6v4MixedAddressSection addr) {
				return ipv4Params.getTrailingSeparatorCount(addr.ipv4Section);
			}
			
			@Override
			protected int getStringLength(IPv6v4MixedAddressSection addr) {
				int ipv6length = ipv6Params.getSegmentsStringLength(addr.ipv6Section);
				int ipv4length = ipv4Params.getSegmentsStringLength(addr.ipv4Section);
				int length = ipv6length + ipv4length;
				if(ipv6Params.nextUncompressedIndex < addr.ipv6Section.getSegmentCount()) {
					length++;
				}
				length += getPrefixStringLength(addr);
				length += ipv6Params.getZoneLength();
				length += ipv6Params.getAddressSuffixLength();
				length += ipv6Params.getAddressLabelLength();
				return length;
			}
			
			@Override
			public String toString(IPv6v4MixedAddressSection addr) {
				int length = getStringLength(addr);
				StringBuilder builder = new StringBuilder(length);
				append(builder, addr);
				checkLengths(length, builder);
				return builder.toString();
			}
			
			@Override
			public StringBuilder append(StringBuilder builder, IPv6v4MixedAddressSection addr) {
				ipv6Params.appendLabel(builder);
				ipv6Params.appendSegments(builder, addr.ipv6Section);
				if(ipv6Params.nextUncompressedIndex < addr.ipv6Section.getSegmentCount()) {
					builder.append(ipv6Params.getTrailingSegmentSeparator());
				}
				ipv4Params.appendSegments(builder, addr.ipv4Section);
				
				/* 
				 * rfc 4038: for bracketed addresses, zone is inside and prefix outside, putting prefix after zone. 
				 * 
				 * Suffixes are things like .in-addr.arpa, .ip6.arpa, .ipv6-literal.net
				 * which generally convert an address string to a host
				 * As with our HostName, we support host/prefix in which case the prefix is applied
				 * to the resolved address.
				 * 
				 * So in summary, our order is zone, then suffix, then prefix length.
				 */
				
				ipv6Params.appendZone(builder);
				ipv6Params.appendSuffix(builder);
				appendPrefixIndicator(builder, addr);
				return builder;
			}

			protected int getPrefixStringLength(IPv6v4MixedAddressSection addr) {
				if(requiresPrefixIndicator(addr.ipv6Section) || requiresPrefixIndicator(addr.ipv4Section)) {
					return addr.getPrefixStringLength();
				}
				return 0;
			}
			
			@Override
			protected void appendPrefixIndicator(StringBuilder builder, IPv6v4MixedAddressSection addr) {
				if(requiresPrefixIndicator(addr.ipv6Section) || requiresPrefixIndicator(addr.ipv4Section)) {
					super.appendPrefixIndicator(builder, addr);
				}
			}
			
			protected boolean requiresPrefixIndicator(IPv4AddressSection ipv4Section)    {
				return ipv4Section.isPrefixed() && ipv4Params.getWildcardOption().wildcardOption != WildcardOptions.WildcardOption.ALL;
			}
			
			protected boolean requiresPrefixIndicator(IPv6AddressSection ipv6Section)    {
				return ipv6Section.isPrefixed() && (ipv6Params.getWildcardOption().wildcardOption != WildcardOptions.WildcardOption.ALL || ipv6Params.hostCompressed);
			}
			
			@Override
			public IPv6v4MixedParams clone() {
				IPv6v4MixedParams params = (IPv6v4MixedParams) super.clone();
				params.ipv6Params = ipv6Params.clone();
				params.ipv4Params = ipv4Params.clone();
				return params;
			}
		}
		
		/**
		 * Each IPv6StringParams has settings to write exactly one IPv6 address section string
		 * 
		 * @author sfoley
		 *
		 */
		static class IPv6StringParams extends StringParams {
			
			int firstCompressedSegmentIndex, nextUncompressedIndex; //the start and end of any compressed section
			
			boolean hostCompressed; //whether the host was compressed, which means we must print the network prefix
			
			String zone;
			
			char zoneSeparator;
			
			IPv6StringParams() {
				this(-1, 0);
			}
			
			IPv6StringParams(int firstCompressedSegmentIndex, int compressedCount) {
				this(false, firstCompressedSegmentIndex, compressedCount, false, IPv6Address.SEGMENT_SEPARATOR, IPv6Address.ZONE_SEPARATOR);
			}
			
			private IPv6StringParams(
					boolean expandSegments,
					int firstCompressedSegmentIndex,
					int compressedCount,
					boolean uppercase, 
					char separator,
					char zoneSeparator) {
				super(IPv6Address.DEFAULT_TEXTUAL_RADIX, separator, uppercase);
				this.expandSegments(expandSegments);
				this.firstCompressedSegmentIndex = firstCompressedSegmentIndex;
				this.nextUncompressedIndex = firstCompressedSegmentIndex + compressedCount;
				this.zoneSeparator = zoneSeparator;
			}
			
			public void setZoneSeparator(char zoneSeparator) {
				this.zoneSeparator = zoneSeparator;
			}
			
			public boolean endIsCompressed(IPAddressPart addr) {
				return nextUncompressedIndex >= addr.getDivisionCount();
			}
			
			public boolean isCompressed(IPAddressPart addr) {
				return firstCompressedSegmentIndex >= 0;
			}
			
			@Override
			public int getTrailingSeparatorCount(IPv6AddressSection addr) {
				return getTrailingSepCount(addr);
			}
			
			public int getTrailingSepCount(IPAddressPart addr) {
				int divisionCount = addr.getDivisionCount();
				if(divisionCount == 0) {
					return 0;
				}
				int count = divisionCount - 1;//separators with no compression
				if(isCompressed(addr)) {
					count -= (nextUncompressedIndex - firstCompressedSegmentIndex) - 1; //missing seps
					if(firstCompressedSegmentIndex == 0 /* additional separator at front */ || 
							nextUncompressedIndex >= divisionCount /* additional separator at end */) {
						count++;
					}
				}
				return count;
			}
			
			@Override
			public int getStringLength(IPv6AddressSection addr) {
				int count = getSegmentsStringLength(addr);
				if(!isReverse() && (getWildcardOption().wildcardOption != WildcardOptions.WildcardOption.ALL || hostCompressed)) {
					count += addr.getPrefixStringLength();
				}
				count += getZoneLength();
				count += getAddressSuffixLength();
				count += getAddressLabelLength();
				return count;
			}
			
			@Override
			public StringBuilder append(StringBuilder builder, IPv6AddressSection addr) {
				appendLabel(builder);
				appendSegments(builder, addr);
				/* 
				 * Our order is zone, then suffix, then prefix length.  This is documented in more detail for the IPv6-only case.
				 */
				appendZone(builder);
				appendSuffix(builder);
				if(!isReverse() && (getWildcardOption().wildcardOption != WildcardOptions.WildcardOption.ALL || hostCompressed)) {
					appendPrefixIndicator(builder, addr);
				}
				return builder;
			}

			protected int getZoneLength() {
				if(zone != null && zone.length() > 0) {
					return zone.length() + 1;
				}
				return 0;
			}
			
			protected void appendZone(StringBuilder builder) {
				if(zone != null && zone.length() > 0) {
					builder.append(zoneSeparator).append(zone);
				}
			}

			 /**
			 * @see inet.ipaddr.format.util.IPAddressPartStringCollection.StringParams#appendSegments(java.lang.StringBuilder, inet.ipaddr.format.IPAddressPart)
			 */
			@Override
			public StringBuilder appendSegments(StringBuilder builder, IPv6AddressSection addr) {
				int divisionCount = addr.getDivisionCount();
				if(divisionCount <= 0) {
					return builder;
				}
				int lastIndex = divisionCount - 1;
				Character separator = getSeparator();
				WildcardOptions wildcardOptions = getWildcardOption();
				WildcardOptions.WildcardOption wildcardOption = wildcardOptions.wildcardOption;
				boolean isAll = wildcardOption == WildcardOptions.WildcardOption.ALL;
				boolean reverse = isReverse();
				int i = 0;
				while(true) {
					int segIndex = reverse ? lastIndex - i : i;
					if(segIndex < firstCompressedSegmentIndex || segIndex >= nextUncompressedIndex) {
						IPAddressDivision seg = addr.getDivision(segIndex);
						int leadingZeroCount = getLeadingZeros(segIndex);
						if(isAll || isSplitDigits()) {
							seg.getWildcardString(wildcardOptions.wildcards, leadingZeroCount, getSegmentStrPrefix(), getRadix(), isUppercase(), isSplitDigits(), separator, isReverse(), builder);
						} else { //wildcardOption == WildcardOptions.WildcardOption.NETWORK_ONLY
							seg.getPrefixAdjustedWildcardString(wildcardOptions.wildcards, leadingZeroCount, getSegmentStrPrefix(), getRadix(), isUppercase(), builder);
						}
						if(++i > lastIndex) {
							break;
						}
						if(separator != null) {
							builder.append(separator);
						}
					} else {
						if(segIndex == (reverse ? nextUncompressedIndex - 1 :  firstCompressedSegmentIndex) && separator != null) { //the segment is compressed
							builder.append(separator);
							if(i == 0) {//when compressing the front we use two separators
								builder.append(separator);
							}
						} //else we are in the middle of a compressed set of segments, so nothing to write
						if(++i > lastIndex) {
							break;
						}
					}
				}
				return builder;
			}

			@Override
			public int getSegmentsStringLength(IPv6AddressSection part) {
				int count = 0;
				int divCount = part.getDivisionCount();
				if(divCount != 0) {
					WildcardOptions wildcardOptions = getWildcardOption();
					WildcardOptions.WildcardOption wildcardOption = wildcardOptions.wildcardOption;
					boolean isAll = wildcardOption == WildcardOptions.WildcardOption.ALL;
					Character separator = getSeparator();
					int i = 0;
					while(true) {
						if(i < firstCompressedSegmentIndex || i >= nextUncompressedIndex) {
							IPAddressDivision seg = part.getDivision(i);
							int leadingZeroCount = getLeadingZeros(i);
							if(isAll || isSplitDigits()) {
								count += seg.getWildcardString(wildcardOptions.wildcards, leadingZeroCount, getSegmentStrPrefix(), getRadix(), isUppercase(), isSplitDigits(), separator, isReverse(), null);
							} else { //wildcardOption == WildcardOptions.WildcardOption.NETWORK_ONLY
								count += seg.getPrefixAdjustedWildcardString(wildcardOptions.wildcards, leadingZeroCount, getSegmentStrPrefix(), getRadix(), isUppercase(), null);
							}
							if(++i >= divCount) {
								break;
							}
							if(separator != null) {
								count++;
							}
						} else {
							if(i == firstCompressedSegmentIndex && separator != null) { //the segment is compressed
								count++;
								if(i == 0) {//when compressing the front we use two separators
									count++;
								}
							} //else we are in the middle of a compressed set of segments, so nothing to write
							if(++i >= divCount) {
								break;
							}
						}
					}
				}
				return count;
			}
			
			@Override
			public IPv6StringParams clone() {
				return (IPv6StringParams) super.clone();
			}
		}

		
		/**
		 * Capable of building any and all possible representations of IPv6 addresses.
		 * Not all such representations are necessarily something you might consider valid.
		 * For example: a:0::b:0c:d:1:2
		 * This string has a single zero segment compressed rather than two consecutive (a partial compression),
		 * it has the number 'c' expanded partially to 0c (a partial expansion), rather than left as is, or expanded to the full 4 chars 000c.
		 * 
		 * Mixed representation strings are produced by the IPv6 mixed builder.
		 * The one other type of variation not produced by this class are mixed case, containing both upper and lower case characters: A-F vs a-f.
		 * That would result in gazillions of possible representations.  
		 * But such variations are easy to work with for comparison purposes because you can easily convert strings to lowercase,
		 * so in general there is no need to cover such variations.
		 * However, this does provide the option to have either all uppercase or all lowercase strings.
		 * 
		 * A single address can have hundreds of thousands, even millions, of possible variations.
		 * The default settings for this class will produce at most a couple thousand possible variations.
		 * 
		 * @author sfoley
		 */
		static class IPv6StringBuilder
				extends AddressPartStringBuilder {
		
			private final String zone;
			
			IPv6StringBuilder(IPv6AddressSection address, IPv6StringBuilderOptions opts, String zone) {
				super(address,  opts, new IPv6AddressSectionStringCollection(address));
				this.zone = zone;
			}
			
			private void addUppercaseVariations(ArrayList allParams, int base) {
				boolean lowerOnly = true; //by default we use NETWORK_ONLY wildcards (we use prefix notation otherwise) so here we check lower values only for alphabetic
				if(options.includes(IPv6StringBuilderOptions.UPPERCASE) && addressSection.hasAlphabeticDigits(base, lowerOnly)) {
					int len = allParams.size();
					for(int j=0; j allParams = new ArrayList();
				allParams.add(stringParams);
				
				int radix = IPv6Address.DEFAULT_TEXTUAL_RADIX;
				if(options.includes(IPStringBuilderOptions.LEADING_ZEROS_FULL_SOME_SEGMENTS)) {
					int expandables[] = getExpandableSegments(radix);
					int nextUncompressedIndex = firstCompressedIndex + count;
					int ipv6SegmentEnd = addressSection.getSegmentCount();
					for(int i=0; i < ipv6SegmentEnd; i++) {
						if(i < firstCompressedIndex || i >= nextUncompressedIndex) {
							int expansionLength = expandables[i];
							int len = allParams.size();
							while(expansionLength > 0) {		
								for(int j=0; j 0) {
						addAllExpansions(zeroStartIndex, len, segmentCount);
					}
				}
			}
			
			/*
			Here is how we get all potential strings:
					//for each zero-segment we choose, including the one case of choosing no zero segment
						//for each sub-segment of that zero-segment compressed (this loop is skipped for the no-zero segment case)
							//for each potential expansion of a non-compressed segment
								//we write the string
			 */
			@Override
			protected void addAllVariations() {
				int segmentCount = addressSection.getSegmentCount();
				
				//start with the case of compressing nothing
				addAllExpansions(-1, 0, segmentCount);
				
				//now do the compressed strings
				if(options.includes(IPv6StringBuilderOptions.COMPRESSION_ALL_FULL)) {
					RangeList zeroSegs  = addressSection.getZeroSegments();
					for(int i = 0; i < zeroSegs.size(); i++) {
						Range range = zeroSegs.getRange(i);
						addAllCompressedStrings(range.index, range.length, options.includes(IPv6StringBuilderOptions.COMPRESSION_ALL_PARTIAL), segmentCount);
					}
				} else if(options.includes(IPv6StringBuilderOptions.COMPRESSION_CANONICAL)) {
					CompressOptions opts = new CompressOptions(options.includes(IPv6StringBuilderOptions.COMPRESSION_SINGLE), CompressOptions.CompressionChoiceOptions.ZEROS);
					int indexes[] = addressSection.getCompressIndexAndCount(opts);
					if(indexes != null) {
						if(options.includes(IPv6StringBuilderOptions.COMPRESSION_LARGEST)) {
							//we compress any section with length that matches the max
							int maxCount = indexes[1];
							RangeList zeroSegs  = addressSection.getZeroSegments();
							for(int i = 0; i < zeroSegs.size(); i++) {
								Range range = zeroSegs.getRange(i);
								int count = range.length;
								if(count == maxCount) {
									addAllCompressedStrings(range.index, count, options.includes(IPv6StringBuilderOptions.COMPRESSION_ALL_PARTIAL), segmentCount);
								}
							}
						} else {
							int maxIndex = indexes[0];
							int maxCount = indexes[1];
							addAllCompressedStrings(maxIndex, maxCount, false, segmentCount);
						}
					} // else nothing to compress, and this case already handled
				}
			}
		}
		
		static class IPv6v4MixedStringBuilder
				extends AddressPartStringBuilder<
					IPv6v4MixedAddressSection,
					IPv6v4MixedParams,
					IPAddressPartConfiguredString,
					IPv6v4MixedStringCollection,
					IPv6StringBuilderOptions> {
			private final String zone;
			
			IPv6v4MixedStringBuilder(IPv6v4MixedAddressSection address, IPv6StringBuilderOptions opts, String zone) {
				super(address, opts, new IPv6v4MixedStringCollection(address));
				this.zone = zone;
			}

			@Override
			protected void addAllVariations() {
				IPv6StringBuilder ipv6Builder = new IPv6StringBuilder(addressSection.ipv6Section, options, zone);
				IPv6AddressSectionStringCollection ipv6Variations = ipv6Builder.getVariations();
				IPAddressPartStringCollection ipv4Collection = 
						addressSection.ipv4Section.toStringCollection(options.mixedOptions);
				for(IPv6AddressSectionString ipv6Variation : ipv6Variations) {
					for(IPAddressPartConfiguredString ipv4Variation : ipv4Collection) {
						IPv6v4MixedParams mixed = new IPv6v4MixedParams(ipv6Variation, ipv4Variation);
						addStringParam(mixed);
					}
				}
			}
		}
	}
	
	private static class IPv6AddressSectionString extends IPAddressPartConfiguredString {
		IPv6AddressSectionString(IPv6AddressSection addr, IPv6StringParams stringParams) {
			super(addr, stringParams);
		}
		
		@SuppressWarnings("unchecked")
		@Override
		public IPv6StringMatcher getNetworkStringMatcher(boolean isEntireAddress, IPAddressSQLTranslator translator) {
			return new IPv6StringMatcher(this, translator);
		}
		
		public boolean endIsCompressed() {
			return stringParams.endIsCompressed(addr);
		}
		
		public boolean isCompressed() {
			return stringParams.isCompressed(addr);
		}
	}
	
	public static class IPv6StringBuilderOptions extends IPStringBuilderOptions {
		public static final int MIXED = 0x2;

		public static final int UPPERCASE = 0x4;

		public static final int COMPRESSION_CANONICAL = 0x100; //use the compression that is part of the canonical string format
		public static final int COMPRESSION_SINGLE = COMPRESSION_CANONICAL | 0x200; //compress a single segment.  If more than one is compressible, choose the largest, and if multiple are largest, choose the most leftward.
		public static final int COMPRESSION_LARGEST = COMPRESSION_SINGLE | 0x400; //compress fully any section that is largest 
		public static final int COMPRESSION_ALL_FULL = COMPRESSION_LARGEST | 0x800; //compress fully any section that can be compressed
		public static final int COMPRESSION_ALL_PARTIAL = COMPRESSION_ALL_FULL | 0x1000;

		public static final int IPV4_CONVERSIONS = 0x10000;

		public final IPv4StringBuilderOptions mixedOptions;
		public final IPv4StringBuilderOptions ipv4ConverterOptions;
		public final IPv4AddressConverter converter;
		
		public static final IPv6StringBuilderOptions STANDARD_OPTS = new IPv6StringBuilderOptions(
				IPStringBuilderOptions.BASIC |
					IPv6StringBuilderOptions.UPPERCASE |
					IPStringBuilderOptions.LEADING_ZEROS_FULL_ALL_SEGMENTS |
					IPv6StringBuilderOptions.COMPRESSION_ALL_FULL, 
			new IPv4StringBuilderOptions(IPStringBuilderOptions.BASIC | IPStringBuilderOptions.LEADING_ZEROS_FULL_ALL_SEGMENTS));
		
		public static final IPv6StringBuilderOptions ALL_OPTS =  
				new IPv6StringBuilderOptions(
						IPStringBuilderOptions.BASIC | 
							IPv6StringBuilderOptions.MIXED | 
							IPv6StringBuilderOptions.UPPERCASE | 
							IPv6StringBuilderOptions.COMPRESSION_ALL_FULL |
							IPv6StringBuilderOptions.IPV4_CONVERSIONS |
							IPStringBuilderOptions.LEADING_ZEROS_FULL_SOME_SEGMENTS, 
						new IPv4StringBuilderOptions(IPStringBuilderOptions.BASIC | IPStringBuilderOptions.LEADING_ZEROS_FULL_SOME_SEGMENTS),//mixed
						null,
						new IPv4StringBuilderOptions(
							IPStringBuilderOptions.BASIC | 
								IPv4StringBuilderOptions.JOIN_ALL | 
								IPv4StringBuilderOptions.JOIN_TWO | 
								IPv4StringBuilderOptions.JOIN_ONE |
								IPv4StringBuilderOptions.HEX |
								IPv4StringBuilderOptions.OCTAL |IPStringBuilderOptions.LEADING_ZEROS_FULL_SOME_SEGMENTS));

		public static final IPv6StringBuilderOptions DATABASE_SEARCH_OPTS =
				new IPv6StringBuilderOptions(IPStringBuilderOptions.BASIC | IPv6StringBuilderOptions.COMPRESSION_LARGEST);

		public IPv6StringBuilderOptions(int options) {
			this(options, null, null, null);
		}

		public IPv6StringBuilderOptions(int options, IPv4StringBuilderOptions mixedOptions) {
			this(options, mixedOptions, null, null);
		}
		
		public IPv6StringBuilderOptions(int options, IPv4StringBuilderOptions mixedOptions, IPv4AddressConverter ipv4AddressConverter, IPv4StringBuilderOptions ipv4ConverterOptions) {
			super(options | (mixedOptions == null ? 0 : MIXED) | (ipv4ConverterOptions == null ? 0 : IPV4_CONVERSIONS));
			if(includes(MIXED) && mixedOptions == null) {
				mixedOptions = new IPv4StringBuilderOptions();
			}
			this.mixedOptions = mixedOptions;
			if(includes(IPV4_CONVERSIONS)) {
				if(ipv4ConverterOptions == null) {
					ipv4ConverterOptions = new IPv4StringBuilderOptions();
				}
				if(ipv4AddressConverter == null) {
					ipv4AddressConverter = IPAddress.addressConverter;
					if(ipv4AddressConverter == null) {
						ipv4AddressConverter = new DefaultAddressConverter();
					}
				}
			}
			this.ipv4ConverterOptions = ipv4ConverterOptions;
			this.converter = ipv4AddressConverter;
		}

		public static IPv6StringBuilderOptions from(IPStringBuilderOptions opts) {
			if(opts instanceof IPv6StringBuilderOptions) {
				return (IPv6StringBuilderOptions) opts;
			}
			return new IPv6StringBuilderOptions(opts.options & ~(MIXED | UPPERCASE | COMPRESSION_ALL_PARTIAL | IPV4_CONVERSIONS));
		}
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy