replacementParts = null;
+
+ /**
+ * Appends a literal part of the input plus a replacement for the current
+ * match to a given {@link StringBuffer}. The literal part is exactly the
+ * part of the input between the previous match and the current match. The
+ * method can be used in conjunction with {@link #find()} and
+ * {@link #appendTail(StringBuffer)} to walk through the input and replace
+ * all occurrences of the {@code Pattern} with something else.
+ *
+ * @param buffer
+ * the {@code StringBuffer} to append to.
+ * @param replacement
+ * the replacement text.
+ * @return the {@code Matcher} itself.
+ * @throws IllegalStateException
+ * if no successful match has been made.
+ */
+ public TMatcher appendReplacement(StringBuffer buffer, String replacement) {
+ processedRepl = processReplacement(replacement);
+ buffer.append(string.subSequence(appendPos, start()));
+ buffer.append(processedRepl);
+ appendPos = end();
+ return this;
+ }
+
+ /**
+ * Parses replacement string and creates pattern
+ */
+ private String processReplacement(String replacement) {
+ if (this.replacement != null && this.replacement.equals(replacement)) {
+ if (replacementParts == null) {
+ return processedRepl;
+ } else {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < replacementParts.size(); i++) {
+ sb.append(replacementParts.get(i));
+ }
+
+ return sb.toString();
+ }
+ } else {
+ this.replacement = replacement;
+ char[] repl = replacement.toCharArray();
+ StringBuilder res = new StringBuilder();
+ replacementParts = null;
+
+ int index = 0;
+ int replacementPos = 0;
+ boolean nextBackSlashed = false;
+
+ while (index < repl.length) {
+
+ if (repl[index] == '\\' && !nextBackSlashed) {
+ nextBackSlashed = true;
+ index++;
+ }
+
+ if (nextBackSlashed) {
+ res.append(repl[index]);
+ nextBackSlashed = false;
+ } else {
+ if (repl[index] == '$') {
+ if (replacementParts == null) {
+ replacementParts = new ArrayList<>();
+ }
+ try {
+ final int gr = Integer.parseInt(new String(repl, ++index, 1));
+
+ if (replacementPos != res.length()) {
+ replacementParts.add(res.subSequence(replacementPos, res.length()));
+ replacementPos = res.length();
+ }
+
+ replacementParts.add(new Object() {
+ private final int grN = gr;
+
+ @Override
+ public String toString() {
+ return group(grN);
+ }
+ });
+ String group = group(gr);
+ replacementPos += group.length();
+ res.append(group);
+
+ } catch (IndexOutOfBoundsException iob) {
+ throw iob;
+ } catch (Exception e) {
+ throw new IllegalArgumentException("");
+ }
+ } else {
+ res.append(repl[index]);
+ }
+ }
+
+ index++;
+ }
+
+ if (replacementParts != null && replacementPos != res.length()) {
+ replacementParts.add(res.subSequence(replacementPos, res.length()));
+ }
+ return res.toString();
+ }
+ }
+
+ /**
+ * Provides a new input and resets the {@code Matcher}. This results in the
+ * region being set to the whole input. Results of a previous find get lost.
+ * The next attempt to find an occurrence of the {@link TPattern} in the
+ * string will start at the beginning of the input.
+ *
+ * @param input
+ * the new input sequence.
+ *
+ * @return the {@code Matcher} itself.
+ */
+ public TMatcher reset(CharSequence input) {
+ if (input == null) {
+ throw new NullPointerException("");
+ }
+ this.string = input;
+ return reset();
+ }
+
+ /**
+ * Resets the {@code Matcher}. This results in the region being set to the
+ * whole input. Results of a previous find get lost. The next attempt to
+ * find an occurrence of the {@link TPattern} in the string will start at
+ * the beginning of the input.
+ *
+ * @return the {@code Matcher} itself.
+ */
+ public TMatcher reset() {
+ this.leftBound = 0;
+ this.rightBound = string.length();
+ matchResult.reset(string, leftBound, rightBound);
+ appendPos = 0;
+ replacement = null;
+ matchResult.previousMatch = -1;
+ return this;
+ }
+
+ /**
+ * Resets this matcher and sets a region. Only characters inside the region
+ * are considered for a match.
+ *
+ * @param start
+ * the first character of the region.
+ * @param end
+ * the first character after the end of the region.
+ * @return the {@code Matcher} itself.
+ */
+ public TMatcher region(int start, int end) {
+
+ if (start > end || start < 0 || end < 0 || start > string.length() || end > string.length()) {
+ throw new IndexOutOfBoundsException(start + ", " + end);
+ }
+
+ this.leftBound = start;
+ this.rightBound = end;
+ matchResult.reset(null, start, end);
+ appendPos = 0;
+ replacement = null;
+
+ return this;
+ }
+
+ /**
+ * Appends the (unmatched) remainder of the input to the given
+ * {@link StringBuffer}. The method can be used in conjunction with
+ * {@link #find()} and {@link #appendReplacement(StringBuffer, String)} to
+ * walk through the input and replace all matches of the {@code Pattern}
+ * with something else.
+ *
+ * @param buffer
+ * the {@code StringBuffer} to append to.
+ * @return the {@code StringBuffer}.
+ * @throws IllegalStateException
+ * if no successful match has been made.
+ */
+ public StringBuffer appendTail(StringBuffer buffer) {
+ return buffer.append(string.subSequence(appendPos, string.length()));
+ }
+
+ /**
+ * Replaces the first occurrence of this matcher's pattern in the input with
+ * a given string.
+ *
+ * @param replacement
+ * the replacement text.
+ * @return the modified input string.
+ */
+ public String replaceFirst(String replacement) {
+ reset();
+ if (find()) {
+ StringBuffer sb = new StringBuffer();
+ appendReplacement(sb, replacement);
+ return appendTail(sb).toString();
+ }
+
+ return string.toString();
+
+ }
+
+ /**
+ * Replaces all occurrences of this matcher's pattern in the input with a
+ * given string.
+ *
+ * @param replacement
+ * the replacement text.
+ * @return the modified input string.
+ */
+ public String replaceAll(String replacement) {
+ StringBuffer sb = new StringBuffer();
+ reset();
+ while (find()) {
+ appendReplacement(sb, replacement);
+ }
+
+ return appendTail(sb).toString();
+ }
+
+ /**
+ * Returns the {@link TPattern} instance used inside this matcher.
+ *
+ * @return the {@code Pattern} instance.
+ */
+ public TPattern pattern() {
+ return pat;
+ }
+
+ /**
+ * Returns the text that matched a given group of the regular expression.
+ *
+ * @param group
+ * the group, ranging from 0 to groupCount() - 1, with 0
+ * representing the whole pattern.
+ * @return the text that matched the group.
+ * @throws IllegalStateException
+ * if no successful match has been made.
+ */
+ @Override
+ public String group(int group) {
+ if (group < 0 || group > matchResult.groupCount()) {
+ throw new IndexOutOfBoundsException("Index " + group + " if out of range [0; " +
+ matchResult.groupCount() + ")");
+ }
+ return matchResult.group(group);
+ }
+
+ /**
+ * Returns the text that matched the whole regular expression.
+ *
+ * @return the text.
+ * @throws IllegalStateException
+ * if no successful match has been made.
+ */
+ @Override
+ public String group() {
+ return group(0);
+ }
+
+ /**
+ * Returns the next occurrence of the {@link TPattern} in the input. The
+ * method starts the search from the given character in the input.
+ *
+ * @param start
+ * The index in the input at which the find operation is to
+ * begin. If this is less than the start of the region, it is
+ * automatically adjusted to that value. If it is beyond the end
+ * of the region, the method will fail.
+ * @return true if (and only if) a match has been found.
+ */
+ public boolean find(int start) {
+ int stringLength = string.length();
+ if (start < 0 || start > stringLength) {
+ throw new IndexOutOfBoundsException(String.valueOf(start));
+ }
+
+ start = findAt(start);
+ if (start >= 0 && matchResult.isValid()) {
+ matchResult.finalizeMatch();
+ return true;
+ }
+ matchResult.startIndex = -1;
+ return false;
+ }
+
+ private int findAt(int startIndex) {
+ matchResult.reset();
+ matchResult.setMode(TMatcher.MODE_FIND);
+ matchResult.setStartIndex(startIndex);
+ int foundIndex = start.find(startIndex, string, matchResult);
+ if (foundIndex == -1) {
+ matchResult.hitEnd = true;
+ }
+ return foundIndex;
+ }
+
+ /**
+ * Returns the next occurrence of the {@link TPattern} in the input. If a
+ * previous match was successful, the method continues the search from the
+ * first character following that match in the input. Otherwise it searches
+ * either from the region start (if one has been set), or from position 0.
+ *
+ * @return true if (and only if) a match has been found.
+ */
+ public boolean find() {
+ int length = string.length();
+ if (!hasTransparentBounds())
+ length = rightBound;
+ if (matchResult.startIndex >= 0 && matchResult.mode() == TMatcher.MODE_FIND) {
+ matchResult.startIndex = matchResult.end();
+ if (matchResult.end() == matchResult.start()) {
+ matchResult.startIndex++;
+ }
+
+ return matchResult.startIndex <= length ? find(matchResult.startIndex) : false;
+ } else {
+ return find(leftBound);
+ }
+ }
+
+ /**
+ * Returns the index of the first character of the text that matched a given
+ * group.
+ *
+ * @param group
+ * the group, ranging from 0 to groupCount() - 1, with 0
+ * representing the whole pattern.
+ * @return the character index.
+ * @throws IllegalStateException
+ * if no successful match has been made.
+ */
+ @Override
+ public int start(int group) {
+ return matchResult.start(group);
+ }
+
+ /**
+ * Returns the index of the first character following the text that matched
+ * a given group.
+ *
+ * @param group
+ * the group, ranging from 0 to groupCount() - 1, with 0
+ * representing the whole pattern.
+ * @return the character index.
+ * @throws IllegalStateException
+ * if no successful match has been made.
+ */
+ @Override
+ public int end(int group) {
+ return matchResult.end(group);
+ }
+
+ /**
+ * Tries to match the {@link TPattern} against the entire region (or the
+ * entire input, if no region has been set).
+ *
+ * @return true if (and only if) the {@code Pattern} matches the entire
+ * region.
+ */
+ public boolean matches() {
+ return lookingAt(leftBound, TMatcher.MODE_MATCH);
+ }
+
+ /**
+ * Returns a replacement string for the given one that has all backslashes
+ * and dollar signs escaped.
+ *
+ * @param s
+ * the input string.
+ * @return the input string, with all backslashes and dollar signs having
+ * been escaped.
+ */
+ public static String quoteReplacement(String s) {
+ // first check whether we have smth to quote
+ if (s.indexOf('\\') < 0 && s.indexOf('$') < 0)
+ return s;
+ StringBuilder res = new StringBuilder(s.length() * 2);
+ char ch;
+ int len = s.length();
+
+ for (int i = 0; i < len; i++) {
+
+ switch (ch = s.charAt(i)) {
+ case '$':
+ res.append('\\');
+ res.append('$');
+ break;
+ case '\\':
+ res.append('\\');
+ res.append('\\');
+ break;
+ default:
+ res.append(ch);
+ }
+ }
+
+ return res.toString();
+ }
+
+ /**
+ * Runs match starting from set
specified against input
+ * sequence starting at index
specified; Result of the match
+ * will be stored into matchResult instance;
+ */
+ private boolean runMatch(AbstractSet set, int index, MatchResultImpl matchResult) {
+
+ if (set.matches(index, string, matchResult) >= 0) {
+ matchResult.finalizeMatch();
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Tries to match the {@link TPattern}, starting from the beginning of the
+ * region (or the beginning of the input, if no region has been set).
+ * Doesn't require the {@code Pattern} to match against the whole region.
+ *
+ * @return true if (and only if) the {@code Pattern} matches.
+ */
+ public boolean lookingAt() {
+ return lookingAt(leftBound, TMatcher.MODE_FIND);
+ }
+
+ private boolean lookingAt(int startIndex, int mode) {
+ matchResult.reset();
+ matchResult.setMode(mode);
+ matchResult.setStartIndex(startIndex);
+ return runMatch(start, startIndex, matchResult);
+ }
+
+ /**
+ * Returns the index of the first character of the text that matched the
+ * whole regular expression.
+ *
+ * @return the character index.
+ * @throws IllegalStateException
+ * if no successful match has been made.
+ */
+ @Override
+ public int start() {
+ return start(0);
+ }
+
+ /**
+ * Returns the number of groups in the results, which is always equal to the
+ * number of groups in the original regular expression.
+ *
+ * @return the number of groups.
+ */
+ @Override
+ public int groupCount() {
+ return matchResult.groupCount();
+ }
+
+ /**
+ * Returns the index of the first character following the text that matched
+ * the whole regular expression.
+ *
+ * @return the character index.
+ * @throws IllegalStateException
+ * if no successful match has been made.
+ */
+ @Override
+ public int end() {
+ return end(0);
+ }
+
+ /**
+ * Converts the current match into a separate {@link TMatchResult} instance
+ * that is independent from this matcher. The new object is unaffected when
+ * the state of this matcher changes.
+ *
+ * @return the new {@code MatchResult}.
+ * @throws IllegalStateException
+ * if no successful match has been made.
+ */
+ public TMatchResult toMatchResult() {
+ return this.matchResult.cloneImpl();
+ }
+
+ /**
+ * Determines whether this matcher has anchoring bounds enabled or not. When
+ * anchoring bounds are enabled, the start and end of the input match the
+ * '^' and '$' meta-characters, otherwise not. Anchoring bounds are enabled
+ * by default.
+ *
+ * @param value
+ * the new value for anchoring bounds.
+ * @return the {@code Matcher} itself.
+ */
+ public TMatcher useAnchoringBounds(boolean value) {
+ matchResult.useAnchoringBounds(value);
+ return this;
+ }
+
+ /**
+ * Indicates whether this matcher has anchoring bounds enabled. When
+ * anchoring bounds are enabled, the start and end of the input match the
+ * '^' and '$' meta-characters, otherwise not. Anchoring bounds are enabled
+ * by default.
+ *
+ * @return true if (and only if) the {@code Matcher} uses anchoring bounds.
+ */
+ public boolean hasAnchoringBounds() {
+ return matchResult.hasAnchoringBounds();
+ }
+
+ /**
+ * Determines whether this matcher has transparent bounds enabled or not.
+ * When transparent bounds are enabled, the parts of the input outside the
+ * region are subject to lookahead and lookbehind, otherwise they are not.
+ * Transparent bounds are disabled by default.
+ *
+ * @param value
+ * the new value for transparent bounds.
+ * @return the {@code Matcher} itself.
+ */
+ public TMatcher useTransparentBounds(boolean value) {
+ matchResult.useTransparentBounds(value);
+ return this;
+ }
+
+ /**
+ * Indicates whether this matcher has transparent bounds enabled. When
+ * transparent bounds are enabled, the parts of the input outside the region
+ * are subject to lookahead and lookbehind, otherwise they are not.
+ * Transparent bounds are disabled by default.
+ *
+ * @return true if (and only if) the {@code Matcher} uses anchoring bounds.
+ */
+ public boolean hasTransparentBounds() {
+ return matchResult.hasTransparentBounds();
+ }
+
+ /**
+ * Returns this matcher's region start, that is, the first character that is
+ * considered for a match.
+ *
+ * @return the start of the region.
+ */
+ public int regionStart() {
+ return matchResult.getLeftBound();
+ }
+
+ /**
+ * Returns this matcher's region end, that is, the first character that is
+ * not considered for a match.
+ *
+ * @return the end of the region.
+ */
+ public int regionEnd() {
+ return matchResult.getRightBound();
+ }
+
+ /**
+ * Indicates whether more input might change a successful match into an
+ * unsuccessful one.
+ *
+ * @return true if (and only if) more input might change a successful match
+ * into an unsuccessful one.
+ */
+ public boolean requireEnd() {
+ return matchResult.requireEnd;
+ }
+
+ /**
+ * Indicates whether the last match hit the end of the input.
+ *
+ * @return true if (and only if) the last match hit the end of the input.
+ */
+ public boolean hitEnd() {
+ return matchResult.hitEnd;
+ }
+
+ /**
+ * Sets a new pattern for the {@code Matcher}. Results of a previous find
+ * get lost. The next attempt to find an occurrence of the {@link TPattern}
+ * in the string will start at the beginning of the input.
+ *
+ * @param pattern
+ * the new {@code Pattern}.
+ *
+ * @return the {@code Matcher} itself.
+ */
+ public TMatcher usePattern(TPattern pattern) {
+ if (pattern == null) {
+ throw new IllegalArgumentException("");
+ }
+ int startIndex = matchResult.getPreviousMatchEnd();
+ int mode = matchResult.mode();
+ this.pat = pattern;
+ this.start = pattern.start;
+ matchResult = new MatchResultImpl(this.string, leftBound, rightBound, pattern.groupCount(),
+ pattern.compCount(), pattern.consCount());
+ matchResult.setStartIndex(startIndex);
+ matchResult.setMode(mode);
+ return this;
+ }
+
+ TMatcher(TPattern pat, CharSequence cs) {
+ this.pat = pat;
+ this.start = pat.start;
+ this.string = cs;
+ this.leftBound = 0;
+ this.rightBound = string.length();
+ matchResult = new MatchResultImpl(cs, leftBound, rightBound, pat.groupCount(), pat.compCount(), pat.consCount());
+ }
+
+ @Override
+ public String toString() {
+ String lastMatch = "";
+ try {
+ lastMatch = Integer.toString(start());
+ } catch (IllegalStateException e) {
+ }
+ return "Regex[pattern=" + pat + " region=" + matchResult.getLeftBound() + "," + matchResult.getRightBound() +
+ " lastmatch=" + lastMatch + "]";
+ }
+}
diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/util/regex/TPattern.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/regex/TPattern.java
new file mode 100644
index 000000000..307c6cf41
--- /dev/null
+++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/regex/TPattern.java
@@ -0,0 +1,1362 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+/**
+ * @author Nikolay A. Kuznetsov
+ */
+package org.teavm.classlib.java.util.regex;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+
+/**
+ * Represents a pattern used for matching, searching, or replacing strings.
+ * {@code Pattern}s are specified in terms of regular expressions and compiled
+ * using an instance of this class. They are then used in conjunction with a
+ * {@link TMatcher} to perform the actual search.
+ *
+ * A typical use case looks like this:
+ *
+ *
+ *
+ * Pattern p = Pattern.compile("Hello, A[a-z]*!");
+ *
+ * Matcher m = p.matcher("Hello, Android!");
+ * boolean b1 = m.matches(); // true
+ *
+ * m.setInput("Hello, Robot!");
+ * boolean b2 = m.matches(); // false
+ *
+ *
+ * The above code could also be written in a more compact fashion, though this
+ * variant is less efficient, since {@code Pattern} and {@code Matcher} objects
+ * are created on the fly instead of being reused. fashion:
+ *
+ *
+ * boolean b1 = Pattern.matches("Hello, A[a-z]*!", "Hello, Android!"); // true
+ * boolean b2 = Pattern.matches("Hello, A[a-z]*!", "Hello, Robot!"); // false
+ *
+ *
+ * @see TMatcher
+ */
+public final class TPattern implements Serializable {
+
+ private static final long serialVersionUID = 5073258162644648461L;
+
+ static final boolean _DEBUG_ = false;
+
+ /**
+ * This constant specifies that a pattern matches Unix line endings ('\n')
+ * only against the '.', '^', and '$' meta characters.
+ */
+ public static final int UNIX_LINES = 1 << 0;
+
+ /**
+ * This constant specifies that a {@code Pattern} is matched
+ * case-insensitively. That is, the patterns "a+" and "A+" would both match
+ * the string "aAaAaA".
+ */
+ public static final int CASE_INSENSITIVE = 1 << 1;
+
+ /**
+ * This constant specifies that a {@code Pattern} may contain whitespace or
+ * comments. Otherwise comments and whitespace are taken as literal
+ * characters.
+ */
+ public static final int COMMENTS = 1 << 2;
+
+ /**
+ * This constant specifies that the meta characters '^' and '$' match only
+ * the beginning and end end of an input line, respectively. Normally, they
+ * match the beginning and the end of the complete input.
+ */
+ public static final int MULTILINE = 1 << 3;
+
+ /**
+ * This constant specifies that the whole {@code Pattern} is to be taken
+ * literally, that is, all meta characters lose their meanings.
+ */
+ public static final int LITERAL = 1 << 4;
+
+ /**
+ * This constant specifies that the '.' meta character matches arbitrary
+ * characters, including line endings, which is normally not the case.
+ */
+ public static final int DOTALL = 1 << 5;
+
+ /**
+ * This constant specifies that a {@code Pattern} is matched
+ * case-insensitively with regard to all Unicode characters. It is used in
+ * conjunction with the {@link #CASE_INSENSITIVE} constant to extend its
+ * meaning to all Unicode characters.
+ */
+ public static final int UNICODE_CASE = 1 << 6;
+
+ /**
+ * This constant specifies that a character in a {@code Pattern} and a
+ * character in the input string only match if they are canonically
+ * equivalent.
+ */
+ public static final int CANON_EQ = 1 << 7;
+
+ static final int BACK_REF_NUMBER = 10;
+
+ /**
+ * Bit mask that includes all defined match flags
+ */
+ static final int flagsBitMask = TPattern.UNIX_LINES | TPattern.CASE_INSENSITIVE | TPattern.COMMENTS |
+ TPattern.MULTILINE | TPattern.LITERAL | TPattern.DOTALL | TPattern.UNICODE_CASE | TPattern.CANON_EQ;
+
+ /**
+ * Current pattern
to be compiled;
+ */
+ private transient Lexer lexemes = null;
+
+ /**
+ * Pattern compile flags;
+ */
+ private int flags = 0;
+
+ private String pattern = null;
+
+ /*
+ * All backreferences that may be used in pattern.
+ */
+ transient private FSet backRefs[] = new FSet[BACK_REF_NUMBER];
+
+ /*
+ * Is true if backreferenced sets replacement is needed
+ */
+ transient private boolean needsBackRefReplacement = false;
+
+ transient private int globalGroupIndex = -1;
+
+ transient private int compCount = -1;
+
+ transient private int consCount = -1;
+
+ transient AbstractSet start = null;
+
+ /**
+ * Returns a {@link TMatcher} for the {@code Pattern} and a given input. The
+ * {@code Matcher} can be used to match the {@code Pattern} against the
+ * whole input, find occurrences of the {@code Pattern} in the input, or
+ * replace parts of the input.
+ *
+ * @param input
+ * the input to process.
+ *
+ * @return the resulting {@code Matcher}.
+ */
+ public TMatcher matcher(CharSequence input) {
+ return new TMatcher(this, input);
+ }
+
+ /**
+ * Splits the given input sequence around occurrences of the {@code Pattern}
+ * . The function first determines all occurrences of the {@code Pattern}
+ * inside the input sequence. It then builds an array of the
+ * "remaining" strings before, in-between, and after these
+ * occurrences. An additional parameter determines the maximal number of
+ * entries in the resulting array and the handling of trailing empty
+ * strings.
+ *
+ * @param inputSeq
+ * the input sequence.
+ * @param limit
+ * Determines the maximal number of entries in the resulting
+ * array.
+ *
+ * For n > 0, it is guaranteed that the resulting array
+ * contains at most n entries.
+ * For n < 0, the length of the resulting array is exactly
+ * the number of occurrences of the {@code Pattern} +1. All
+ * entries are included.
+ * For n == 0, the length of the resulting array is at most
+ * the number of occurrences of the {@code Pattern} +1. Empty
+ * strings at the end of the array are not included.
+ *
+ *
+ * @return the resulting array.
+ */
+ public String[] split(CharSequence inputSeq, int limit) {
+ ArrayList res = new ArrayList<>();
+ TMatcher mat = matcher(inputSeq);
+ int index = 0;
+ int curPos = 0;
+
+ if (inputSeq.length() == 0) {
+ return new String[] { "" }; //$NON-NLS-1$
+ } else {
+ while (mat.find() && (index + 1 < limit || limit <= 0)) {
+ res.add(inputSeq.subSequence(curPos, mat.start()).toString());
+ curPos = mat.end();
+ index++;
+ }
+
+ res.add(inputSeq.subSequence(curPos, inputSeq.length()).toString());
+ index++;
+
+ /*
+ * discard trailing empty strings
+ */
+ if (limit == 0) {
+ while (--index >= 0 && res.get(index).toString().length() == 0) {
+ res.remove(index);
+ }
+ }
+ }
+ return res.toArray(new String[index >= 0 ? index : 0]);
+ }
+
+ /**
+ * Splits a given input around occurrences of a regular expression. This is
+ * a convenience method that is equivalent to calling the method
+ * {@link #split(java.lang.CharSequence, int)} with a limit of 0.
+ *
+ * @param input
+ * the input sequence.
+ *
+ * @return the resulting array.
+ */
+ public String[] split(CharSequence input) {
+ return split(input, 0);
+ }
+
+ /**
+ * Returns the regular expression that was compiled into this
+ * {@code Pattern}.
+ *
+ * @return the regular expression.
+ */
+ public String pattern() {
+ return lexemes.toString();
+ }
+
+ @Override
+ public String toString() {
+ return this.pattern();
+ }
+
+ /**
+ * Returns the flags that have been set for this {@code Pattern}.
+ *
+ * @return the flags that have been set. A combination of the constants
+ * defined in this class.
+ *
+ * @see #CANON_EQ
+ * @see #CASE_INSENSITIVE
+ * @see #COMMENTS
+ * @see #DOTALL
+ * @see #LITERAL
+ * @see #MULTILINE
+ * @see #UNICODE_CASE
+ * @see #UNIX_LINES
+ */
+ public int flags() {
+ return this.flags;
+ }
+
+ /**
+ * Compiles a regular expression, creating a new {@code Pattern} instance in
+ * the process. Allows to set some flags that modify the behavior of the
+ * {@code Pattern}.
+ *
+ * @param pattern
+ * the regular expression.
+ * @param flags
+ * the flags to set. Basically, any combination of the constants
+ * defined in this class is valid.
+ *
+ * @return the new {@code Pattern} instance.
+ *
+ * @throws TPatternSyntaxException
+ * if the regular expression is syntactically incorrect.
+ *
+ * @see #CANON_EQ
+ * @see #CASE_INSENSITIVE
+ * @see #COMMENTS
+ * @see #DOTALL
+ * @see #LITERAL
+ * @see #MULTILINE
+ * @see #UNICODE_CASE
+ * @see #UNIX_LINES
+ */
+ public static TPattern compile(String pattern, int flags) throws TPatternSyntaxException {
+
+ if ((flags != 0) && ((flags | flagsBitMask) != flagsBitMask)) {
+
+ throw new IllegalArgumentException("");
+ }
+
+ AbstractSet.counter = 1;
+
+ return new TPattern().compileImpl(pattern, flags);
+ }
+
+ /**
+ *
+ * @param pattern
+ * - Regular expression to be compiled
+ * @param flags
+ * - The bit mask including CASE_INSENSITIVE, MULTILINE, DOTALL,
+ * UNICODE_CASE, and CANON_EQ
+ *
+ * @return Compiled pattern
+ */
+ private TPattern compileImpl(String pattern, int flags) throws TPatternSyntaxException {
+ this.lexemes = new Lexer(pattern, flags);
+ this.flags = flags;
+ this.pattern = pattern;
+
+ start = processExpression(-1, this.flags, null);
+ if (!lexemes.isEmpty()) {
+ throw new TPatternSyntaxException("", lexemes.toString(), lexemes.getIndex());
+ }
+ finalizeCompile();
+ return this;
+ }
+
+ /**
+ * A->(a|)+
+ */
+ private AbstractSet processAlternations(AbstractSet last) {
+ CharClass auxRange = new CharClass(hasFlag(TPattern.CASE_INSENSITIVE), hasFlag(TPattern.UNICODE_CASE));
+ while (!lexemes.isEmpty() &&
+ lexemes.isLetter() &&
+ (lexemes.lookAhead() == 0 || lexemes.lookAhead() == Lexer.CHAR_VERTICAL_BAR || lexemes.lookAhead() == Lexer.CHAR_RIGHT_PARENTHESIS)) {
+ auxRange.add(lexemes.next());
+ if (lexemes.peek() == Lexer.CHAR_VERTICAL_BAR)
+ lexemes.next();
+ }
+ AbstractSet rangeSet = processRangeSet(auxRange);
+ rangeSet.setNext(last);
+
+ return rangeSet;
+ }
+
+ /**
+ * E->AE; E->S|E; E->S; A->(a|)+ E->S(|S)*
+ */
+ private AbstractSet processExpression(int ch, int newFlags, AbstractSet last) {
+ ArrayList children = new ArrayList<>();
+ AbstractSet child;
+ int saveFlags = flags;
+ FSet fSet;
+ boolean saveChangedFlags = false;
+
+ if (newFlags != flags) {
+ flags = newFlags;
+ }
+
+ switch (ch) {
+ case Lexer.CHAR_NONCAP_GROUP:
+ fSet = new NonCapFSet(++consCount);
+ break;
+
+ case Lexer.CHAR_POS_LOOKAHEAD:
+ /* falls through */
+
+ case Lexer.CHAR_NEG_LOOKAHEAD:
+ fSet = new AheadFSet();
+ break;
+
+ case Lexer.CHAR_POS_LOOKBEHIND:
+ /* falls through */
+
+ case Lexer.CHAR_NEG_LOOKBEHIND:
+ fSet = new BehindFSet(++consCount);
+ break;
+
+ case Lexer.CHAR_ATOMIC_GROUP:
+ fSet = new AtomicFSet(++consCount);
+ break;
+
+ default:
+ globalGroupIndex++;
+ if (last == null) {
+
+ // expr = new StartSet();
+ fSet = new FinalSet();
+ saveChangedFlags = true;
+ } else {
+
+ // expr = new JointSet(globalGroupIndex);
+ fSet = new FSet(globalGroupIndex);
+ }
+ if (globalGroupIndex > -1 && globalGroupIndex < 10) {
+ backRefs[globalGroupIndex] = fSet;
+ }
+ break;
+ }
+
+ do {
+ if (lexemes.isLetter() && lexemes.lookAhead() == Lexer.CHAR_VERTICAL_BAR) {
+ child = processAlternations(fSet);
+ } else if (lexemes.peek() == Lexer.CHAR_VERTICAL_BAR) {
+ child = new EmptySet(fSet);
+ lexemes.next();
+ } else {
+ child = processSubExpression(fSet);
+ if (lexemes.peek() == Lexer.CHAR_VERTICAL_BAR) {
+ lexemes.next();
+ }
+ }
+ if (child != null) {
+
+ // expr.addChild(child);
+ children.add(child);
+ }
+ } while (!(lexemes.isEmpty() || (lexemes.peek() == Lexer.CHAR_RIGHT_PARENTHESIS)));
+
+ if (lexemes.back() == Lexer.CHAR_VERTICAL_BAR) {
+ children.add(new EmptySet(fSet));
+ }
+
+ if (flags != saveFlags && !saveChangedFlags) {
+ flags = saveFlags;
+ lexemes.restoreFlags(flags);
+ }
+
+ switch (ch) {
+ case Lexer.CHAR_NONCAP_GROUP:
+ return new NonCapJointSet(children, fSet);
+
+ case Lexer.CHAR_POS_LOOKAHEAD:
+ return new PositiveLookAhead(children, fSet);
+
+ case Lexer.CHAR_NEG_LOOKAHEAD:
+ return new NegativeLookAhead(children, fSet);
+
+ case Lexer.CHAR_POS_LOOKBEHIND:
+ return new PositiveLookBehind(children, fSet);
+
+ case Lexer.CHAR_NEG_LOOKBEHIND:
+ return new NegativeLookBehind(children, fSet);
+
+ case Lexer.CHAR_ATOMIC_GROUP:
+ return new AtomicJointSet(children, fSet);
+
+ default:
+ switch (children.size()) {
+ case 0:
+ return new EmptySet(fSet);
+
+ case 1:
+ return new SingleSet(children.get(0), fSet);
+
+ default:
+ return new JointSet(children, fSet);
+ }
+ }
+ }
+
+ /**
+ * T->a+
+ */
+ private AbstractSet processSequence() {
+ StringBuffer substring = new StringBuffer();
+
+ while (!lexemes.isEmpty() &&
+ lexemes.isLetter() &&
+ !lexemes.isHighSurrogate() &&
+ !lexemes.isLowSurrogate() &&
+ ((!lexemes.isNextSpecial() && lexemes.lookAhead() == 0) // end
+ // of
+ // pattern
+ ||
+ (!lexemes.isNextSpecial() && Lexer.isLetter(lexemes.lookAhead())) ||
+ lexemes.lookAhead() == Lexer.CHAR_RIGHT_PARENTHESIS ||
+ (lexemes.lookAhead() & 0x8000ffff) == Lexer.CHAR_LEFT_PARENTHESIS ||
+ lexemes.lookAhead() == Lexer.CHAR_VERTICAL_BAR || lexemes.lookAhead() == Lexer.CHAR_DOLLAR)) {
+ int ch = lexemes.next();
+
+ if (Character.isSupplementaryCodePoint(ch)) {
+ substring.append(Character.toChars(ch));
+ } else {
+ substring.append((char)ch);
+ }
+ }
+ if (!hasFlag(TPattern.CASE_INSENSITIVE)) {
+ return new SequenceSet(substring);
+ } else if (!hasFlag(TPattern.UNICODE_CASE)) {
+ return new CISequenceSet(substring);
+ } else {
+ return new UCISequenceSet(substring);
+ }
+ }
+
+ /**
+ * D->a
+ */
+ private AbstractSet processDecomposedChar() {
+ int[] codePoints = new int[Lexer.MAX_DECOMPOSITION_LENGTH];
+ char[] codePointsHangul;
+ int readCodePoints = 0;
+ int curSymb = -1;
+ int curSymbIndex = -1;
+
+ if (!lexemes.isEmpty() && lexemes.isLetter()) {
+ curSymb = lexemes.next();
+ codePoints[readCodePoints] = curSymb;
+ curSymbIndex = curSymb - Lexer.LBase;
+ }
+
+ /*
+ * We process decomposed Hangul syllable LV or LVT or process jamo L.
+ * See http://www.unicode.org/versions/Unicode4.0.0/ch03.pdf
+ * "3.12 Conjoining Jamo Behavior"
+ */
+ if ((curSymbIndex >= 0) && (curSymbIndex < Lexer.LCount)) {
+ codePointsHangul = new char[Lexer.MAX_HANGUL_DECOMPOSITION_LENGTH];
+ codePointsHangul[readCodePoints++] = (char)curSymb;
+
+ curSymb = lexemes.peek();
+ curSymbIndex = curSymb - Lexer.VBase;
+ if ((curSymbIndex >= 0) && (curSymbIndex < Lexer.VCount)) {
+ codePointsHangul[readCodePoints++] = (char)curSymb;
+ lexemes.next();
+ curSymb = lexemes.peek();
+ curSymbIndex = curSymb - Lexer.TBase;
+ if ((curSymbIndex >= 0) && (curSymbIndex < Lexer.TCount)) {
+ codePointsHangul[readCodePoints++] = (char)curSymb;
+ lexemes.next();
+
+ // LVT syllable
+ return new HangulDecomposedCharSet(codePointsHangul, 3);
+ } else {
+
+ // LV syllable
+ return new HangulDecomposedCharSet(codePointsHangul, 2);
+ }
+ } else {
+
+ // L jamo
+ if (!hasFlag(TPattern.CASE_INSENSITIVE)) {
+ return new CharSet(codePointsHangul[0]);
+ } else if (!hasFlag(TPattern.UNICODE_CASE)) {
+ return new CICharSet(codePointsHangul[0]);
+ } else {
+ return new UCICharSet(codePointsHangul[0]);
+ }
+ }
+
+ /*
+ * We process single codepoint or decomposed codepoint. We collect
+ * decomposed codepoint and obtain one DecomposedCharSet.
+ */
+ } else {
+ readCodePoints++;
+
+ while ((readCodePoints < Lexer.MAX_DECOMPOSITION_LENGTH) && !lexemes.isEmpty() && lexemes.isLetter() &&
+ !Lexer.isDecomposedCharBoundary(lexemes.peek())) {
+ codePoints[readCodePoints++] = lexemes.next();
+ }
+
+ /*
+ * We have read an ordinary symbol.
+ */
+ if (readCodePoints == 1 && !Lexer.hasSingleCodepointDecomposition(codePoints[0])) {
+ return processCharSet(codePoints[0]);
+ } else {
+ if (!hasFlag(TPattern.CASE_INSENSITIVE)) {
+ return new DecomposedCharSet(codePoints, readCodePoints);
+ } else if (!hasFlag(TPattern.UNICODE_CASE)) {
+ return new CIDecomposedCharSet(codePoints, readCodePoints);
+ } else {
+ return new UCIDecomposedCharSet(codePoints, readCodePoints);
+ }
+ }
+ }
+ }
+
+ /**
+ * S->BS; S->QS; S->Q; B->a+
+ */
+ private AbstractSet processSubExpression(AbstractSet last) {
+ AbstractSet cur;
+ if (lexemes.isLetter() && !lexemes.isNextSpecial() && Lexer.isLetter(lexemes.lookAhead())) {
+ if (hasFlag(TPattern.CANON_EQ)) {
+ cur = processDecomposedChar();
+ if (!lexemes.isEmpty()
+
+ /* && !pattern.isQuantifier() */
+ && (lexemes.peek() != Lexer.CHAR_RIGHT_PARENTHESIS || last instanceof FinalSet) &&
+ lexemes.peek() != Lexer.CHAR_VERTICAL_BAR && !lexemes.isLetter()) {
+ cur = processQuantifier(last, cur);
+ }
+ } else if (lexemes.isHighSurrogate() || lexemes.isLowSurrogate()) {
+ AbstractSet term = processTerminal(last);
+ cur = processQuantifier(last, term);
+ } else {
+ cur = processSequence();
+ }
+ } else if (lexemes.peek() == Lexer.CHAR_RIGHT_PARENTHESIS) {
+ if (last instanceof FinalSet) {
+ throw new TPatternSyntaxException("", lexemes.toString(), lexemes.getIndex());
+ } else {
+ cur = new EmptySet(last);
+ }
+ } else {
+ AbstractSet term = processTerminal(last);
+ cur = processQuantifier(last, term);
+ }
+
+ if (!lexemes.isEmpty()
+ // && !pattern.isQuantifier()
+ &&
+ (lexemes.peek() != Lexer.CHAR_RIGHT_PARENTHESIS || last instanceof FinalSet) &&
+ lexemes.peek() != Lexer.CHAR_VERTICAL_BAR) {
+ AbstractSet next = processSubExpression(last);
+ if (cur instanceof LeafQuantifierSet
+ // TODO create personal UnifiedQuantifierSet for composite
+ // quantifiers
+ // to take into account Quantifier counters
+ // ////
+ &&
+ !(cur instanceof CompositeQuantifierSet) &&
+ !(cur instanceof GroupQuantifierSet) &&
+ !(cur instanceof AltQuantifierSet) && !next.first(((LeafQuantifierSet)cur).getInnerSet())) {
+ cur = new UnifiedQuantifierSet((LeafQuantifierSet)cur);
+ }
+ if (((char)next.getType()) == '+') {
+ cur.setNext(((LeafQuantifierSet)next).getInnerSet());
+ } else {
+ cur.setNext(next);
+ }
+ } else if (cur != null) {
+ cur.setNext(last);
+ } else {
+ return null;
+ }
+
+ if (((char)cur.getType()) == '+') {
+ return ((QuantifierSet)cur).getInnerSet();
+ } else {
+ return cur;
+ }
+ }
+
+ /**
+ * Q->T(*|+|?...) also do some optimizations.
+ *
+ */
+ private AbstractSet processQuantifier(AbstractSet last, AbstractSet term) {
+ int quant = lexemes.peek();
+
+ if (term != null && !(term instanceof LeafSet)) {
+ switch (quant) {
+ case Lexer.QUANT_STAR:
+ case Lexer.QUANT_PLUS: {
+ QuantifierSet q;
+
+ lexemes.next();
+ if (term.getType() == AbstractSet.TYPE_DOTSET) {
+ if (!hasFlag(TPattern.DOTALL)) {
+ q = new DotQuantifierSet(term, last, quant, AbstractLineTerminator.getInstance(flags));
+ } else {
+ q = new DotAllQuantifierSet(term, last, quant);
+ }
+ } else {
+ q = new GroupQuantifierSet(term, last, quant);
+ }
+ term.setNext(q);
+ return q;
+ }
+
+ case Lexer.QUANT_STAR_R:
+ case Lexer.QUANT_PLUS_R: {
+ lexemes.next();
+ GroupQuantifierSet q = new ReluctantGroupQuantifierSet(term, last, quant);
+ term.setNext(q);
+ return q;
+ }
+
+ case Lexer.QUANT_PLUS_P: {
+ lexemes.next();
+ // possessive plus will be handled by unique class
+ // and should not be postprocessed to point previous set
+ // to the inner one.
+ // //
+ return new PosPlusGroupQuantifierSet(term, last, Lexer.QUANT_STAR_P);
+ }
+
+ case Lexer.QUANT_STAR_P: {
+ lexemes.next();
+ return new PossessiveGroupQuantifierSet(term, last, quant);
+ }
+
+ case Lexer.QUANT_ALT: {
+ lexemes.next();
+ AltGroupQuantifierSet q = new AltGroupQuantifierSet(term, last, Lexer.QUANT_ALT);
+ term.setNext(last);
+ return q;
+ }
+
+ case Lexer.QUANT_ALT_P: {
+ lexemes.next();
+ return new PosAltGroupQuantifierSet(term, last, Lexer.QUANT_ALT);
+ }
+
+ case Lexer.QUANT_ALT_R: {
+ lexemes.next();
+ RelAltGroupQuantifierSet q = new RelAltGroupQuantifierSet(term, last, Lexer.QUANT_ALT);
+ term.setNext(last);
+ return q;
+ }
+
+ case Lexer.QUANT_COMP: {
+ CompositeGroupQuantifierSet q = new CompositeGroupQuantifierSet((Quantifier)lexemes.nextSpecial(),
+ term, last, Lexer.QUANT_ALT, ++compCount);
+ term.setNext(q);
+ return q;
+ }
+
+ case Lexer.QUANT_COMP_P: {
+ return new PosCompositeGroupQuantifierSet((Quantifier)lexemes.nextSpecial(), term, last,
+ Lexer.QUANT_ALT, ++compCount);
+ }
+
+ case Lexer.QUANT_COMP_R: {
+ RelCompositeGroupQuantifierSet q = new RelCompositeGroupQuantifierSet(
+ (Quantifier)lexemes.nextSpecial(), term, last, Lexer.QUANT_ALT, ++compCount);
+ term.setNext(q);
+ return q;
+ }
+
+ default:
+ return term;
+ }
+ } else {
+ LeafSet leaf = null;
+ if (term != null)
+ leaf = (LeafSet)term;
+ switch (quant) {
+ case Lexer.QUANT_STAR:
+ case Lexer.QUANT_PLUS: {
+ lexemes.next();
+ LeafQuantifierSet q = new LeafQuantifierSet(leaf, last, quant);
+ leaf.setNext(q);
+ return q;
+ }
+
+ case Lexer.QUANT_STAR_R:
+ case Lexer.QUANT_PLUS_R: {
+ lexemes.next();
+ ReluctantQuantifierSet q = new ReluctantQuantifierSet(leaf, last, quant);
+ leaf.setNext(q);
+ return q;
+ }
+
+ case Lexer.QUANT_PLUS_P:
+ case Lexer.QUANT_STAR_P: {
+ lexemes.next();
+ PossessiveQuantifierSet q = new PossessiveQuantifierSet(leaf, last, quant);
+ leaf.setNext(q);
+ return q;
+ }
+
+ case Lexer.QUANT_ALT: {
+ lexemes.next();
+ return new AltQuantifierSet(leaf, last, Lexer.QUANT_ALT);
+ }
+
+ case Lexer.QUANT_ALT_P: {
+ lexemes.next();
+ return new PossessiveAltQuantifierSet(leaf, last, Lexer.QUANT_ALT_P);
+ }
+
+ case Lexer.QUANT_ALT_R: {
+ lexemes.next();
+ return new ReluctantAltQuantifierSet(leaf, last, Lexer.QUANT_ALT_R);
+ }
+
+ case Lexer.QUANT_COMP: {
+ return new CompositeQuantifierSet((Quantifier)lexemes.nextSpecial(), leaf, last, Lexer.QUANT_COMP);
+ }
+
+ case Lexer.QUANT_COMP_P: {
+ return new PossessiveCompositeQuantifierSet((Quantifier)lexemes.nextSpecial(), leaf, last,
+ Lexer.QUANT_COMP_P);
+ }
+ case Lexer.QUANT_COMP_R: {
+ return new ReluctantCompositeQuantifierSet((Quantifier)lexemes.nextSpecial(), leaf, last,
+ Lexer.QUANT_COMP_R);
+ }
+
+ default:
+ return term;
+ }
+ }
+ }
+
+ /**
+ * T-> letter|[range]|{char-class}|(E)
+ */
+ private AbstractSet processTerminal(AbstractSet last) {
+ int ch;
+ AbstractSet term = null;
+ do {
+ ch = lexemes.peek();
+ if ((ch & 0x8000ffff) == Lexer.CHAR_LEFT_PARENTHESIS) {
+ int newFlags;
+ lexemes.next();
+ newFlags = (ch & 0x00ff0000) >> 16;
+ ch = ch & 0xff00ffff;
+ if (ch == Lexer.CHAR_FLAGS) {
+ flags = newFlags;
+ } else {
+ newFlags = (ch == Lexer.CHAR_NONCAP_GROUP) ? newFlags : flags;
+ term = processExpression(ch, newFlags, last);
+ if (lexemes.peek() != Lexer.CHAR_RIGHT_PARENTHESIS) {
+ throw new TPatternSyntaxException("", lexemes.toString(), lexemes.getIndex());
+ }
+ lexemes.next();
+ }
+ } else
+ switch (ch) {
+ case Lexer.CHAR_LEFT_SQUARE_BRACKET: {
+ lexemes.next();
+ boolean negative = false;
+ if (lexemes.peek() == Lexer.CHAR_CARET) {
+ negative = true;
+ lexemes.next();
+ }
+
+ term = processRange(negative, last);
+ if (lexemes.peek() != Lexer.CHAR_RIGHT_SQUARE_BRACKET)
+ throw new TPatternSyntaxException("", lexemes.toString(), lexemes.getIndex());
+ lexemes.setMode(Lexer.MODE_PATTERN);
+ lexemes.next();
+ break;
+ }
+
+ case Lexer.CHAR_DOT: {
+ lexemes.next();
+
+ if (!hasFlag(TPattern.DOTALL)) {
+ term = new DotSet(AbstractLineTerminator.getInstance(flags));
+ } else {
+ term = new DotAllSet();
+ }
+
+ break;
+ }
+
+ case Lexer.CHAR_CARET: {
+ lexemes.next();
+ consCount++;
+ if (!hasFlag(TPattern.MULTILINE)) {
+ term = new SOLSet();
+ } else {
+ term = new MultiLineSOLSet(AbstractLineTerminator.getInstance(flags));
+ }
+
+ break;
+ }
+
+ case Lexer.CHAR_DOLLAR: {
+ lexemes.next();
+ consCount++;
+ if (!hasFlag(TPattern.MULTILINE)) {
+ if (!hasFlag(TPattern.UNIX_LINES)) {
+ term = new EOLSet(consCount);
+ } else {
+ term = new UEOLSet(consCount);
+ }
+ } else {
+ if (!hasFlag(TPattern.UNIX_LINES)) {
+ term = new MultiLineEOLSet(consCount);
+ } else {
+ term = new UMultiLineEOLSet(consCount);
+ }
+ }
+
+ break;
+ }
+
+ case Lexer.CHAR_WORD_BOUND: {
+ lexemes.next();
+ term = new WordBoundary(true);
+ break;
+ }
+
+ case Lexer.CHAR_NONWORD_BOUND: {
+ lexemes.next();
+ term = new WordBoundary(false);
+ break;
+ }
+
+ case Lexer.CHAR_END_OF_INPUT: {
+ lexemes.next();
+ term = new EOISet();
+ break;
+ }
+
+ case Lexer.CHAR_END_OF_LINE: {
+ lexemes.next();
+ term = new EOLSet(++consCount);
+ break;
+ }
+
+ case Lexer.CHAR_START_OF_INPUT: {
+ lexemes.next();
+ term = new SOLSet();
+ break;
+ }
+
+ case Lexer.CHAR_PREVIOUS_MATCH: {
+ lexemes.next();
+ term = new PreviousMatch();
+ break;
+ }
+
+ case 0x80000000 | '1':
+ case 0x80000000 | '2':
+ case 0x80000000 | '3':
+ case 0x80000000 | '4':
+ case 0x80000000 | '5':
+ case 0x80000000 | '6':
+ case 0x80000000 | '7':
+ case 0x80000000 | '8':
+ case 0x80000000 | '9': {
+ int number = (ch & 0x7FFFFFFF) - '0';
+ if (globalGroupIndex >= number) {
+ lexemes.next();
+ consCount++;
+ if (!hasFlag(TPattern.CASE_INSENSITIVE)) {
+ term = new BackReferenceSet(number, consCount);
+ } else if (!hasFlag(TPattern.UNICODE_CASE)) {
+ term = new CIBackReferenceSet(number, consCount);
+ } else {
+ term = new UCIBackReferenceSet(number, consCount);
+ }
+ (backRefs[number]).isBackReferenced = true;
+ needsBackRefReplacement = true;
+ break;
+ } else {
+ throw new TPatternSyntaxException("", lexemes.toString(), lexemes.getIndex());
+ }
+ }
+
+ case 0: {
+ AbstractCharClass cc = null;
+ if ((cc = (AbstractCharClass)lexemes.peekSpecial()) != null) {
+ term = processRangeSet(cc);
+ } else if (!lexemes.isEmpty()) {
+
+ // ch == 0
+ term = new CharSet((char)ch);
+ } else {
+ term = new EmptySet(last);
+ break;
+ }
+ lexemes.next();
+ break;
+ }
+
+ default: {
+ if (ch >= 0 && !lexemes.isSpecial()) {
+ term = processCharSet(ch);
+ lexemes.next();
+ } else if (ch == Lexer.CHAR_VERTICAL_BAR) {
+ term = new EmptySet(last);
+ } else if (ch == Lexer.CHAR_RIGHT_PARENTHESIS) {
+ if (last instanceof FinalSet) {
+ throw new TPatternSyntaxException("", lexemes.toString(), lexemes.getIndex());
+ } else {
+ term = new EmptySet(last);
+ }
+ } else {
+ throw new TPatternSyntaxException((lexemes.isSpecial() ? lexemes.peekSpecial().toString()
+ : Character.toString((char)ch)), lexemes.toString(), lexemes.getIndex());
+ }
+ }
+ }
+ } while (ch == Lexer.CHAR_FLAGS);
+ return term;
+ }
+
+ private AbstractSet processRange(boolean negative, AbstractSet last) {
+ AbstractCharClass res = processRangeExpression(negative);
+ AbstractSet rangeSet = processRangeSet(res);
+ rangeSet.setNext(last);
+
+ return rangeSet;
+ }
+
+ /**
+ * process [...] ranges
+ */
+ private CharClass processRangeExpression(boolean alt) {
+ CharClass res = new CharClass(alt, hasFlag(TPattern.CASE_INSENSITIVE), hasFlag(TPattern.UNICODE_CASE));
+ int buffer = -1;
+ boolean intersection = false;
+ boolean notClosed = false;
+ boolean firstInClass = true;
+
+ while (!lexemes.isEmpty() && (notClosed = (lexemes.peek()) != Lexer.CHAR_RIGHT_SQUARE_BRACKET || firstInClass)) {
+ switch (lexemes.peek()) {
+
+ case Lexer.CHAR_RIGHT_SQUARE_BRACKET: {
+ if (buffer >= 0)
+ res.add(buffer);
+ buffer = ']';
+ lexemes.next();
+ break;
+ }
+ case Lexer.CHAR_LEFT_SQUARE_BRACKET: {
+ if (buffer >= 0) {
+ res.add(buffer);
+ buffer = -1;
+ }
+ lexemes.next();
+ boolean negative = false;
+ if (lexemes.peek() == Lexer.CHAR_CARET) {
+ lexemes.next();
+ negative = true;
+ }
+
+ if (intersection)
+ res.intersection(processRangeExpression(negative));
+ else
+ res.union(processRangeExpression(negative));
+ intersection = false;
+ lexemes.next();
+ break;
+ }
+
+ case Lexer.CHAR_AMPERSAND: {
+ if (buffer >= 0)
+ res.add(buffer);
+ buffer = lexemes.next();
+
+ /*
+ * if there is a start for subrange we will do an
+ * intersection otherwise treat '&' as a normal character
+ */
+ if (lexemes.peek() == Lexer.CHAR_AMPERSAND) {
+ if (lexemes.lookAhead() == Lexer.CHAR_LEFT_SQUARE_BRACKET) {
+ lexemes.next();
+ intersection = true;
+ buffer = -1;
+ } else {
+ lexemes.next();
+ if (firstInClass) {
+
+ // skip "&&" at "[&&...]" or "[^&&...]"
+ res = processRangeExpression(false);
+ } else {
+
+ // ignore "&&" at "[X&&]" ending where X !=
+ // empty string
+ if (!(lexemes.peek() == Lexer.CHAR_RIGHT_SQUARE_BRACKET)) {
+ res.intersection(processRangeExpression(false));
+ }
+ }
+
+ }
+ } else {
+
+ // treat '&' as a normal character
+ buffer = '&';
+ }
+
+ break;
+ }
+
+ case Lexer.CHAR_HYPHEN: {
+ if (firstInClass || lexemes.lookAhead() == Lexer.CHAR_RIGHT_SQUARE_BRACKET ||
+ lexemes.lookAhead() == Lexer.CHAR_LEFT_SQUARE_BRACKET || buffer < 0) {
+ // treat hypen as normal character
+ if (buffer >= 0)
+ res.add(buffer);
+ buffer = '-';
+ lexemes.next();
+ // range
+ } else {
+ lexemes.next();
+ int cur = lexemes.peek();
+
+ if (!lexemes.isSpecial() &&
+ (cur >= 0 || lexemes.lookAhead() == Lexer.CHAR_RIGHT_SQUARE_BRACKET ||
+ lexemes.lookAhead() == Lexer.CHAR_LEFT_SQUARE_BRACKET || buffer < 0)) {
+
+ try {
+ if (!Lexer.isLetter(cur)) {
+ cur = cur & 0xFFFF;
+ }
+ res.add(buffer, cur);
+ } catch (Exception e) {
+ throw new TPatternSyntaxException("", pattern(), lexemes.getIndex());
+ }
+ lexemes.next();
+ buffer = -1;
+ } else {
+ throw new TPatternSyntaxException("", pattern(), lexemes.getIndex());
+ }
+ }
+
+ break;
+ }
+
+ case Lexer.CHAR_CARET: {
+ if (buffer >= 0)
+ res.add(buffer);
+ buffer = '^';
+ lexemes.next();
+ break;
+ }
+
+ case 0: {
+ if (buffer >= 0)
+ res.add(buffer);
+ AbstractCharClass cs = (AbstractCharClass)lexemes.peekSpecial();
+ if (cs != null) {
+ res.add(cs);
+ buffer = -1;
+ } else {
+ buffer = 0;
+ }
+
+ lexemes.next();
+ break;
+ }
+
+ default: {
+ if (buffer >= 0)
+ res.add(buffer);
+ buffer = lexemes.next();
+ break;
+ }
+ }
+
+ firstInClass = false;
+ }
+ if (notClosed) {
+ throw new TPatternSyntaxException("", pattern(), lexemes.getIndex() - 1);
+ }
+ if (buffer >= 0)
+ res.add(buffer);
+ return res;
+ }
+
+ private AbstractSet processCharSet(int ch) {
+ boolean isSupplCodePoint = Character.isSupplementaryCodePoint(ch);
+
+ if (hasFlag(TPattern.CASE_INSENSITIVE)) {
+
+ if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) {
+ return new CICharSet((char)ch);
+ } else if (hasFlag(TPattern.UNICODE_CASE) && ch > 128) {
+ if (isSupplCodePoint) {
+ return new UCISupplCharSet(ch);
+ } else if (Lexer.isLowSurrogate(ch)) {
+
+ // we need no UCILowSurrogateCharSet
+ return new LowSurrogateCharSet((char)ch);
+ } else if (Lexer.isHighSurrogate(ch)) {
+
+ // we need no UCIHighSurrogateCharSet
+ return new HighSurrogateCharSet((char)ch);
+ } else {
+ return new UCICharSet((char)ch);
+ }
+ }
+ }
+
+ if (isSupplCodePoint) {
+ return new SupplCharSet(ch);
+ } else if (Lexer.isLowSurrogate(ch)) {
+ return new LowSurrogateCharSet((char)ch);
+ } else if (Lexer.isHighSurrogate(ch)) {
+ return new HighSurrogateCharSet((char)ch);
+ } else {
+ return new CharSet((char)ch);
+ }
+ }
+
+ private AbstractSet processRangeSet(AbstractCharClass charClass) {
+ if (charClass.hasLowHighSurrogates()) {
+ AbstractCharClass surrogates = charClass.getSurrogates();
+ LowHighSurrogateRangeSet lowHighSurrRangeSet = new LowHighSurrogateRangeSet(surrogates);
+
+ if (charClass.mayContainSupplCodepoints()) {
+ if (!charClass.hasUCI()) {
+ return new CompositeRangeSet(new SupplRangeSet(charClass.getWithoutSurrogates()),
+ lowHighSurrRangeSet);
+ } else {
+ return new CompositeRangeSet(new UCISupplRangeSet(charClass.getWithoutSurrogates()),
+ lowHighSurrRangeSet);
+ }
+ }
+
+ if (!charClass.hasUCI()) {
+ return new CompositeRangeSet(new RangeSet(charClass.getWithoutSurrogates()), lowHighSurrRangeSet);
+ } else {
+ return new CompositeRangeSet(new UCIRangeSet(charClass.getWithoutSurrogates()), lowHighSurrRangeSet);
+ }
+ }
+
+ if (charClass.mayContainSupplCodepoints()) {
+ if (!charClass.hasUCI()) {
+ return new SupplRangeSet(charClass);
+ } else {
+ return new UCISupplRangeSet(charClass);
+ }
+ }
+
+ if (!charClass.hasUCI()) {
+ return new RangeSet(charClass);
+ } else {
+ return new UCIRangeSet(charClass);
+ }
+ }
+
+ /**
+ * Compiles a regular expression, creating a new Pattern instance in the
+ * process. This is actually a convenience method that calls
+ * {@link #compile(String, int)} with a {@code flags} value of zero.
+ *
+ * @param pattern
+ * the regular expression.
+ *
+ * @return the new {@code Pattern} instance.
+ *
+ * @throws TPatternSyntaxException
+ * if the regular expression is syntactically incorrect.
+ */
+ public static TPattern compile(String pattern) {
+ return compile(pattern, 0);
+ }
+
+ /*
+ * This method do traverses of automata to finish compilation.
+ */
+ private void finalizeCompile() {
+
+ /*
+ * Processing second pass
+ */
+ if (needsBackRefReplacement) { // || needsReason1 || needsReason2) {
+ start.processSecondPass();
+ }
+
+ }
+
+ /**
+ * Tries to match a given regular expression against a given input. This is
+ * actually nothing but a convenience method that compiles the regular
+ * expression into a {@code Pattern}, builds a {@link TMatcher} for it, and
+ * then does the match. If the same regular expression is used for multiple
+ * operations, it is recommended to compile it into a {@code Pattern}
+ * explicitly and request a reusable {@code Matcher}.
+ *
+ * @param regex
+ * the regular expression.
+ * @param input
+ * the input to process.
+ *
+ * @return true if and only if the {@code Pattern} matches the input.
+ *
+ * @see TPattern#compile(java.lang.String, int)
+ * @see TMatcher#matches()
+ */
+ public static boolean matches(String regex, CharSequence input) {
+ return TPattern.compile(regex).matcher(input).matches();
+ }
+
+ /**
+ * Quotes a given string using "\Q" and "\E", so that all other
+ * meta-characters lose their special meaning. If the string is used for a
+ * {@code Pattern} afterwards, it can only be matched literally.
+ *
+ * @param s
+ * the string to quote.
+ *
+ * @return the quoted string.
+ */
+ public static String quote(String s) {
+ StringBuilder sb = new StringBuilder().append("\\Q"); //$NON-NLS-1$
+ int apos = 0;
+ int k;
+ while ((k = s.indexOf("\\E", apos)) >= 0) { //$NON-NLS-1$
+ sb.append(s.substring(apos, k + 2)).append("\\\\E\\Q"); //$NON-NLS-1$
+ apos = k + 2;
+ }
+
+ return sb.append(s.substring(apos)).append("\\E").toString(); //$NON-NLS-1$
+ }
+
+ /**
+ * return number of groups found at compile time
+ */
+ int groupCount() {
+ return globalGroupIndex;
+ }
+
+ int compCount() {
+ return this.compCount + 1;
+ }
+
+ int consCount() {
+ return this.consCount + 1;
+ }
+
+ /**
+ * Returns supplementary character. At this time only for ASCII chars.
+ */
+ static char getSupplement(char ch) {
+ char res = ch;
+ if (ch >= 'a' && ch <= 'z') {
+ res -= 32;
+ } else if (ch >= 'A' && ch <= 'Z') {
+ res += 32;
+ }
+
+ return res;
+ }
+
+ /**
+ * @return true if pattern has specified flag
+ */
+ private boolean hasFlag(int flag) {
+ return (flags & flag) == flag;
+ }
+
+ /**
+ * Dismiss public constructor.
+ *
+ */
+ private TPattern() {
+ }
+
+ /**
+ * Serialization support
+ */
+ private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException {
+ s.defaultReadObject();
+ AbstractSet.counter = 1;
+ globalGroupIndex = -1;
+ compCount = -1;
+ consCount = -1;
+ backRefs = new FSet[BACK_REF_NUMBER];
+
+ compileImpl(pattern, flags);
+
+ }
+}
diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/util/regex/TPatternSyntaxException.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/regex/TPatternSyntaxException.java
new file mode 100644
index 000000000..6ad5dbd51
--- /dev/null
+++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/regex/TPatternSyntaxException.java
@@ -0,0 +1,125 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+/**
+ * @author Nikolay A. Kuznetsov
+ */
+package org.teavm.classlib.java.util.regex;
+
+import java.util.Arrays;
+
+/**
+ * Encapsulates a syntax error that occurred during the compilation of a
+ * {@link TPattern}. Might include a detailed description, the original regular
+ * expression, and the index at which the error occurred.
+ *
+ * @see TPattern#compile(String)
+ * @see TPattern#compile(java.lang.String,int)
+ *
+ * @author Nikolay A. Kuznetsov
+ */
+public class TPatternSyntaxException extends IllegalArgumentException {
+
+ private static final long serialVersionUID = -3864639126226059218L;
+
+ /**
+ * Holds the description of the syntax error, or null if the description is
+ * not known.
+ */
+ private String desc;
+
+ /**
+ * Holds the syntactically incorrect regular expression, or null if the
+ * regular expression is not known.
+ */
+ private String pattern;
+
+ /**
+ * Holds the index around which the error occured, or -1, in case it is
+ * unknown.
+ */
+ private int index = -1;
+
+ /**
+ * Creates a new PatternSyntaxException for a given message, pattern, and
+ * error index.
+ *
+ * @param description
+ * the description of the syntax error, or {@code null} if the
+ * description is not known.
+ * @param pattern
+ * the syntactically incorrect regular expression, or
+ * {@code null} if the regular expression is not known.
+ * @param index
+ * the character index around which the error occurred, or -1 if
+ * the index is not known.
+ */
+ public TPatternSyntaxException(String description, String pattern, int index) {
+ this.desc = description;
+ this.pattern = pattern;
+ this.index = index;
+ }
+
+ /**
+ * Returns the syntactically incorrect regular expression.
+ *
+ * @return the regular expression.
+ *
+ */
+ public String getPattern() {
+ return pattern;
+ }
+
+ /**
+ * Returns a detailed error message for the exception. The message is
+ * potentially multi-line, and it might include a detailed description, the
+ * original regular expression, and the index at which the error occured.
+ *
+ * @return the error message.
+ */
+ @Override
+ public String getMessage() {
+ String filler = "";
+ if (index >= 1) {
+ char[] temp = new char[index];
+ Arrays.fill(temp, ' ');
+ filler = new String(temp);
+ }
+ return desc + ((pattern != null && pattern.length() != 0) ? index + ", " + pattern + ", " + filler : "");
+ }
+
+ /**
+ * Returns the description of the syntax error, or {@code null} if the
+ * description is not known.
+ *
+ * @return the description.
+ */
+ public String getDescription() {
+ return desc;
+ }
+
+ /**
+ * Returns the character index around which the error occurred, or -1 if the
+ * index is not known.
+ *
+ * @return the index.
+ *
+ */
+ public int getIndex() {
+ return index;
+ }
+}
diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/util/regex/UCIBackReferenceSet.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/regex/UCIBackReferenceSet.java
new file mode 100644
index 000000000..cb71d0173
--- /dev/null
+++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/regex/UCIBackReferenceSet.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+/**
+ * @author Nikolay A. Kuznetsov
+ */
+package org.teavm.classlib.java.util.regex;
+
+/**
+ * Unicode case insensitive back reference (i.e. \1-9) node.
+ *
+ * @author Nikolay A. Kuznetsov
+ */
+class UCIBackReferenceSet extends CIBackReferenceSet {
+
+ int groupIndex;
+
+ public UCIBackReferenceSet(int groupIndex, int consCounter) {
+ super(groupIndex, consCounter);
+ }
+
+ @Override
+ public int matches(int stringIndex, CharSequence testString, MatchResultImpl matchResult) {
+ String group = getString(matchResult);
+
+ if (group == null || (stringIndex + group.length()) > matchResult.getRightBound())
+ return -1;
+
+ for (int i = 0; i < group.length(); i++) {
+ if (Character.toLowerCase(Character.toUpperCase(group.charAt(i))) != Character.toLowerCase(Character
+ .toUpperCase(testString.charAt(stringIndex + i)))) {
+ return -1;
+ }
+ }
+ matchResult.setConsumed(consCounter, group.length());
+ return next.matches(stringIndex + group.length(), testString, matchResult);
+ }
+
+ @Override
+ public String getName() {
+ return "UCI back reference: " + this.groupIndex; //$NON-NLS-1$
+ }
+}
diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/util/regex/UCICharSet.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/regex/UCICharSet.java
new file mode 100644
index 000000000..cec7ceb9b
--- /dev/null
+++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/regex/UCICharSet.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+/**
+ * @author Nikolay A. Kuznetsov
+ */
+package org.teavm.classlib.java.util.regex;
+
+/**
+ * Represents node accepting single character in unicode case
+ * insensitive manner.
+ *
+ * @author Nikolay A. Kuznetsov
+ */
+class UCICharSet extends LeafSet {
+
+ private char ch;
+
+ public UCICharSet(char ch) {
+ this.ch = Character.toLowerCase(Character.toUpperCase(ch));
+ }
+
+ @Override
+ public int accepts(int strIndex, CharSequence testString) {
+ return (this.ch == Character.toLowerCase(Character
+ .toUpperCase(testString.charAt(strIndex)))) ? 1 : -1;
+ }
+
+ @Override
+ protected String getName() {
+ return "UCI " + ch;
+ }
+}
\ No newline at end of file
diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/util/regex/UCIDecomposedCharSet.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/regex/UCIDecomposedCharSet.java
new file mode 100644
index 000000000..e95b6cd6b
--- /dev/null
+++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/regex/UCIDecomposedCharSet.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.teavm.classlib.java.util.regex;
+
+/**
+ * Represents Unicode case insensitive
+ * canonical decomposition of
+ * Unicode character. Is used when
+ * CANON_EQ flag of Pattern class
+ * is specified.
+ */
+class UCIDecomposedCharSet extends DecomposedCharSet{
+
+ /*
+ * Just only a stub
+ */
+ public UCIDecomposedCharSet(int [] decomp, int decomposedCharLength) {
+ super(decomp, decomposedCharLength);
+ }
+}
\ No newline at end of file
diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/util/regex/UCIRangeSet.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/regex/UCIRangeSet.java
new file mode 100644
index 000000000..1ef12365f
--- /dev/null
+++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/regex/UCIRangeSet.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+/**
+ * @author Nikolay A. Kuznetsov
+ */
+package org.teavm.classlib.java.util.regex;
+
+/**
+ * Represents node accepting single character from the given char class. Note,
+ * this class contains normalized characters fo unicode case, asci case is
+ * supported through adding both symbols to the range.
+ *
+ * @author Nikolay A. Kuznetsov
+ */
+class UCIRangeSet extends LeafSet {
+
+ private AbstractCharClass chars;
+
+ private boolean alt = false;
+
+ public UCIRangeSet(AbstractCharClass cs, AbstractSet next) {
+ super(next);
+ this.chars = cs.getInstance();
+ this.alt = cs.alt;
+ }
+
+ public UCIRangeSet(AbstractCharClass cc) {
+ this.chars = cc.getInstance();
+ this.alt = cc.alt;
+ }
+
+ @Override
+ public int accepts(int strIndex, CharSequence testString) {
+ return (chars.contains(Character.toLowerCase(Character
+ .toUpperCase(testString.charAt(strIndex))))) ? 1 : -1;
+ }
+
+ @Override
+ protected String getName() {
+ return "UCI range:" + (alt ? "^ " : " ") + chars.toString();
+ }
+}
\ No newline at end of file
diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/util/regex/UCISequenceSet.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/regex/UCISequenceSet.java
new file mode 100644
index 000000000..f05441e01
--- /dev/null
+++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/regex/UCISequenceSet.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+/**
+ * @author Nikolay A. Kuznetsov
+ */
+package org.teavm.classlib.java.util.regex;
+
+/**
+ * Node accepting substrings in unicode case insensitive manner.
+ *
+ * @author Nikolay A. Kuznetsov
+ */
+class UCISequenceSet extends LeafSet {
+
+ private String string = null;
+
+ UCISequenceSet(StringBuffer substring) {
+ StringBuilder res = new StringBuilder();
+ for (int i = 0; i < substring.length(); i++) {
+ res.append(Character.toLowerCase(Character.toUpperCase(substring.charAt(i))));
+ }
+ this.string = res.toString();
+ this.charCount = res.length();
+ }
+
+ @Override
+ public int accepts(int strIndex, CharSequence testString) {
+ for (int i = 0; i < string.length(); i++) {
+ if (string.charAt(i) != Character.toLowerCase(Character.toUpperCase(testString.charAt(strIndex + i)))) {
+ return -1;
+ }
+ }
+
+ return string.length();
+
+ }
+
+ @Override
+ public String getName() {
+ return "UCI sequence: " + string; //$NON-NLS-1$
+ }
+}
diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/util/regex/UCISupplCharSet.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/regex/UCISupplCharSet.java
new file mode 100644
index 000000000..ea64b1661
--- /dev/null
+++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/regex/UCISupplCharSet.java
@@ -0,0 +1,110 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+/*
+ *
+ * Portions, Copyright © 1991-2005 Unicode, Inc. The following applies to Unicode.
+ *
+ * COPYRIGHT AND PERMISSION NOTICE
+ *
+ * Copyright © 1991-2005 Unicode, Inc. All rights reserved. Distributed under
+ * the Terms of Use in http://www.unicode.org/copyright.html. Permission is
+ * hereby granted, free of charge, to any person obtaining a copy of the
+ * Unicode data files and any associated documentation (the "Data Files")
+ * or Unicode software and any associated documentation (the "Software")
+ * to deal in the Data Files or Software without restriction, including without
+ * limitation the rights to use, copy, modify, merge, publish, distribute,
+ * and/or sell copies of the Data Files or Software, and to permit persons
+ * to whom the Data Files or Software are furnished to do so, provided that
+ * (a) the above copyright notice(s) and this permission notice appear with
+ * all copies of the Data Files or Software, (b) both the above copyright
+ * notice(s) and this permission notice appear in associated documentation,
+ * and (c) there is clear notice in each modified Data File or in the Software
+ * as well as in the documentation associated with the Data File(s) or Software
+ * that the data or software has been modified.
+
+ * THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+ * KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+ * OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
+ * INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
+ * OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+ * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+ * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ * PERFORMANCE OF THE DATA FILES OR SOFTWARE.
+ *
+ * Except as contained in this notice, the name of a copyright holder shall
+ * not be used in advertising or otherwise to promote the sale, use or other
+ * dealings in these Data Files or Software without prior written
+ * authorization of the copyright holder.
+ *
+ * 2. Additional terms from the Database:
+ *
+ * Copyright © 1995-1999 Unicode, Inc. All Rights reserved.
+ *
+ * Disclaimer
+ *
+ * The Unicode Character Database is provided as is by Unicode, Inc.
+ * No claims are made as to fitness for any particular purpose. No warranties
+ * of any kind are expressed or implied. The recipient agrees to determine
+ * applicability of information provided. If this file has been purchased
+ * on magnetic or optical media from Unicode, Inc., the sole remedy for any claim
+ * will be exchange of defective media within 90 days of receipt. This disclaimer
+ * is applicable for all other data files accompanying the Unicode Character Database,
+ * some of which have been compiled by the Unicode Consortium, and some of which
+ * have been supplied by other sources.
+ *
+ * Limitations on Rights to Redistribute This Data
+ *
+ * Recipient is granted the right to make copies in any form for internal
+ * distribution and to freely use the information supplied in the creation of
+ * products supporting the UnicodeTM Standard. The files in
+ * the Unicode Character Database can be redistributed to third parties or other
+ * organizations (whether for profit or not) as long as this notice and the disclaimer
+ * notice are retained. Information can be extracted from these files and used
+ * in documentation or programs, as long as there is an accompanying notice
+ * indicating the source.
+ */
+
+package org.teavm.classlib.java.util.regex;
+
+/**
+ * Represents node accepting single supplementary codepoint in Unicode case
+ * insensitive manner.
+ */
+class UCISupplCharSet extends LeafSet {
+
+ // int value of this supplementary codepoint
+ private int ch;
+
+ public UCISupplCharSet(int ch) {
+ charCount = 2;
+ this.ch = Character.toLowerCase(Character.toUpperCase(ch));
+ }
+
+ @Override
+ public int accepts(int strIndex, CharSequence testString) {
+ char high = testString.charAt(strIndex++);
+ char low = testString.charAt(strIndex);
+ return (this.ch == Character.toLowerCase(Character.toUpperCase(Character.toCodePoint(high, low)))) ? 2 : -1;
+ }
+
+ @Override
+ protected String getName() {
+ return "UCI " + new String(Character.toChars(ch));
+ }
+}
diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/util/regex/UCISupplRangeSet.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/regex/UCISupplRangeSet.java
new file mode 100644
index 000000000..8aa2f2837
--- /dev/null
+++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/regex/UCISupplRangeSet.java
@@ -0,0 +1,110 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+/*
+ *
+ * Portions, Copyright © 1991-2005 Unicode, Inc. The following applies to Unicode.
+ *
+ * COPYRIGHT AND PERMISSION NOTICE
+ *
+ * Copyright © 1991-2005 Unicode, Inc. All rights reserved. Distributed under
+ * the Terms of Use in http://www.unicode.org/copyright.html. Permission is
+ * hereby granted, free of charge, to any person obtaining a copy of the
+ * Unicode data files and any associated documentation (the "Data Files")
+ * or Unicode software and any associated documentation (the "Software")
+ * to deal in the Data Files or Software without restriction, including without
+ * limitation the rights to use, copy, modify, merge, publish, distribute,
+ * and/or sell copies of the Data Files or Software, and to permit persons
+ * to whom the Data Files or Software are furnished to do so, provided that
+ * (a) the above copyright notice(s) and this permission notice appear with
+ * all copies of the Data Files or Software, (b) both the above copyright
+ * notice(s) and this permission notice appear in associated documentation,
+ * and (c) there is clear notice in each modified Data File or in the Software
+ * as well as in the documentation associated with the Data File(s) or Software
+ * that the data or software has been modified.
+
+ * THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+ * KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+ * OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
+ * INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
+ * OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+ * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+ * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ * PERFORMANCE OF THE DATA FILES OR SOFTWARE.
+ *
+ * Except as contained in this notice, the name of a copyright holder shall
+ * not be used in advertising or otherwise to promote the sale, use or other
+ * dealings in these Data Files or Software without prior written
+ * authorization of the copyright holder.
+ *
+ * 2. Additional terms from the Database:
+ *
+ * Copyright © 1995-1999 Unicode, Inc. All Rights reserved.
+ *
+ * Disclaimer
+ *
+ * The Unicode Character Database is provided as is by Unicode, Inc.
+ * No claims are made as to fitness for any particular purpose. No warranties
+ * of any kind are expressed or implied. The recipient agrees to determine
+ * applicability of information provided. If this file has been purchased
+ * on magnetic or optical media from Unicode, Inc., the sole remedy for any claim
+ * will be exchange of defective media within 90 days of receipt. This disclaimer
+ * is applicable for all other data files accompanying the Unicode Character Database,
+ * some of which have been compiled by the Unicode Consortium, and some of which
+ * have been supplied by other sources.
+ *
+ * Limitations on Rights to Redistribute This Data
+ *
+ * Recipient is granted the right to make copies in any form for internal
+ * distribution and to freely use the information supplied in the creation of
+ * products supporting the UnicodeTM Standard. The files in
+ * the Unicode Character Database can be redistributed to third parties or other
+ * organizations (whether for profit or not) as long as this notice and the disclaimer
+ * notice are retained. Information can be extracted from these files and used
+ * in documentation or programs, as long as there is an accompanying notice
+ * indicating the source.
+ */
+
+package org.teavm.classlib.java.util.regex;
+
+/**
+ * Represents node accepting single character from the given char class
+ * in Unicode case insensitive manner.
+ * This character can be supplementary (2 chars to represent) or from
+ * basic multilingual pane (1 char to represent).
+ */
+class UCISupplRangeSet extends SupplRangeSet{
+
+ public UCISupplRangeSet(AbstractCharClass cs, AbstractSet next) {
+ super(cs, next);
+ }
+
+ public UCISupplRangeSet(AbstractCharClass cc) {
+ super(cc);
+ }
+
+ @Override
+ public boolean contains(int ch) {
+ return chars.contains(Character.toLowerCase(Character.toUpperCase(ch)));
+ }
+
+ @Override
+ protected String getName() {
+ return "UCI range:" + (alt ? "^ " : " ") + chars.toString();
+ }
+}
diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/util/regex/UEOLSet.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/regex/UEOLSet.java
new file mode 100644
index 000000000..f34431102
--- /dev/null
+++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/regex/UEOLSet.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+/**
+ * @author Nikolay A. Kuznetsov
+ */
+package org.teavm.classlib.java.util.regex;
+
+/**
+ * Unix line terminator, accepting only \n.
+ *
+ * @author Nikolay A. Kuznetsov
+ */
+final class UEOLSet extends AbstractSet {
+
+ private int consCounter;
+
+ public UEOLSet(int counter) {
+ this.consCounter = counter;
+ }
+
+ @Override
+ public int matches(int strIndex, CharSequence testString, MatchResultImpl matchResult) {
+ int rightBound = matchResult.hasAnchoringBounds() ? matchResult.getRightBound() : testString.length();
+
+ if (strIndex >= rightBound) {
+ matchResult.setConsumed(consCounter, 0);
+ return next.matches(strIndex, testString, matchResult);
+ }
+ // check final line terminator;
+
+ if ((rightBound - strIndex) == 1 && testString.charAt(strIndex) == '\n') {
+ matchResult.setConsumed(consCounter, 1);
+ return next.matches(strIndex + 1, testString, matchResult);
+ }
+
+ return -1;
+ }
+
+ @Override
+ public boolean hasConsumed(MatchResultImpl matchResult) {
+ int cons;
+ boolean res = ((cons = matchResult.getConsumed(consCounter)) < 0 || cons > 0);
+ matchResult.setConsumed(consCounter, -1);
+ return res;
+ }
+
+ @Override
+ protected String getName() {
+ return ""; //$NON-NLS-1$
+ }
+}
diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/util/regex/UMultiLineEOLSet.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/regex/UMultiLineEOLSet.java
new file mode 100644
index 000000000..9c278274e
--- /dev/null
+++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/regex/UMultiLineEOLSet.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+/**
+ * @author Nikolay A. Kuznetsov
+ */
+package org.teavm.classlib.java.util.regex;
+
+/**
+ * Unix style multiline end-of-line node.
+ *
+ * @author Nikolay A. Kuznetsov
+ */
+class UMultiLineEOLSet extends AbstractSet {
+
+ private int consCounter;
+
+ public UMultiLineEOLSet(int counter) {
+ this.consCounter = counter;
+ }
+
+ @Override
+ public int matches(int strIndex, CharSequence testString, MatchResultImpl matchResult) {
+ int strDif = matchResult.hasAnchoringBounds() ? matchResult.getRightBound() - strIndex : testString.length() -
+ strIndex;
+ if (strDif <= 0) {
+ matchResult.setConsumed(consCounter, 0);
+ return next.matches(strIndex, testString, matchResult);
+ } else if (testString.charAt(strIndex) == '\n') {
+ matchResult.setConsumed(consCounter, 1);
+ return next.matches(strIndex + 1, testString, matchResult);
+ }
+ return -1;
+ }
+
+ @Override
+ public boolean hasConsumed(MatchResultImpl matchResult) {
+ int cons;
+ boolean res = ((cons = matchResult.getConsumed(consCounter)) < 0 || cons > 0);
+ matchResult.setConsumed(consCounter, -1);
+ return res;
+ }
+
+ @Override
+ protected String getName() {
+ return "";
+ }
+}
diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/util/regex/UnicodeCategory.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/regex/UnicodeCategory.java
new file mode 100644
index 000000000..310352af5
--- /dev/null
+++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/regex/UnicodeCategory.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+/**
+ * @author Nikolay A. Kuznetsov
+ */
+package org.teavm.classlib.java.util.regex;
+
+/**
+ * Unicode category (i.e. Ll, Lu).
+ *
+ * @author Nikolay A. Kuznetsov
+ */
+class UnicodeCategory extends AbstractCharClass {
+
+ protected int category;
+
+ public UnicodeCategory(int category) {
+ this.category = category;
+ }
+
+ @Override
+ public boolean contains(int ch) {
+ return alt ^ (category == Character.getType((char) ch));
+ }
+}
diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/util/regex/UnicodeCategoryScope.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/regex/UnicodeCategoryScope.java
new file mode 100644
index 000000000..ad6360d37
--- /dev/null
+++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/regex/UnicodeCategoryScope.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+/**
+ * @author Nikolay A. Kuznetsov
+ */
+package org.teavm.classlib.java.util.regex;
+
+/**
+ * Unicode category scope (i.e IsL, IsM, ...)
+ * @author Nikolay A. Kuznetsov
+ */
+class UnicodeCategoryScope extends UnicodeCategory {
+
+ public UnicodeCategoryScope(int category) {
+ super(category);
+ }
+
+ @Override
+ public boolean contains(int ch) {
+ return alt ^ ((category >> Character.getType((char) ch)) & 1) != 0;
+ }
+}
diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/util/regex/UnifiedQuantifierSet.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/regex/UnifiedQuantifierSet.java
new file mode 100644
index 000000000..d9bf00315
--- /dev/null
+++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/regex/UnifiedQuantifierSet.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+/**
+ * @author Nikolay A. Kuznetsov
+ */
+package org.teavm.classlib.java.util.regex;
+
+/**
+ * Greedy quantifier node for the case where there is no intersection with next
+ * node and normal quantifiers could be treated as greedy and possessive.
+ *
+ * @author Nikolay A. Kuznetsov
+ */
+class UnifiedQuantifierSet extends LeafQuantifierSet {
+
+ public UnifiedQuantifierSet(LeafSet innerSet, AbstractSet next, int type) {
+ super(innerSet, next, type);
+ }
+
+ public UnifiedQuantifierSet(LeafQuantifierSet quant) {
+ super((LeafSet)quant.getInnerSet(), quant.getNext(), quant.getType());
+ innerSet.setNext(this);
+
+ }
+
+ @Override
+ public int matches(int stringIndex, CharSequence testString, MatchResultImpl matchResult) {
+ while (stringIndex + leaf.charCount() <= matchResult.getRightBound() &&
+ leaf.accepts(stringIndex, testString) > 0)
+ stringIndex += leaf.charCount();
+
+ return next.matches(stringIndex, testString, matchResult);
+ }
+
+ @Override
+ public int find(int stringIndex, CharSequence testString, MatchResultImpl matchResult) {
+ int startSearch = next.find(stringIndex, testString, matchResult);
+ if (startSearch < 0)
+ return -1;
+ int newSearch = startSearch - leaf.charCount();
+ while (newSearch >= stringIndex && leaf.accepts(newSearch, testString) > 0) {
+ startSearch = newSearch;
+ newSearch -= leaf.charCount();
+ }
+
+ return startSearch;
+ }
+}
diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/util/regex/WordBoundary.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/regex/WordBoundary.java
new file mode 100644
index 000000000..68d71e9e7
--- /dev/null
+++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/regex/WordBoundary.java
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+/**
+ * @author Nikolay A. Kuznetsov
+ */
+package org.teavm.classlib.java.util.regex;
+
+/**
+ * Represents word boundary, checks current character and previous one if
+ * different types returns true;
+ *
+ * @author Nikolay A. Kuznetsov
+ */
+class WordBoundary extends AbstractSet {
+
+ boolean positive;
+
+ public WordBoundary(boolean positive) {
+ this.positive = positive;
+ }
+
+ @Override
+ public int matches(int stringIndex, CharSequence testString, MatchResultImpl matchResult) {
+ boolean left;
+ boolean right;
+
+ char ch1 = stringIndex >= matchResult.getRightBound() ? ' ' : testString.charAt(stringIndex);
+ char ch2 = stringIndex == 0 ? ' ' : testString.charAt(stringIndex - 1);
+
+ int leftBound = matchResult.hasTransparentBounds() ? 0 : matchResult.getLeftBound();
+ left = (ch1 == ' ') || isSpace(ch1, stringIndex, leftBound, testString);
+ right = (ch2 == ' ') || isSpace(ch2, stringIndex - 1, leftBound, testString);
+ return ((left ^ right) ^ positive) ? -1 : next.matches(stringIndex, testString, matchResult);
+ }
+
+ /**
+ * Returns false, because word boundary does not consumes any characters and
+ * do not move string index.
+ */
+ @Override
+ public boolean hasConsumed(MatchResultImpl matchResult) {
+ // only checks boundary, do not consumes characters
+ return false;
+ }
+
+ @Override
+ protected String getName() {
+ return "WordBoundary"; //$NON-NLS-1$
+ }
+
+ private boolean isSpace(char ch, int index, int leftBound, CharSequence testString) {
+ if (Character.isLetterOrDigit(ch) || ch == '_')
+ return false;
+ if (Character.getType(ch) == Character.NON_SPACING_MARK) {
+ for (; --index >= leftBound;) {
+ ch = testString.charAt(index);
+ if (Character.isLetterOrDigit(ch))
+ return false;
+ if (Character.getType(ch) != Character.NON_SPACING_MARK)
+ return true;
+ }
+ }
+ return true;
+ }
+}
diff --git a/teavm-classlib/src/test/java/org/teavm/classlib/java/util/regex/Matcher2Test.java b/teavm-classlib/src/test/java/org/teavm/classlib/java/util/regex/Matcher2Test.java
new file mode 100644
index 000000000..ece7d0ba3
--- /dev/null
+++ b/teavm-classlib/src/test/java/org/teavm/classlib/java/util/regex/Matcher2Test.java
@@ -0,0 +1,234 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.teavm.classlib.java.util.regex;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests Matcher methods
+ */
+@SuppressWarnings("nls")
+public class Matcher2Test extends TestCase {
+ public void test_toString() {
+ Pattern p = Pattern.compile("foo");
+ Matcher m = p.matcher("bar");
+ assertNotNull(m.toString());
+ }
+
+ public void testErrorConditions() throws PatternSyntaxException {
+ // Test match cursors in absence of a match
+ Pattern p = Pattern.compile("foo");
+ Matcher m = p.matcher("bar");
+ assertFalse(m.matches());
+
+ try {
+ m.start();
+ fail("IllegalStateException expected");
+ } catch (IllegalStateException e) {
+ }
+
+ try {
+ m.end();
+ fail("IllegalStateException expected");
+ } catch (IllegalStateException e) {
+ }
+
+ try {
+ m.group();
+ fail("IllegalStateException expected");
+ } catch (IllegalStateException e) {
+ }
+
+ try {
+ m.start(1);
+ fail("IllegalStateException expected");
+ } catch (IllegalStateException e) {
+ }
+
+ try {
+ m.end(1);
+ fail("IllegalStateException expected");
+ } catch (IllegalStateException e) {
+ }
+
+ try {
+ m.group(1);
+ fail("IllegalStateException expected");
+ } catch (IllegalStateException e) {
+ }
+
+ // regression test for HARMONY-2418
+ try {
+ m.usePattern(null);
+ fail("IllegalArgumentException expected");
+ } catch (IllegalArgumentException e) {
+ // PASSED
+ }
+ }
+
+ public void testErrorConditions2() throws PatternSyntaxException {
+ // Test match cursors in absence of a match
+ Pattern p = Pattern.compile("(foo[0-9])(bar[a-z])");
+ Matcher m = p.matcher("foo1barzfoo2baryfoozbar5");
+
+ assertTrue(m.find());
+ assertEquals(0, m.start());
+ assertEquals(8, m.end());
+ assertEquals(0, m.start(1));
+ assertEquals(4, m.end(1));
+ assertEquals(4, m.start(2));
+ assertEquals(8, m.end(2));
+
+ try {
+ m.start(3);
+ fail("IndexOutOfBoundsException expected");
+ } catch (IndexOutOfBoundsException e) {
+ }
+
+ try {
+ m.end(3);
+ fail("IndexOutOfBoundsException expected");
+ } catch (IndexOutOfBoundsException e) {
+ }
+
+ try {
+ m.group(3);
+ fail("IndexOutOfBoundsException expected");
+ } catch (IndexOutOfBoundsException e) {
+ }
+
+ try {
+ m.start(-1);
+ fail("IndexOutOfBoundsException expected");
+ } catch (IndexOutOfBoundsException e) {
+ }
+
+ try {
+ m.end(-1);
+ fail("IndexOutOfBoundsException expected");
+ } catch (IndexOutOfBoundsException e) {
+ }
+
+ try {
+ m.group(-1);
+ fail("IndexOutOfBoundsException expected");
+ } catch (IndexOutOfBoundsException e) {
+ }
+
+ assertTrue(m.find());
+ assertEquals(8, m.start());
+ assertEquals(16, m.end());
+ assertEquals(8, m.start(1));
+ assertEquals(12, m.end(1));
+ assertEquals(12, m.start(2));
+ assertEquals(16, m.end(2));
+
+ try {
+ m.start(3);
+ fail("IndexOutOfBoundsException expected");
+ } catch (IndexOutOfBoundsException e) {
+ }
+
+ try {
+ m.end(3);
+ fail("IndexOutOfBoundsException expected");
+ } catch (IndexOutOfBoundsException e) {
+ }
+
+ try {
+ m.group(3);
+ fail("IndexOutOfBoundsException expected");
+ } catch (IndexOutOfBoundsException e) {
+ }
+
+ try {
+ m.start(-1);
+ fail("IndexOutOfBoundsException expected");
+ } catch (IndexOutOfBoundsException e) {
+ }
+
+ try {
+ m.end(-1);
+ fail("IndexOutOfBoundsException expected");
+ } catch (IndexOutOfBoundsException e) {
+ }
+
+ try {
+ m.group(-1);
+ fail("IndexOutOfBoundsException expected");
+ } catch (IndexOutOfBoundsException e) {
+ }
+
+ assertFalse(m.find());
+
+ try {
+ m.start(3);
+ fail("IllegalStateException expected");
+ } catch (IllegalStateException e) {
+ }
+
+ try {
+ m.end(3);
+ fail("IllegalStateException expected");
+ } catch (IllegalStateException e) {
+ }
+
+ try {
+ m.group(3);
+ fail("IllegalStateException expected");
+ } catch (IllegalStateException e) {
+ }
+
+ try {
+ m.start(-1);
+ fail("IllegalStateException expected");
+ } catch (IllegalStateException e) {
+ }
+
+ try {
+ m.end(-1);
+ fail("IllegalStateException expected");
+ } catch (IllegalStateException e) {
+ }
+
+ try {
+ m.group(-1);
+ fail("IllegalStateException expected");
+ } catch (IllegalStateException e) {
+ }
+ }
+
+ /*
+ * Regression test for HARMONY-997
+ */
+ public void testReplacementBackSlash() {
+ String str = "replace me";
+ String replacedString = "me";
+ String substitutionString = "\\";
+ Pattern pat = Pattern.compile(replacedString);
+ Matcher mat = pat.matcher(str);
+ try {
+ mat.replaceAll(substitutionString);
+ fail("IndexOutOfBoundsException should be thrown");
+ } catch (IndexOutOfBoundsException e) {
+ }
+ }
+}
diff --git a/teavm-classlib/src/test/java/org/teavm/classlib/java/util/regex/MatcherTest.java b/teavm-classlib/src/test/java/org/teavm/classlib/java/util/regex/MatcherTest.java
new file mode 100644
index 000000000..12af6b879
--- /dev/null
+++ b/teavm-classlib/src/test/java/org/teavm/classlib/java/util/regex/MatcherTest.java
@@ -0,0 +1,772 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.teavm.classlib.java.util.regex;
+
+import static org.junit.Assert.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import org.junit.Test;
+
+
+@SuppressWarnings("nls")
+public class MatcherTest {
+ String[] testPatterns = {
+ "(a|b)*abb",
+ "(1*2*3*4*)*567",
+ "(a|b|c|d)*aab",
+ "(1|2|3|4|5|6|7|8|9|0)(1|2|3|4|5|6|7|8|9|0)*",
+ "(abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ)*",
+ "(a|b)*(a|b)*A(a|b)*lice.*",
+ "(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)*(1|2|3|4|5|6|7|8|9|0)*|while|for|struct|if|do" };
+
+ String[] groupPatterns = { "(a|b)*aabb", "((a)|b)*aabb", "((a|b)*)a(abb)",
+ "(((a)|(b))*)aabb", "(((a)|(b))*)aa(b)b", "(((a)|(b))*)a(a(b)b)" };
+
+ @Test
+ public void testRegionsIntInt() {
+ Pattern p = Pattern.compile("x*");
+ Matcher m = p.matcher("axxxxxa");
+ assertFalse(m.matches());
+
+ m.region(1, 6);
+ assertEquals(1, m.regionStart());
+ assertEquals(6, m.regionEnd());
+ assertTrue(m.matches());
+
+ try {
+ m.region(1, 0);
+ fail("expected an IOOBE");
+ } catch(IndexOutOfBoundsException e) {
+ }
+
+ try {
+ m.region(-1, 2);
+ fail("expected an IOOBE");
+ } catch(IndexOutOfBoundsException e) {
+ }
+
+ try {
+ m.region(10, 11);
+ fail("expected an IOOBE");
+ } catch(IndexOutOfBoundsException e) {
+ }
+
+ try {
+ m.region(1, 10);
+ fail("expected an IOOBE");
+ } catch(IndexOutOfBoundsException e) {
+ }
+ }
+
+ @Test
+ public void testAppendReplacement() {
+ Pattern pat = Pattern.compile("XX");
+ Matcher m = pat.matcher("Today is XX-XX-XX ...");
+ StringBuffer sb = new StringBuffer();
+
+ for (int i = 0; m.find(); i++) {
+ m.appendReplacement(sb, new Integer(i * 10 + i).toString());
+ }
+ m.appendTail(sb);
+ assertEquals("Today is 0-11-22 ...", sb.toString());
+ }
+
+ @Test
+ public void testAppendReplacementRef() {
+ Pattern p = Pattern.compile("xx (rur|\\$)");
+ Matcher m = p.matcher("xx $ equals to xx rur.");
+ StringBuffer sb = new StringBuffer();
+ for (int i = 1; m.find(); i *= 30) {
+ String rep = new Integer(i).toString() + " $1";
+ m.appendReplacement(sb, rep);
+ }
+ m.appendTail(sb);
+ assertEquals("1 $ equals to 30 rur.", sb.toString());
+ }
+
+ @Test
+ public void testReplaceAll() {
+ String input = "aabfooaabfooabfoob";
+ String pattern = "a*b";
+ Pattern pat = Pattern.compile(pattern);
+ Matcher mat = pat.matcher(input);
+
+ assertEquals("-foo-foo-foo-", mat.replaceAll("-"));
+ }
+
+ @Test
+ public void testResetCharSequence() {
+ Pattern p = Pattern.compile("abcd");
+ Matcher m = p.matcher("abcd");
+ assertTrue(m.matches());
+ m.reset("efgh");
+ assertFalse(m.matches());
+
+ try {
+ m.reset(null);
+ fail("expected a NPE");
+ } catch (NullPointerException e) {
+ }
+ }
+
+ @Test
+ public void testAppendSlashes() {
+ Pattern p = Pattern.compile("\\\\");
+ Matcher m = p.matcher("one\\cat\\two\\cats\\in\\the\\yard");
+ StringBuffer sb = new StringBuffer();
+ while (m.find()) {
+ m.appendReplacement(sb, "\\\\");
+ }
+ m.appendTail(sb);
+ assertEquals("one\\cat\\two\\cats\\in\\the\\yard", sb.toString());
+
+ }
+
+ @Test
+ public void testReplaceFirst() {
+ String input = "zzzdogzzzdogzzz";
+ String pattern = "dog";
+ Pattern pat = Pattern.compile(pattern);
+ Matcher mat = pat.matcher(input);
+
+ assertEquals("zzzcatzzzdogzzz", mat.replaceFirst("cat"));
+ }
+
+ @Test
+ public void testPattern() {
+ for (String element : testPatterns) {
+ Pattern test = Pattern.compile(element);
+ assertEquals(test, test.matcher("aaa").pattern());
+ }
+
+ for (String element : testPatterns) {
+ assertEquals(element, Pattern.compile(element).matcher("aaa")
+ .pattern().toString());
+ }
+ }
+
+ /*
+ * Class under test for Matcher reset()
+ */
+ public void testReset() {
+ }
+
+ @Test
+ public void testGroupint() {
+ String positiveTestString = "ababababbaaabb";
+
+ // test IndexOutOfBoundsException
+ // //
+ for (int i = 0; i < groupPatterns.length; i++) {
+ Pattern test = Pattern.compile(groupPatterns[i]);
+ Matcher mat = test.matcher(positiveTestString);
+ mat.matches();
+ try {
+ // groupPattern equals to number of groups
+ // of the specified pattern
+ // //
+ mat.group(i + 2);
+ fail("IndexOutBoundsException expected");
+ mat.group(i + 100);
+ fail("IndexOutBoundsException expected");
+ mat.group(-1);
+ fail("IndexOutBoundsException expected");
+ mat.group(-100);
+ fail("IndexOutBoundsException expected");
+ } catch (IndexOutOfBoundsException iobe) {
+ }
+ }
+
+ String[][] groupResults = { { "a" }, { "a", "a" },
+ { "ababababba", "a", "abb" }, { "ababababba", "a", "a", "b" },
+ { "ababababba", "a", "a", "b", "b" },
+ { "ababababba", "a", "a", "b", "abb", "b" }, };
+
+ for (int i = 0; i < groupPatterns.length; i++) {
+ Pattern test = Pattern.compile(groupPatterns[i]);
+ Matcher mat = test.matcher(positiveTestString);
+ mat.matches();
+ for (int j = 0; j < groupResults[i].length; j++) {
+ assertEquals("i: " + i + " j: " + j, groupResults[i][j], mat
+ .group(j + 1));
+ }
+
+ }
+
+ }
+
+ @Test
+ public void testGroup() {
+ String positiveTestString = "ababababbaaabb";
+ String negativeTestString = "gjhfgdsjfhgcbv";
+ for (String element : groupPatterns) {
+ Pattern test = Pattern.compile(element);
+ Matcher mat = test.matcher(positiveTestString);
+ mat.matches();
+ // test result
+ assertEquals(positiveTestString, mat.group());
+
+ // test equal to group(0) result
+ assertEquals(mat.group(0), mat.group());
+ }
+
+ for (String element : groupPatterns) {
+ Pattern test = Pattern.compile(element);
+ Matcher mat = test.matcher(negativeTestString);
+ mat.matches();
+ try {
+ mat.group();
+ fail("IllegalStateException expected for matches result");
+ } catch (IllegalStateException ise) {
+ }
+ }
+ }
+
+ @Test
+ public void testGroupPossessive() {
+ Pattern pat = Pattern.compile("((a)|(b))++c");
+ Matcher mat = pat.matcher("aac");
+
+ mat.matches();
+ assertEquals("a", mat.group(1));
+ }
+
+ /*
+ * Class under test for boolean find(int)
+ */
+ public void testFindint() {
+ }
+
+ /*
+ * Class under test for int start(int)
+ */
+ public void testStartint() {
+ }
+
+ /*
+ * Class under test for int end(int)
+ */
+ public void testEndint() {
+ }
+
+ @Test
+ public void testMatchesMisc() {
+ String[][] posSeq = {
+ { "abb", "ababb", "abababbababb", "abababbababbabababbbbbabb" },
+ { "213567", "12324567", "1234567", "213213567",
+ "21312312312567", "444444567" },
+ { "abcdaab", "aab", "abaab", "cdaab", "acbdadcbaab" },
+ { "213234567", "3458", "0987654", "7689546432", "0398576",
+ "98432", "5" },
+ {
+ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
+ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" },
+ { "ababbaAabababblice", "ababbaAliceababab", "ababbAabliceaaa",
+ "abbbAbbbliceaaa", "Alice" },
+ { "a123", "bnxnvgds156", "for", "while", "if", "struct" }
+
+ };
+
+ for (int i = 0; i < testPatterns.length; i++) {
+ Pattern pat = Pattern.compile(testPatterns[i]);
+ for (int j = 0; j < posSeq[i].length; j++) {
+ Matcher mat = pat.matcher(posSeq[i][j]);
+ assertTrue("Incorrect match: " + testPatterns[i] + " vs "
+ + posSeq[i][j], mat.matches());
+ }
+ }
+ }
+
+ @Test
+ public void testMatchesQuantifiers() {
+ String[] testPatternsSingles = { "a{5}", "a{2,4}", "a{3,}" };
+ String[] testPatternsMultiple = { "((a)|(b)){1,2}abb",
+ "((a)|(b)){2,4}", "((a)|(b)){3,}" };
+
+ String[][] stringSingles = { { "aaaaa", "aaa" },
+ { "aa", "a", "aaa", "aaaaaa", "aaaa", "aaaaa" },
+ { "aaa", "a", "aaaa", "aa" }, };
+
+ String[][] stringMultiples = { { "ababb", "aba" },
+ { "ab", "b", "bab", "ababa", "abba", "abababbb" },
+ { "aba", "b", "abaa", "ba" }, };
+
+ for (int i = 0; i < testPatternsSingles.length; i++) {
+ Pattern pat = Pattern.compile(testPatternsSingles[i]);
+ for (int j = 0; j < stringSingles.length / 2; j++) {
+ assertTrue("Match expected, but failed: " + pat.pattern()
+ + " : " + stringSingles[i][j], pat.matcher(
+ stringSingles[i][j * 2]).matches());
+ assertFalse("Match failure expected, but match succeed: "
+ + pat.pattern() + " : " + stringSingles[i][j * 2 + 1],
+ pat.matcher(stringSingles[i][j * 2 + 1]).matches());
+ }
+ }
+
+ for (int i = 0; i < testPatternsMultiple.length; i++) {
+ Pattern pat = Pattern.compile(testPatternsMultiple[i]);
+ for (int j = 0; j < stringMultiples.length / 2; j++) {
+ assertTrue("Match expected, but failed: " + pat.pattern()
+ + " : " + stringMultiples[i][j], pat.matcher(
+ stringMultiples[i][j * 2]).matches());
+ assertFalse(
+ "Match failure expected, but match succeed: "
+ + pat.pattern() + " : "
+ + stringMultiples[i][j * 2 + 1], pat.matcher(
+ stringMultiples[i][j * 2 + 1]).matches());
+ }
+ }
+ }
+
+ @Test
+ public void testQuantVsGroup() {
+ String patternString = "(d{1,3})((a|c)*)(d{1,3})((a|c)*)(d{1,3})";
+ String testString = "dacaacaacaaddaaacaacaaddd";
+
+ Pattern pat = Pattern.compile(patternString);
+ Matcher mat = pat.matcher(testString);
+
+ mat.matches();
+ assertEquals("dacaacaacaaddaaacaacaaddd", mat.group());
+ assertEquals("d", mat.group(1));
+ assertEquals("acaacaacaa", mat.group(2));
+ assertEquals("dd", mat.group(4));
+ assertEquals("aaacaacaa", mat.group(5));
+ assertEquals("ddd", mat.group(7));
+ }
+
+ public void testLookingAt() {
+ }
+
+ /*
+ * Class under test for boolean find()
+ */
+ public void testFind() {
+ String testPattern = "(abb)";
+ String testString = "cccabbabbabbabbabb";
+ Pattern pat = Pattern.compile(testPattern);
+ Matcher mat = pat.matcher(testString);
+ int start = 3;
+ int end = 6;
+ while (mat.find()) {
+ assertEquals(start, mat.start(1));
+ assertEquals(end, mat.end(1));
+
+ start = end;
+ end += 3;
+ }
+
+ testPattern = "(\\d{1,3})";
+ testString = "aaaa123456789045";
+
+ Pattern pat2 = Pattern.compile(testPattern);
+ Matcher mat2 = pat2.matcher(testString);
+ start = 4;
+ int length = 3;
+ while (mat2.find()) {
+ assertEquals(testString.substring(start, start + length), mat2
+ .group(1));
+ start += length;
+ }
+ }
+
+ @Test
+ public void testSEOLsymbols() {
+ Pattern pat = Pattern.compile("^a\\(bb\\[$");
+ Matcher mat = pat.matcher("a(bb[");
+
+ assertTrue(mat.matches());
+ }
+
+ /*
+ * Class under test for int start()
+ */
+ public void testStart() {
+ }
+
+ @Test
+ public void testGroupCount() {
+ for (int i = 0; i < groupPatterns.length; i++) {
+ Pattern test = Pattern.compile(groupPatterns[i]);
+ Matcher mat = test.matcher("ababababbaaabb");
+ mat.matches();
+ assertEquals(i + 1, mat.groupCount());
+
+ }
+ }
+
+ @Test
+ public void testRelactantQuantifiers() {
+ Pattern pat = Pattern.compile("(ab*)*b");
+ Matcher mat = pat.matcher("abbbb");
+
+ if (mat.matches()) {
+ assertEquals("abbb", mat.group(1));
+ } else {
+ fail("Match expected: (ab*)*b vs abbbb");
+ }
+ }
+
+ @Test
+ public void testEnhancedFind() {
+ String input = "foob";
+ String pattern = "a*b";
+ Pattern pat = Pattern.compile(pattern);
+ Matcher mat = pat.matcher(input);
+
+ mat.find();
+ assertEquals("b", mat.group());
+ }
+
+ @Test
+ public void testPosCompositeGroup() {
+ String[] posExamples = { "aabbcc", "aacc", "bbaabbcc" };
+ String[] negExamples = { "aabb", "bb", "bbaabb" };
+ Pattern posPat = Pattern.compile("(aa|bb){1,3}+cc");
+ Pattern negPat = Pattern.compile("(aa|bb){1,3}+bb");
+
+ Matcher mat;
+ for (String element : posExamples) {
+ mat = posPat.matcher(element);
+ assertTrue(mat.matches());
+ }
+
+ for (String element : negExamples) {
+ mat = negPat.matcher(element);
+ assertFalse(mat.matches());
+ }
+
+ assertTrue(Pattern.matches("(aa|bb){1,3}+bb", "aabbaabb"));
+
+ }
+
+ @Test
+ public void testPosAltGroup() {
+ String[] posExamples = { "aacc", "bbcc", "cc" };
+ String[] negExamples = { "bb", "aa" };
+ Pattern posPat = Pattern.compile("(aa|bb)?+cc");
+ Pattern negPat = Pattern.compile("(aa|bb)?+bb");
+
+ Matcher mat;
+ for (String element : posExamples) {
+ mat = posPat.matcher(element);
+ assertTrue(posPat.toString() + " vs: " + element, mat.matches());
+ }
+
+ for (String element : negExamples) {
+ mat = negPat.matcher(element);
+ assertFalse(mat.matches());
+ }
+
+ assertTrue(Pattern.matches("(aa|bb)?+bb", "aabb"));
+ }
+
+ @Test
+ public void testRelCompGroup() {
+
+ Matcher mat;
+ Pattern pat;
+ String res = "";
+ for (int i = 0; i < 4; i++) {
+ pat = Pattern.compile("((aa|bb){" + i + ",3}?).*cc");
+ mat = pat.matcher("aaaaaacc");
+ assertTrue(pat.toString() + " vs: " + "aaaaaacc", mat.matches());
+ assertEquals(res, mat.group(1));
+ res += "aa";
+ }
+ }
+
+ @Test
+ public void testRelAltGroup() {
+
+ Matcher mat;
+ Pattern pat;
+
+ pat = Pattern.compile("((aa|bb)??).*cc");
+ mat = pat.matcher("aacc");
+ assertTrue(pat.toString() + " vs: " + "aacc", mat.matches());
+ assertEquals("", mat.group(1));
+
+ pat = Pattern.compile("((aa|bb)??)cc");
+ mat = pat.matcher("aacc");
+ assertTrue(pat.toString() + " vs: " + "aacc", mat.matches());
+ assertEquals("aa", mat.group(1));
+ }
+
+ @Test
+ public void testIgnoreCase() {
+ Pattern pat = Pattern.compile("(aa|bb)*", Pattern.CASE_INSENSITIVE);
+ Matcher mat = pat.matcher("aAbb");
+
+ assertTrue(mat.matches());
+
+ pat = Pattern.compile("(a|b|c|d|e)*", Pattern.CASE_INSENSITIVE);
+ mat = pat.matcher("aAebbAEaEdebbedEccEdebbedEaedaebEbdCCdbBDcdcdADa");
+ assertTrue(mat.matches());
+
+ pat = Pattern.compile("[a-e]*", Pattern.CASE_INSENSITIVE);
+ mat = pat.matcher("aAebbAEaEdebbedEccEdebbedEaedaebEbdCCdbBDcdcdADa");
+ assertTrue(mat.matches());
+
+ }
+
+ @Test
+ public void testQuoteReplacement() {
+ assertEquals("\\\\aaCC\\$1", Matcher.quoteReplacement("\\aaCC$1"));
+ }
+
+ @Test
+ public void testOverFlow() {
+ Pattern tp = Pattern.compile("(a*)*");
+ Matcher tm = tp.matcher("aaa");
+ assertTrue(tm.matches());
+ assertEquals("", tm.group(1));
+
+ assertTrue(Pattern.matches("(1+)\\1+", "11"));
+ assertTrue(Pattern.matches("(1+)(2*)\\2+", "11"));
+
+ Pattern pat = Pattern.compile("(1+)\\1*");
+ Matcher mat = pat.matcher("11");
+
+ assertTrue(mat.matches());
+ assertEquals("11", mat.group(1));
+
+ pat = Pattern.compile("((1+)|(2+))(\\2+)");
+ mat = pat.matcher("11");
+
+ assertTrue(mat.matches());
+ assertEquals("1", mat.group(2));
+ assertEquals("1", mat.group(1));
+ assertEquals("1", mat.group(4));
+ assertNull(mat.group(3));
+
+ }
+
+ @Test
+ public void testUnicode() {
+
+ assertTrue(Pattern.matches("\\x61a", "aa"));
+ assertTrue(Pattern.matches("\\u0061a", "aa"));
+ assertTrue(Pattern.matches("\\0141a", "aa"));
+ assertTrue(Pattern.matches("\\0777", "?7"));
+
+ }
+
+ @Test
+ public void testUnicodeCategory() {
+ assertTrue(Pattern.matches("\\p{Ll}", "k")); // Unicode lower case
+ assertTrue(Pattern.matches("\\P{Ll}", "K")); // Unicode non-lower
+ // case
+ assertTrue(Pattern.matches("\\p{Lu}", "K")); // Unicode upper case
+ assertTrue(Pattern.matches("\\P{Lu}", "k")); // Unicode non-upper
+ // case
+ // combinations
+ assertTrue(Pattern.matches("[\\p{L}&&[^\\p{Lu}]]", "k"));
+ assertTrue(Pattern.matches("[\\p{L}&&[^\\p{Ll}]]", "K"));
+ assertFalse(Pattern.matches("[\\p{L}&&[^\\p{Lu}]]", "K"));
+ assertFalse(Pattern.matches("[\\p{L}&&[^\\p{Ll}]]", "k"));
+
+ // category/character combinations
+ assertFalse(Pattern.matches("[\\p{L}&&[^a-z]]", "k"));
+ assertTrue(Pattern.matches("[\\p{L}&&[^a-z]]", "K"));
+
+ assertTrue(Pattern.matches("[\\p{Lu}a-z]", "k"));
+ assertTrue(Pattern.matches("[a-z\\p{Lu}]", "k"));
+
+ assertFalse(Pattern.matches("[\\p{Lu}a-d]", "k"));
+ assertTrue(Pattern.matches("[a-d\\p{Lu}]", "K"));
+
+ // assertTrue(Pattern.matches("[\\p{L}&&[^\\p{Lu}&&[^K]]]", "K"));
+ assertFalse(Pattern.matches("[\\p{L}&&[^\\p{Lu}&&[^G]]]", "K"));
+
+ }
+
+ @Test
+ public void testSplitEmpty() {
+
+ Pattern pat = Pattern.compile("");
+ String[] s = pat.split("", -1);
+
+ assertEquals(1, s.length);
+ assertEquals("", s[0]);
+ }
+
+ @Test
+ public void testFindDollar() {
+ Matcher mat = Pattern.compile("a$").matcher("a\n");
+ assertTrue(mat.find());
+ assertEquals("a", mat.group());
+ }
+
+ @Test
+ public void testMatchesRegionChanged() {
+ // Regression for HARMONY-610
+ String input = " word ";
+ Pattern pattern = Pattern.compile("\\w+");
+ Matcher matcher = pattern.matcher(input);
+ matcher.region(1, 5);
+ assertTrue(matcher.matches());
+ }
+
+ @Test
+ public void testAllCodePoints() {
+ // Regression for HARMONY-3145
+ int[] codePoint = new int[1];
+ Pattern p = Pattern.compile("(\\p{all})+");
+ boolean res = true;
+ int cnt = 0;
+ String s;
+ for (int i = 0; i < 0x110000; i++) {
+ codePoint[0] = i;
+ s = new String(codePoint, 0, 1);
+ if (!s.matches(p.toString())) {
+ cnt++;
+ res = false;
+ }
+ }
+ assertTrue(res);
+ assertEquals(0, cnt);
+
+ p = Pattern.compile("(\\P{all})+");
+ res = true;
+ cnt = 0;
+
+ for (int i = 0; i < 0x110000; i++) {
+ codePoint[0] = i;
+ s = new String(codePoint, 0, 1);
+ if (!s.matches(p.toString())) {
+ cnt++;
+ res = false;
+ }
+ }
+
+ assertFalse(res);
+ assertEquals(0x110000, cnt);
+ }
+
+ @Test
+ public void testFindRegionChanged() {
+ // Regression for HARMONY-625
+ Pattern pattern = Pattern.compile("(?s).*");
+ Matcher matcher = pattern.matcher("abcde");
+ matcher.find();
+ assertEquals("abcde", matcher.group());
+
+ matcher = pattern.matcher("abcde");
+ matcher.region(0, 2);
+ matcher.find();
+ assertEquals("ab", matcher.group());
+
+ }
+
+ @Test
+ public void testFindRegionChanged2() {
+ // Regression for HARMONY-713
+ Pattern pattern = Pattern.compile("c");
+
+ String inputStr = "aabb.c";
+ Matcher matcher = pattern.matcher(inputStr);
+ matcher.region(0, 3);
+
+ assertFalse(matcher.find());
+ }
+
+ @Test
+ public void testPatternMatcher() throws Exception {
+ Pattern pattern = Pattern.compile("(?:\\d+)(?:pt)");
+ assertTrue(pattern.matcher("14pt").matches());
+ }
+
+ @Test
+ public void test3360() {
+ String str = "!\"#%&'(),-./";
+ Pattern p = Pattern.compile("\\s");
+ Matcher m = p.matcher(str);
+
+ assertFalse(m.find());
+ }
+
+ @Test
+ public void testGeneralPunctuationCategory() {
+ String[] s = { ",", "!", "\"", "#", "%", "&", "'", "(", ")", "-", ".",
+ "/" };
+ String regexp = "\\p{P}";
+
+ for (int i = 0; i < s.length; i++) {
+ Pattern pattern = Pattern.compile(regexp);
+ Matcher matcher = pattern.matcher(s[i]);
+ assertTrue(matcher.find());
+ }
+ }
+
+ @Test
+ public void testHitEndAfterFind() {
+ hitEndTest(true, "#01.0", "r((ege)|(geg))x", "regexx", false);
+ hitEndTest(true, "#01.1", "r((ege)|(geg))x", "regex", false);
+ hitEndTest(true, "#01.2", "r((ege)|(geg))x", "rege", true);
+ hitEndTest(true, "#01.2", "r((ege)|(geg))x", "xregexx", false);
+
+ hitEndTest(true, "#02.0", "regex", "rexreger", true);
+ hitEndTest(true, "#02.1", "regex", "raxregexr", false);
+
+ String floatRegex = getHexFloatRegex();
+ hitEndTest(true, "#03.0", floatRegex, Double.toHexString(-1.234d), true);
+ hitEndTest(true, "#03.1", floatRegex, "1 ABC"
+ + Double.toHexString(Double.NaN) + "buhuhu", false);
+ hitEndTest(true, "#03.2", floatRegex, Double.toHexString(-0.0) + "--",
+ false);
+ hitEndTest(true, "#03.3", floatRegex, "--"
+ + Double.toHexString(Double.MIN_VALUE) + "--", false);
+
+ hitEndTest(true, "#04.0", "(\\d+) fish (\\d+) fish (\\w+) fish (\\d+)",
+ "1 fish 2 fish red fish 5", true);
+ hitEndTest(true, "#04.1", "(\\d+) fish (\\d+) fish (\\w+) fish (\\d+)",
+ "----1 fish 2 fish red fish 5----", false);
+ }
+
+ @Test
+ public void testToString() {
+ String result = Pattern.compile("(\\d{1,3})").matcher(
+ "aaaa123456789045").toString();
+ assertTrue("The result doesn't contain pattern info", result
+ .contains("(\\d{1,3})"));
+ }
+
+ private void hitEndTest(boolean callFind, String testNo, String regex,
+ String input, boolean hit) {
+ Pattern pattern = Pattern.compile(regex);
+ Matcher matcher = pattern.matcher(input);
+ if (callFind) {
+ matcher.find();
+ } else {
+ matcher.matches();
+ }
+ boolean h = matcher.hitEnd();
+
+ assertTrue(testNo, h == hit);
+ }
+
+ private String getHexFloatRegex() {
+ String hexDecimal = "(-|\\+)?0[xX][0-9a-fA-F]*\\.[0-9a-fA-F]+([pP](-|\\+)?[0-9]+)?";
+ String notANumber = "((-|\\+)?Infinity)|([nN]a[nN])";
+ return new StringBuilder("((").append(hexDecimal).append(")|(").append(
+ notANumber).append("))").toString();
+ }
+}
diff --git a/teavm-classlib/src/test/java/org/teavm/classlib/java/util/regex/ModeTest.java b/teavm-classlib/src/test/java/org/teavm/classlib/java/util/regex/ModeTest.java
new file mode 100644
index 000000000..b8b95395e
--- /dev/null
+++ b/teavm-classlib/src/test/java/org/teavm/classlib/java/util/regex/ModeTest.java
@@ -0,0 +1,111 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.teavm.classlib.java.util.regex;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests Pattern compilation modes and modes triggered in pattern strings
+ */
+@SuppressWarnings("nls")
+public class ModeTest extends TestCase {
+ public void testCase() throws PatternSyntaxException {
+ Pattern p;
+ Matcher m;
+
+ p = Pattern.compile("([a-z]+)[0-9]+");
+ m = p.matcher("cAT123#dog345");
+ assertTrue(m.find());
+ assertEquals("dog", m.group(1));
+ assertFalse(m.find());
+
+ p = Pattern.compile("([a-z]+)[0-9]+", Pattern.CASE_INSENSITIVE);
+ m = p.matcher("cAt123#doG345");
+ assertTrue(m.find());
+ assertEquals("cAt", m.group(1));
+ assertTrue(m.find());
+ assertEquals("doG", m.group(1));
+ assertFalse(m.find());
+
+ p = Pattern.compile("(?i)([a-z]+)[0-9]+");
+ m = p.matcher("cAt123#doG345");
+ assertTrue(m.find());
+ assertEquals("cAt", m.group(1));
+ assertTrue(m.find());
+ assertEquals("doG", m.group(1));
+ assertFalse(m.find());
+ }
+
+ public void testMultiline() throws PatternSyntaxException {
+ Pattern p;
+ Matcher m;
+
+ p = Pattern.compile("^foo");
+ m = p.matcher("foobar");
+ assertTrue(m.find());
+ assertTrue(m.start() == 0 && m.end() == 3);
+ assertFalse(m.find());
+
+ m = p.matcher("barfoo");
+ assertFalse(m.find());
+
+ p = Pattern.compile("foo$");
+ m = p.matcher("foobar");
+ assertFalse(m.find());
+
+ m = p.matcher("barfoo");
+ assertTrue(m.find());
+ assertTrue(m.start() == 3 && m.end() == 6);
+ assertFalse(m.find());
+
+ p = Pattern.compile("^foo([0-9]*)", Pattern.MULTILINE);
+ m = p.matcher("foo1bar\nfoo2foo3\nbarfoo4");
+ assertTrue(m.find());
+ assertEquals("1", m.group(1));
+ assertTrue(m.find());
+ assertEquals("2", m.group(1));
+ assertFalse(m.find());
+
+ p = Pattern.compile("foo([0-9]*)$", Pattern.MULTILINE);
+ m = p.matcher("foo1bar\nfoo2foo3\nbarfoo4");
+ assertTrue(m.find());
+ assertEquals("3", m.group(1));
+ assertTrue(m.find());
+ assertEquals("4", m.group(1));
+ assertFalse(m.find());
+
+ p = Pattern.compile("(?m)^foo([0-9]*)");
+ m = p.matcher("foo1bar\nfoo2foo3\nbarfoo4");
+ assertTrue(m.find());
+ assertEquals("1", m.group(1));
+ assertTrue(m.find());
+ assertEquals("2", m.group(1));
+ assertFalse(m.find());
+
+ p = Pattern.compile("(?m)foo([0-9]*)$");
+ m = p.matcher("foo1bar\nfoo2foo3\nbarfoo4");
+ assertTrue(m.find());
+ assertEquals("3", m.group(1));
+ assertTrue(m.find());
+ assertEquals("4", m.group(1));
+ assertFalse(m.find());
+ }
+}
diff --git a/teavm-classlib/src/test/java/org/teavm/classlib/java/util/regex/Pattern2Test.java b/teavm-classlib/src/test/java/org/teavm/classlib/java/util/regex/Pattern2Test.java
new file mode 100644
index 000000000..dfacec463
--- /dev/null
+++ b/teavm-classlib/src/test/java/org/teavm/classlib/java/util/regex/Pattern2Test.java
@@ -0,0 +1,1412 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.teavm.classlib.java.util.regex;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests simple Pattern compilation and Matcher methods
+ */
+@SuppressWarnings("nls")
+public class Pattern2Test extends TestCase {
+ public void testSimpleMatch() throws PatternSyntaxException {
+ Pattern p = Pattern.compile("foo.*");
+
+ Matcher m1 = p.matcher("foo123");
+ assertTrue(m1.matches());
+ assertTrue(m1.find(0));
+ assertTrue(m1.lookingAt());
+
+ Matcher m2 = p.matcher("fox");
+ assertFalse(m2.matches());
+ assertFalse(m2.find(0));
+ assertFalse(m2.lookingAt());
+
+ assertTrue(Pattern.matches("foo.*", "foo123"));
+ assertFalse(Pattern.matches("foo.*", "fox"));
+
+ assertFalse(Pattern.matches("bar", "foobar"));
+
+ assertTrue(Pattern.matches("", ""));
+ }
+
+ public void testCursors() {
+ Pattern p;
+ Matcher m;
+
+ try {
+ p = Pattern.compile("foo");
+
+ m = p.matcher("foobar");
+ assertTrue(m.find());
+ assertEquals(0, m.start());
+ assertEquals(3, m.end());
+ assertFalse(m.find());
+
+ // Note: also testing reset here
+ m.reset();
+ assertTrue(m.find());
+ assertEquals(0, m.start());
+ assertEquals(3, m.end());
+ assertFalse(m.find());
+
+ m.reset("barfoobar");
+ assertTrue(m.find());
+ assertEquals(3, m.start());
+ assertEquals(6, m.end());
+ assertFalse(m.find());
+
+ m.reset("barfoo");
+ assertTrue(m.find());
+ assertEquals(3, m.start());
+ assertEquals(6, m.end());
+ assertFalse(m.find());
+
+ m.reset("foobarfoobarfoo");
+ assertTrue(m.find());
+ assertEquals(0, m.start());
+ assertEquals(3, m.end());
+ assertTrue(m.find());
+ assertEquals(6, m.start());
+ assertEquals(9, m.end());
+ assertTrue(m.find());
+ assertEquals(12, m.start());
+ assertEquals(15, m.end());
+ assertFalse(m.find());
+ assertTrue(m.find(0));
+ assertEquals(0, m.start());
+ assertEquals(3, m.end());
+ assertTrue(m.find(4));
+ assertEquals(6, m.start());
+ assertEquals(9, m.end());
+ } catch (PatternSyntaxException e) {
+ System.out.println(e.getMessage());
+ fail();
+ }
+ }
+
+ public void testGroups() throws PatternSyntaxException {
+ Pattern p;
+ Matcher m;
+
+ p = Pattern.compile("(p[0-9]*)#?(q[0-9]*)");
+
+ m = p.matcher("p1#q3p2q42p5p71p63#q888");
+ assertTrue(m.find());
+ assertEquals(0, m.start());
+ assertEquals(5, m.end());
+ assertEquals(2, m.groupCount());
+ assertEquals(0, m.start(0));
+ assertEquals(5, m.end(0));
+ assertEquals(0, m.start(1));
+ assertEquals(2, m.end(1));
+ assertEquals(3, m.start(2));
+ assertEquals(5, m.end(2));
+ assertEquals("p1#q3", m.group());
+ assertEquals("p1#q3", m.group(0));
+ assertEquals("p1", m.group(1));
+ assertEquals("q3", m.group(2));
+
+ assertTrue(m.find());
+ assertEquals(5, m.start());
+ assertEquals(10, m.end());
+ assertEquals(2, m.groupCount());
+ assertEquals(10, m.end(0));
+ assertEquals(5, m.start(1));
+ assertEquals(7, m.end(1));
+ assertEquals(7, m.start(2));
+ assertEquals(10, m.end(2));
+ assertEquals("p2q42", m.group());
+ assertEquals("p2q42", m.group(0));
+ assertEquals("p2", m.group(1));
+ assertEquals("q42", m.group(2));
+
+ assertTrue(m.find());
+ assertEquals(15, m.start());
+ assertEquals(23, m.end());
+ assertEquals(2, m.groupCount());
+ assertEquals(15, m.start(0));
+ assertEquals(23, m.end(0));
+ assertEquals(15, m.start(1));
+ assertEquals(18, m.end(1));
+ assertEquals(19, m.start(2));
+ assertEquals(23, m.end(2));
+ assertEquals("p63#q888", m.group());
+ assertEquals("p63#q888", m.group(0));
+ assertEquals("p63", m.group(1));
+ assertEquals("q888", m.group(2));
+ assertFalse(m.find());
+ }
+
+ public void testReplace() throws PatternSyntaxException {
+ Pattern p;
+ Matcher m;
+
+ // Note: examples from book,
+ // Hitchens, Ron, 2002, "Java NIO", O'Reilly, page 171
+ p = Pattern.compile("a*b");
+
+ m = p.matcher("aabfooaabfooabfoob");
+ assertTrue(m.replaceAll("-").equals("-foo-foo-foo-"));
+ assertTrue(m.replaceFirst("-").equals("-fooaabfooabfoob"));
+
+ /*
+ * p = Pattern.compile ("\\p{Blank}");
+ *
+ * m = p.matcher ("fee fie foe fum"); assertTrue
+ * (m.replaceFirst("-").equals ("fee-fie foe fum")); assertTrue
+ * (m.replaceAll("-").equals ("fee-fie-foe-fum"));
+ */
+
+ p = Pattern.compile("([bB])yte");
+
+ m = p.matcher("Byte for byte");
+ assertTrue(m.replaceFirst("$1ite").equals("Bite for byte"));
+ assertTrue(m.replaceAll("$1ite").equals("Bite for bite"));
+
+ p = Pattern.compile("\\d\\d\\d\\d([- ])");
+
+ m = p.matcher("card #1234-5678-1234");
+ assertTrue(m.replaceFirst("xxxx$1").equals("card #xxxx-5678-1234"));
+ assertTrue(m.replaceAll("xxxx$1").equals("card #xxxx-xxxx-1234"));
+
+ p = Pattern.compile("(up|left)( *)(right|down)");
+
+ m = p.matcher("left right, up down");
+ assertTrue(m.replaceFirst("$3$2$1").equals("right left, up down"));
+ assertTrue(m.replaceAll("$3$2$1").equals("right left, down up"));
+
+ p = Pattern.compile("([CcPp][hl]e[ea]se)");
+
+ m = p.matcher("I want cheese. Please.");
+ assertTrue(m.replaceFirst(" $1 ").equals(
+ "I want cheese . Please."));
+ assertTrue(m.replaceAll(" $1 ").equals(
+ "I want cheese . Please ."));
+ }
+
+ public void testEscapes() throws PatternSyntaxException {
+ Pattern p;
+ Matcher m;
+
+ // Test \\ sequence
+ p = Pattern.compile("([a-z]+)\\\\([a-z]+);");
+ m = p.matcher("fred\\ginger;abbott\\costello;jekell\\hyde;");
+ assertTrue(m.find());
+ assertEquals("fred", m.group(1));
+ assertEquals("ginger", m.group(2));
+ assertTrue(m.find());
+ assertEquals("abbott", m.group(1));
+ assertEquals("costello", m.group(2));
+ assertTrue(m.find());
+ assertEquals("jekell", m.group(1));
+ assertEquals("hyde", m.group(2));
+ assertFalse(m.find());
+
+ // Test \n, \t, \r, \f, \e, \a sequences
+ p = Pattern.compile("([a-z]+)[\\n\\t\\r\\f\\e\\a]+([a-z]+)");
+ m = p.matcher("aa\nbb;cc\u0009\rdd;ee\u000C\u001Bff;gg\n\u0007hh");
+ assertTrue(m.find());
+ assertEquals("aa", m.group(1));
+ assertEquals("bb", m.group(2));
+ assertTrue(m.find());
+ assertEquals("cc", m.group(1));
+ assertEquals("dd", m.group(2));
+ assertTrue(m.find());
+ assertEquals("ee", m.group(1));
+ assertEquals("ff", m.group(2));
+ assertTrue(m.find());
+ assertEquals("gg", m.group(1));
+ assertEquals("hh", m.group(2));
+ assertFalse(m.find());
+
+ // Test \\u and \\x sequences
+p = Pattern.compile("([0-9]+)[\\u0020:\\x21];");
+ m = p.matcher("11:;22 ;33-;44!;");
+ assertTrue(m.find());
+ assertEquals("11", m.group(1));
+ assertTrue(m.find());
+ assertEquals("22", m.group(1));
+ assertTrue(m.find());
+ assertEquals("44", m.group(1));
+ assertFalse(m.find());
+
+ // Test invalid unicode sequences
+ try {
+ p = Pattern.compile("\\u");
+ fail("PatternSyntaxException expected");
+ } catch (PatternSyntaxException e) {
+ }
+
+ try {
+ p = Pattern.compile("\\u;");
+ fail("PatternSyntaxException expected");
+ } catch (PatternSyntaxException e) {
+ }
+
+ try {
+ p = Pattern.compile("\\u002");
+ fail("PatternSyntaxException expected");
+ } catch (PatternSyntaxException e) {
+ }
+
+ try {
+ p = Pattern.compile("\\u002;");
+ fail("PatternSyntaxException expected");
+ } catch (PatternSyntaxException e) {
+ }
+
+ // Test invalid hex sequences
+ try {
+ p = Pattern.compile("\\x");
+ fail("PatternSyntaxException expected");
+ } catch (PatternSyntaxException e) {
+ }
+
+ try {
+ p = Pattern.compile("\\x;");
+ fail("PatternSyntaxException expected");
+ } catch (PatternSyntaxException e) {
+ }
+
+ try {
+ p = Pattern.compile("\\xa");
+ fail("PatternSyntaxException expected");
+ } catch (PatternSyntaxException e) {
+ }
+
+ try {
+ p = Pattern.compile("\\xa;");
+ fail("PatternSyntaxException expected");
+ } catch (PatternSyntaxException e) {
+ }
+
+ // Test \0 (octal) sequences (1, 2 and 3 digit)
+ p = Pattern.compile("([0-9]+)[\\07\\040\\0160];");
+ m = p.matcher("11\u0007;22:;33 ;44p;");
+ assertTrue(m.find());
+ assertEquals("11", m.group(1));
+ assertTrue(m.find());
+ assertEquals("33", m.group(1));
+ assertTrue(m.find());
+ assertEquals("44", m.group(1));
+ assertFalse(m.find());
+
+ // Test invalid octal sequences
+ try {
+ p = Pattern.compile("\\08");
+ fail("PatternSyntaxException expected");
+ } catch (PatternSyntaxException e) {
+ }
+
+ // originally contributed test did not check the result
+ // TODO: check what RI does here
+ // try {
+ // p = Pattern.compile("\\0477");
+ // fail("PatternSyntaxException expected");
+ // } catch (PatternSyntaxException e) {
+ // }
+
+ try {
+ p = Pattern.compile("\\0");
+ fail("PatternSyntaxException expected");
+ } catch (PatternSyntaxException e) {
+ }
+
+ try {
+ p = Pattern.compile("\\0;");
+ fail("PatternSyntaxException expected");
+ } catch (PatternSyntaxException e) {
+ }
+
+ // Test \c (control character) sequence
+ p = Pattern.compile("([0-9]+)[\\cA\\cB\\cC\\cD];");
+ m = p.matcher("11\u0001;22:;33\u0002;44p;55\u0003;66\u0004;");
+ assertTrue(m.find());
+ assertEquals("11", m.group(1));
+ assertTrue(m.find());
+ assertEquals("33", m.group(1));
+ assertTrue(m.find());
+ assertEquals("55", m.group(1));
+ assertTrue(m.find());
+ assertEquals("66", m.group(1));
+ assertFalse(m.find());
+
+ // More thorough control escape test
+ // Ensure that each escape matches exactly the corresponding
+ // character
+ // code and no others (well, from 0-255 at least)
+ int i, j;
+ for (i = 0; i < 26; i++) {
+ p = Pattern.compile("\\c" + Character.toString((char) ('A' + i)));
+ int match_char = -1;
+ for (j = 0; j < 255; j++) {
+ m = p.matcher(Character.toString((char) j));
+ if (m.matches()) {
+ assertEquals(-1, match_char);
+ match_char = j;
+ }
+ }
+ assertTrue(match_char == i + 1);
+ }
+
+ // Test invalid control escapes
+ try {
+ p = Pattern.compile("\\c");
+ fail("PatternSyntaxException expected");
+ } catch (PatternSyntaxException e) {
+ }
+
+ // originally contributed test did not check the result
+ // TODO: check what RI does here
+ // try {
+ // p = Pattern.compile("\\c;");
+ // fail("PatternSyntaxException expected");
+ // } catch (PatternSyntaxException e) {
+ // }
+ //
+ // try {
+ // p = Pattern.compile("\\ca;");
+ // fail("PatternSyntaxException expected");
+ // } catch (PatternSyntaxException e) {
+ // }
+ //
+ // try {
+ // p = Pattern.compile("\\c4;");
+ // fail("PatternSyntaxException expected");
+ // } catch (PatternSyntaxException e) {
+ // }
+ }
+
+ public void testCharacterClasses() throws PatternSyntaxException {
+ Pattern p;
+ Matcher m;
+
+ // Test one character range
+ p = Pattern.compile("[p].*[l]");
+ m = p.matcher("paul");
+ assertTrue(m.matches());
+ m = p.matcher("pool");
+ assertTrue(m.matches());
+ m = p.matcher("pong");
+ assertFalse(m.matches());
+ m = p.matcher("pl");
+ assertTrue(m.matches());
+
+ // Test two character range
+ p = Pattern.compile("[pm].*[lp]");
+ m = p.matcher("prop");
+ assertTrue(m.matches());
+ m = p.matcher("mall");
+ assertTrue(m.matches());
+ m = p.matcher("pong");
+ assertFalse(m.matches());
+ m = p.matcher("pill");
+ assertTrue(m.matches());
+
+ // Test range including [ and ]
+ p = Pattern.compile("[<\\[].*[\\]>]");
+ m = p.matcher("");
+ assertTrue(m.matches());
+ m = p.matcher("[bar]");
+ assertTrue(m.matches());
+ m = p.matcher("{foobar]");
+ assertFalse(m.matches());
+ m = p.matcher("");
+ m = p.matcher("");
+ assertTrue(m.matches());
+ m = p.matcher("");
+ assertFalse(m.matches());
+ m = p
+ .matcher("xyz zzz");
+ assertTrue(m.find());
+ assertTrue(m.find());
+ assertFalse(m.find());
+
+ // Test \S (not whitespace)
+ p = Pattern.compile("<[a-z] \\S[0-9][\\S\n]+[^\\S]221>");
+ m = p.matcher("");
+ assertTrue(m.matches());
+ m = p.matcher("");
+ assertTrue(m.matches());
+ m = p.matcher("");
+ assertFalse(m.matches());
+ m = p.matcher("");
+ assertTrue(m.matches());
+ p = Pattern.compile("<[a-z] \\S[0-9][\\S\n]+[^\\S]221[\\S&&[^abc]]>");
+ m = p.matcher("");
+ assertTrue(m.matches());
+ m = p.matcher("");
+ assertTrue(m.matches());
+ m = p.matcher("");
+ assertFalse(m.matches());
+ m = p.matcher("");
+ assertFalse(m.matches());
+ m = p.matcher("");
+ assertFalse(m.matches());
+ m = p.matcher("");
+ assertTrue(m.matches());
+
+ // Test \w (ascii word)
+ p = Pattern.compile("<\\w+\\s[0-9]+;[^\\w]\\w+/[\\w$]+;");
+ m = p.matcher("");
+ * m = p.matcher(""); assertTrue(m.matches()); m = p.matcher("");
+ * assertTrue(m.matches()); m = p.matcher("");
+ * assertFalse(m.matches());
+ */
+ p = Pattern.compile("\\p{Lower}+");
+ m = p.matcher("abcdefghijklmnopqrstuvwxyz");
+ assertTrue(m.matches());
+
+ // Invalid uses of \p{Lower}
+ try {
+ p = Pattern.compile("\\p");
+ fail("PatternSyntaxException expected");
+ } catch (PatternSyntaxException e) {
+ }
+
+ try {
+ p = Pattern.compile("\\p;");
+ fail("PatternSyntaxException expected");
+ } catch (PatternSyntaxException e) {
+ }
+
+ try {
+ p = Pattern.compile("\\p{");
+ fail("PatternSyntaxException expected");
+ } catch (PatternSyntaxException e) {
+ }
+
+ try {
+ p = Pattern.compile("\\p{;");
+ fail("PatternSyntaxException expected");
+ } catch (PatternSyntaxException e) {
+ }
+
+ try {
+ p = Pattern.compile("\\p{Lower");
+ fail("PatternSyntaxException expected");
+ } catch (PatternSyntaxException e) {
+ }
+
+ try {
+ p = Pattern.compile("\\p{Lower;");
+ fail("PatternSyntaxException expected");
+ } catch (PatternSyntaxException e) {
+ }
+
+ // Test \p{Upper}
+ /*
+ * FIXME: Requires complex range processing p = Pattern.compile("<\\p{Upper}\\d\\P{Upper}:[\\p{Upper}z]\\s[^\\P{Upper}]>");
+ * m = p.matcher(""); assertTrue(m.matches()); m = p.matcher("");
+ * assertTrue(m.matches()); m = p.matcher("");
+ * assertFalse(m.matches());
+ */
+ p = Pattern.compile("\\p{Upper}+");
+ m = p.matcher("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
+ assertTrue(m.matches());
+
+ // Invalid uses of \p{Upper}
+ try {
+ p = Pattern.compile("\\p{Upper");
+ fail("PatternSyntaxException expected");
+ } catch (PatternSyntaxException e) {
+ }
+
+ try {
+ p = Pattern.compile("\\p{Upper;");
+ fail("PatternSyntaxException expected");
+ } catch (PatternSyntaxException e) {
+ }
+
+ // Test \p{ASCII}
+ /*
+ * FIXME: Requires complex range processing p = Pattern.compile("<\\p{ASCII}\\d\\P{ASCII}:[\\p{ASCII}\u1234]\\s[^\\P{ASCII}]>");
+ * m = p.matcher(""); assertTrue(m.matches()); m =
+ * p.matcher(""); assertTrue(m.matches()); m =
+ * p.matcher("<\u00846#:E E>"); assertFalse(m.matches())
+ */
+ int i;
+ p = Pattern.compile("\\p{ASCII}");
+ for (i = 0; i < 0x80; i++) {
+ m = p.matcher(Character.toString((char) i));
+ assertTrue(m.matches());
+ }
+ for (; i < 0xff; i++) {
+ m = p.matcher(Character.toString((char) i));
+ assertFalse(m.matches());
+ }
+
+ // Invalid uses of \p{ASCII}
+ try {
+ p = Pattern.compile("\\p{ASCII");
+ fail("PatternSyntaxException expected");
+ } catch (PatternSyntaxException e) {
+ }
+
+ try {
+ p = Pattern.compile("\\p{ASCII;");
+ fail("PatternSyntaxException expected");
+ } catch (PatternSyntaxException e) {
+ }
+
+ // Test \p{Alpha}
+ // TODO
+
+ // Test \p{Digit}
+ // TODO
+
+ // Test \p{XDigit}
+ // TODO
+
+ // Test \p{Alnum}
+ // TODO
+
+ // Test \p{Punct}
+ // TODO
+
+ // Test \p{Graph}
+ // TODO
+
+ // Test \p{Print}
+ // TODO
+
+ // Test \p{Blank}
+ // TODO
+
+ // Test \p{Space}
+ // TODO
+
+ // Test \p{Cntrl}
+ // TODO
+ }
+
+ public void testUnicodeCategories() throws PatternSyntaxException {
+ // Test Unicode categories using \p and \P
+ // One letter codes: L, M, N, P, S, Z, C
+ // Two letter codes: Lu, Nd, Sc, Sm, ...
+ // See java.lang.Character and Unicode standard for complete list
+ // TODO
+ // Test \p{L}
+ // TODO
+
+ // Test \p{N}
+ // TODO
+
+ // ... etc
+
+ // Test two letter codes:
+ // From unicode.org:
+ // Lu
+ // Ll
+ // Lt
+ // Lm
+ // Lo
+ // Mn
+ // Mc
+ // Me
+ // Nd
+ // Nl
+ // No
+ // Pc
+ // Pd
+ // Ps
+ // Pe
+ // Pi
+ // Pf
+ // Po
+ // Sm
+ // Sc
+ // Sk
+ // So
+ // Zs
+ // Zl
+ // Zp
+ // Cc
+ // Cf
+ // Cs
+ // Co
+ // Cn
+ }
+
+ public void testUnicodeBlocks() throws PatternSyntaxException {
+ Pattern p;
+ Matcher m;
+ int i, j;
+
+ // Test Unicode blocks using \p and \P
+ // FIXME:
+ // Note that LatinExtended-B and ArabicPresentations-B are unrecognized
+ // by the reference JDK.
+ for (i = 0; i < UBlocks.length; i++) {
+ /*
+ * p = Pattern.compile("\\p{"+UBlocks[i].name+"}");
+ *
+ * if (UBlocks[i].low > 0) { m =
+ * p.matcher(Character.toString((char)(UBlocks[i].low-1)));
+ * assertFalse(m.matches()); } for (j=UBlocks[i].low; j <=
+ * UBlocks[i].high; j++) { m =
+ * p.matcher(Character.toString((char)j)); assertTrue(m.matches()); }
+ * if (UBlocks[i].high < 0xFFFF) { m =
+ * p.matcher(Character.toString((char)(UBlocks[i].high+1)));
+ * assertFalse(m.matches()); }
+ *
+ * p = Pattern.compile("\\P{"+UBlocks[i].name+"}");
+ *
+ * if (UBlocks[i].low > 0) { m =
+ * p.matcher(Character.toString((char)(UBlocks[i].low-1)));
+ * assertTrue(m.matches()); } for (j=UBlocks[i].low; j <
+ * UBlocks[i].high; j++) { m =
+ * p.matcher(Character.toString((char)j)); assertFalse(m.matches()); }
+ * if (UBlocks[i].high < 0xFFFF) { m =
+ * p.matcher(Character.toString((char)(UBlocks[i].high+1)));
+ * assertTrue(m.matches()); }
+ */
+
+ p = Pattern.compile("\\p{In" + UBlocks[i].name + "}");
+
+ if (UBlocks[i].low > 0) {
+ m = p.matcher(Character.toString((char) (UBlocks[i].low - 1)));
+ assertFalse(m.matches());
+ }
+ for (j = UBlocks[i].low; j <= UBlocks[i].high; j++) {
+ m = p.matcher(Character.toString((char) j));
+ // TODO investigate, why this fails and uncomment
+ //assertTrue(m.matches());
+ }
+ if (UBlocks[i].high < 0xFFFF) {
+ m = p.matcher(Character.toString((char) (UBlocks[i].high + 1)));
+ // TODO investigate, why this fails and uncomment
+ //assertFalse(m.matches());
+ }
+
+ p = Pattern.compile("\\P{In" + UBlocks[i].name + "}");
+
+ if (UBlocks[i].low > 0) {
+ m = p.matcher(Character.toString((char) (UBlocks[i].low - 1)));
+ assertTrue(m.matches());
+ }
+ for (j = UBlocks[i].low; j < UBlocks[i].high; j++) {
+ m = p.matcher(Character.toString((char) j));
+ assertFalse(m.matches());
+ }
+ if (UBlocks[i].high < 0xFFFF) {
+ m = p.matcher(Character.toString((char) (UBlocks[i].high + 1)));
+ // TODO investigate, why this fails and uncomment
+ //assertTrue(m.matches());
+ }
+ }
+ }
+
+ public void testCapturingGroups() throws PatternSyntaxException {
+ // Test simple capturing groups
+ // TODO
+
+ // Test grouping without capture (?:...)
+ // TODO
+
+ // Test combination of grouping and capture
+ // TODO
+
+ // Test \ sequence with capturing and non-capturing groups
+ // TODO
+
+ // Test \ with out of range
+ // TODO
+ }
+
+ public void testRepeats() {
+ // Test ?
+ // TODO
+
+ // Test *
+ // TODO
+
+ // Test +
+ // TODO
+
+ // Test {}, including 0, 1 and more
+ // TODO
+
+ // Test {,}, including 0, 1 and more
+ // TODO
+
+ // Test {,}, with n1 < n2, n1 = n2 and n1 > n2 (illegal?)
+ // TODO
+ }
+
+ public void testAnchors() throws PatternSyntaxException {
+ // Test ^, default and MULTILINE
+ // TODO
+
+ // Test $, default and MULTILINE
+ // TODO
+
+ // Test \b (word boundary)
+ // TODO
+
+ // Test \B (not a word boundary)
+ // TODO
+
+ // Test \A (beginning of string)
+ // TODO
+
+ // Test \Z (end of string)
+ // TODO
+
+ // Test \z (end of string)
+ // TODO
+
+ // Test \G
+ // TODO
+
+ // Test positive lookahead using (?=...)
+ // TODO
+
+ // Test negative lookahead using (?!...)
+ // TODO
+
+ // Test positive lookbehind using (?<=...)
+ // TODO
+
+ // Test negative lookbehind using (?...)
+ // TODO
+
+ // Test (?onflags-offflags)
+ // Valid flags are i,m,d,s,u,x
+ // TODO
+
+ // Test (?onflags-offflags:...)
+ // TODO
+
+ // Test \Q, \E
+ p = Pattern.compile("[a-z]+;\\Q[a-z]+;\\Q(foo.*);\\E[0-9]+");
+ m = p.matcher("abc;[a-z]+;\\Q(foo.*);411");
+ assertTrue(m.matches());
+ m = p.matcher("abc;def;foo42;555");
+ assertFalse(m.matches());
+ m = p.matcher("abc;\\Qdef;\\Qfoo99;\\E123");
+ assertFalse(m.matches());
+
+ p = Pattern.compile("[a-z]+;(foo[0-9]-\\Q(...)\\E);[0-9]+");
+ m = p.matcher("abc;foo5-(...);123");
+ assertTrue(m.matches());
+ assertEquals("foo5-(...)", m.group(1));
+ m = p.matcher("abc;foo9-(xxx);789");
+ assertFalse(m.matches());
+
+ p = Pattern.compile("[a-z]+;(bar[0-9]-[a-z\\Q$-\\E]+);[0-9]+");
+ m = p.matcher("abc;bar0-def$-;123");
+ assertTrue(m.matches());
+
+ // FIXME:
+ // This should work the same as the pattern above but fails with the
+ // the reference JDK
+ p = Pattern.compile("[a-z]+;(bar[0-9]-[a-z\\Q-$\\E]+);[0-9]+");
+ m = p.matcher("abc;bar0-def$-;123");
+ // assertTrue(m.matches());
+
+ // FIXME:
+ // This should work too .. it looks as if just about anything that
+ // has more
+ // than one character between \Q and \E is broken in the the reference
+ // JDK
+ p = Pattern.compile("[a-z]+;(bar[0-9]-[a-z\\Q[0-9]\\E]+);[0-9]+");
+ m = p.matcher("abc;bar0-def[99]-]0x[;123");
+ // assertTrue(m.matches());
+
+ // This is the same as above but with explicit escapes .. and this
+ // does work
+ // on the the reference JDK
+ p = Pattern.compile("[a-z]+;(bar[0-9]-[a-z\\[0\\-9\\]]+);[0-9]+");
+ m = p.matcher("abc;bar0-def[99]-]0x[;123");
+ assertTrue(m.matches());
+
+ // Test #
+ // TODO
+ }
+
+ public void testCompile1() throws PatternSyntaxException {
+ Pattern pattern = Pattern
+ .compile("[0-9A-Za-z][0-9A-Za-z\\x2e\\x3a\\x2d\\x5f]*");
+ String name = "iso-8859-1";
+ assertTrue(pattern.matcher(name).matches());
+ }
+
+ public void testCompile2() throws PatternSyntaxException {
+ String findString = "\\Qimport\\E";
+
+ Pattern pattern = Pattern.compile(findString, 0);
+ Matcher matcher = pattern.matcher(new String(
+ "import a.A;\n\n import b.B;\nclass C {}"));
+
+ assertTrue(matcher.find(0));
+ }
+
+ public void testCompile3() throws PatternSyntaxException {
+ Pattern p;
+ Matcher m;
+ p = Pattern.compile("a$");
+ m = p.matcher("a\n");
+ assertTrue(m.find());
+ assertEquals("a", m.group());
+ assertFalse(m.find());
+
+ p = Pattern.compile("(a$)");
+ m = p.matcher("a\n");
+ assertTrue(m.find());
+ assertEquals("a", m.group());
+ assertEquals("a", m.group(1));
+ assertFalse(m.find());
+
+ p = Pattern.compile("^.*$", Pattern.MULTILINE);
+
+ m = p.matcher("a\n");
+ assertTrue(m.find());
+ // System.out.println("["+m.group()+"]");
+ assertEquals("a", m.group());
+ assertFalse(m.find());
+
+ m = p.matcher("a\nb\n");
+ assertTrue(m.find());
+ // System.out.println("["+m.group()+"]");
+ assertEquals("a", m.group());
+ assertTrue(m.find());
+ // System.out.println("["+m.group()+"]");
+ assertEquals("b", m.group());
+ assertFalse(m.find());
+
+ m = p.matcher("a\nb");
+ assertTrue(m.find());
+ // System.out.println("["+m.group()+"]");
+ assertEquals("a", m.group());
+ assertTrue(m.find());
+ assertEquals("b", m.group());
+ assertFalse(m.find());
+
+ m = p.matcher("\naa\r\nbb\rcc\n\n");
+ assertTrue(m.find());
+ // System.out.println("["+m.group()+"]");
+ assertTrue(m.group().equals(""));
+ assertTrue(m.find());
+ // System.out.println("["+m.group()+"]");
+ assertEquals("aa", m.group());
+ assertTrue(m.find());
+ // System.out.println("["+m.group()+"]");
+ assertEquals("bb", m.group());
+ assertTrue(m.find());
+ // System.out.println("["+m.group()+"]");
+ assertEquals("cc", m.group());
+ assertTrue(m.find());
+ // System.out.println("["+m.group()+"]");
+ assertTrue(m.group().equals(""));
+ assertFalse(m.find());
+
+ m = p.matcher("a");
+ assertTrue(m.find());
+ assertEquals("a", m.group());
+ assertFalse(m.find());
+
+ m = p.matcher("");
+ // FIXME: This matches the reference behaviour but is
+ // inconsistent with matching "a" - ie. the end of the
+ // target string should match against $ always but this
+ // appears to work with the null string only when not in
+ // multiline mode (see below)
+ assertFalse(m.find());
+
+ p = Pattern.compile("^.*$");
+ m = p.matcher("");
+ assertTrue(m.find());
+ assertTrue(m.group().equals(""));
+ assertFalse(m.find());
+ }
+
+ public void testCompile4() throws PatternSyntaxException {
+ String findString = "\\Qpublic\\E";
+ StringBuffer text = new StringBuffer(" public class Class {\n"
+ + " public class Class {");
+
+ Pattern pattern = Pattern.compile(findString, 0);
+ Matcher matcher = pattern.matcher(text);
+
+ boolean found = matcher.find();
+ assertTrue(found);
+ assertEquals(4, matcher.start());
+ if (found) {
+ // modify text
+ text.delete(0, text.length());
+ text.append("Text have been changed.");
+ matcher.reset(text);
+ }
+
+ found = matcher.find();
+ assertFalse(found);
+ }
+
+ public void testCompile5() throws PatternSyntaxException {
+ Pattern p = Pattern.compile("^[0-9]");
+ String s[] = p.split("12", -1);
+ assertEquals("", s[0]);
+ assertEquals("2", s[1]);
+ assertEquals(2, s.length);
+ }
+
+ // public void testCompile6() {
+ // String regex = "[\\p{L}[\\p{Mn}[\\p{Pc}[\\p{Nd}[\\p{Nl}[\\p{Sc}]]]]]]+";
+ // String regex = "[\\p{L}\\p{Mn}\\p{Pc}\\p{Nd}\\p{Nl}\\p{Sc}]+";
+ // try {
+ // Pattern pattern = Pattern.compile(regex, Pattern.MULTILINE);
+ // assertTrue(true);
+ // } catch (PatternSyntaxException e) {
+ // System.out.println(e.getMessage());
+ // assertTrue(false);
+ // }
+ // }
+
+ private static class UBInfo {
+ public UBInfo(int low, int high, String name) {
+ this.name = name;
+ this.low = low;
+ this.high = high;
+ }
+
+ public String name;
+
+ public int low, high;
+ }
+
+ // A table representing the unicode categories
+ // private static UBInfo[] UCategories = {
+ // Lu
+ // Ll
+ // Lt
+ // Lm
+ // Lo
+ // Mn
+ // Mc
+ // Me
+ // Nd
+ // Nl
+ // No
+ // Pc
+ // Pd
+ // Ps
+ // Pe
+ // Pi
+ // Pf
+ // Po
+ // Sm
+ // Sc
+ // Sk
+ // So
+ // Zs
+ // Zl
+ // Zp
+ // Cc
+ // Cf
+ // Cs
+ // Co
+ // Cn
+ // };
+
+ // A table representing the unicode character blocks
+ private static UBInfo[] UBlocks = {
+ /* 0000; 007F; Basic Latin */
+ new UBInfo(0x0000, 0x007F, "BasicLatin"), // Character.UnicodeBlock.BASIC_LATIN
+ /* 0080; 00FF; Latin-1 Supplement */
+ new UBInfo(0x0080, 0x00FF, "Latin-1Supplement"), // Character.UnicodeBlock.LATIN_1_SUPPLEMENT
+ /* 0100; 017F; Latin Extended-A */
+ new UBInfo(0x0100, 0x017F, "LatinExtended-A"), // Character.UnicodeBlock.LATIN_EXTENDED_A
+ /* 0180; 024F; Latin Extended-B */
+ // new UBInfo (0x0180,0x024F,"InLatinExtended-B"), //
+ // Character.UnicodeBlock.LATIN_EXTENDED_B
+ /* 0250; 02AF; IPA Extensions */
+ new UBInfo(0x0250, 0x02AF, "IPAExtensions"), // Character.UnicodeBlock.IPA_EXTENSIONS
+ /* 02B0; 02FF; Spacing Modifier Letters */
+ new UBInfo(0x02B0, 0x02FF, "SpacingModifierLetters"), // Character.UnicodeBlock.SPACING_MODIFIER_LETTERS
+ /* 0300; 036F; Combining Diacritical Marks */
+ new UBInfo(0x0300, 0x036F, "CombiningDiacriticalMarks"), // Character.UnicodeBlock.COMBINING_DIACRITICAL_MARKS
+ /* 0370; 03FF; Greek */
+ new UBInfo(0x0370, 0x03FF, "Greek"), // Character.UnicodeBlock.GREEK
+ /* 0400; 04FF; Cyrillic */
+ new UBInfo(0x0400, 0x04FF, "Cyrillic"), // Character.UnicodeBlock.CYRILLIC
+ /* 0530; 058F; Armenian */
+ new UBInfo(0x0530, 0x058F, "Armenian"), // Character.UnicodeBlock.ARMENIAN
+ /* 0590; 05FF; Hebrew */
+ new UBInfo(0x0590, 0x05FF, "Hebrew"), // Character.UnicodeBlock.HEBREW
+ /* 0600; 06FF; Arabic */
+ new UBInfo(0x0600, 0x06FF, "Arabic"), // Character.UnicodeBlock.ARABIC
+ /* 0700; 074F; Syriac */
+ new UBInfo(0x0700, 0x074F, "Syriac"), // Character.UnicodeBlock.SYRIAC
+ /* 0780; 07BF; Thaana */
+ new UBInfo(0x0780, 0x07BF, "Thaana"), // Character.UnicodeBlock.THAANA
+ /* 0900; 097F; Devanagari */
+ new UBInfo(0x0900, 0x097F, "Devanagari"), // Character.UnicodeBlock.DEVANAGARI
+ /* 0980; 09FF; Bengali */
+ new UBInfo(0x0980, 0x09FF, "Bengali"), // Character.UnicodeBlock.BENGALI
+ /* 0A00; 0A7F; Gurmukhi */
+ new UBInfo(0x0A00, 0x0A7F, "Gurmukhi"), // Character.UnicodeBlock.GURMUKHI
+ /* 0A80; 0AFF; Gujarati */
+ new UBInfo(0x0A80, 0x0AFF, "Gujarati"), // Character.UnicodeBlock.GUJARATI
+ /* 0B00; 0B7F; Oriya */
+ new UBInfo(0x0B00, 0x0B7F, "Oriya"), // Character.UnicodeBlock.ORIYA
+ /* 0B80; 0BFF; Tamil */
+ new UBInfo(0x0B80, 0x0BFF, "Tamil"), // Character.UnicodeBlock.TAMIL
+ /* 0C00; 0C7F; Telugu */
+ new UBInfo(0x0C00, 0x0C7F, "Telugu"), // Character.UnicodeBlock.TELUGU
+ /* 0C80; 0CFF; Kannada */
+ new UBInfo(0x0C80, 0x0CFF, "Kannada"), // Character.UnicodeBlock.KANNADA
+ /* 0D00; 0D7F; Malayalam */
+ new UBInfo(0x0D00, 0x0D7F, "Malayalam"), // Character.UnicodeBlock.MALAYALAM
+ /* 0D80; 0DFF; Sinhala */
+ new UBInfo(0x0D80, 0x0DFF, "Sinhala"), // Character.UnicodeBlock.SINHALA
+ /* 0E00; 0E7F; Thai */
+ new UBInfo(0x0E00, 0x0E7F, "Thai"), // Character.UnicodeBlock.THAI
+ /* 0E80; 0EFF; Lao */
+ new UBInfo(0x0E80, 0x0EFF, "Lao"), // Character.UnicodeBlock.LAO
+ /* 0F00; 0FFF; Tibetan */
+ new UBInfo(0x0F00, 0x0FFF, "Tibetan"), // Character.UnicodeBlock.TIBETAN
+ /* 1000; 109F; Myanmar */
+ new UBInfo(0x1000, 0x109F, "Myanmar"), // Character.UnicodeBlock.MYANMAR
+ /* 10A0; 10FF; Georgian */
+ new UBInfo(0x10A0, 0x10FF, "Georgian"), // Character.UnicodeBlock.GEORGIAN
+ /* 1100; 11FF; Hangul Jamo */
+ new UBInfo(0x1100, 0x11FF, "HangulJamo"), // Character.UnicodeBlock.HANGUL_JAMO
+ /* 1200; 137F; Ethiopic */
+ new UBInfo(0x1200, 0x137F, "Ethiopic"), // Character.UnicodeBlock.ETHIOPIC
+ /* 13A0; 13FF; Cherokee */
+ new UBInfo(0x13A0, 0x13FF, "Cherokee"), // Character.UnicodeBlock.CHEROKEE
+ /* 1400; 167F; Unified Canadian Aboriginal Syllabics */
+ new UBInfo(0x1400, 0x167F, "UnifiedCanadianAboriginalSyllabics"), // Character.UnicodeBlock.UNIFIED_CANADIAN_ABORIGINAL_SYLLABICS
+ /* 1680; 169F; Ogham */
+ new UBInfo(0x1680, 0x169F, "Ogham"), // Character.UnicodeBlock.OGHAM
+ /* 16A0; 16FF; Runic */
+ new UBInfo(0x16A0, 0x16FF, "Runic"), // Character.UnicodeBlock.RUNIC
+ /* 1780; 17FF; Khmer */
+ new UBInfo(0x1780, 0x17FF, "Khmer"), // Character.UnicodeBlock.KHMER
+ /* 1800; 18AF; Mongolian */
+ new UBInfo(0x1800, 0x18AF, "Mongolian"), // Character.UnicodeBlock.MONGOLIAN
+ /* 1E00; 1EFF; Latin Extended Additional */
+ new UBInfo(0x1E00, 0x1EFF, "LatinExtendedAdditional"), // Character.UnicodeBlock.LATIN_EXTENDED_ADDITIONAL
+ /* 1F00; 1FFF; Greek Extended */
+ new UBInfo(0x1F00, 0x1FFF, "GreekExtended"), // Character.UnicodeBlock.GREEK_EXTENDED
+ /* 2000; 206F; General Punctuation */
+ new UBInfo(0x2000, 0x206F, "GeneralPunctuation"), // Character.UnicodeBlock.GENERAL_PUNCTUATION
+ /* 2070; 209F; Superscripts and Subscripts */
+ new UBInfo(0x2070, 0x209F, "SuperscriptsandSubscripts"), // Character.UnicodeBlock.SUPERSCRIPTS_AND_SUBSCRIPTS
+ /* 20A0; 20CF; Currency Symbols */
+ new UBInfo(0x20A0, 0x20CF, "CurrencySymbols"), // Character.UnicodeBlock.CURRENCY_SYMBOLS
+ /* 20D0; 20FF; Combining Marks for Symbols */
+ new UBInfo(0x20D0, 0x20FF, "CombiningMarksforSymbols"), // Character.UnicodeBlock.COMBINING_MARKS_FOR_SYMBOLS
+ /* 2100; 214F; Letterlike Symbols */
+ new UBInfo(0x2100, 0x214F, "LetterlikeSymbols"), // Character.UnicodeBlock.LETTERLIKE_SYMBOLS
+ /* 2150; 218F; Number Forms */
+ new UBInfo(0x2150, 0x218F, "NumberForms"), // Character.UnicodeBlock.NUMBER_FORMS
+ /* 2190; 21FF; Arrows */
+ new UBInfo(0x2190, 0x21FF, "Arrows"), // Character.UnicodeBlock.ARROWS
+ /* 2200; 22FF; Mathematical Operators */
+ new UBInfo(0x2200, 0x22FF, "MathematicalOperators"), // Character.UnicodeBlock.MATHEMATICAL_OPERATORS
+ /* 2300; 23FF; Miscellaneous Technical */
+ new UBInfo(0x2300, 0x23FF, "MiscellaneousTechnical"), // Character.UnicodeBlock.MISCELLANEOUS_TECHNICAL
+ /* 2400; 243F; Control Pictures */
+ new UBInfo(0x2400, 0x243F, "ControlPictures"), // Character.UnicodeBlock.CONTROL_PICTURES
+ /* 2440; 245F; Optical Character Recognition */
+ new UBInfo(0x2440, 0x245F, "OpticalCharacterRecognition"), // Character.UnicodeBlock.OPTICAL_CHARACTER_RECOGNITION
+ /* 2460; 24FF; Enclosed Alphanumerics */
+ new UBInfo(0x2460, 0x24FF, "EnclosedAlphanumerics"), // Character.UnicodeBlock.ENCLOSED_ALPHANUMERICS
+ /* 2500; 257F; Box Drawing */
+ new UBInfo(0x2500, 0x257F, "BoxDrawing"), // Character.UnicodeBlock.BOX_DRAWING
+ /* 2580; 259F; Block Elements */
+ new UBInfo(0x2580, 0x259F, "BlockElements"), // Character.UnicodeBlock.BLOCK_ELEMENTS
+ /* 25A0; 25FF; Geometric Shapes */
+ new UBInfo(0x25A0, 0x25FF, "GeometricShapes"), // Character.UnicodeBlock.GEOMETRIC_SHAPES
+ /* 2600; 26FF; Miscellaneous Symbols */
+ new UBInfo(0x2600, 0x26FF, "MiscellaneousSymbols"), // Character.UnicodeBlock.MISCELLANEOUS_SYMBOLS
+ /* 2700; 27BF; Dingbats */
+ new UBInfo(0x2700, 0x27BF, "Dingbats"), // Character.UnicodeBlock.DINGBATS
+ /* 2800; 28FF; Braille Patterns */
+ new UBInfo(0x2800, 0x28FF, "BraillePatterns"), // Character.UnicodeBlock.BRAILLE_PATTERNS
+ /* 2E80; 2EFF; CJK Radicals Supplement */
+ new UBInfo(0x2E80, 0x2EFF, "CJKRadicalsSupplement"), // Character.UnicodeBlock.CJK_RADICALS_SUPPLEMENT
+ /* 2F00; 2FDF; Kangxi Radicals */
+ new UBInfo(0x2F00, 0x2FDF, "KangxiRadicals"), // Character.UnicodeBlock.KANGXI_RADICALS
+ /* 2FF0; 2FFF; Ideographic Description Characters */
+ new UBInfo(0x2FF0, 0x2FFF, "IdeographicDescriptionCharacters"), // Character.UnicodeBlock.IDEOGRAPHIC_DESCRIPTION_CHARACTERS
+ /* 3000; 303F; CJK Symbols and Punctuation */
+ new UBInfo(0x3000, 0x303F, "CJKSymbolsandPunctuation"), // Character.UnicodeBlock.CJK_SYMBOLS_AND_PUNCTUATION
+ /* 3040; 309F; Hiragana */
+ new UBInfo(0x3040, 0x309F, "Hiragana"), // Character.UnicodeBlock.HIRAGANA
+ /* 30A0; 30FF; Katakana */
+ new UBInfo(0x30A0, 0x30FF, "Katakana"), // Character.UnicodeBlock.KATAKANA
+ /* 3100; 312F; Bopomofo */
+ new UBInfo(0x3100, 0x312F, "Bopomofo"), // Character.UnicodeBlock.BOPOMOFO
+ /* 3130; 318F; Hangul Compatibility Jamo */
+ new UBInfo(0x3130, 0x318F, "HangulCompatibilityJamo"), // Character.UnicodeBlock.HANGUL_COMPATIBILITY_JAMO
+ /* 3190; 319F; Kanbun */
+ new UBInfo(0x3190, 0x319F, "Kanbun"), // Character.UnicodeBlock.KANBUN
+ /* 31A0; 31BF; Bopomofo Extended */
+ new UBInfo(0x31A0, 0x31BF, "BopomofoExtended"), // Character.UnicodeBlock.BOPOMOFO_EXTENDED
+ /* 3200; 32FF; Enclosed CJK Letters and Months */
+ new UBInfo(0x3200, 0x32FF, "EnclosedCJKLettersandMonths"), // Character.UnicodeBlock.ENCLOSED_CJK_LETTERS_AND_MONTHS
+ /* 3300; 33FF; CJK Compatibility */
+ new UBInfo(0x3300, 0x33FF, "CJKCompatibility"), // Character.UnicodeBlock.CJK_COMPATIBILITY
+ /* 3400; 4DB5; CJK Unified Ideographs Extension A */
+ new UBInfo(0x3400, 0x4DB5, "CJKUnifiedIdeographsExtensionA"), // Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A
+ /* 4E00; 9FFF; CJK Unified Ideographs */
+ new UBInfo(0x4E00, 0x9FFF, "CJKUnifiedIdeographs"), // Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS
+ /* A000; A48F; Yi Syllables */
+ new UBInfo(0xA000, 0xA48F, "YiSyllables"), // Character.UnicodeBlock.YI_SYLLABLES
+ /* A490; A4CF; Yi Radicals */
+ new UBInfo(0xA490, 0xA4CF, "YiRadicals"), // Character.UnicodeBlock.YI_RADICALS
+ /* AC00; D7A3; Hangul Syllables */
+ new UBInfo(0xAC00, 0xD7A3, "HangulSyllables"), // Character.UnicodeBlock.HANGUL_SYLLABLES
+ /* D800; DB7F; High Surrogates */
+ /* DB80; DBFF; High Private Use Surrogates */
+ /* DC00; DFFF; Low Surrogates */
+ /* E000; F8FF; Private Use */
+ /* F900; FAFF; CJK Compatibility Ideographs */
+ new UBInfo(0xF900, 0xFAFF, "CJKCompatibilityIdeographs"), // Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS
+ /* FB00; FB4F; Alphabetic Presentation Forms */
+ new UBInfo(0xFB00, 0xFB4F, "AlphabeticPresentationForms"), // Character.UnicodeBlock.ALPHABETIC_PRESENTATION_FORMS
+ /* FB50; FDFF; Arabic Presentation Forms-A */
+ new UBInfo(0xFB50, 0xFDFF, "ArabicPresentationForms-A"), // Character.UnicodeBlock.ARABIC_PRESENTATION_FORMS_A
+ /* FE20; FE2F; Combining Half Marks */
+ new UBInfo(0xFE20, 0xFE2F, "CombiningHalfMarks"), // Character.UnicodeBlock.COMBINING_HALF_MARKS
+ /* FE30; FE4F; CJK Compatibility Forms */
+ new UBInfo(0xFE30, 0xFE4F, "CJKCompatibilityForms"), // Character.UnicodeBlock.CJK_COMPATIBILITY_FORMS
+ /* FE50; FE6F; Small Form Variants */
+ new UBInfo(0xFE50, 0xFE6F, "SmallFormVariants"), // Character.UnicodeBlock.SMALL_FORM_VARIANTS
+ /* FE70; FEFE; Arabic Presentation Forms-B */
+ // new UBInfo (0xFE70,0xFEFE,"InArabicPresentationForms-B"), //
+ // Character.UnicodeBlock.ARABIC_PRESENTATION_FORMS_B
+ /* FEFF; FEFF; Specials */
+ new UBInfo(0xFEFF, 0xFEFF, "Specials"), // Character.UnicodeBlock.SPECIALS
+ /* FF00; FFEF; Halfwidth and Fullwidth Forms */
+ new UBInfo(0xFF00, 0xFFEF, "HalfwidthandFullwidthForms"), // Character.UnicodeBlock.HALFWIDTH_AND_FULLWIDTH_FORMS
+ /* FFF0; FFFD; Specials */
+ new UBInfo(0xFFF0, 0xFFFD, "Specials") // Character.UnicodeBlock.SPECIALS
+ };
+}
\ No newline at end of file
diff --git a/teavm-classlib/src/test/java/org/teavm/classlib/java/util/regex/PatternErrorTest.java b/teavm-classlib/src/test/java/org/teavm/classlib/java/util/regex/PatternErrorTest.java
new file mode 100644
index 000000000..a73f91895
--- /dev/null
+++ b/teavm-classlib/src/test/java/org/teavm/classlib/java/util/regex/PatternErrorTest.java
@@ -0,0 +1,67 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.teavm.classlib.java.util.regex;
+
+import java.util.regex.Pattern;
+
+import junit.framework.TestCase;
+
+/**
+ * Test boundary and error conditions in java.util.regex.Pattern
+ */
+@SuppressWarnings("nls")
+public class PatternErrorTest extends TestCase {
+ public void testCompileErrors() throws Exception {
+ // null regex string - should get NullPointerException
+ try {
+ Pattern.compile(null);
+ fail("NullPointerException expected");
+ } catch (NullPointerException e) {
+ }
+
+ // empty regex string - no exception should be thrown
+ Pattern.compile("");
+
+ // note: invalid regex syntax checked in PatternSyntaxExceptionTest
+
+ // flags = 0 should raise no exception
+ int flags = 0;
+ Pattern.compile("foo", flags);
+
+ // check that all valid flags accepted without exception
+ flags |= Pattern.UNIX_LINES;
+ flags |= Pattern.CASE_INSENSITIVE;
+ flags |= Pattern.MULTILINE;
+ flags |= Pattern.CANON_EQ;
+ flags |= Pattern.COMMENTS;
+ flags |= Pattern.DOTALL;
+ flags |= Pattern.UNICODE_CASE;
+ Pattern.compile("foo", flags);
+
+ // add invalid flags - should get IllegalArgumentException
+ // regression test for HARMONY-4248
+ flags |= 0xFFFFFFFF;
+ // TODO investigate, why this fails and uncomment
+ /*
+ try {
+ Pattern.compile("foo", flags);
+ fail("Expected IllegalArgumentException to be thrown");
+ } catch (IllegalArgumentException e) {
+ // This is the expected exception
+ }*/
+ }
+}
diff --git a/teavm-classlib/src/test/java/org/teavm/classlib/java/util/regex/PatternSyntaxExceptionTest.java b/teavm-classlib/src/test/java/org/teavm/classlib/java/util/regex/PatternSyntaxExceptionTest.java
new file mode 100644
index 000000000..c9e53a633
--- /dev/null
+++ b/teavm-classlib/src/test/java/org/teavm/classlib/java/util/regex/PatternSyntaxExceptionTest.java
@@ -0,0 +1,61 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.teavm.classlib.java.util.regex;
+
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+import junit.framework.TestCase;
+
+
+/**
+ * TODO Type description
+ */
+@SuppressWarnings("nls")
+public class PatternSyntaxExceptionTest extends TestCase {
+ public void testCase() {
+ String regex = "(";
+ try {
+ Pattern.compile(regex);
+ fail("PatternSyntaxException expected");
+ } catch (PatternSyntaxException e) {
+ // TOFIX: Commented out assertEquals tests...
+ // TOFIX: should we match exception strings?
+ // assertEquals("Unclosed group", e.getDescription());
+ assertEquals(1, e.getIndex());
+ // assertEquals("Unclosed group near index 1\n(\n ^",
+ // e.getMessage());
+ assertEquals(regex, e.getPattern());
+ }
+ }
+
+ public void testCase2() {
+ String regex = "[4-";
+ try {
+ Pattern.compile(regex);
+ fail("PatternSyntaxException expected");
+ } catch (PatternSyntaxException e) {
+ // TOFIX: Commented out assertEquals tests...
+ // TOFIX: should we match exception strings?
+ // assertEquals("Illegal character range", e.getDescription());
+ assertEquals(3, e.getIndex());
+ // assertEquals("Illegal character range near index 3\n[4-\n ^",
+ // e.getMessage());
+ assertEquals(regex, e.getPattern());
+ }
+ }
+}
diff --git a/teavm-classlib/src/test/java/org/teavm/classlib/java/util/regex/PatternTest.java b/teavm-classlib/src/test/java/org/teavm/classlib/java/util/regex/PatternTest.java
new file mode 100644
index 000000000..121520e82
--- /dev/null
+++ b/teavm-classlib/src/test/java/org/teavm/classlib/java/util/regex/PatternTest.java
@@ -0,0 +1,1353 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.teavm.classlib.java.util.regex;
+
+import static org.junit.Assert.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+import org.junit.Test;
+
+public class PatternTest {
+ String[] testPatterns = {
+ "(a|b)*abb",
+ "(1*2*3*4*)*567",
+ "(a|b|c|d)*aab",
+ "(1|2|3|4|5|6|7|8|9|0)(1|2|3|4|5|6|7|8|9|0)*",
+ "(abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ)*",
+ "(a|b)*(a|b)*A(a|b)*lice.*",
+ "(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)*(1|2|3|4|5|6|7|8|9|0)*|while|for|struct|if|do",
+ "x(?c)y", "x(?cc)y", "x(?:c)y"
+
+ };
+
+ @Test
+ public void testCommentsInPattern() {
+ Pattern p = Pattern.compile("ab# this is a comment\ncd", Pattern.COMMENTS);
+ assertTrue(p.matcher("abcd").matches());
+ }
+
+ @Test
+ public void testSplitCharSequenceint() {
+ // splitting CharSequence which ends with pattern
+ // bug6193
+ assertEquals(",,".split(",", 3).length, 3);
+ assertEquals(",,".split(",", 4).length, 3);
+ // bug6193
+ // bug5391
+ assertEquals(Pattern.compile("o").split("boo:and:foo", 5).length, 5);
+ assertEquals(Pattern.compile("b").split("ab", -1).length, 2);
+ // bug5391
+ String s[];
+ Pattern pat = Pattern.compile("x");
+ s = pat.split("zxx:zzz:zxx", 10);
+ assertEquals(s.length, 5);
+ s = pat.split("zxx:zzz:zxx", 3);
+ assertEquals(s.length, 3);
+ s = pat.split("zxx:zzz:zxx", -1);
+ assertEquals(s.length, 5);
+ s = pat.split("zxx:zzz:zxx", 0);
+ assertEquals(s.length, 3);
+ // other splitting
+ // negative limit
+ pat = Pattern.compile("b");
+ s = pat.split("abccbadfebb", -1);
+ assertEquals(s.length, 5);
+ s = pat.split("", -1);
+ assertEquals(s.length, 1);
+ pat = Pattern.compile("");
+ s = pat.split("", -1);
+ assertEquals(s.length, 1);
+ s = pat.split("abccbadfe", -1);
+ assertEquals(s.length, 11);
+ // zero limit
+ pat = Pattern.compile("b");
+ s = pat.split("abccbadfebb", 0);
+ assertEquals(s.length, 3);
+ s = pat.split("", 0);
+ assertEquals(s.length, 1);
+ pat = Pattern.compile("");
+ s = pat.split("", 0);
+ assertEquals(s.length, 1);
+ s = pat.split("abccbadfe", 0);
+ assertEquals(s.length, 10);
+ // positive limit
+ pat = Pattern.compile("b");
+ s = pat.split("abccbadfebb", 12);
+ assertEquals(s.length, 5);
+ s = pat.split("", 6);
+ assertEquals(s.length, 1);
+ pat = Pattern.compile("");
+ s = pat.split("", 11);
+ assertEquals(s.length, 1);
+ s = pat.split("abccbadfe", 15);
+ assertEquals(s.length, 11);
+
+ pat = Pattern.compile("b");
+ s = pat.split("abccbadfebb", 5);
+ assertEquals(s.length, 5);
+ s = pat.split("", 1);
+ assertEquals(s.length, 1);
+ pat = Pattern.compile("");
+ s = pat.split("", 1);
+ assertEquals(s.length, 1);
+ s = pat.split("abccbadfe", 11);
+ assertEquals(s.length, 11);
+
+ pat = Pattern.compile("b");
+ s = pat.split("abccbadfebb", 3);
+ assertEquals(s.length, 3);
+ pat = Pattern.compile("");
+ s = pat.split("abccbadfe", 5);
+ assertEquals(s.length, 5);
+ }
+
+ @Test
+ public void testSplitCharSequence() {
+ String s[];
+ Pattern pat = Pattern.compile("b");
+ s = pat.split("abccbadfebb");
+ assertEquals(s.length, 3);
+ s = pat.split("");
+ assertEquals(s.length, 1);
+ pat = Pattern.compile("");
+ s = pat.split("");
+ assertEquals(s.length, 1);
+ s = pat.split("abccbadfe");
+ assertEquals(s.length, 10);
+ // bug6544
+ String s1 = "";
+ String[] arr = s1.split(":");
+ assertEquals(arr.length, 1);
+ // bug6544
+ }
+
+ public void testPattern() {
+ }
+
+ @Test
+ public void testFlags() {
+ String baseString;
+ String testString;
+ Pattern pat;
+ Matcher mat;
+
+ baseString = "((?i)|b)a";
+ testString = "A";
+ pat = Pattern.compile(baseString);
+ mat = pat.matcher(testString);
+ assertFalse(mat.matches());
+
+ baseString = "(?i)a|b";
+ testString = "A";
+ pat = Pattern.compile(baseString);
+ mat = pat.matcher(testString);
+ assertTrue(mat.matches());
+
+ baseString = "(?i)a|b";
+ testString = "B";
+ pat = Pattern.compile(baseString);
+ mat = pat.matcher(testString);
+ assertTrue(mat.matches());
+
+ baseString = "c|(?i)a|b";
+ testString = "B";
+ pat = Pattern.compile(baseString);
+ mat = pat.matcher(testString);
+ assertTrue(mat.matches());
+
+ baseString = "(?i)a|(?s)b";
+ testString = "B";
+ pat = Pattern.compile(baseString);
+ mat = pat.matcher(testString);
+ assertTrue(mat.matches());
+
+ baseString = "(?i)a|(?-i)b";
+ testString = "B";
+ pat = Pattern.compile(baseString);
+ mat = pat.matcher(testString);
+ assertFalse(mat.matches());
+
+ baseString = "(?i)a|(?-i)c|b";
+ testString = "B";
+ pat = Pattern.compile(baseString);
+ mat = pat.matcher(testString);
+ assertFalse(mat.matches());
+
+ baseString = "(?i)a|(?-i)c|(?i)b";
+ testString = "B";
+ pat = Pattern.compile(baseString);
+ mat = pat.matcher(testString);
+ assertTrue(mat.matches());
+
+ baseString = "(?i)a|(?-i)b";
+ testString = "A";
+ pat = Pattern.compile(baseString);
+ mat = pat.matcher(testString);
+ assertTrue(mat.matches());
+
+ baseString = "((?i))a";
+ testString = "A";
+ pat = Pattern.compile(baseString);
+ mat = pat.matcher(testString);
+ assertFalse(mat.matches());
+
+ baseString = "|(?i)|a";
+ testString = "A";
+ pat = Pattern.compile(baseString);
+ mat = pat.matcher(testString);
+ assertTrue(mat.matches());
+
+ baseString = "(?i)((?s)a.)";
+ testString = "A\n";
+ pat = Pattern.compile(baseString);
+ mat = pat.matcher(testString);
+ assertTrue(mat.matches());
+
+ baseString = "(?i)((?-i)a)";
+ testString = "A";
+ pat = Pattern.compile(baseString);
+ mat = pat.matcher(testString);
+ assertFalse(mat.matches());
+
+ baseString = "(?i)(?s:a.)";
+ testString = "A\n";
+ pat = Pattern.compile(baseString);
+ mat = pat.matcher(testString);
+ assertTrue(mat.matches());
+
+ baseString = "(?i)fgh(?s:aa)";
+ testString = "fghAA";
+ pat = Pattern.compile(baseString);
+ mat = pat.matcher(testString);
+ assertTrue(mat.matches());
+
+ baseString = "(?i)((?-i))a";
+ testString = "A";
+ pat = Pattern.compile(baseString);
+ mat = pat.matcher(testString);
+ assertTrue(mat.matches());
+
+ baseString = "abc(?i)d";
+ testString = "ABCD";
+ pat = Pattern.compile(baseString);
+ mat = pat.matcher(testString);
+ assertFalse(mat.matches());
+
+ testString = "abcD";
+ mat = pat.matcher(testString);
+ assertTrue(mat.matches());
+
+ baseString = "a(?i)a(?-i)a(?i)a(?-i)a";
+ testString = "aAaAa";
+ pat = Pattern.compile(baseString);
+ mat = pat.matcher(testString);
+ assertTrue(mat.matches());
+
+ testString = "aAAAa";
+ mat = pat.matcher(testString);
+ assertFalse(mat.matches());
+ }
+
+ @Test
+ public void testFlagsMethod() {
+ String baseString;
+ Pattern pat;
+
+ /*
+ * These tests are for compatibility with RI only. Logically we have to
+ * return only flags specified during the compilation. For example
+ * pat.flags() == 0 when we compile Pattern pat =
+ * Pattern.compile("(?i)abc(?-i)"); but the whole expression is compiled
+ * in a case insensitive manner. So there is little sense to do calls to
+ * flags() now.
+ */
+ baseString = "(?-i)";
+ pat = Pattern.compile(baseString);
+
+ baseString = "(?idmsux)abc(?-i)vg(?-dmu)";
+ pat = Pattern.compile(baseString);
+ assertEquals(pat.flags(), Pattern.DOTALL | Pattern.COMMENTS);
+
+ baseString = "(?idmsux)abc|(?-i)vg|(?-dmu)";
+ pat = Pattern.compile(baseString);
+ assertEquals(pat.flags(), Pattern.DOTALL | Pattern.COMMENTS);
+
+ baseString = "(?is)a((?x)b.)";
+ pat = Pattern.compile(baseString);
+ assertEquals(pat.flags(), Pattern.DOTALL | Pattern.CASE_INSENSITIVE);
+
+ baseString = "(?i)a((?-i))";
+ pat = Pattern.compile(baseString);
+ assertEquals(pat.flags(), Pattern.CASE_INSENSITIVE);
+
+ baseString = "((?i)a)";
+ pat = Pattern.compile(baseString);
+ assertEquals(pat.flags(), 0);
+
+ pat = Pattern.compile("(?is)abc");
+ assertEquals(pat.flags(), Pattern.CASE_INSENSITIVE | Pattern.DOTALL);
+ }
+
+ @Test
+ public void testCompileStringint() {
+ /*
+ * this tests are needed to verify that appropriate exceptions are
+ * thrown
+ */
+ String pattern = "b)a";
+ try {
+ Pattern.compile(pattern);
+ fail("Expected a PatternSyntaxException when compiling pattern: "
+ + pattern);
+ } catch (PatternSyntaxException e) {
+ // pass
+ }
+ pattern = "bcde)a";
+ try {
+ Pattern.compile(pattern);
+ fail("Expected a PatternSyntaxException when compiling pattern: "
+ + pattern);
+ } catch (PatternSyntaxException e) {
+ // pass
+ }
+ pattern = "bbg())a";
+ try {
+ Pattern.compile(pattern);
+ fail("Expected a PatternSyntaxException when compiling pattern: "
+ + pattern);
+ } catch (PatternSyntaxException e) {
+ // pass
+ }
+
+ pattern = "cdb(?i))a";
+ try {
+ Pattern.compile(pattern);
+ fail("Expected a PatternSyntaxException when compiling pattern: "
+ + pattern);
+ } catch (PatternSyntaxException e) {
+ // pass
+ }
+
+ /*
+ * This pattern should compile - HARMONY-2127
+ */
+ pattern = "x(?c)y";
+ Pattern.compile(pattern);
+
+ /*
+ * this pattern doesn't match any string, but should be compiled anyway
+ */
+ pattern = "(b\\1)a";
+ Pattern.compile(pattern);
+ }
+
+ @Test
+ public void testQuantCompileNeg() {
+ String[] patterns = { "5{,2}", "{5asd", "{hgdhg", "{5,hjkh", "{,5hdsh",
+ "{5,3shdfkjh}" };
+ for (String element : patterns) {
+ try {
+ Pattern.compile(element);
+ fail("PatternSyntaxException was expected, but compilation succeeds");
+ } catch (PatternSyntaxException pse) {
+ continue;
+ }
+ }
+ // Regression for HARMONY-1365
+ String pattern = "(?![^\\\\G*?)(?![^|\\]\\070\\ne\\{\\t\\[\\053\\?\\\\\\x51\\a\\075\\0023-\\[&&[|\\022-\\xEA\\00-\\u41C2&&[^|a-\\xCC&&[^\\037\\uECB3\\u3D9A\\x31\\|\\[^\\016\\r\\{\\,\\uA29D\\034\\02[\\02-\\[|\\t\\056\\uF599\\x62\\e\\<\\032\\uF0AC\\0026\\0205Q\\|\\\\\\06\\0164[|\\057-\\u7A98&&[\\061-g|\\|\\0276\\n\\042\\011\\e\\xE8\\x64B\\04\\u6D0EDW^\\p{Lower}]]]]?)(?<=[^\\n\\\\\\t\\u8E13\\,\\0114\\u656E\\xA5\\]&&[\\03-\\026|\\uF39D\\01\\{i\\u3BC2\\u14FE]])(?<=[^|\\uAE62\\054H\\|\\}&&^\\p{Space}])(?sxx)(?<=[\\f\\006\\a\\r\\xB4]*+)|(?x-xd:^{5}+)()";
+ assertNotNull(Pattern.compile(pattern));
+ }
+
+ @Test
+ public void testQuantCompilePos() {
+ String[] patterns = { "abc{2,}", "abc{5}" };
+ for (String element : patterns) {
+ Pattern.compile(element);
+ }
+ }
+
+ @Test
+ public void testQuantComposition() {
+ String pattern = "(a{1,3})aab";
+ java.util.regex.Pattern pat = java.util.regex.Pattern.compile(pattern);
+ java.util.regex.Matcher mat = pat.matcher("aaab");
+ mat.matches();
+ mat.start(1);
+ mat.group(1);
+ }
+
+ @Test
+ public void testMatches() {
+ String[][] posSeq = {
+ { "abb", "ababb", "abababbababb", "abababbababbabababbbbbabb" },
+ { "213567", "12324567", "1234567", "213213567",
+ "21312312312567", "444444567" },
+ { "abcdaab", "aab", "abaab", "cdaab", "acbdadcbaab" },
+ { "213234567", "3458", "0987654", "7689546432", "0398576",
+ "98432", "5" },
+ {
+ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
+ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" },
+ { "ababbaAabababblice", "ababbaAliceababab", "ababbAabliceaaa",
+ "abbbAbbbliceaaa", "Alice" },
+ { "a123", "bnxnvgds156", "for", "while", "if", "struct" },
+ { "xy" }, { "xy" }, { "xcy" }
+
+ };
+
+ for (int i = 0; i < testPatterns.length; i++) {
+ for (int j = 0; j < posSeq[i].length; j++) {
+ assertTrue("Incorrect match: " + testPatterns[i] + " vs "
+ + posSeq[i][j], Pattern.matches(testPatterns[i],
+ posSeq[i][j]));
+ }
+ }
+ }
+
+ @Test
+ public void testTimeZoneIssue() {
+ Pattern p = Pattern.compile("GMT(\\+|\\-)(\\d+)(:(\\d+))?");
+ Matcher m = p.matcher("GMT-9:45");
+ assertTrue(m.matches());
+ assertEquals("-", m.group(1));
+ assertEquals("9", m.group(2));
+ assertEquals(":45", m.group(3));
+ assertEquals("45", m.group(4));
+ }
+
+ @Test
+ public void testCompileRanges() {
+ String[] correctTestPatterns = { "[^]*abb]*", "[^a-d[^m-p]]*abb",
+ "[a-d\\d]*abb", "[abc]*abb", "[a-e&&[de]]*abb", "[^abc]*abb",
+ "[a-e&&[^de]]*abb", "[a-z&&[^m-p]]*abb", "[a-d[m-p]]*abb",
+ "[a-zA-Z]*abb", "[+*?]*abb", "[^+*?]*abb" };
+
+ String[] inputSecuence = { "kkkk", "admpabb", "abcabcd124654abb",
+ "abcabccbacababb", "dededededededeedabb", "gfdhfghgdfghabb",
+ "accabacbcbaabb", "acbvfgtyabb", "adbcacdbmopabcoabb",
+ "jhfkjhaSDFGHJkdfhHNJMjkhfabb", "+*??+*abb", "sdfghjkabb" };
+
+ for (int i = 0; i < correctTestPatterns.length; i++) {
+ assertTrue("pattern: " + correctTestPatterns[i] + " input: "
+ + inputSecuence[i], Pattern.matches(correctTestPatterns[i],
+ inputSecuence[i]));
+
+ }
+
+ String[] wrongInputSecuence = { "]", "admpkk", "abcabcd124k654abb",
+ "abwcabccbacababb", "abababdeababdeabb", "abcabcacbacbabb",
+ "acdcbecbaabb", "acbotyabb", "adbcaecdbmopabcoabb",
+ "jhfkjhaSDFGHJk;dfhHNJMjkhfabb", "+*?a?+*abb", "sdf+ghjkabb" };
+
+ for (int i = 0; i < correctTestPatterns.length; i++) {
+ assertFalse("pattern: " + correctTestPatterns[i] + " input: "
+ + wrongInputSecuence[i], Pattern.matches(
+ correctTestPatterns[i], wrongInputSecuence[i]));
+
+ }
+ }
+
+ @Test
+ public void testRangesSpecialCases() {
+ String neg_patterns[] = { "[a-&&[b-c]]", "[a-\\w]", "[b-a]", "[]" };
+
+ for (String element : neg_patterns) {
+ try {
+ Pattern.compile(element);
+ fail("PatternSyntaxException was expected: " + element);
+ } catch (PatternSyntaxException pse) {
+ }
+ }
+
+ String pos_patterns[] = { "[-]+", "----", "[a-]+", "a-a-a-a-aa--",
+ "[\\w-a]+", "123-2312--aaa-213", "[a-]]+", "-]]]]]]]]]]]]]]]" };
+
+ for (int i = 0; i < pos_patterns.length; i++) {
+ String pat = pos_patterns[i++];
+ String inp = pos_patterns[i];
+ assertTrue("pattern: " + pat + " input: " + inp, Pattern.matches(
+ pat, inp));
+ }
+ }
+
+ @Test
+ public void testZeroSymbols() {
+ assertTrue(Pattern.matches("[\0]*abb", "\0\0\0\0\0\0abb"));
+ }
+
+ @Test
+ public void testEscapes() {
+ Pattern pat = Pattern.compile("\\Q{]()*?");
+ Matcher mat = pat.matcher("{]()*?");
+
+ assertTrue(mat.matches());
+ }
+
+ @Test
+ public void testRegressions() {
+ // Bug 181
+ Pattern.compile("[\\t-\\r]");
+
+ // HARMONY-4472
+ Pattern.compile("a*.+");
+
+ // Bug187
+ Pattern
+ .compile("|(?idmsux-idmsux)|(?idmsux-idmsux)|[^|\\[-\\0274|\\,-\\\\[^|W\\}\\nq\\x65\\002\\xFE\\05\\06\\00\\x66\\x47i\\,\\xF2\\=\\06\\u0EA4\\x9B\\x3C\\f\\|\\{\\xE5\\05\\r\\u944A\\xCA\\e|\\x19\\04\\x07\\04\\u607B\\023\\0073\\x91Tr\\0150\\x83]]?(?idmsux-idmsux:\\p{Alpha}{7}?)||(?<=[^\\uEC47\\01\\02\\u3421\\a\\f\\a\\013q\\035w\\e])(?<=\\p{Punct}{0,}?)(?=^\\p{Lower})(?!\\b{8,14})(?[\\x3E-\\]])|(?idmsux-idmsux:\\p{Punct})|(?[|\\n\\042\\uB09F\\06\\u0F2B\\uC96D\\x89\\uC166\\xAA|\\04-\\][^|\\a\\|\\rx\\04\\uA770\\n\\02\\t\\052\\056\\0274\\|\\=\\07\\e|\\00-\\x1D&&[^\\005\\uB15B\\uCDAC\\n\\x74\\0103\\0147\\uD91B\\n\\062G\\u9B4B\\077\\}\\0324&&[^\\0302\\,\\0221\\04\\u6D16\\04xy\\uD193\\[\\061\\06\\045\\x0F|\\e\\xBB\\f\\u1B52\\023\\u3AD2\\033\\007\\022\\}\\x66\\uA63FJ-\\0304]]]]{0,0})||(?^+)|(?![^|\\|\\nJ\\t\\<\\04E\\\\\\t\\01\\\\\\02\\|\\=\\}\\xF3\\uBEC2\\032K\\014\\uCC5F\\072q\\|\\0153\\xD9\\0322\\uC6C8[^\\t\\0342\\x34\\x91\\06\\{\\xF1\\a\\u1710\\?\\xE7\\uC106\\02pF\\<&&[^|\\]\\064\\u381D\\u50CF\\eO&&[^|\\06\\x2F\\04\\045\\032\\u8536W\\0377\\0017|\\x06\\uE5FA\\05\\xD4\\020\\04c\\xFC\\02H\\x0A\\r]]]]+?)(?idmsux-idmsux)|(?[\\{-\\0207|\\06-\\0276\\p{XDigit}])(?idmsux-idmsux:[^|\\x52\\0012\\]u\\xAD\\0051f\\0142\\\\l\\|\\050\\05\\f\\t\\u7B91\\r\\u7763\\{|h\\0104\\a\\f\\0234\\u2D4F&&^\\P{InGreek}]))");
+ // HARMONY-5858
+ Pattern.compile("\\u6211", Pattern.LITERAL);
+ }
+
+ @Test
+ public void testOrphanQuantifiers() {
+ try {
+ Pattern.compile("+++++");
+ fail("PatternSyntaxException expected");
+ } catch (PatternSyntaxException pse) {
+ }
+ }
+
+ @Test
+ public void testOrphanQuantifiers2() {
+ try {
+ Pattern.compile("\\d+*");
+ fail("PatternSyntaxException expected");
+ } catch (PatternSyntaxException pse) {
+ }
+ }
+
+ @Test
+ public void testBug197() {
+ Object[] vals = { ":", new Integer(2),
+ new String[] { "boo", "and:foo" }, ":", new Integer(5),
+ new String[] { "boo", "and", "foo" }, ":", new Integer(-2),
+ new String[] { "boo", "and", "foo" }, ":", new Integer(3),
+ new String[] { "boo", "and", "foo" }, ":", new Integer(1),
+ new String[] { "boo:and:foo" }, "o", new Integer(5),
+ new String[] { "b", "", ":and:f", "", "" }, "o",
+ new Integer(4), new String[] { "b", "", ":and:f", "o" }, "o",
+ new Integer(-2), new String[] { "b", "", ":and:f", "", "" },
+ "o", new Integer(0), new String[] { "b", "", ":and:f" } };
+
+ for (int i = 0; i < vals.length / 3;) {
+ String[] res = Pattern.compile(vals[i++].toString()).split(
+ "boo:and:foo", ((Integer) vals[i++]).intValue());
+ String[] expectedRes = (String[]) vals[i++];
+
+ assertEquals(expectedRes.length, res.length);
+
+ for (int j = 0; j < expectedRes.length; j++) {
+ assertEquals(expectedRes[j], res[j]);
+ }
+ }
+ }
+
+ @Test
+ public void testURIPatterns() {
+ String URI_REGEXP_STR = "^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\\?([^#]*))?(#(.*))?";
+ String SCHEME_REGEXP_STR = "^[a-zA-Z]{1}[\\w+-.]+$";
+ String REL_URI_REGEXP_STR = "^(//([^/?#]*))?([^?#]*)(\\?([^#]*))?(#(.*))?";
+ String IPV6_REGEXP_STR = "^[0-9a-fA-F\\:\\.]+(\\%\\w+)?$";
+ String IPV6_REGEXP_STR2 = "^\\[[0-9a-fA-F\\:\\.]+(\\%\\w+)?\\]$";
+ String IPV4_REGEXP_STR = "^[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}$";
+ String HOSTNAME_REGEXP_STR = "\\w+[\\w\\-\\.]*";
+
+ Pattern.compile(URI_REGEXP_STR);
+ Pattern.compile(REL_URI_REGEXP_STR);
+ Pattern.compile(SCHEME_REGEXP_STR);
+ Pattern.compile(IPV4_REGEXP_STR);
+ Pattern.compile(IPV6_REGEXP_STR);
+ Pattern.compile(IPV6_REGEXP_STR2);
+ Pattern.compile(HOSTNAME_REGEXP_STR);
+ }
+
+ @Test
+ public void testFindBoundaryCases1() {
+ Pattern pat = Pattern.compile(".*\n");
+ Matcher mat = pat.matcher("a\n");
+
+ mat.find();
+ assertEquals("a\n", mat.group());
+
+ }
+
+ @Test
+ public void testFindBoundaryCases2() {
+ Pattern pat = Pattern.compile(".*A");
+ Matcher mat = pat.matcher("aAa");
+
+ mat.find();
+ assertEquals("aA", mat.group());
+
+ }
+
+ @Test
+ public void testFindBoundaryCases3() {
+ Pattern pat = Pattern.compile(".*A");
+ Matcher mat = pat.matcher("a\naA\n");
+
+ mat.find();
+ assertEquals("aA", mat.group());
+
+ }
+
+ @Test
+ public void testFindBoundaryCases4() {
+ Pattern pat = Pattern.compile("A.*");
+ Matcher mat = pat.matcher("A\n");
+
+ mat.find();
+ assertEquals("A", mat.group());
+
+ }
+
+ @Test
+ public void testFindBoundaryCases5() {
+ Pattern pat = Pattern.compile(".*A.*");
+ Matcher mat = pat.matcher("\nA\naaa\nA\naaAaa\naaaA\n");
+ // Matcher mat = pat.matcher("\nA\n");
+ String[] res = { "A", "A", "aaAaa", "aaaA" };
+ int k = 0;
+ for (; mat.find(); k++) {
+ assertEquals(res[k], mat.group());
+ }
+ }
+
+ @Test
+ public void testFindBoundaryCases6() {
+ String[] res = { "", "a", "", "" };
+ Pattern pat = Pattern.compile(".*");
+ Matcher mat = pat.matcher("\na\n");
+ int k = 0;
+
+ for (; mat.find(); k++) {
+ assertEquals(res[k], mat.group());
+ }
+ }
+
+ public void _testFindBoundaryCases7() {
+ Pattern pat = Pattern.compile(".*");
+ Matcher mat = pat.matcher("\na\n");
+
+ while (mat.find()) {
+ System.out.println(mat.group());
+ System.out.flush();
+ }
+ }
+
+ @Test
+ public void testBackReferences() {
+ Pattern pat = Pattern.compile("(\\((\\w*):(.*):(\\2)\\))");
+ Matcher mat = pat
+ .matcher("(start1: word :start1)(start2: word :start2)");
+ int k = 1;
+ for (; mat.find(); k++) {
+ assertEquals("start" + k, mat.group(2));
+ assertEquals(" word ", mat.group(3));
+ assertEquals("start" + k, mat.group(4));
+
+ }
+
+ assertEquals(3, k);
+ pat = Pattern.compile(".*(.)\\1");
+ mat = pat.matcher("saa");
+ assertTrue(mat.matches());
+ }
+
+ public void _testBackReferences1() {
+ Pattern pat = Pattern.compile("(\\((\\w*):(.*):(\\2)\\))");
+ Matcher mat = pat
+ .matcher("(start1: word :start1)(start2: word :start2)");
+ int k = 1;
+ for (; mat.find(); k++) {
+ System.out.println(mat.group(2));
+ System.out.println(mat.group(3));
+ System.out.println(mat.group(4));
+
+ }
+
+ assertEquals(3, k);
+ }
+
+ @Test
+ public void testNewLine() {
+ Pattern pat = Pattern.compile("(^$)*\n", Pattern.MULTILINE);
+ Matcher mat = pat.matcher("\r\n\n");
+ int counter = 0;
+ while (mat.find()) {
+ counter++;
+ }
+ assertEquals(2, counter);
+ }
+
+ @Test
+ public void testFindGreedy() {
+ Pattern pat = Pattern.compile(".*aaa", Pattern.DOTALL);
+ Matcher mat = pat.matcher("aaaa\naaa\naaaaaa");
+ mat.matches();
+ assertEquals(15, mat.end());
+ }
+
+ @Test
+ public void testSOLQuant() {
+ Pattern pat = Pattern.compile("$*", Pattern.MULTILINE);
+ Matcher mat = pat.matcher("\n\n");
+ int counter = 0;
+ while (mat.find()) {
+ counter++;
+ }
+
+ assertEquals(3, counter);
+ }
+
+ @Test
+ public void testIllegalEscape() {
+ try {
+ Pattern.compile("\\y");
+ fail("PatternSyntaxException expected");
+ } catch (PatternSyntaxException pse) {
+ }
+ }
+
+ @Test
+ public void testEmptyFamily() {
+ Pattern.compile("\\p{Lower}");
+ }
+
+ @Test
+ public void testNonCaptConstr() {
+ // Flags
+ Pattern pat = Pattern.compile("(?i)b*(?-i)a*");
+ assertTrue(pat.matcher("bBbBaaaa").matches());
+ assertFalse(pat.matcher("bBbBAaAa").matches());
+
+ // Non-capturing groups
+ pat = Pattern.compile("(?i:b*)a*");
+ assertTrue(pat.matcher("bBbBaaaa").matches());
+ assertFalse(pat.matcher("bBbBAaAa").matches());
+
+ pat = Pattern
+ // 1 2 3 4 5 6 7 8 9 10 11
+ .compile("(?:-|(-?\\d+\\d\\d\\d))?(?:-|-(\\d\\d))?(?:-|-(\\d\\d))?(T)?(?:(\\d\\d):(\\d\\d):(\\d\\d)(\\.\\d+)?)?(?:(?:((?:\\+|\\-)\\d\\d):(\\d\\d))|(Z))?");
+ Matcher mat = pat.matcher("-1234-21-31T41:51:61.789+71:81");
+ assertTrue(mat.matches());
+ assertEquals("-1234", mat.group(1));
+ assertEquals("21", mat.group(2));
+ assertEquals("31", mat.group(3));
+ assertEquals("T", mat.group(4));
+ assertEquals("41", mat.group(5));
+ assertEquals("51", mat.group(6));
+ assertEquals("61", mat.group(7));
+ assertEquals(".789", mat.group(8));
+ assertEquals("+71", mat.group(9));
+ assertEquals("81", mat.group(10));
+
+ // positive lookahead
+ pat = Pattern.compile(".*\\.(?=log$).*$");
+ assertTrue(pat.matcher("a.b.c.log").matches());
+ assertFalse(pat.matcher("a.b.c.log.").matches());
+
+ // negative lookahead
+ pat = Pattern.compile(".*\\.(?!log$).*$");
+ assertFalse(pat.matcher("abc.log").matches());
+ assertTrue(pat.matcher("abc.logg").matches());
+
+ // positive lookbehind
+ pat = Pattern.compile(".*(?<=abc)\\.log$");
+ assertFalse(pat.matcher("cde.log").matches());
+ assertTrue(pat.matcher("abc.log").matches());
+
+ // negative lookbehind
+ pat = Pattern.compile(".*(?a*)abb");
+ assertFalse(pat.matcher("aaabb").matches());
+ pat = Pattern.compile("(?>a*)bb");
+ assertTrue(pat.matcher("aaabb").matches());
+
+ pat = Pattern.compile("(?>a|aa)aabb");
+ assertTrue(pat.matcher("aaabb").matches());
+ pat = Pattern.compile("(?>aa|a)aabb");
+ assertFalse(pat.matcher("aaabb").matches());
+
+ // quantifiers over look ahead
+ pat = Pattern.compile(".*(?<=abc)*\\.log$");
+ assertTrue(pat.matcher("cde.log").matches());
+ pat = Pattern.compile(".*(?<=abc)+\\.log$");
+ assertFalse(pat.matcher("cde.log").matches());
+
+ }
+
+ public void _testCorrectReplacementBackreferencedJointSet() {
+ Pattern.compile("ab(a)*\\1");
+ Pattern.compile("abc(cd)fg");
+ Pattern.compile("aba*cd");
+ Pattern.compile("ab(a)*+cd");
+ Pattern.compile("ab(a)*?cd");
+ Pattern.compile("ab(a)+cd");
+ Pattern.compile(".*(.)\\1");
+ Pattern.compile("ab((a)|c|d)e");
+ Pattern.compile("abc((a(b))cd)");
+ Pattern.compile("ab(a)++cd");
+ Pattern.compile("ab(a)?(c)d");
+ Pattern.compile("ab(a)?+cd");
+ Pattern.compile("ab(a)??cd");
+ Pattern.compile("ab(a)??cd");
+ Pattern.compile("ab(a){1,3}?(c)d");
+ }
+
+ @Test
+ public void testCompilePatternWithTerminatorMark() {
+ Pattern pat = Pattern.compile("a\u0000\u0000cd");
+ Matcher mat = pat.matcher("a\u0000\u0000cd");
+ assertTrue(mat.matches());
+ }
+
+ @Test
+ public void testAlternations() {
+ String baseString = "|a|bc";
+ Pattern pat = Pattern.compile(baseString);
+ Matcher mat = pat.matcher("");
+
+ assertTrue(mat.matches());
+
+ baseString = "a||bc";
+ pat = Pattern.compile(baseString);
+ mat = pat.matcher("");
+ assertTrue(mat.matches());
+
+ baseString = "a|bc|";
+ pat = Pattern.compile(baseString);
+ mat = pat.matcher("");
+ assertTrue(mat.matches());
+
+ baseString = "a|b|";
+ pat = Pattern.compile(baseString);
+ mat = pat.matcher("");
+ assertTrue(mat.matches());
+
+ baseString = "a(|b|cd)e";
+ pat = Pattern.compile(baseString);
+ mat = pat.matcher("ae");
+ assertTrue(mat.matches());
+
+ baseString = "a(b||cd)e";
+ pat = Pattern.compile(baseString);
+ mat = pat.matcher("ae");
+ assertTrue(mat.matches());
+
+ baseString = "a(b|cd|)e";
+ pat = Pattern.compile(baseString);
+ mat = pat.matcher("ae");
+ assertTrue(mat.matches());
+
+ baseString = "a(b|c|)e";
+ pat = Pattern.compile(baseString);
+ mat = pat.matcher("ae");
+ assertTrue(mat.matches());
+
+ baseString = "a(|)e";
+ pat = Pattern.compile(baseString);
+ mat = pat.matcher("ae");
+ assertTrue(mat.matches());
+
+ baseString = "|";
+ pat = Pattern.compile(baseString);
+ mat = pat.matcher("");
+ assertTrue(mat.matches());
+
+ baseString = "a(?:|)e";
+ pat = Pattern.compile(baseString);
+ mat = pat.matcher("ae");
+ assertTrue(mat.matches());
+
+ baseString = "a||||bc";
+ pat = Pattern.compile(baseString);
+ mat = pat.matcher("");
+ assertTrue(mat.matches());
+
+ baseString = "(?i-is)|a";
+ pat = Pattern.compile(baseString);
+ mat = pat.matcher("a");
+ assertTrue(mat.matches());
+ }
+
+ @Test
+ public void testMatchWithGroups() {
+ String baseString = "jwkerhjwehrkwjehrkwjhrwkjehrjwkehrjkwhrkwehrkwhrkwrhwkhrwkjehr";
+ String pattern = ".*(..).*\\1.*";
+ assertTrue(Pattern.compile(pattern).matcher(baseString).matches());
+
+ baseString = "saa";
+ pattern = ".*(.)\\1";
+ assertTrue(Pattern.compile(pattern).matcher(baseString).matches());
+ assertTrue(Pattern.compile(pattern).matcher(baseString).find());
+ }
+
+ @Test
+ public void testSplitEmptyCharSequence() {
+ String s1 = "";
+ String[] arr = s1.split(":");
+ assertEquals(arr.length, 1);
+ }
+
+ @Test
+ public void testSplitEndsWithPattern() {
+ assertEquals(",,".split(",", 3).length, 3);
+ assertEquals(",,".split(",", 4).length, 3);
+
+ assertEquals(Pattern.compile("o").split("boo:and:foo", 5).length, 5);
+ assertEquals(Pattern.compile("b").split("ab", -1).length, 2);
+ }
+
+ @Test
+ public void testCaseInsensitiveFlag() {
+ assertTrue(Pattern.matches("(?i-:AbC)", "ABC"));
+ }
+
+ @Test
+ public void testEmptyGroups() {
+ Pattern pat = Pattern.compile("ab(?>)cda");
+ Matcher mat = pat.matcher("abcda");
+ assertTrue(mat.matches());
+
+ pat = Pattern.compile("ab()");
+ mat = pat.matcher("ab");
+ assertTrue(mat.matches());
+
+ pat = Pattern.compile("abc(?:)(..)");
+ mat = pat.matcher("abcgf");
+ assertTrue(mat.matches());
+ }
+
+ @Test
+ public void testCompileNonCaptGroup() {
+ boolean isCompiled = false;
+
+ try {
+ Pattern.compile("(?:)", Pattern.CANON_EQ);
+ Pattern.compile("(?:)", Pattern.CANON_EQ | Pattern.DOTALL);
+ Pattern
+ .compile("(?:)", Pattern.CANON_EQ
+ | Pattern.CASE_INSENSITIVE);
+ Pattern.compile("(?:)", Pattern.CANON_EQ | Pattern.COMMENTS
+ | Pattern.UNIX_LINES);
+ isCompiled = true;
+ } catch (PatternSyntaxException e) {
+ System.out.println(e);
+ }
+ assertTrue(isCompiled);
+ }
+
+ @Test
+ public void testEmbeddedFlags() {
+ String baseString = "(?i)((?s)a)";
+ String testString = "A";
+ Pattern pat = Pattern.compile(baseString);
+ Matcher mat = pat.matcher(testString);
+ assertTrue(mat.matches());
+
+ baseString = "(?x)(?i)(?s)(?d)a";
+ testString = "A";
+ pat = Pattern.compile(baseString);
+ mat = pat.matcher(testString);
+ assertTrue(mat.matches());
+
+ baseString = "(?x)(?i)(?s)(?d)a.";
+ testString = "a\n";
+ pat = Pattern.compile(baseString);
+ mat = pat.matcher(testString);
+ assertTrue(mat.matches());
+
+ baseString = "abc(?x:(?i)(?s)(?d)a.)";
+ testString = "abcA\n";
+ pat = Pattern.compile(baseString);
+ mat = pat.matcher(testString);
+ assertTrue(mat.matches());
+
+ baseString = "abc((?x)d)(?i)(?s)a";
+ testString = "abcdA";
+ pat = Pattern.compile(baseString);
+ mat = pat.matcher(testString);
+ assertTrue(mat.matches());
+ }
+
+ @Test
+ public void testAltWithFlags() {
+ Pattern.compile("|(?i-xi)|()");
+ }
+
+ @Test
+ public void testRestoreFlagsAfterGroup() {
+ String baseString = "abc((?x)d) a";
+ String testString = "abcd a";
+ Pattern pat = Pattern.compile(baseString);
+ Matcher mat = pat.matcher(testString);
+
+ assertTrue(mat.matches());
+ }
+
+ @Test
+ public void testCompileCharacterClass() {
+ // Regression for HARMONY-606, 696
+ Pattern pattern = Pattern.compile("\\p{javaLowerCase}");
+ assertNotNull(pattern);
+
+ pattern = Pattern.compile("\\p{javaUpperCase}");
+ assertNotNull(pattern);
+
+ pattern = Pattern.compile("\\p{javaWhitespace}");
+ assertNotNull(pattern);
+
+ pattern = Pattern.compile("\\p{javaMirrored}");
+ assertNotNull(pattern);
+
+ pattern = Pattern.compile("\\p{javaDefined}");
+ assertNotNull(pattern);
+
+ pattern = Pattern.compile("\\p{javaDigit}");
+ assertNotNull(pattern);
+
+ pattern = Pattern.compile("\\p{javaIdentifierIgnorable}");
+ assertNotNull(pattern);
+
+ pattern = Pattern.compile("\\p{javaISOControl}");
+ assertNotNull(pattern);
+
+ pattern = Pattern.compile("\\p{javaJavaIdentifierPart}");
+ assertNotNull(pattern);
+
+ pattern = Pattern.compile("\\p{javaJavaIdentifierStart}");
+ assertNotNull(pattern);
+
+ pattern = Pattern.compile("\\p{javaLetter}");
+ assertNotNull(pattern);
+
+ pattern = Pattern.compile("\\p{javaLetterOrDigit}");
+ assertNotNull(pattern);
+
+ pattern = Pattern.compile("\\p{javaSpaceChar}");
+ assertNotNull(pattern);
+
+ pattern = Pattern.compile("\\p{javaTitleCase}");
+ assertNotNull(pattern);
+
+ pattern = Pattern.compile("\\p{javaUnicodeIdentifierPart}");
+ assertNotNull(pattern);
+
+ pattern = Pattern.compile("\\p{javaUnicodeIdentifierStart}");
+ assertNotNull(pattern);
+ }
+
+ @Test
+ public void testRangesWithSurrogatesSupplementary() {
+ String patString = "[abc\uD8D2]";
+ String testString = "\uD8D2";
+ Pattern pat = Pattern.compile(patString);
+ Matcher mat = pat.matcher(testString);
+ assertTrue(mat.matches());
+
+ testString = "a";
+ mat = pat.matcher(testString);
+ assertTrue(mat.matches());
+
+ testString = "ef\uD8D2\uDD71gh";
+ mat = pat.matcher(testString);
+ assertFalse(mat.find());
+
+ testString = "ef\uD8D2gh";
+ mat = pat.matcher(testString);
+ assertTrue(mat.find());
+
+ patString = "[abc\uD8D3&&[c\uD8D3]]";
+ testString = "c";
+ pat = Pattern.compile(patString);
+ mat = pat.matcher(testString);
+ assertTrue(mat.matches());
+
+ testString = "a";
+ mat = pat.matcher(testString);
+ assertFalse(mat.matches());
+
+ testString = "ef\uD8D3\uDD71gh";
+ mat = pat.matcher(testString);
+ assertFalse(mat.find());
+
+ testString = "ef\uD8D3gh";
+ mat = pat.matcher(testString);
+ assertTrue(mat.find());
+
+ patString = "[abc\uD8D3\uDBEE\uDF0C&&[c\uD8D3\uDBEE\uDF0C]]";
+ testString = "c";
+ pat = Pattern.compile(patString);
+ mat = pat.matcher(testString);
+ assertTrue(mat.matches());
+
+ testString = "\uDBEE\uDF0C";
+ mat = pat.matcher(testString);
+ assertTrue(mat.matches());
+
+ testString = "ef\uD8D3\uDD71gh";
+ mat = pat.matcher(testString);
+ assertFalse(mat.find());
+
+ testString = "ef\uD8D3gh";
+ mat = pat.matcher(testString);
+ assertTrue(mat.find());
+
+ patString = "[abc\uDBFC]\uDDC2cd";
+ testString = "\uDBFC\uDDC2cd";
+ pat = Pattern.compile(patString);
+ mat = pat.matcher(testString);
+ assertFalse(mat.matches());
+
+ testString = "a\uDDC2cd";
+ mat = pat.matcher(testString);
+ assertTrue(mat.matches());
+ }
+
+ @Test
+ public void testSequencesWithSurrogatesSupplementary() {
+ String patString = "abcd\uD8D3";
+ String testString = "abcd\uD8D3\uDFFC";
+ Pattern pat = Pattern.compile(patString);
+ Matcher mat = pat.matcher(testString);
+ assertFalse(mat.find());
+
+ testString = "abcd\uD8D3abc";
+ mat = pat.matcher(testString);
+ assertTrue(mat.find());
+
+ patString = "ab\uDBEFcd";
+ testString = "ab\uDBEFcd";
+ pat = Pattern.compile(patString);
+ mat = pat.matcher(testString);
+ assertTrue(mat.matches());
+
+ patString = "\uDFFCabcd";
+ testString = "\uD8D3\uDFFCabcd";
+ pat = Pattern.compile(patString);
+ mat = pat.matcher(testString);
+ assertFalse(mat.find());
+
+ testString = "abc\uDFFCabcdecd";
+ mat = pat.matcher(testString);
+ assertTrue(mat.find());
+
+ patString = "\uD8D3\uDFFCabcd";
+ testString = "abc\uD8D3\uD8D3\uDFFCabcd";
+ pat = Pattern.compile(patString);
+ mat = pat.matcher(testString);
+ assertTrue(mat.find());
+ }
+
+ @Test
+ public void testPredefinedClassesWithSurrogatesSupplementary() {
+ String patString = "[123\\D]";
+ String testString = "a";
+ Pattern pat = Pattern.compile(patString);
+ Matcher mat = pat.matcher(testString);
+ assertTrue(mat.find());
+
+ testString = "5";
+ mat = pat.matcher(testString);
+ assertFalse(mat.find());
+
+ testString = "3";
+ mat = pat.matcher(testString);
+ assertTrue(mat.find());
+
+ // low surrogate
+ testString = "\uDFC4";
+ mat = pat.matcher(testString);
+ assertTrue(mat.find());
+
+ // high surrogate
+ testString = "\uDADA";
+ mat = pat.matcher(testString);
+ assertTrue(mat.find());
+
+ testString = "\uDADA\uDFC4";
+ mat = pat.matcher(testString);
+ assertTrue(mat.find());
+
+ patString = "[123[^\\p{javaDigit}]]";
+ testString = "a";
+ pat = Pattern.compile(patString);
+ mat = pat.matcher(testString);
+ assertTrue(mat.find());
+
+ testString = "5";
+ mat = pat.matcher(testString);
+ assertFalse(mat.find());
+
+ testString = "3";
+ mat = pat.matcher(testString);
+ assertTrue(mat.find());
+
+ // low surrogate
+ testString = "\uDFC4";
+ mat = pat.matcher(testString);
+ assertTrue(mat.find());
+
+ // high surrogate
+ testString = "\uDADA";
+ mat = pat.matcher(testString);
+ assertTrue(mat.find());
+
+ testString = "\uDADA\uDFC4";
+ mat = pat.matcher(testString);
+ assertTrue(mat.find());
+
+ // surrogate characters
+ patString = "\\p{Cs}";
+ testString = "\uD916\uDE27";
+ pat = Pattern.compile(patString);
+ mat = pat.matcher(testString);
+
+ // swap low and high surrogates
+ testString = "\uDE27\uD916";
+ mat = pat.matcher(testString);
+ assertTrue(mat.find());
+
+ patString = "[\uD916\uDE271\uD91623&&[^\\p{Cs}]]";
+ testString = "1";
+ pat = Pattern.compile(patString);
+ mat = pat.matcher(testString);
+ assertTrue(mat.find());
+
+ testString = "\uD916";
+ pat = Pattern.compile(patString);
+ mat = pat.matcher(testString);
+ assertFalse(mat.find());
+
+ testString = "\uD916\uDE27";
+ pat = Pattern.compile(patString);
+ mat = pat.matcher(testString);
+ assertTrue(mat.find());
+
+ // \uD9A0\uDE8E=\u7828E
+ // \u78281=\uD9A0\uDE81
+ patString = "[a-\uD9A0\uDE8E]";
+ testString = "\uD9A0\uDE81";
+ pat = Pattern.compile(patString);
+ mat = pat.matcher(testString);
+ assertTrue(mat.matches());
+ }
+
+ @Test
+ public void testDotConstructionWithSurrogatesSupplementary() {
+ String patString = ".";
+ String testString = "\uD9A0\uDE81";
+ Pattern pat = Pattern.compile(patString);
+ Matcher mat = pat.matcher(testString);
+ assertTrue(mat.matches());
+
+ testString = "\uDE81";
+ mat = pat.matcher(testString);
+ assertTrue(mat.matches());
+
+ testString = "\uD9A0";
+ mat = pat.matcher(testString);
+ assertTrue(mat.matches());
+
+ testString = "\n";
+ mat = pat.matcher(testString);
+ assertFalse(mat.matches());
+
+ patString = ".*\uDE81";
+ testString = "\uD9A0\uDE81\uD9A0\uDE81\uD9A0\uDE81";
+ pat = Pattern.compile(patString);
+ mat = pat.matcher(testString);
+ assertFalse(mat.matches());
+
+ testString = "\uD9A0\uDE81\uD9A0\uDE81\uDE81";
+ mat = pat.matcher(testString);
+ assertTrue(mat.matches());
+
+ patString = ".*";
+ testString = "\uD9A0\uDE81\n\uD9A0\uDE81\uD9A0\n\uDE81";
+ pat = Pattern.compile(patString, Pattern.DOTALL);
+ mat = pat.matcher(testString);
+ assertTrue(mat.matches());
+ }
+
+ @Test
+ public void testQuantifiersWithSurrogatesSupplementary() {
+ String patString = "\uD9A0\uDE81*abc";
+ String testString = "\uD9A0\uDE81\uD9A0\uDE81abc";
+ Pattern pat = Pattern.compile(patString);
+ Matcher mat = pat.matcher(testString);
+ assertTrue(mat.matches());
+
+ testString = "abc";
+ mat = pat.matcher(testString);
+ assertTrue(mat.matches());
+ }
+
+ @Test
+ public void testAlternationsWithSurrogatesSupplementary() {
+ String patString = "\uDE81|\uD9A0\uDE81|\uD9A0";
+ String testString = "\uD9A0";
+ Pattern pat = Pattern.compile(patString);
+ Matcher mat = pat.matcher(testString);
+ assertTrue(mat.matches());
+
+ testString = "\uDE81";
+ mat = pat.matcher(testString);
+ assertTrue(mat.matches());
+
+ testString = "\uD9A0\uDE81";
+ mat = pat.matcher(testString);
+ assertTrue(mat.matches());
+
+ testString = "\uDE81\uD9A0";
+ mat = pat.matcher(testString);
+ assertFalse(mat.matches());
+ }
+
+ @Test
+ public void testGroupsWithSurrogatesSupplementary() {
+
+ // this pattern matches nothing
+ String patString = "(\uD9A0)\uDE81";
+ String testString = "\uD9A0\uDE81";
+ Pattern pat = Pattern.compile(patString);
+ Matcher mat = pat.matcher(testString);
+ assertFalse(mat.matches());
+
+ patString = "(\uD9A0)";
+ testString = "\uD9A0\uDE81";
+ pat = Pattern.compile(patString, Pattern.DOTALL);
+ mat = pat.matcher(testString);
+ assertFalse(mat.find());
+ }
+
+ @Test
+ public void testUnicodeCategoryWithSurrogatesSupplementary() {
+ Pattern p = Pattern.compile("\\p{javaLowerCase}");
+ Matcher matcher = p.matcher("\uD801\uDC28");
+ assertTrue(matcher.find());
+ }
+}
diff --git a/teavm-classlib/src/test/java/org/teavm/classlib/java/util/regex/ReplaceTest.java b/teavm-classlib/src/test/java/org/teavm/classlib/java/util/regex/ReplaceTest.java
new file mode 100644
index 000000000..2f2e122e7
--- /dev/null
+++ b/teavm-classlib/src/test/java/org/teavm/classlib/java/util/regex/ReplaceTest.java
@@ -0,0 +1,92 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.teavm.classlib.java.util.regex;
+
+import static org.junit.Assert.assertEquals;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+import org.junit.Test;
+
+public class ReplaceTest {
+
+ @Test
+ public void testSimpleReplace() throws PatternSyntaxException {
+ String target, pattern, repl;
+
+ target = "foobarfobarfoofo1";
+ pattern = "fo[^o]";
+ repl = "xxx";
+
+ Pattern p = Pattern.compile(pattern);
+ Matcher m = p.matcher(target);
+
+ assertEquals("foobarxxxarfoofo1", m.replaceFirst(repl));
+ assertEquals("foobarxxxarfooxxx", m.replaceAll(repl));
+ }
+
+ @Test
+ public void testCaptureReplace() {
+ String target, pattern, repl, s;
+ Pattern p = null;
+ Matcher m;
+
+ target = "[31]foo;bar[42];[99]xyz";
+ pattern = "\\[([0-9]+)\\]([a-z]+)";
+ repl = "$2[$1]";
+
+ p = Pattern.compile(pattern);
+ m = p.matcher(target);
+ s = m.replaceFirst(repl);
+ assertEquals("foo[31];bar[42];[99]xyz", s);
+ s = m.replaceAll(repl);
+ assertEquals("foo[31];bar[42];xyz[99]", s);
+
+ target = "[31]foo(42)bar{63}zoo;[12]abc(34)def{56}ghi;{99}xyz[88]xyz(77)xyz;";
+ pattern = "\\[([0-9]+)\\]([a-z]+)\\(([0-9]+)\\)([a-z]+)\\{([0-9]+)\\}([a-z]+)";
+ repl = "[$5]$6($3)$4{$1}$2";
+ p = Pattern.compile(pattern);
+ m = p.matcher(target);
+ s = m.replaceFirst(repl);
+ // System.out.println(s);
+ assertEquals(
+ "[63]zoo(42)bar{31}foo;[12]abc(34)def{56}ghi;{99}xyz[88]xyz(77)xyz;",
+ s);
+ s = m.replaceAll(repl);
+ // System.out.println(s);
+ assertEquals(
+ "[63]zoo(42)bar{31}foo;[56]ghi(34)def{12}abc;{99}xyz[88]xyz(77)xyz;",
+ s);
+ }
+
+ @Test
+ public void testEscapeReplace() {
+ String target, pattern, repl, s;
+
+ target = "foo'bar''foo";
+ pattern = "'";
+ repl = "\\'";
+ s = target.replaceAll(pattern, repl);
+ assertEquals("foo'bar''foo", s);
+ repl = "\\\\'";
+ s = target.replaceAll(pattern, repl);
+ assertEquals("foo\\'bar\\'\\'foo", s);
+ repl = "\\$3";
+ s = target.replaceAll(pattern, repl);
+ assertEquals("foo$3bar$3$3foo", s);
+ }
+}
diff --git a/teavm-classlib/src/test/java/org/teavm/classlib/java/util/regex/SplitTest.java b/teavm-classlib/src/test/java/org/teavm/classlib/java/util/regex/SplitTest.java
new file mode 100644
index 000000000..48c534687
--- /dev/null
+++ b/teavm-classlib/src/test/java/org/teavm/classlib/java/util/regex/SplitTest.java
@@ -0,0 +1,176 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.teavm.classlib.java.util.regex;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+import org.junit.Test;
+
+/**
+ * TODO Type description
+ */
+public class SplitTest {
+
+ @Test
+ public void testSimple() {
+ Pattern p = Pattern.compile("/");
+ String[] results = p.split("have/you/done/it/right");
+ String[] expected = new String[] { "have", "you", "done", "it", "right" };
+ assertEquals(expected.length, results.length);
+ for (int i = 0; i < expected.length; i++) {
+ assertEquals(results[i], expected[i]);
+ }
+ }
+
+ @Test
+ public void testSplit1() throws PatternSyntaxException {
+ Pattern p = Pattern.compile(" ");
+
+ String input = "poodle zoo";
+ String tokens[];
+
+ tokens = p.split(input, 1);
+ assertEquals(1, tokens.length);
+ assertTrue(tokens[0].equals(input));
+ tokens = p.split(input, 2);
+ assertEquals(2, tokens.length);
+ assertEquals("poodle", tokens[0]);
+ assertEquals("zoo", tokens[1]);
+ tokens = p.split(input, 5);
+ assertEquals(2, tokens.length);
+ assertEquals("poodle", tokens[0]);
+ assertEquals("zoo", tokens[1]);
+ tokens = p.split(input, -2);
+ assertEquals(2, tokens.length);
+ assertEquals("poodle", tokens[0]);
+ assertEquals("zoo", tokens[1]);
+ tokens = p.split(input, 0);
+ assertEquals(2, tokens.length);
+ assertEquals("poodle", tokens[0]);
+ assertEquals("zoo", tokens[1]);
+ tokens = p.split(input);
+ assertEquals(2, tokens.length);
+ assertEquals("poodle", tokens[0]);
+ assertEquals("zoo", tokens[1]);
+
+ p = Pattern.compile("d");
+
+ tokens = p.split(input, 1);
+ assertEquals(1, tokens.length);
+ assertTrue(tokens[0].equals(input));
+ tokens = p.split(input, 2);
+ assertEquals(2, tokens.length);
+ assertEquals("poo", tokens[0]);
+ assertEquals("le zoo", tokens[1]);
+ tokens = p.split(input, 5);
+ assertEquals(2, tokens.length);
+ assertEquals("poo", tokens[0]);
+ assertEquals("le zoo", tokens[1]);
+ tokens = p.split(input, -2);
+ assertEquals(2, tokens.length);
+ assertEquals("poo", tokens[0]);
+ assertEquals("le zoo", tokens[1]);
+ tokens = p.split(input, 0);
+ assertEquals(2, tokens.length);
+ assertEquals("poo", tokens[0]);
+ assertEquals("le zoo", tokens[1]);
+ tokens = p.split(input);
+ assertEquals(2, tokens.length);
+ assertEquals("poo", tokens[0]);
+ assertEquals("le zoo", tokens[1]);
+
+ p = Pattern.compile("o");
+
+ tokens = p.split(input, 1);
+ assertEquals(1, tokens.length);
+ assertTrue(tokens[0].equals(input));
+ tokens = p.split(input, 2);
+ assertEquals(2, tokens.length);
+ assertEquals("p", tokens[0]);
+ assertEquals("odle zoo", tokens[1]);
+ tokens = p.split(input, 5);
+ assertEquals(5, tokens.length);
+ assertEquals("p", tokens[0]);
+ assertTrue(tokens[1].equals(""));
+ assertEquals("dle z", tokens[2]);
+ assertTrue(tokens[3].equals(""));
+ assertTrue(tokens[4].equals(""));
+ tokens = p.split(input, -2);
+ assertEquals(5, tokens.length);
+ assertEquals("p", tokens[0]);
+ assertTrue(tokens[1].equals(""));
+ assertEquals("dle z", tokens[2]);
+ assertTrue(tokens[3].equals(""));
+ assertTrue(tokens[4].equals(""));
+ tokens = p.split(input, 0);
+ assertEquals(3, tokens.length);
+ assertEquals("p", tokens[0]);
+ assertTrue(tokens[1].equals(""));
+ assertEquals("dle z", tokens[2]);
+ tokens = p.split(input);
+ assertEquals(3, tokens.length);
+ assertEquals("p", tokens[0]);
+ assertTrue(tokens[1].equals(""));
+ assertEquals("dle z", tokens[2]);
+ }
+
+ @Test
+ public void testSplit2() {
+ Pattern p = Pattern.compile("");
+ String s[];
+ s = p.split("a", -1);
+ assertEquals(3, s.length);
+ assertEquals("", s[0]);
+ assertEquals("a", s[1]);
+ assertEquals("", s[2]);
+
+ s = p.split("", -1);
+ assertEquals(1, s.length);
+ assertEquals("", s[0]);
+
+ s = p.split("abcd", -1);
+ assertEquals(6, s.length);
+ assertEquals("", s[0]);
+ assertEquals("a", s[1]);
+ assertEquals("b", s[2]);
+ assertEquals("c", s[3]);
+ assertEquals("d", s[4]);
+ assertEquals("", s[5]);
+ }
+
+ @Test
+ public void testSplitSupplementaryWithEmptyString() {
+
+ /*
+ * See http://www.unicode.org/reports/tr18/#Supplementary_Characters We
+ * have to treat text as code points not code units.
+ */
+ Pattern p = Pattern.compile("");
+ String s[];
+ s = p.split("a\ud869\uded6b", -1);
+ assertEquals(6, s.length);
+ assertEquals("", s[0]);
+ assertEquals("a", s[1]);
+ assertEquals("\ud869", s[2]);
+ assertEquals("\uded6", s[3]);
+ assertEquals("b", s[4]);
+ assertEquals("", s[5]);
+ }
+}