CharSet.java

/* ========================================================================
 * PlantUML : a free UML diagram generator
 * ========================================================================
 *
 * (C) Copyright 2009-2024, Arnaud Roques
 *
 * Project Info:  https://plantuml.com
 * 
 * If you like this project or if you find it useful, you can support us at:
 * 
 * https://plantuml.com/patreon (only 1$ per month!)
 * https://plantuml.com/paypal
 * 
 * This file is part of PlantUML.
 *
 * PlantUML is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * PlantUML distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
 * License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
 * USA.
 *
 *
 * Original Author:  Arnaud Roques
 *
 */
package com.plantuml.ubrex;

public class CharSet {

	private final CaseMode mode = CaseMode.CASE_INSENSITIVE;

	private long mask1 = 0L;
	private long mask2 = 0L;

	public void addChar(char ch) {
		if (mode == CaseMode.CASE_INSENSITIVE)
			ch = CaseMode.ensureLowercase(ch);
		final int offset = getOffset(ch);
		if (ch < 32 || ch > 128)
			throw new IllegalArgumentException("Bad char : " + ch);

		if (offset < 64)
			mask1 |= (1L << offset);
		else
			mask2 |= (1L << (offset - 64));

	}

	public void addRange(char start, char end) {
		if (start > end)
			throw new IllegalArgumentException("Invalid range: '" + start + "' is greater than '" + end + "'.");

		if (start < 32 || end > 128)
			throw new IllegalArgumentException("Characters must be in the range 32 to 128.");

		if (mode == CaseMode.CASE_INSENSITIVE) {
			start = CaseMode.ensureLowercase(start);
			end = CaseMode.ensureLowercase(end);
		}

		final int startOffset = start - 32;
		final int endOffset = end - 32;

		// Case 1: Entire range is in mask1 (offsets 0 to 63)
		if (endOffset < 64) {
			int length = endOffset - startOffset + 1;
			long rangeMask = (length == 64 ? -1L : ((1L << length) - 1)) << startOffset;
			mask1 |= rangeMask;
		}
		// Case 2: Entire range is in mask2 (offsets 64 and above)
		else if (startOffset >= 64) {
			int adjustedStart = startOffset - 64;
			int adjustedEnd = endOffset - 64;
			int length = adjustedEnd - adjustedStart + 1;
			long rangeMask = ((1L << length) - 1) << adjustedStart;
			mask2 |= rangeMask;
		}
		// Case 3: Range spans both mask1 and mask2
		else {
			// For mask1: add bits from startOffset up to bit 63
			int length1 = 64 - startOffset;
			long maskPart1 = (length1 == 64 ? -1L : ((1L << length1) - 1)) << startOffset;
			mask1 |= maskPart1;

			// For mask2: add bits from 0 up to (endOffset - 64)
			int adjustedEnd = endOffset - 64;
			int length2 = adjustedEnd + 1;
			long maskPart2 = ((1L << length2) - 1);
			mask2 |= maskPart2;
		}
	}

	public boolean contains(char ch) {
		if (mode == CaseMode.CASE_INSENSITIVE)
			ch = CaseMode.ensureLowercase(ch);

		final int offset = getOffset(ch);
		if (offset > 128)
			return false;
		if (offset < 64)
			return (mask1 & (1L << offset)) != 0;
		else
			return (mask2 & (1L << (offset - 64))) != 0;
	}

	private int getOffset(char ch) {
		return ch - 32;
	}

}