/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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 * * 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 org.wildfly.security.util; import org.wildfly.common.Assert; import java.util.NoSuchElementException; import java.util.function.IntPredicate; import static org.wildfly.security.util.Alphabet.Base32Alphabet; import static org.wildfly.security.util.Alphabet.Base64Alphabet; /** * A code point by code point iterator. * * @author <a href="mailto:david.lloyd@redhat.com">David M. Lloyd</a> */ public abstract class CodePointIterator extends NumericIterator { /** * Determine if there are more code points after the current code point. * * @return {@code true} if there are more code points, {@code false} otherwise */ public abstract boolean hasNext(); /** * Determine if there are more code points before the current code point. * * @return {@code true} if there are more code points, {@code false} otherwise */ public abstract boolean hasPrev(); /** * Get the next code point. * * @return the next code point * @throws NoSuchElementException if {@link #hasNext()} returns {@code false} */ public abstract int next() throws NoSuchElementException; /** * Peek at the next code point without advancing. * * @return the next code point * @throws NoSuchElementException if {@link #hasNext()} returns {@code false} */ public abstract int peekNext() throws NoSuchElementException; /** * Get the previous code point. * * @return the previous code point * @throws NoSuchElementException if {@link #hasPrev()} returns {@code false} */ public abstract int prev() throws NoSuchElementException; /** * Peek at the previous code point without moving backwards. * * @return the previous code point * @throws NoSuchElementException if {@link #hasPrev()} returns {@code false} */ public abstract int peekPrev() throws NoSuchElementException; /** * Get the current offset, by code point. * * @return the code point offset */ public abstract int offset(); /** * Determine if the remaining contents of this iterator are identical to the remaining contents of the other iterator. If the * contents are not equal, the iterators will be positioned at the location of the first difference (i.e. the code point * returned by {@link #next()} will be the differing code point. If the contents are equal, the iterators will both be * positioned at the end of their contents. * * @param other the other byte iterator * @return {@code true} if the contents are equal, {@code false} otherwise */ public final boolean contentEquals(CodePointIterator other) { for (;;) { if (hasNext()) { if (! other.hasNext()) { return false; } if (peekNext() != other.peekNext()) { return false; } next(); other.next(); } else { return ! other.hasNext(); } } } /** * Determine if the remaining contents of this iterator are identical to the given string. If the * contents are not equal, the iterator will be positioned at the location of the first difference (i.e. the code point * returned by {@link #next()} will be the differing code point. If the contents are equal, the iterator will be * positioned at the end of its contents. * * @param other the other string * @return {@code true} if the contents are equal, {@code false} otherwise */ public boolean contentEquals(String other) { return contentEquals(CodePointIterator.ofString(other)); } /** * Return a copy of this iterator which is limited to the given number of code points after the current one. Advancing * the returned iterator will also advance this one. * * @param size the number of code points * @return the limited code point iterator */ public final CodePointIterator limitedTo(final int size) { if (size <= 0 || ! hasNext()) { return EMPTY; } return new CodePointIterator() { int offset = 0; public boolean hasNext() { return offset < size && CodePointIterator.this.hasNext(); } public boolean hasPrev() { return offset > 0; } public int next() { if (! hasNext()) { throw new NoSuchElementException(); } offset ++; return CodePointIterator.this.next(); } public int peekNext() throws NoSuchElementException { if (! hasNext()) { throw new NoSuchElementException(); } return CodePointIterator.this.peekNext(); } public int prev() { if (! hasPrev()) { throw new NoSuchElementException(); } offset --; return CodePointIterator.this.prev(); } public int peekPrev() throws NoSuchElementException { if (! hasPrev()) { throw new NoSuchElementException(); } return CodePointIterator.this.peekPrev(); } public int offset() { return offset; } }; } /** * Get a sub-iterator that is delimited by the given code points. The returned iterator offset starts at 0 and cannot * be backed up before that point. The returned iterator will return {@code false} for {@code hasNext()} if the next * character in the encapsulated iterator is a delimiter or if the underlying iterator returns {@code false} for * {@code hasNext()}. * * @param delims the code point delimiters * @return the sub-iterator */ public final CodePointIterator delimitedBy(final int... delims) { if ((delims == null) || (delims.length == 0) || ! hasNext()) { return EMPTY; } for (int delim : delims) { if (! Character.isValidCodePoint(delim)) { return EMPTY; } } return new CodePointIterator() { int offset = 0; public boolean hasNext() { return CodePointIterator.this.hasNext() && ! isDelim(CodePointIterator.this.peekNext()); } public boolean hasPrev() { return offset > 0; } public int next() { if (! hasNext()) { throw new NoSuchElementException(); } offset ++; return CodePointIterator.this.next(); } public int peekNext() throws NoSuchElementException { if (! hasNext()) { throw new NoSuchElementException(); } return CodePointIterator.this.peekNext(); } public int prev() { if (! hasPrev()) { throw new NoSuchElementException(); } offset --; return CodePointIterator.this.prev(); } public int peekPrev() throws NoSuchElementException { if (! hasPrev()) { throw new NoSuchElementException(); } return CodePointIterator.this.peekPrev(); } public int offset() { return offset; } private boolean isDelim(int c) { for (int delim : delims) { if (delim == c) { return true; } } return false; } }; } /** * Drain all the remaining code points in this iterator to the given string builder. * * @param b the string builder * @return the same string builder */ public StringBuilder drainTo(StringBuilder b) { while (hasNext()) { b.appendCodePoint(next()); } return b; } /** * Skip all the remaining code points in this iterator. * (Useful in combination with {@link #delimitedBy(int...)}) * * @return the same code point iterator */ public CodePointIterator skipAll() { while (hasNext()) next(); return this; } /** * Drain all the remaining code points in this iterator to the given string builder, * inserting the given prefix and delimiter before and after every {@code n} code points, * respectively. * * @param b the string builder * @param prefix the prefix * @param delim the delimiter * @param n the number of code points between each prefix and delimiter * @return the same string builder */ public StringBuilder drainTo(StringBuilder b, final String prefix, final int delim, final int n) { int i = 0; boolean insertPrefix = (prefix != null); boolean insertDelim = Character.isValidCodePoint(delim); if (hasNext()) { if (insertPrefix) { b.append(prefix); } b.appendCodePoint(next()); i ++; while (hasNext()) { if (i == n) { if (insertDelim) { b.appendCodePoint(delim); } if (insertPrefix) { b.append(prefix); } b.appendCodePoint(next()); i = 1; } else { b.appendCodePoint(next()); i ++; } } } return b; } /** * Drain all the remaining code points in this iterator to the given string builder, * inserting the given delimiter after every {@code n} code points. * * @param b the string builder * @param delim the delimiter * @param n the number of code points between each delimiter * @return the same string builder */ public StringBuilder drainTo(StringBuilder b, final int delim, final int n) { return drainTo(b, null, delim, n); } /** * Drain all the remaining code points in this iterator to the given string builder, * inserting the given prefix before every {@code n} code points. * * @param b the string builder * @param prefix the prefix * @param n the number of code points between each prefix * @return the same string builder */ public StringBuilder drainTo(StringBuilder b, final String prefix, final int n) { return drainTo(b, prefix, -1, n); } /** * Drain all the remaining code points in this iterator to a new string. * * @return the string */ public String drainToString() { return hasNext() ? drainTo(new StringBuilder()).toString() : ""; } /** * Drain all the remaining code points in this iterator to a new string, * inserting the given prefix and delimiter before and after every {@code n} * code points, respectively. * * @param prefix the prefix * @param delim the delimiter * @param n the number of code points between each prefix and delimiter * @return the string */ public String drainToString(final String prefix, final int delim, final int n) { return hasNext() ? drainTo(new StringBuilder(), prefix, delim, n).toString() : ""; } /** * Drain all the remaining code points in this iterator to a new string, * inserting the given delimiter after every {@code n} code points. * * @param delim the delimiter * @param n the number of code points between each delimiter * @return the string */ public String drainToString(final int delim, final int n) { return hasNext() ? drainTo(new StringBuilder(), null, delim, n).toString() : ""; } /** * Drain all the remaining code points in this iterator to a new string, * inserting the given prefix before every {@code n} code points. * * @param prefix the prefix * @param n the number of code points between each prefix * @return the string */ public String drainToString(final String prefix, final int n) { return hasNext() ? drainTo(new StringBuilder(), prefix, -1, n).toString() : ""; } /** * Base64-decode the current stream. * * @param alphabet the alphabet to use * @param requirePadding {@code true} to require padding, {@code false} if padding is optional * @return an iterator over the decoded bytes */ public ByteIterator base64Decode(final Base64Alphabet alphabet, boolean requirePadding) { return super.base64Decode(alphabet, requirePadding); } /** * Base64-decode the current stream. * * @param alphabet the alphabet to use * @return an iterator over the decoded bytes */ public ByteIterator base64Decode(final Base64Alphabet alphabet) { return super.base64Decode(alphabet, true); } /** * Base64-decode the current stream. * * @return an iterator over the decoded bytes */ public ByteIterator base64Decode() { return super.base64Decode(Base64Alphabet.STANDARD, true); } /** * Base32-decode the current stream. * * @param alphabet the alphabet to use * @param requirePadding {@code true} to require padding, {@code false} if padding is optional * @return an iterator over the decoded bytes */ public ByteIterator base32Decode(final Base32Alphabet alphabet, boolean requirePadding) { return super.base32Decode(alphabet, requirePadding); } /** * Base32-decode the current stream. * * @param alphabet the alphabet to use * @return an iterator over the decoded bytes */ public ByteIterator base32Decode(final Base32Alphabet alphabet) { return super.base32Decode(alphabet, true); } /** * Base32-decode the current stream. * * @return an iterator over the decoded bytes */ public ByteIterator base32Decode() { return super.base32Decode(Base32Alphabet.STANDARD, true); } /** * Hex-decode the current stream. * * @return an iterator over the decoded bytes */ public ByteIterator hexDecode() { return super.hexDecode(); } /** * Get a byte iterator over the latin-1 encoding of this code point iterator. * * @return the byte iterator */ public ByteIterator asLatin1() { return new ByteIterator() { public boolean hasNext() { return CodePointIterator.this.hasNext(); } public boolean hasPrev() { return CodePointIterator.this.hasPrev(); } public int next() throws NoSuchElementException { final int v = CodePointIterator.this.next(); return v > 255 ? '?' : v; } public int peekNext() throws NoSuchElementException { final int v = CodePointIterator.this.peekNext(); return v > 255 ? '?' : v; } public int prev() throws NoSuchElementException { final int v = CodePointIterator.this.prev(); return v > 255 ? '?' : v; } public int peekPrev() throws NoSuchElementException { final int v = CodePointIterator.this.peekPrev(); return v > 255 ? '?' : v; } public int offset() { return CodePointIterator.this.offset(); } }; } /** * Get a byte iterator over the UTF-8 encoding of this code point iterator. * * @return the byte iterator */ public ByteIterator asUtf8() { return asUtf8(false); } /** * Get a byte iterator over the UTF-8 encoding of this code point iterator. * * @param escapeNul {@code true} to escape NUL (0) characters as two bytes, {@code false} to encode them as one byte * @return the byte iterator */ public ByteIterator asUtf8(final boolean escapeNul) { return new ByteIterator() { // state 0 = between code points // state 1 = after byte 1 of 2 // state 2 = after byte 1 of 3 // state 3 = after byte 2 of 3 // state 4 = after byte 1 of 4 // state 5 = after byte 2 of 4 // state 6 = after byte 3 of 4 private int st; private int cp = -1; private int offset; public boolean hasNext() { return st != 0 || CodePointIterator.this.hasNext(); } public boolean hasPrev() { return st != 0 || CodePointIterator.this.hasPrev(); } public int next() throws NoSuchElementException { if (! hasNext()) throw new NoSuchElementException(); offset++; switch (st) { case 0: { int cp = CodePointIterator.this.next(); if (cp == 0 && ! escapeNul || cp < 0x80) { return cp; } else if (cp < 0x800) { this.cp = cp; st = 1; return 0b110_00000 | cp >> 6; } else if (cp < 0x10000) { this.cp = cp; st = 2; return 0b1110_0000 | cp >> 12; } else if (cp < 0x110000) { this.cp = cp; st = 4; return 0b11110_000 | cp >> 18; } else { this.cp = '�'; st = 2; return 0b1110_0000 | '�' >> 12; } } case 1: case 3: case 6: { st = 0; return 0b10_000000 | cp & 0x3f; } case 2: { st = 3; return 0b10_000000 | cp >> 6 & 0x3f; } case 4: { st = 5; return 0b10_000000 | cp >> 12 & 0x3f; } case 5: { st = 6; return 0b10_000000 | cp >> 6 & 0x3f; } default: { throw Assert.impossibleSwitchCase(st); } } } public int peekNext() throws NoSuchElementException { if (! hasNext()) throw new NoSuchElementException(); switch (st) { case 0: { int cp = CodePointIterator.this.peekNext(); if (cp < 0x80) { return cp; } else if (cp < 0x800) { return 0b110_00000 | cp >> 6; } else if (cp < 0x10000) { return 0b1110_0000 | cp >> 12; } else if (cp < 0x110000) { return 0b11110_000 | cp >> 18; } else { return 0b1110_0000 | '�' >> 12; } } case 1: case 3: case 6: { return 0b10_000000 | cp & 0x3f; } case 2: case 5: { return 0b10_000000 | cp >> 6 & 0x3f; } case 4: { return 0b10_000000 | cp >> 12 & 0x3f; } default: { throw Assert.impossibleSwitchCase(st); } } } public int prev() throws NoSuchElementException { if (! hasPrev()) throw new NoSuchElementException(); offset--; switch (st) { case 0: { int cp = CodePointIterator.this.prev(); if (cp == 0 && ! escapeNul || cp < 0x80) { return cp; } else if (cp < 0x800) { this.cp = cp; st = 1; return 0b10_000000 | cp & 0x3f; } else if (cp < 0x10000) { this.cp = cp; st = 3; return 0b10_000000 | cp & 0x3f; } else if (cp < 0x110000) { this.cp = cp; st = 6; return 0b10_000000 | cp & 0x3f; } else { this.cp = '�'; st = 3; return 0b10_000000 | '�' & 0x3f; } } case 1: { st = 0; return 0b110_00000 | cp >> 6; } case 2: { st = 0; return 0b1110_0000 | cp >> 12; } case 3: { st = 2; return 0b10_000000 | cp >> 6 & 0x3f; } case 4: { st = 0; return 0b11110_000 | cp >> 18; } case 5: { st = 4; return 0b10_000000 | cp >> 12 & 0x3f; } case 6: { st = 5; return 0b10_000000 | cp >> 6 & 0x3f; } default: { throw Assert.impossibleSwitchCase(st); } } } public int peekPrev() throws NoSuchElementException { if (! hasPrev()) throw new NoSuchElementException(); switch (st) { case 0: { int cp = CodePointIterator.this.peekPrev(); if (cp == 0 && ! escapeNul || cp < 0x80) { return cp; } else if (cp < 0x800) { return 0b10_000000 | cp & 0x3f; } else if (cp < 0x10000) { return 0b10_000000 | cp & 0x3f; } else if (cp < 0x110000) { return 0b10_000000 | cp & 0x3f; } else { return 0b10_000000 | '�' & 0x3f; } } case 1: { return 0b110_00000 | cp >> 6; } case 2: { return 0b1110_0000 | cp >> 12; } case 3: case 6: { return 0b10_000000 | cp >> 6 & 0x3f; } case 4: { return 0b11110_000 | cp >> 18; } case 5: { return 0b10_000000 | cp >> 12 & 0x3f; } default: { throw Assert.impossibleSwitchCase(st); } } } public ByteStringBuilder appendTo(final ByteStringBuilder builder) { if (st == 0) { // this is faster final int oldLen = builder.length(); builder.appendUtf8(CodePointIterator.this); offset += builder.length() - oldLen; } else { super.appendTo(builder); } return builder; } public int offset() { return offset; } }; } /** * Get a code point iterator for a string. * * @param string the string * @return the code point iterator */ public static CodePointIterator ofString(final String string) { return ofString(string, 0, string.length()); } /** * Get a code point iterator for a string. * * @param string the string * @return the code point iterator */ public static CodePointIterator ofString(final String string, final int offs, final int len) { if (len == 0) { return EMPTY; } return new CodePointIterator() { private int idx = 0; private int offset = 0; public boolean hasNext() { return idx < len; } public boolean hasPrev() { return offset > 0; } public int next() { if (! hasNext()) throw new NoSuchElementException(); try { offset++; return string.codePointAt(idx + offs); } finally { idx = string.offsetByCodePoints(idx + offs, 1) - offs; } } public int peekNext() throws NoSuchElementException { if (! hasNext()) throw new NoSuchElementException(); return string.codePointAt(idx + offs); } public int prev() { if (! hasPrev()) throw new NoSuchElementException(); idx = string.offsetByCodePoints(idx + offs, -1) - offs; offset--; return string.codePointAt(idx + offs); } public int peekPrev() throws NoSuchElementException { if (! hasPrev()) throw new NoSuchElementException(); return string.codePointBefore(idx + offs); } public int offset() { return offset; } public StringBuilder drainTo(final StringBuilder b) { try { return b.append(string, idx + offs, offs + len); } finally { offset += string.codePointCount(idx + offs, offs + len); idx = len; } } public String drainToString() { try { return string.substring(idx + offs, offs + len); } finally { offset += string.codePointCount(idx + offs, offs + len); idx = len; } } }; } /** * Get a code point iterator for a character array. * * @param chars the array * @return the code point iterator */ public static CodePointIterator ofChars(final char[] chars) { return ofChars(chars, 0, chars.length); } /** * Get a code point iterator for a character array. * * @param chars the array * @param offs the array offset * @return the code point iterator */ public static CodePointIterator ofChars(final char[] chars, final int offs) { return ofChars(chars, offs, chars.length - offs); } /** * Get a code point iterator for a character array. * * @param chars the array * @param offs the array offset * @param len the number of characters to include * @return the code point iterator */ public static CodePointIterator ofChars(final char[] chars, final int offs, final int len) { if (len <= 0) { return EMPTY; } return new CodePointIterator() { private int idx = 0; private int offset = 0; public boolean hasNext() { return idx < len; } public boolean hasPrev() { return idx > 0; } public int next() { if (! hasNext()) throw new NoSuchElementException(); try { offset++; return Character.codePointAt(chars, offs + idx); } finally { idx = Character.offsetByCodePoints(chars, offs, len, offs + idx, 1) - offs; } } public int peekNext() throws NoSuchElementException { if (! hasNext()) throw new NoSuchElementException(); return Character.codePointAt(chars, offs + idx); } public int prev() { if (! hasPrev()) throw new NoSuchElementException(); idx = Character.offsetByCodePoints(chars, offs, len, offs + idx, -1) - offs; offset--; return Character.codePointAt(chars, offs + idx); } public int peekPrev() throws NoSuchElementException { if (! hasPrev()) throw new NoSuchElementException(); return Character.codePointBefore(chars, offs + idx); } public int offset() { return offset; } }; } /** * Get a code point iterator for a UTF-8 encoded byte array. * * @param bytes the array * @return the code point iterator */ public static CodePointIterator ofUtf8Bytes(final byte[] bytes) { return ofUtf8Bytes(bytes, 0, bytes.length); } /** * Get a code point iterator for a UTF-8 encoded array. * * @param bytes the array * @param offs the array offset * @param len the number of characters to include * @return the code point iterator */ public static CodePointIterator ofUtf8Bytes(final byte[] bytes, final int offs, final int len) { if (len <= 0) { return EMPTY; } return ByteIterator.ofBytes(bytes, offs, len).asUtf8String(); } /** * Get a code point iterator for a ISO-8859-1 (Latin-1) encoded array. * * @param bytes the array * @return the code point iterator */ public static CodePointIterator ofLatin1Bytes(final byte[] bytes) { return ofLatin1Bytes(bytes, 0, bytes.length); } /** * Get a code point iterator for a ISO-8859-1 (Latin-1) encoded array. * * @param bytes the array * @param offs the array offset * @param len the number of characters to include * @return the code point iterator */ public static CodePointIterator ofLatin1Bytes(final byte[] bytes, final int offs, final int len) { if (len <= 0) { return EMPTY; } return ByteIterator.ofBytes(bytes, offs, len).asLatin1String(); } /** * Get a sub-iterator that removes the following code points: <code>10</code>(\n) and <code>13</code>(\r). * * @return the code point iterator */ public CodePointIterator skipCrLf() { return skip(value -> value == '\n' || value == '\r'); } /** * Get a sub-iterator that removes code points based on a <code>predicate</code>. * * @param predicate a {@link IntPredicate} that evaluates the code points that should be skipper. Returning true from the predicate * indicates that the code point must be skipped. * @return the code point iterator */ public CodePointIterator skip(IntPredicate predicate) { if (!hasNext()) { return EMPTY; } return new CodePointIterator() { public boolean hasNext() { return CodePointIterator.this.hasNext() && !skip(peekNext()); } public boolean hasPrev() { return CodePointIterator.this.hasPrev() && !skip(peekPrev()); } public int next() { if (!hasNext()) { throw new NoSuchElementException(); } return CodePointIterator.this.next(); } public int peekNext() throws NoSuchElementException { if (!CodePointIterator.this.hasNext()) { throw new NoSuchElementException(); } int next = seekNext(CodePointIterator.this.peekNext()); if (!skip(next)) { return next; } return next; } private int seekNext(int next) throws NoSuchElementException { if (!CodePointIterator.this.hasNext()) { return next; } next = CodePointIterator.this.next(); if (skip(next)) { return seekNext(next); } CodePointIterator.this.prev(); return next; } public int prev() { if (!hasPrev()) { throw new NoSuchElementException(); } return CodePointIterator.this.prev(); } public int peekPrev() throws NoSuchElementException { if (!CodePointIterator.this.hasPrev()) { throw new NoSuchElementException(); } int prev = seekPrev(CodePointIterator.this.peekPrev()); if (!skip(prev)) { return prev; } return prev; } private int seekPrev(int prev) throws NoSuchElementException { if (!CodePointIterator.this.hasPrev()) { return prev; } prev = CodePointIterator.this.prev(); if (skip(prev)) { return seekPrev(prev); } CodePointIterator.this.next(); return prev; } public int offset() { return CodePointIterator.this.offset(); } private boolean skip(int c) { return predicate.test(c); } }; } /** * The empty code point iterator. */ public static final CodePointIterator EMPTY = new CodePointIterator() { public boolean hasNext() { return false; } public boolean hasPrev() { return false; } public int next() { throw new NoSuchElementException(); } public int peekNext() throws NoSuchElementException { throw new NoSuchElementException(); } public int prev() { throw new NoSuchElementException(); } public int peekPrev() throws NoSuchElementException { throw new NoSuchElementException(); } public int offset() { return 0; } public ByteIterator base64Decode(final Base64Alphabet alphabet, final boolean requirePadding) { return ByteIterator.EMPTY; } public String drainToString() { return ""; } }; }