15317bbafSopenharmony_ci/*
25317bbafSopenharmony_ci * Fast QR Code generator library
35317bbafSopenharmony_ci *
45317bbafSopenharmony_ci * Copyright (c) Project Nayuki. (MIT License)
55317bbafSopenharmony_ci * https://www.nayuki.io/page/fast-qr-code-generator-library
65317bbafSopenharmony_ci *
75317bbafSopenharmony_ci * Permission is hereby granted, free of charge, to any person obtaining a copy of
85317bbafSopenharmony_ci * this software and associated documentation files (the "Software"), to deal in
95317bbafSopenharmony_ci * the Software without restriction, including without limitation the rights to
105317bbafSopenharmony_ci * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
115317bbafSopenharmony_ci * the Software, and to permit persons to whom the Software is furnished to do so,
125317bbafSopenharmony_ci * subject to the following conditions:
135317bbafSopenharmony_ci * - The above copyright notice and this permission notice shall be included in
145317bbafSopenharmony_ci *   all copies or substantial portions of the Software.
155317bbafSopenharmony_ci * - The Software is provided "as is", without warranty of any kind, express or
165317bbafSopenharmony_ci *   implied, including but not limited to the warranties of merchantability,
175317bbafSopenharmony_ci *   fitness for a particular purpose and noninfringement. In no event shall the
185317bbafSopenharmony_ci *   authors or copyright holders be liable for any claim, damages or other
195317bbafSopenharmony_ci *   liability, whether in an action of contract, tort or otherwise, arising from,
205317bbafSopenharmony_ci *   out of or in connection with the Software or the use or other dealings in the
215317bbafSopenharmony_ci *   Software.
225317bbafSopenharmony_ci */
235317bbafSopenharmony_ci
245317bbafSopenharmony_cipackage io.nayuki.fastqrcodegen;
255317bbafSopenharmony_ci
265317bbafSopenharmony_ciimport java.nio.charset.StandardCharsets;
275317bbafSopenharmony_ciimport java.util.ArrayList;
285317bbafSopenharmony_ciimport java.util.Arrays;
295317bbafSopenharmony_ciimport java.util.List;
305317bbafSopenharmony_ciimport java.util.Objects;
315317bbafSopenharmony_ci
325317bbafSopenharmony_ci
335317bbafSopenharmony_ci/**
345317bbafSopenharmony_ci * A segment of character/binary/control data in a QR Code symbol.
355317bbafSopenharmony_ci * Instances of this class are immutable.
365317bbafSopenharmony_ci * <p>The mid-level way to create a segment is to take the payload data and call a
375317bbafSopenharmony_ci * static factory function such as {@link QrSegment#makeNumeric(String)}. The low-level
385317bbafSopenharmony_ci * way to create a segment is to custom-make the bit buffer and call the {@link
395317bbafSopenharmony_ci * QrSegment#QrSegment(Mode,int,int[],int) constructor} with appropriate values.</p>
405317bbafSopenharmony_ci * <p>This segment class imposes no length restrictions, but QR Codes have restrictions.
415317bbafSopenharmony_ci * Even in the most favorable conditions, a QR Code can only hold 7089 characters of data.
425317bbafSopenharmony_ci * Any segment longer than this is meaningless for the purpose of generating QR Codes.
435317bbafSopenharmony_ci * This class can represent kanji mode segments, but provides no help in encoding them
445317bbafSopenharmony_ci * - see {@link QrSegmentAdvanced} for full kanji support.</p>
455317bbafSopenharmony_ci */
465317bbafSopenharmony_cipublic final class QrSegment {
475317bbafSopenharmony_ci
485317bbafSopenharmony_ci	/*---- Static factory functions (mid level) ----*/
495317bbafSopenharmony_ci
505317bbafSopenharmony_ci	/**
515317bbafSopenharmony_ci	 * Returns a segment representing the specified binary data
525317bbafSopenharmony_ci	 * encoded in byte mode. All input byte arrays are acceptable.
535317bbafSopenharmony_ci	 * <p>Any text string can be converted to UTF-8 bytes ({@code
545317bbafSopenharmony_ci	 * s.getBytes(StandardCharsets.UTF_8)}) and encoded as a byte mode segment.</p>
555317bbafSopenharmony_ci	 * @param data the binary data (not {@code null})
565317bbafSopenharmony_ci	 * @return a segment (not {@code null}) containing the data
575317bbafSopenharmony_ci	 * @throws NullPointerException if the array is {@code null}
585317bbafSopenharmony_ci	 */
595317bbafSopenharmony_ci	public static QrSegment makeBytes(byte[] data) {
605317bbafSopenharmony_ci		Objects.requireNonNull(data);
615317bbafSopenharmony_ci		if (data.length * 8L > Integer.MAX_VALUE)
625317bbafSopenharmony_ci			throw new IllegalArgumentException("Data too long");
635317bbafSopenharmony_ci		int[] bits = new int[(data.length + 3) / 4];
645317bbafSopenharmony_ci		for (int i = 0; i < data.length; i++)
655317bbafSopenharmony_ci			bits[i >>> 2] |= (data[i] & 0xFF) << (~i << 3);
665317bbafSopenharmony_ci		return new QrSegment(Mode.BYTE, data.length, bits, data.length * 8);
675317bbafSopenharmony_ci	}
685317bbafSopenharmony_ci
695317bbafSopenharmony_ci
705317bbafSopenharmony_ci	/**
715317bbafSopenharmony_ci	 * Returns a segment representing the specified string of decimal digits encoded in numeric mode.
725317bbafSopenharmony_ci	 * @param digits the text (not {@code null}), with only digits from 0 to 9 allowed
735317bbafSopenharmony_ci	 * @return a segment (not {@code null}) containing the text
745317bbafSopenharmony_ci	 * @throws NullPointerException if the string is {@code null}
755317bbafSopenharmony_ci	 * @throws IllegalArgumentException if the string contains non-digit characters
765317bbafSopenharmony_ci	 */
775317bbafSopenharmony_ci	public static QrSegment makeNumeric(String digits) {
785317bbafSopenharmony_ci		Objects.requireNonNull(digits);
795317bbafSopenharmony_ci		BitBuffer bb = new BitBuffer();
805317bbafSopenharmony_ci		int accumData = 0;
815317bbafSopenharmony_ci		int accumCount = 0;
825317bbafSopenharmony_ci		for (int i = 0; i < digits.length(); i++) {
835317bbafSopenharmony_ci			char c = digits.charAt(i);
845317bbafSopenharmony_ci			if (c < '0' || c > '9')
855317bbafSopenharmony_ci				throw new IllegalArgumentException("String contains non-numeric characters");
865317bbafSopenharmony_ci			accumData = accumData * 10 + (c - '0');
875317bbafSopenharmony_ci			accumCount++;
885317bbafSopenharmony_ci			if (accumCount == 3) {
895317bbafSopenharmony_ci				bb.appendBits(accumData, 10);
905317bbafSopenharmony_ci				accumData = 0;
915317bbafSopenharmony_ci				accumCount = 0;
925317bbafSopenharmony_ci			}
935317bbafSopenharmony_ci		}
945317bbafSopenharmony_ci		if (accumCount > 0)  // 1 or 2 digits remaining
955317bbafSopenharmony_ci			bb.appendBits(accumData, accumCount * 3 + 1);
965317bbafSopenharmony_ci		return new QrSegment(Mode.NUMERIC, digits.length(), bb.data, bb.bitLength);
975317bbafSopenharmony_ci	}
985317bbafSopenharmony_ci
995317bbafSopenharmony_ci
1005317bbafSopenharmony_ci	/**
1015317bbafSopenharmony_ci	 * Returns a segment representing the specified text string encoded in alphanumeric mode.
1025317bbafSopenharmony_ci	 * The characters allowed are: 0 to 9, A to Z (uppercase only), space,
1035317bbafSopenharmony_ci	 * dollar, percent, asterisk, plus, hyphen, period, slash, colon.
1045317bbafSopenharmony_ci	 * @param text the text (not {@code null}), with only certain characters allowed
1055317bbafSopenharmony_ci	 * @return a segment (not {@code null}) containing the text
1065317bbafSopenharmony_ci	 * @throws NullPointerException if the string is {@code null}
1075317bbafSopenharmony_ci	 * @throws IllegalArgumentException if the string contains non-encodable characters
1085317bbafSopenharmony_ci	 */
1095317bbafSopenharmony_ci	public static QrSegment makeAlphanumeric(String text) {
1105317bbafSopenharmony_ci		Objects.requireNonNull(text);
1115317bbafSopenharmony_ci		BitBuffer bb = new BitBuffer();
1125317bbafSopenharmony_ci		int accumData = 0;
1135317bbafSopenharmony_ci		int accumCount = 0;
1145317bbafSopenharmony_ci		for (int i = 0; i < text.length(); i++) {
1155317bbafSopenharmony_ci			char c = text.charAt(i);
1165317bbafSopenharmony_ci			if (c >= ALPHANUMERIC_MAP.length || ALPHANUMERIC_MAP[c] == -1)
1175317bbafSopenharmony_ci				throw new IllegalArgumentException("String contains unencodable characters in alphanumeric mode");
1185317bbafSopenharmony_ci			accumData = accumData * 45 + ALPHANUMERIC_MAP[c];
1195317bbafSopenharmony_ci			accumCount++;
1205317bbafSopenharmony_ci			if (accumCount == 2) {
1215317bbafSopenharmony_ci				bb.appendBits(accumData, 11);
1225317bbafSopenharmony_ci				accumData = 0;
1235317bbafSopenharmony_ci				accumCount = 0;
1245317bbafSopenharmony_ci			}
1255317bbafSopenharmony_ci		}
1265317bbafSopenharmony_ci		if (accumCount > 0)  // 1 character remaining
1275317bbafSopenharmony_ci			bb.appendBits(accumData, 6);
1285317bbafSopenharmony_ci		return new QrSegment(Mode.ALPHANUMERIC, text.length(), bb.data, bb.bitLength);
1295317bbafSopenharmony_ci	}
1305317bbafSopenharmony_ci
1315317bbafSopenharmony_ci
1325317bbafSopenharmony_ci	/**
1335317bbafSopenharmony_ci	 * Returns a list of zero or more segments to represent the specified Unicode text string.
1345317bbafSopenharmony_ci	 * The result may use various segment modes and switch modes to optimize the length of the bit stream.
1355317bbafSopenharmony_ci	 * @param text the text to be encoded, which can be any Unicode string
1365317bbafSopenharmony_ci	 * @return a new mutable list (not {@code null}) of segments (not {@code null}) containing the text
1375317bbafSopenharmony_ci	 * @throws NullPointerException if the text is {@code null}
1385317bbafSopenharmony_ci	 */
1395317bbafSopenharmony_ci	public static List<QrSegment> makeSegments(String text) {
1405317bbafSopenharmony_ci		Objects.requireNonNull(text);
1415317bbafSopenharmony_ci
1425317bbafSopenharmony_ci		// Select the most efficient segment encoding automatically
1435317bbafSopenharmony_ci		List<QrSegment> result = new ArrayList<>();
1445317bbafSopenharmony_ci		if (text.equals(""));  // Leave result empty
1455317bbafSopenharmony_ci		else if (isNumeric(text))
1465317bbafSopenharmony_ci			result.add(makeNumeric(text));
1475317bbafSopenharmony_ci		else if (isAlphanumeric(text))
1485317bbafSopenharmony_ci			result.add(makeAlphanumeric(text));
1495317bbafSopenharmony_ci		else
1505317bbafSopenharmony_ci			result.add(makeBytes(text.getBytes(StandardCharsets.UTF_8)));
1515317bbafSopenharmony_ci		return result;
1525317bbafSopenharmony_ci	}
1535317bbafSopenharmony_ci
1545317bbafSopenharmony_ci
1555317bbafSopenharmony_ci	/**
1565317bbafSopenharmony_ci	 * Returns a segment representing an Extended Channel Interpretation
1575317bbafSopenharmony_ci	 * (ECI) designator with the specified assignment value.
1585317bbafSopenharmony_ci	 * @param assignVal the ECI assignment number (see the AIM ECI specification)
1595317bbafSopenharmony_ci	 * @return a segment (not {@code null}) containing the data
1605317bbafSopenharmony_ci	 * @throws IllegalArgumentException if the value is outside the range [0, 10<sup>6</sup>)
1615317bbafSopenharmony_ci	 */
1625317bbafSopenharmony_ci	public static QrSegment makeEci(int assignVal) {
1635317bbafSopenharmony_ci		BitBuffer bb = new BitBuffer();
1645317bbafSopenharmony_ci		if (assignVal < 0)
1655317bbafSopenharmony_ci			throw new IllegalArgumentException("ECI assignment value out of range");
1665317bbafSopenharmony_ci		else if (assignVal < (1 << 7))
1675317bbafSopenharmony_ci			bb.appendBits(assignVal, 8);
1685317bbafSopenharmony_ci		else if (assignVal < (1 << 14)) {
1695317bbafSopenharmony_ci			bb.appendBits(2, 2);
1705317bbafSopenharmony_ci			bb.appendBits(assignVal, 14);
1715317bbafSopenharmony_ci		} else if (assignVal < 1_000_000) {
1725317bbafSopenharmony_ci			bb.appendBits(6, 3);
1735317bbafSopenharmony_ci			bb.appendBits(assignVal, 21);
1745317bbafSopenharmony_ci		} else
1755317bbafSopenharmony_ci			throw new IllegalArgumentException("ECI assignment value out of range");
1765317bbafSopenharmony_ci		return new QrSegment(Mode.ECI, 0, bb.data, bb.bitLength);
1775317bbafSopenharmony_ci	}
1785317bbafSopenharmony_ci
1795317bbafSopenharmony_ci
1805317bbafSopenharmony_ci	/**
1815317bbafSopenharmony_ci	 * Tests whether the specified string can be encoded as a segment in numeric mode.
1825317bbafSopenharmony_ci	 * A string is encodable iff each character is in the range 0 to 9.
1835317bbafSopenharmony_ci	 * @param text the string to test for encodability (not {@code null})
1845317bbafSopenharmony_ci	 * @return {@code true} iff each character is in the range 0 to 9.
1855317bbafSopenharmony_ci	 * @throws NullPointerException if the string is {@code null}
1865317bbafSopenharmony_ci	 * @see #makeNumeric(String)
1875317bbafSopenharmony_ci	 */
1885317bbafSopenharmony_ci	public static boolean isNumeric(String text) {
1895317bbafSopenharmony_ci		for (int i = 0; i < text.length(); i++) {
1905317bbafSopenharmony_ci			char c = text.charAt(i);
1915317bbafSopenharmony_ci			if (c < '0' || c > '9')
1925317bbafSopenharmony_ci				return false;
1935317bbafSopenharmony_ci		}
1945317bbafSopenharmony_ci		return true;
1955317bbafSopenharmony_ci	}
1965317bbafSopenharmony_ci
1975317bbafSopenharmony_ci
1985317bbafSopenharmony_ci	/**
1995317bbafSopenharmony_ci	 * Tests whether the specified string can be encoded as a segment in alphanumeric mode.
2005317bbafSopenharmony_ci	 * A string is encodable iff each character is in the following set: 0 to 9, A to Z
2015317bbafSopenharmony_ci	 * (uppercase only), space, dollar, percent, asterisk, plus, hyphen, period, slash, colon.
2025317bbafSopenharmony_ci	 * @param text the string to test for encodability (not {@code null})
2035317bbafSopenharmony_ci	 * @return {@code true} iff each character is in the alphanumeric mode character set
2045317bbafSopenharmony_ci	 * @throws NullPointerException if the string is {@code null}
2055317bbafSopenharmony_ci	 * @see #makeAlphanumeric(String)
2065317bbafSopenharmony_ci	 */
2075317bbafSopenharmony_ci	public static boolean isAlphanumeric(String text) {
2085317bbafSopenharmony_ci		for (int i = 0; i < text.length(); i++) {
2095317bbafSopenharmony_ci			char c = text.charAt(i);
2105317bbafSopenharmony_ci			if (c >= ALPHANUMERIC_MAP.length || ALPHANUMERIC_MAP[c] == -1)
2115317bbafSopenharmony_ci				return false;
2125317bbafSopenharmony_ci		}
2135317bbafSopenharmony_ci		return true;
2145317bbafSopenharmony_ci	}
2155317bbafSopenharmony_ci
2165317bbafSopenharmony_ci
2175317bbafSopenharmony_ci
2185317bbafSopenharmony_ci	/*---- Instance fields ----*/
2195317bbafSopenharmony_ci
2205317bbafSopenharmony_ci	/** The mode indicator of this segment. Not {@code null}. */
2215317bbafSopenharmony_ci	public final Mode mode;
2225317bbafSopenharmony_ci
2235317bbafSopenharmony_ci	/** The length of this segment's unencoded data. Measured in characters for
2245317bbafSopenharmony_ci	 * numeric/alphanumeric/kanji mode, bytes for byte mode, and 0 for ECI mode.
2255317bbafSopenharmony_ci	 * Always zero or positive. Not the same as the data's bit length. */
2265317bbafSopenharmony_ci	public final int numChars;
2275317bbafSopenharmony_ci
2285317bbafSopenharmony_ci	// The data bits of this segment. Not null.
2295317bbafSopenharmony_ci	final int[] data;
2305317bbafSopenharmony_ci
2315317bbafSopenharmony_ci	// Requires 0 <= bitLength <= data.length * 32.
2325317bbafSopenharmony_ci	final int bitLength;
2335317bbafSopenharmony_ci
2345317bbafSopenharmony_ci
2355317bbafSopenharmony_ci	/*---- Constructor (low level) ----*/
2365317bbafSopenharmony_ci
2375317bbafSopenharmony_ci	/**
2385317bbafSopenharmony_ci	 * Constructs a QR Code segment with the specified attributes and data.
2395317bbafSopenharmony_ci	 * The character count (numCh) must agree with the mode and the bit buffer length,
2405317bbafSopenharmony_ci	 * but the constraint isn't checked. The specified bit buffer is cloned and stored.
2415317bbafSopenharmony_ci	 * @param md the mode (not {@code null})
2425317bbafSopenharmony_ci	 * @param numCh the data length in characters or bytes, which is non-negative
2435317bbafSopenharmony_ci	 * @param data the data bits (not {@code null})
2445317bbafSopenharmony_ci	 * @param bitLen the number of valid prefix bits in the data array
2455317bbafSopenharmony_ci	 * @throws NullPointerException if the mode or data is {@code null}
2465317bbafSopenharmony_ci	 * @throws IllegalArgumentException if the character count is negative
2475317bbafSopenharmony_ci	 */
2485317bbafSopenharmony_ci	public QrSegment(Mode md, int numCh, int[] data, int bitLen) {
2495317bbafSopenharmony_ci		mode = Objects.requireNonNull(md);
2505317bbafSopenharmony_ci		this.data = Objects.requireNonNull(data);
2515317bbafSopenharmony_ci		if (numCh < 0 || bitLen < 0 || bitLen > data.length * 32L)
2525317bbafSopenharmony_ci			throw new IllegalArgumentException("Invalid value");
2535317bbafSopenharmony_ci		numChars = numCh;
2545317bbafSopenharmony_ci		bitLength = bitLen;
2555317bbafSopenharmony_ci	}
2565317bbafSopenharmony_ci
2575317bbafSopenharmony_ci
2585317bbafSopenharmony_ci	// Calculates the number of bits needed to encode the given segments at the given version.
2595317bbafSopenharmony_ci	// Returns a non-negative number if successful. Otherwise returns -1 if a segment has too
2605317bbafSopenharmony_ci	// many characters to fit its length field, or the total bits exceeds Integer.MAX_VALUE.
2615317bbafSopenharmony_ci	static int getTotalBits(List<QrSegment> segs, int version) {
2625317bbafSopenharmony_ci		Objects.requireNonNull(segs);
2635317bbafSopenharmony_ci		long result = 0;
2645317bbafSopenharmony_ci		for (QrSegment seg : segs) {
2655317bbafSopenharmony_ci			Objects.requireNonNull(seg);
2665317bbafSopenharmony_ci			int ccbits = seg.mode.numCharCountBits(version);
2675317bbafSopenharmony_ci			if (seg.numChars >= (1 << ccbits))
2685317bbafSopenharmony_ci				return -1;  // The segment's length doesn't fit the field's bit width
2695317bbafSopenharmony_ci			result += 4L + ccbits + seg.bitLength;
2705317bbafSopenharmony_ci			if (result > Integer.MAX_VALUE)
2715317bbafSopenharmony_ci				return -1;  // The sum will overflow an int type
2725317bbafSopenharmony_ci		}
2735317bbafSopenharmony_ci		return (int)result;
2745317bbafSopenharmony_ci	}
2755317bbafSopenharmony_ci
2765317bbafSopenharmony_ci
2775317bbafSopenharmony_ci	/*---- Constants ----*/
2785317bbafSopenharmony_ci
2795317bbafSopenharmony_ci	static final int[] ALPHANUMERIC_MAP;
2805317bbafSopenharmony_ci
2815317bbafSopenharmony_ci	static {
2825317bbafSopenharmony_ci		final String ALPHANUMERIC_CHARSET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:";
2835317bbafSopenharmony_ci		int maxCh = -1;
2845317bbafSopenharmony_ci		for (int i = 0; i < ALPHANUMERIC_CHARSET.length(); i++)
2855317bbafSopenharmony_ci			maxCh = Math.max(ALPHANUMERIC_CHARSET.charAt(i), maxCh);
2865317bbafSopenharmony_ci		ALPHANUMERIC_MAP = new int[maxCh + 1];
2875317bbafSopenharmony_ci		Arrays.fill(ALPHANUMERIC_MAP, -1);
2885317bbafSopenharmony_ci		for (int i = 0; i < ALPHANUMERIC_CHARSET.length(); i++)
2895317bbafSopenharmony_ci			ALPHANUMERIC_MAP[ALPHANUMERIC_CHARSET.charAt(i)] = i;
2905317bbafSopenharmony_ci	}
2915317bbafSopenharmony_ci
2925317bbafSopenharmony_ci
2935317bbafSopenharmony_ci
2945317bbafSopenharmony_ci	/*---- Public helper enumeration ----*/
2955317bbafSopenharmony_ci
2965317bbafSopenharmony_ci	/**
2975317bbafSopenharmony_ci	 * Describes how a segment's data bits are interpreted.
2985317bbafSopenharmony_ci	 */
2995317bbafSopenharmony_ci	public enum Mode {
3005317bbafSopenharmony_ci
3015317bbafSopenharmony_ci		/*-- Constants --*/
3025317bbafSopenharmony_ci
3035317bbafSopenharmony_ci		NUMERIC     (0x1, 10, 12, 14),
3045317bbafSopenharmony_ci		ALPHANUMERIC(0x2,  9, 11, 13),
3055317bbafSopenharmony_ci		BYTE        (0x4,  8, 16, 16),
3065317bbafSopenharmony_ci		KANJI       (0x8,  8, 10, 12),
3075317bbafSopenharmony_ci		ECI         (0x7,  0,  0,  0);
3085317bbafSopenharmony_ci
3095317bbafSopenharmony_ci
3105317bbafSopenharmony_ci		/*-- Fields --*/
3115317bbafSopenharmony_ci
3125317bbafSopenharmony_ci		// The mode indicator bits, which is a uint4 value (range 0 to 15).
3135317bbafSopenharmony_ci		final int modeBits;
3145317bbafSopenharmony_ci
3155317bbafSopenharmony_ci		// Number of character count bits for three different version ranges.
3165317bbafSopenharmony_ci		private final int[] numBitsCharCount;
3175317bbafSopenharmony_ci
3185317bbafSopenharmony_ci
3195317bbafSopenharmony_ci		/*-- Constructor --*/
3205317bbafSopenharmony_ci
3215317bbafSopenharmony_ci		private Mode(int mode, int... ccbits) {
3225317bbafSopenharmony_ci			modeBits = mode;
3235317bbafSopenharmony_ci			numBitsCharCount = ccbits;
3245317bbafSopenharmony_ci		}
3255317bbafSopenharmony_ci
3265317bbafSopenharmony_ci
3275317bbafSopenharmony_ci		/*-- Method --*/
3285317bbafSopenharmony_ci
3295317bbafSopenharmony_ci		// Returns the bit width of the character count field for a segment in this mode
3305317bbafSopenharmony_ci		// in a QR Code at the given version number. The result is in the range [0, 16].
3315317bbafSopenharmony_ci		int numCharCountBits(int ver) {
3325317bbafSopenharmony_ci			assert QrCode.MIN_VERSION <= ver && ver <= QrCode.MAX_VERSION;
3335317bbafSopenharmony_ci			return numBitsCharCount[(ver + 7) / 17];
3345317bbafSopenharmony_ci		}
3355317bbafSopenharmony_ci
3365317bbafSopenharmony_ci	}
3375317bbafSopenharmony_ci
3385317bbafSopenharmony_ci}
339