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

inet.ipaddr.format.IPAddressLargeDivision Maven / Gradle / Ivy

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

package inet.ipaddr.format;

import java.math.BigInteger;
import java.util.Arrays;

import inet.ipaddr.Address;
import inet.ipaddr.AddressValueException;
import inet.ipaddr.IPAddressNetwork;
import inet.ipaddr.IncompatibleAddressException;
import inet.ipaddr.PrefixLenException;
import inet.ipaddr.format.util.AddressSegmentParams;

/**
 * This class supports a segment of an arbitrary number of bits.
 * 
 * For a bit count less than or equal to 63 bits, AddressDivision is a more efficient choice.
 * 
 * @author sfoley
 *
 */
public class IPAddressLargeDivision extends AddressDivisionBase implements IPAddressStringDivision {

	private static BigInteger LONG_MAX = BigInteger.valueOf(Long.MAX_VALUE);

	public static final char EXTENDED_DIGITS_RANGE_SEPARATOR = Address.ALTERNATIVE_RANGE_SEPARATOR;
	public static final String EXTENDED_DIGITS_RANGE_SEPARATOR_STR = String.valueOf(EXTENDED_DIGITS_RANGE_SEPARATOR);
	
	public static final char[] EXTENDED_DIGITS = {
			 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 
			 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 
			 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 
			 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 
			 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 
			 'y', 'z', '!', '#', '$', '%', '&', '(', ')', '*', '+', '-', 
			 ';', '<', '=', '>', '?', '@', '^', '_', '`', '{', '|', '}', 
			 '~' };
	
	private static final long serialVersionUID = 4L;
	
	private final BigInteger value, upperValue, maxValue, upperValueMasked;
	private final BigInteger defaultRadix;//we keep radix as a big integer because some operations required it, but we only support integer radices so it can be converted via BigInteger.intValue() at any time
	private final int bitCount;
	private final Integer networkPrefixLength;
	private final boolean isSinglePrefixBlock, isPrefixBlock;
	protected transient String cachedWildcardString;
	
	public IPAddressLargeDivision(byte bytes[], int bitCount, int defaultRadix) throws AddressValueException {
		maxValue = getMaxValue(bitCount);
		this.bitCount = bitCount;
		this.defaultRadix = BigInteger.valueOf(defaultRadix);
		isPrefixBlock = isSinglePrefixBlock = false;
		upperValueMasked = upperValue = value = new BigInteger(1, bytes);
		networkPrefixLength = null;
		if(upperValue.compareTo(maxValue) > 0) {
			throw new AddressValueException(upperValue);
		}
	}
	
	/**
	 * 
	 * @param bytes
	 * @param bitCount
	 * @param defaultRadix
	 * @param network can be null if prefixLength is null
	 * @param prefixLength
	 */
	public IPAddressLargeDivision(byte bytes[], int bitCount, int defaultRadix, IPAddressNetwork network, Integer prefixLength) throws AddressValueException {
		if(prefixLength != null && prefixLength < 0) {
			throw new PrefixLenException(prefixLength);
		}
		maxValue = getMaxValue(bitCount);
		this.bitCount = bitCount;
		this.defaultRadix = BigInteger.valueOf(defaultRadix);
		if(prefixLength == null || prefixLength >= bitCount) {
			if(prefixLength != null && prefixLength > bitCount) {
				prefixLength = bitCount;
			}
			isPrefixBlock = isSinglePrefixBlock = prefixLength != null;
			upperValueMasked = upperValue = value = new BigInteger(1, bytes);
		} else {
			bytes = extend(bytes, bitCount);
			byte upperBytes[] = bytes.clone();
			int shift = bitCount - prefixLength;
			int byteShift = (shift + 7) / 8;
			int byteIndex = bytes.length - byteShift;
			int mask = 0xff & (~0 << (((shift - 1) % 8) + 1));
			if(network.getPrefixConfiguration().allPrefixedAddressesAreSubnets()) {
				bytes[byteIndex] &= mask;
				Arrays.fill(bytes, byteIndex + 1, bytes.length, (byte) 0);
				upperValueMasked = value = new BigInteger(1, bytes);
				upperBytes[byteIndex] |= ~mask;
				Arrays.fill(upperBytes, byteIndex + 1, bytes.length, (byte) 0xff);
				upperValue = new BigInteger(1, upperBytes);
				isPrefixBlock = isSinglePrefixBlock = true;
			} else {
				byte maskedUpperBytes[] = upperBytes.clone();
				maskedUpperBytes[byteIndex] &= mask;
				Arrays.fill(maskedUpperBytes, byteIndex + 1, bytes.length, (byte) 0);
				upperValueMasked = new BigInteger(1, maskedUpperBytes);
				upperValue = value = new BigInteger(1, bytes);
				isPrefixBlock = isSinglePrefixBlock = false;
			}
		}
		if(upperValue.compareTo(maxValue) > 0) {
			throw new AddressValueException(upperValue);
		}
		networkPrefixLength = prefixLength;
	}

	public IPAddressLargeDivision(
			byte bytes[], byte upperBytes[], int bitCount, int defaultRadix, IPAddressNetwork network, Integer prefixLength) throws AddressValueException {
		if(prefixLength != null && prefixLength < 0) {
			throw new PrefixLenException(prefixLength);
		}
		bytes = extend(bytes, bitCount);
		upperBytes = extend(upperBytes, bitCount);
		maxValue = getMaxValue(bitCount);
		this.bitCount = bitCount;
		this.defaultRadix = BigInteger.valueOf(defaultRadix);
		if(prefixLength == null || prefixLength >= bitCount) {
			if(prefixLength != null && prefixLength > bitCount) {
				prefixLength = bitCount;
			}
			BigInteger low, high;
			if(Arrays.equals(bytes, upperBytes)) {
				low = high = new BigInteger(1, bytes);
			} else {
				low = new BigInteger(1, bytes);
				high = new BigInteger(1, upperBytes);
				if(low.compareTo(high) > 0) {
					BigInteger tmp = high;
					high = low;
					low = tmp;
				}
			}
			isSinglePrefixBlock = isPrefixBlock = prefixLength != null;
			value = low;
			upperValueMasked = upperValue = high;
		} else {
			int shift = bitCount - prefixLength;
			int byteShift = (shift + 7) / 8;
			int byteIndex = bytes.length - byteShift;
			int mask = 0xff & (~0 << (((shift - 1) % 8) + 1));
			int upperByteIndex = upperBytes.length - byteShift;
			if(network.getPrefixConfiguration().allPrefixedAddressesAreSubnets()) {
				BigInteger low, high, highMasked;
				while(true) {
					bytes[byteIndex] &= mask;
					Arrays.fill(bytes, byteIndex + 1, bytes.length, (byte) 0);
					low = new BigInteger(1, bytes);
					
					upperBytes[upperByteIndex] |= ~mask;
					Arrays.fill(upperBytes, upperByteIndex + 1, upperBytes.length, (byte) 0xff);
					high = new BigInteger(1, upperBytes);
				
					byte maskedUpperBytes[] = upperBytes.clone();
					maskedUpperBytes[upperByteIndex] &= mask;
					Arrays.fill(maskedUpperBytes, upperByteIndex + 1, upperBytes.length, (byte) 0);
					highMasked = new BigInteger(1, maskedUpperBytes);
					
					if(low.compareTo(high) > 0) {
						byte tmp[] = upperBytes;
						upperBytes = bytes;
						bytes = tmp;
						continue;
					}
					break;
				}
				value = low;
				upperValue = high;
				upperValueMasked = highMasked;
				isPrefixBlock = true;
				isSinglePrefixBlock = isPrefixSubnetBlock(bytes, upperBytes, bitCount, prefixLength, true, false);
			} else {
				BigInteger low, high;
				if(Arrays.equals(bytes, upperBytes)) {
					low = high = new BigInteger(1, bytes);
					isPrefixBlock = isSinglePrefixBlock = false;
				} else {
					low = new BigInteger(1, bytes);
					high = new BigInteger(1, upperBytes);
					boolean backIsPrefixed = isPrefixSubnetBlock(bytes, upperBytes, bitCount, prefixLength, false, true);
					if(backIsPrefixed) {
						isPrefixBlock = true;
						isSinglePrefixBlock = isPrefixSubnetBlock(bytes, upperBytes, bitCount, prefixLength, true, false);
					} else {
						isPrefixBlock = isSinglePrefixBlock = false;
					}
					if(low.compareTo(high) > 0) {
						BigInteger tmp = high;
						high = low;
						low = tmp;
					}
				}
				value = low;
				upperValue = high;
				byte maskedUpperBytes[] = upperBytes.clone();
				maskedUpperBytes[byteIndex] &= mask;
				Arrays.fill(maskedUpperBytes, byteIndex + 1, bytes.length, (byte) 0);
				upperValueMasked = new BigInteger(1, maskedUpperBytes);
			}
			
		}
		if(upperValue.compareTo(maxValue) > 0) {
			throw new AddressValueException(upperValue);
		}
		networkPrefixLength = prefixLength;
	}
	
	private static boolean isPrefixSubnetBlock(byte bytes[], byte upperBytes[], int bitCount, Integer prefix, boolean front, boolean back) {
		if(prefix == null) {
			return false;
		}
		int shift = bitCount - prefix;
		int byteShift = (shift + 7) / 8;
		int byteIndex = bytes.length - byteShift;
		int mask = 0xff & (~0 << (((shift - 1) % 8) + 1));
		byte lowerByte = bytes[byteIndex];
		byte upperByte = upperBytes[byteIndex];
		if(front) {
			int lower = lowerByte & mask;
			int upper = upperByte & mask;
			if(lower != upper) {
				return false;
			}
			for(int i = byteIndex - 1; i >= 0; i--) {
				if(bytes[i] != upperBytes[i]) {
					return false;
				}
			}
		}
		if(back) {
			int hostMask = 0xff & ~mask;
			int lower = lowerByte & hostMask;
			int upper = upperByte & hostMask;
			if(lower != 0 || upper != hostMask) {
				return false;
			}
			for(int i = byteIndex + 1; i < bytes.length; i++) {
				if(bytes[i] != 0 || upperBytes[i] != (byte) 0xff) {
					return false;
				}
			}
		}
		return true;
	}
	
	private static byte[] extend(byte bytes[], int bitCount) {
		return convert(bytes, (bitCount + 7) / 8, "");
	}
	
	private static byte[] convert(byte bytes[], int requiredByteCount, String key) {
		int len = bytes.length;
		if(len < requiredByteCount) {
			byte oldBytes[] = bytes;
			bytes = new byte[requiredByteCount];
			int diff = bytes.length - oldBytes.length;
			int mostSignificantBit = 0x80 & oldBytes[0];
			if(mostSignificantBit != 0) {//sign extension
				Arrays.fill(bytes, 0, diff, (byte) 0xff);
			}
			System.arraycopy(oldBytes, 0, bytes, diff, oldBytes.length);
		} else {
			if(len > requiredByteCount) {
				int i = 0;
				do {
					if(bytes[i++] != 0) {
						throw new AddressValueException(key, len);
					}
				} while(--len > requiredByteCount);
				bytes = Arrays.copyOfRange(bytes, i, bytes.length);
			}
		}
		return bytes;
	}

	@Override
	public boolean isBoundedBy(int val) {
		BigInteger bigVal = BigInteger.valueOf(val);
		return upperValue.compareTo(bigVal) < 0;
	}

	@Override
	public int getDigitCount(int radix) {
		if(!isMultiple() && radix == getDefaultTextualRadix()) {//optimization - just get the string, which is cached, which speeds up further calls to this method or getString()
			return getString().length();
		}
		return getDigitCountStatic(upperValue, radix);
	}

	@Override
	public BigInteger getCount() {
		return upperValue.subtract(value).add(BigInteger.ONE);
	}

	@Override
	public int getBitCount() {
		return bitCount;
	}

	@Override
	public boolean isMultiple() {
		return !value.equals(upperValue);
	}
	
	@Override
	public boolean includesZero() {
		return value.equals(BigInteger.ZERO);
	}
	
	@Override
	public boolean includesMax() {
		return upperValue.equals(maxValue);
	}

	@Override
	public boolean isMax() {
		return includesMax() && !isMultiple();
	}
	
	@Override
	public boolean isZero() {
		return includesZero() && !isMultiple();
	}

	@Override
	public boolean isFullRange() {
		return includesZero() && includesMax();
	}

	@Override
	protected byte[] getBytesImpl(boolean low) {
		return convert(low ? value.toByteArray() : upperValue.toByteArray(), (bitCount + 7) / 8, "");
	}

	@Override
	public int getDefaultTextualRadix() {
		return defaultRadix.intValue();
	}

	@Override
	public int getMaxDigitCount() {
		return getMaxDigitCount(defaultRadix.intValue(), bitCount, maxValue);
	}

	@Override
	public int getMaxDigitCount(int radix) {
		return getMaxDigitCount(radix, bitCount, maxValue);
	}

	@Override
	protected int adjustLowerLeadingZeroCount(int leadingZeroCount, int radix) {
		return adjustLeadingZeroCount(leadingZeroCount, value, radix);
	}

	@Override
	protected int adjustUpperLeadingZeroCount(int leadingZeroCount, int radix) {
		return adjustLeadingZeroCount(leadingZeroCount, upperValue, radix);
	}
	
	private int adjustLeadingZeroCount(int leadingZeroCount, BigInteger value, int radix) {
		if(leadingZeroCount < 0) {
			int width = getDigitCount(value, radix);
			return Math.max(0, getMaxDigitCount(radix) - width);
		}
		return leadingZeroCount;
	}
	
	private int getDigitCount(BigInteger val, int radix) {
		BigInteger bigRadix = defaultRadix.intValue() == radix ? defaultRadix : BigInteger.valueOf(radix);
		return getDigitCount(val, bigRadix);
	}
	
	private static int getDigitCountStatic(BigInteger val, int radix) {
		return getDigitCount(val, BigInteger.valueOf(radix));
	}
	
	private String toDefaultString(BigInteger val, int radix, boolean uppercase, int choppedDigits) {
		BigInteger bigRadix = defaultRadix.intValue() == radix ? defaultRadix : BigInteger.valueOf(radix);
		return toDefaultString(val, bigRadix, uppercase, choppedDigits, getMaxDigitCount(radix, bitCount, null));
	}

	private static void toDefaultStringRecursive(BigInteger val, BigInteger radix, boolean uppercase, int choppedDigits, int digitCount, char dig[], boolean highest, StringBuilder builder) {
		//if we ensure that our recursion always defers to the most significant digits first, then we can simply append to a string builder
		if(val.compareTo(LONG_MAX) <= 0) {
			long longVal = val.longValue();
			int intRadix = radix.intValue();
			if(!highest) {
				getLeadingZeros(digitCount - AddressDivision.toUnsignedStringLength(longVal, intRadix), builder);
			}
			AddressDivision.toUnsignedString(longVal, intRadix, choppedDigits, uppercase, dig, builder);
		} else {
			int halfCount = digitCount >>> 1;
			if(halfCount > choppedDigits) {
				BigInteger radixPower = getRadixPower(radix, halfCount);
				BigInteger highLow[] = val.divideAndRemainder(radixPower);
				BigInteger high = highLow[0];
				BigInteger low = highLow[1];
				if(highest && high.equals(BigInteger.ZERO)) {
					toDefaultStringRecursive(low, radix, uppercase, choppedDigits, halfCount, dig, true, builder);
				} else {
					if(digitCount > choppedDigits) {
						toDefaultStringRecursive(high, radix, uppercase, Math.max(0,  choppedDigits - halfCount), digitCount - halfCount, dig, highest, builder);
					}
					toDefaultStringRecursive(low, radix, uppercase, choppedDigits, halfCount, dig, false, builder);
				}
			}
		}
	}
	
	private boolean isExtendedDigits() {
		return isExtendedDigits(defaultRadix.intValue());
	}
	
	private static boolean isExtendedDigits(int radix) {
		return radix > 36;
	}
	
	private static char[] getDigits(int radix, boolean uppercase) {
		if(isExtendedDigits(radix)) {
			return EXTENDED_DIGITS;
		}
		return uppercase ? UPPERCASE_DIGITS : DIGITS;
	}
	
	@Override
	protected void appendUppercase(CharSequence str, int radix, StringBuilder appendable) {
		if(radix > 10 && !isExtendedDigits()) {
			for(int i = 0; i < str.length(); i++) {
				char c = str.charAt(i);
				if(c >= 'a' && c <= 'z') {
					c += 'A' - 'a';
				}
				appendable.append(c);
			}
		} else {
			appendable.append(str);
		}
	}
	
	private static String toDefaultString(BigInteger val, BigInteger radix, boolean uppercase, int choppedDigits, int maxDigits) {
		if(val.equals(BigInteger.ZERO)) {
			return "0";
		}
		if(val.equals(BigInteger.ONE)) {
			return "1";
		}
		char dig[] = getDigits(radix.intValue(), uppercase);
		StringBuilder builder = new StringBuilder();
		if(maxDigits > 0) {//maxDigits is 0 or less if the max digits is unknown
			if(maxDigits > choppedDigits) {
				toDefaultStringRecursive(val, radix, uppercase, choppedDigits, maxDigits, dig, true, builder);
			}
		} else {
			do {//value2 == quotient * 16 + remainder
				BigInteger divisorRemainder[] = val.divideAndRemainder(radix);
				BigInteger quotient = divisorRemainder[0];
				BigInteger remainder = divisorRemainder[1];
				if(choppedDigits > 0) {
					--choppedDigits;
					continue;
				}
				builder.append(dig[remainder.intValue()]);
				val = quotient;
			} while(!val.equals(BigInteger.ZERO));
			builder.reverse();
		}
		return builder.toString();
	}
	
	/**
	 * Produces a normalized string to represent the segment.
	 * If the segment CIDR prefix length covers the range, then it is assumed to be a CIDR, and the string has only the lower value of the CIDR range.
	 * Otherwise, the explicit range will be printed.
	 * @return
	 */
	@Override
	public String getString() {
		String result = cachedString;
		if(result == null) {
			synchronized(this) {
				result = cachedString;
				if(result == null) {
					if(isSinglePrefixBlock() || !isMultiple()) { //covers the case of !isMultiple, ie single addresses, when there is no prefix or the prefix is the bit count
						result = getDefaultString();
					} else if(!isFullRange() || (result = getDefaultSegmentWildcardString()) == null) {
						if(isPrefixed() && isPrefixBlock(getDivisionPrefixLength())) {
							result = getDefaultMaskedRangeString();
						} else {
							result = getDefaultRangeString();
						}
					}
					cachedString = result;
				}
			}
		}
		return result;
	}

	/**
	 * Produces a string to represent the segment, favouring wildcards and range characters over the network prefix to represent subnets.
	 * If it exists, the segment CIDR prefix is ignored and the explicit range is printed.
	 * @return
	 */
	@Override
	public String getWildcardString() {
		String result = cachedWildcardString;
		if(result == null) {
			synchronized(this) {
				result = cachedWildcardString;
				if(result == null) {
					if(!isPrefixed() || !isMultiple()) {
						result = getString();
					} else if(!isFullRange() || (result = getDefaultSegmentWildcardString()) == null) {
						result = getDefaultRangeString();
					}
					cachedWildcardString = result;
				}
			}
		}
		return result;
	}

	@Override
	protected String getDefaultString() {
		return toDefaultString(value, defaultRadix, false, 0, getMaxDigitCount());
	}

	@Override
	protected String getDefaultRangeString() {
		int maxDigitCount = getMaxDigitCount();
		return toDefaultString(value, defaultRadix, false, 0, maxDigitCount) + 
				getDefaultRangeSeparatorString() + 
				toDefaultString(upperValue, defaultRadix, false, 0, maxDigitCount);
	}
	
	protected String getDefaultMaskedRangeString() {
		int maxDigitCount = getMaxDigitCount();
		return toDefaultString(value, defaultRadix, false, 0, maxDigitCount) + 
				getDefaultRangeSeparatorString() + 
				toDefaultString(upperValueMasked, defaultRadix, false, 0, maxDigitCount);
	}
	
	@Override
	protected String getDefaultSegmentWildcardString() {
		return isExtendedDigits() ? null : Address.SEGMENT_WILDCARD_STR;
	}
	
	@Override
	protected String getDefaultRangeSeparatorString() {
		return isExtendedDigits()  ? EXTENDED_DIGITS_RANGE_SEPARATOR_STR : Address.RANGE_SEPARATOR_STR;
	}

	@Override
	protected int getLowerStringLength(int radix) {
		return getDigitCount(value, defaultRadix);
	}

	@Override
	protected int getUpperStringLength(int radix) {
		return getDigitCount(upperValue, defaultRadix);
	}

	@Override
	protected void getLowerString(int radix, boolean uppercase, StringBuilder appendable) {
		appendable.append(toDefaultString(value, radix, uppercase, 0));
	}

	@Override
	protected void getLowerString(int radix, int choppedDigits, boolean uppercase, StringBuilder appendable) {
		appendable.append(toDefaultString(value, radix, uppercase, choppedDigits));
	}

	@Override
	protected void getUpperString(int radix, boolean uppercase, StringBuilder appendable) {
		appendable.append(toDefaultString(upperValue, radix, uppercase, 0));
	}

	@Override
	protected void getUpperStringMasked(int radix, boolean uppercase, StringBuilder appendable) {
		appendable.append(toDefaultString(upperValueMasked, radix, uppercase, 0));
	}

	@Override
	protected void getSplitLowerString(int radix, int choppedDigits, boolean uppercase,
			char splitDigitSeparator, boolean reverseSplitDigits, String stringPrefix, StringBuilder appendable) {
		StringBuilder builder = new StringBuilder();
		getLowerString(radix, choppedDigits, uppercase, builder);
		int prefLen = stringPrefix.length();
		for(int i = 0; i < builder.length(); i++) {
			if(i > 0) {
				appendable.append(splitDigitSeparator);
			}
			if(prefLen > 0) {
				appendable.append(stringPrefix);
			}
			appendable.append(builder.charAt(reverseSplitDigits ? (builder.length() - i - 1) : i));
		}
	}

	@Override
	protected void getSplitRangeString(String rangeSeparator, String wildcard, int radix, boolean uppercase,
			char splitDigitSeparator, boolean reverseSplitDigits, String stringPrefix, StringBuilder appendable) {
		StringBuilder lowerBuilder = new StringBuilder();
		StringBuilder upperBuilder = new StringBuilder();
		getLowerString(radix, uppercase, lowerBuilder);
		getUpperString(radix, uppercase, upperBuilder);
		int diff = upperBuilder.length() - lowerBuilder.length();
		if(diff > 0) {
			StringBuilder newLowerBuilder = new StringBuilder();
			while(diff-- > 0) {
				newLowerBuilder.append('0');
			}
			newLowerBuilder.append(lowerBuilder);
			lowerBuilder = newLowerBuilder;
		}
		boolean previousWasFull = true;
		boolean nextMustBeFull = false;
		char dig[] = getDigits(radix, uppercase);
		char zeroDigit = dig[0];
		char highestDigit = dig[radix - 1];
		int len = lowerBuilder.length();
		int prefLen = stringPrefix.length();
		for(int i = 0; i < len; i++) {
			int index = reverseSplitDigits ? (len - i - 1) : i;
			char lower = lowerBuilder.charAt(index);
			char upper = upperBuilder.charAt(index);
			if(i > 0) {
				appendable.append(splitDigitSeparator);
			}
			if(lower == upper) {
				if(nextMustBeFull) {
					throw new IncompatibleAddressException(lower, upper, "ipaddress.error.splitMismatch");
				}
				if(prefLen > 0) {
					appendable.append(stringPrefix);
				}
				appendable.append(lower);
			} else {
				boolean isFullRange = (lower == zeroDigit) && (upper == highestDigit);
				if(isFullRange) {
					appendable.append(wildcard);
				} else {
					if(nextMustBeFull) {
						throw new IncompatibleAddressException(lower, upper, "ipaddress.error.splitMismatch");
					}
					if(prefLen > 0) {
						appendable.append(stringPrefix);
					}
					appendable.append(lower);
					appendable.append(rangeSeparator);
					appendable.append(upper);
				}
				if(reverseSplitDigits) {
					if(!previousWasFull) {
						throw new IncompatibleAddressException(lower, upper, "ipaddress.error.splitMismatch");
					}
					previousWasFull = isFullRange;
				} else {
					nextMustBeFull = true;
				}
				
			}
		}
	}

	@Override
	protected int getSplitRangeStringLength(String rangeSeparator, String wildcard, int leadingZeroCount,
			int radix, boolean uppercase, char splitDigitSeparator, boolean reverseSplitDigits, String stringPrefix) {
		int digitsLength = -1;
		int stringPrefixLength = stringPrefix.length();
		StringBuilder lowerBuilder = new StringBuilder();
		StringBuilder upperBuilder = new StringBuilder();
		getLowerString(radix, uppercase, lowerBuilder);
		getUpperString(radix, uppercase, upperBuilder);
		char dig[] = getDigits(radix, uppercase);
		char zeroDigit = dig[0];
		char highestDigit = dig[radix - 1];
		int remainingAfterLoop = leadingZeroCount;
		for(int i = 1; i <= upperBuilder.length(); i++) {
			char lower = (i <= lowerBuilder.length()) ? lowerBuilder.charAt(lowerBuilder.length() - i) : 0;
			int upperIndex = upperBuilder.length() - i;
			char upper = upperBuilder.charAt(upperIndex);
			boolean isFullRange = (lower == zeroDigit) && (upper == highestDigit);
			if(isFullRange) {
				digitsLength += wildcard.length() + 1;
			} else if (lower != upper ){
				digitsLength += (stringPrefixLength << 1) + 4 ; //1 for each digit, 1 for range separator, 1 for split digit separator
			} else {
				//this and any remaining must be singles
				remainingAfterLoop += upperIndex + 1;
				break;
			}
		}
		if(remainingAfterLoop > 0) {
			digitsLength += remainingAfterLoop * (stringPrefixLength + 2);// one for each splitDigitSeparator, 1 for each digit 
		}
		return digitsLength;
	}

	@Override
	protected int getRangeDigitCount(int radix) {
		if(!isMultiple()) {
			return 0;
		}
		BigInteger val = value, upperVal = upperValue;
		int count = 1;
		BigInteger bigRadix = BigInteger.valueOf(radix);
		BigInteger bigUpper = BigInteger.valueOf(radix - 1);
		while(true) {
			BigInteger highLow[] = val.divideAndRemainder(bigRadix);
			BigInteger quotient = highLow[0];
			BigInteger remainder = highLow[1];
			if(remainder.equals(BigInteger.ZERO)) {
				highLow = upperVal.divideAndRemainder(bigRadix);
				BigInteger upperQuotient = highLow[0];
				remainder = highLow[1];
				if(remainder.equals(bigUpper)) {
					val = quotient;
					upperVal = upperQuotient;
					if(val.equals(upperVal)) {
						return count;
					} else {
						count++;
						continue;
					}
				}
			}
			return 0;
		}
	}
	
	@Override
	public int getPrefixAdjustedRangeString(int segmentIndex, AddressSegmentParams params, StringBuilder appendable) {
		return super.getPrefixAdjustedRangeString(segmentIndex, params, appendable);
	}

	public boolean isPrefixBlock() {
		return isPrefixBlock;
	}
	
	/**
	 * Returns whether the division range matches the block of values for its prefix length
	 */
	@Override
	public boolean isSinglePrefixBlock() {
		return isSinglePrefixBlock;
	}

	@Override
	public Integer getDivisionPrefixLength() {
		return networkPrefixLength;
	}
	
	public boolean isPrefixed() {
		return networkPrefixLength != null;
	}

	@Override
	public boolean isPrefixBlock(Integer divisionPrefixLen) {
		if(divisionPrefixLen == null) {
			return true;
		}
		if(divisionPrefixLen == 0) {
			return isFullRange();
		}
		int prefixLen = divisionPrefixLen;
		BigInteger lower = value, upper = upperValue;
		int hostBits = bitCount - prefixLen;
		long fullMask = ~0L;
		do {
			long low = lower.longValue(); //returns lower % 2^64
			long up = upper.longValue();
			if(hostBits <= 64) {
				long networkMask, hostMask;
				if(hostBits == 64) {
					networkMask = 0;
					hostMask = fullMask;
				} else {
					networkMask = fullMask << hostBits;//prefixLen must be 6 digits at most for this shift to work per the java spec (so it must be less than 2^6 = 64)
					hostMask = ~networkMask; 
				}
				long maskedLow = low & networkMask;
				long maskedUp = up | hostMask;
				return low == maskedLow
						&& up == maskedUp;
			} else {
				if(low != 0 || up != fullMask) {
					return false;
				}
			}
			lower = lower.shiftRight(64);
			upper = upper.shiftRight(64);
			hostBits -= 64;
		} while(!upper.equals(BigInteger.ZERO));
		return false;
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy