diff --git a/README.md b/README.md
index 585aeb5..519752c 100644
--- a/README.md
+++ b/README.md
@@ -70,7 +70,7 @@ Add the following lines in the dependencies section of your `pom.xml` file.
com.fulmicoton
multiregexp
- 0.3
+ 0.5.1
```
diff --git a/src/main/java/com/fulmicoton/multiregexp/MultiPattern.java b/src/main/java/com/fulmicoton/multiregexp/MultiPattern.java
index a65a0b3..0012c71 100644
--- a/src/main/java/com/fulmicoton/multiregexp/MultiPattern.java
+++ b/src/main/java/com/fulmicoton/multiregexp/MultiPattern.java
@@ -23,15 +23,22 @@ public static MultiPattern of(String... patterns) {
return new MultiPattern(Arrays.asList(patterns));
}
- public MultiPatternAutomaton makeAutomatonWithPrefix(String prefix) {
- final List automata = new ArrayList<>();
- for (final String ptn: this.patterns) {
- final String prefixedPattern = prefix + ptn;
+ public MultiPatternAutomaton makeAutomatonWithPrefix(String prefix, String... exceptions) {
+ final List automata = new ArrayList<>(this.patterns.size());
+ for (final String ptn : this.patterns) {
+ boolean addPrefix = true;
+ for (String exception : exceptions) {
+ if (ptn.startsWith(exception)) {
+ addPrefix = false;
+ break;
+ }
+ }
+ final String prefixedPattern = (addPrefix ? prefix: "") + ptn;
final Automaton automaton = new RegExp(prefixedPattern).toAutomaton();
automaton.minimize();
automata.add(automaton);
}
- return MultiPatternAutomaton.make(automata);
+ return MultiPatternAutomaton.multithreadedMake(automata);
}
/**
@@ -44,15 +51,20 @@ public MultiPatternAutomaton makeAutomatonWithPrefix(String prefix) {
* @return A searcher object
*/
public MultiPatternSearcher searcher() {
- final MultiPatternAutomaton searcherAutomaton = makeAutomatonWithPrefix(".*");
- final List indidivualAutomatons = new ArrayList<>();
- for (final String pattern: this.patterns) {
+ return searcher(true);
+ }
+
+
+ public MultiPatternSearcher searcher(final boolean tableize) {
+ final MultiPatternAutomaton searcherAutomaton = makeAutomatonWithPrefix(".*", ".*", "^");
+ final List individualAutomatons = new ArrayList<>(this.patterns.size());
+ for (final String pattern : this.patterns) {
final Automaton automaton = new RegExp(pattern).toAutomaton();
automaton.minimize();
automaton.determinize();
- indidivualAutomatons.add(automaton);
+ individualAutomatons.add(automaton);
}
- return new MultiPatternSearcher(searcherAutomaton, indidivualAutomatons);
+ return new MultiPatternSearcher(searcherAutomaton, individualAutomatons, tableize);
}
diff --git a/src/main/java/com/fulmicoton/multiregexp/MultiPatternAutomaton.java b/src/main/java/com/fulmicoton/multiregexp/MultiPatternAutomaton.java
index 43565b0..eaaadd5 100644
--- a/src/main/java/com/fulmicoton/multiregexp/MultiPatternAutomaton.java
+++ b/src/main/java/com/fulmicoton/multiregexp/MultiPatternAutomaton.java
@@ -1,17 +1,25 @@
package com.fulmicoton.multiregexp;
+import java.io.Serializable;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.CountDownLatch;
import dk.brics.automaton.Automaton;
import dk.brics.automaton.DkBricsAutomatonHelper;
import dk.brics.automaton.State;
-public class MultiPatternAutomaton {
+public class MultiPatternAutomaton
+ implements Serializable {
+
+ private static final long serialVersionUID = -8269666436361824366L;
public final int[][] accept;
final boolean[] atLeastOneAccept;
@@ -56,6 +64,112 @@ static MultiState initialState(List automata) {
return new MultiState(initialStates);
}
+ static MultiPatternAutomaton multithreadedMake(final List automata) {
+ for (final Automaton automaton: automata) {
+ automaton.determinize();
+ }
+
+ final char[] points = DkBricsAutomatonHelper.pointsUnion(automata);
+
+ // states that are still to be visited
+ final Queue statesToVisits = new ConcurrentLinkedQueue<>();
+ final MultiState initialState = initialState(automata);
+ statesToVisits.add(initialState);
+
+ final Map transitionMap = new ConcurrentHashMap<>();
+
+ final Map multiStateIndex = new ConcurrentHashMap<>();
+ multiStateIndex.put(initialState, 0);
+
+ final int numberOfThreads = Runtime.getRuntime().availableProcessors();
+
+ final Object lockObject = new Object();
+ final List activeThreads = Collections.synchronizedList(new ArrayList());
+ final CountDownLatch doneSignal = new CountDownLatch(numberOfThreads);
+ for (int thread = 0; thread < numberOfThreads; thread++) {
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ activeThreads.add(Thread.currentThread());
+
+ while (true) {
+ MultiState visitingState;
+ while ((visitingState = statesToVisits.poll()) != null) {
+// assert multiStateIndex.containsKey(visitingState);
+
+ final int[] curTransitions = new int[points.length];
+ final int stateId = multiStateIndex.get(visitingState);
+ transitionMap.put(stateId, curTransitions);
+
+ for (int c = 0; c < points.length; c++) {
+ final MultiState destState = visitingState.step(points[c]);
+ if (destState.isNull()) {
+ curTransitions[c] = -1;
+ } else {
+ Integer destStateId;
+ synchronized (multiStateIndex) {
+ destStateId = multiStateIndex.get(destState);
+ if (destStateId == null) {
+ destStateId = multiStateIndex.size();
+ multiStateIndex.put(destState, destStateId);
+ statesToVisits.add(destState);
+ synchronized (lockObject) {
+ // wake a thread to process destState
+ lockObject.notify();
+ }
+ }
+ }
+ curTransitions[c] = destStateId;
+ }
+ }
+ }
+ activeThreads.remove(Thread.currentThread());
+ // if there are no active threads then we are done
+ if (activeThreads.isEmpty()) {
+ synchronized (lockObject) {
+ // wake waiting threads so they can end
+ lockObject.notifyAll();
+ }
+ // end this thread
+ break;
+ } else {
+ synchronized (lockObject) {
+ try {
+ lockObject.wait();
+ }
+ catch (InterruptedException ignore) {
+ }
+ }
+ activeThreads.add(Thread.currentThread());
+ }
+ }
+ doneSignal.countDown();
+ }
+ }).start();
+ }
+ try {
+ // wait for all to finish
+ doneSignal.await();
+ }
+ catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+
+ assert transitionMap.size() == multiStateIndex.size();
+
+ final int[] transitions = new int[transitionMap.size() * points.length];
+ for (final Map.Entry entry : transitionMap.entrySet()) {
+ System.arraycopy(entry.getValue(), 0, transitions, entry.getKey() * points.length, points.length);
+ }
+
+ final int[][] acceptValues = new int[multiStateIndex.size()][];
+ for (final Map.Entry entry : multiStateIndex.entrySet()) {
+ acceptValues[entry.getValue()] = entry.getKey().toAcceptValues();
+ }
+
+ return new MultiPatternAutomaton(acceptValues, transitions, points, automata.size());
+ }
+
static MultiPatternAutomaton make(final List automata) {
for (final Automaton automaton: automata) {
automaton.determinize();
diff --git a/src/main/java/com/fulmicoton/multiregexp/MultiPatternSearcher.java b/src/main/java/com/fulmicoton/multiregexp/MultiPatternSearcher.java
index ec98398..f8413e2 100644
--- a/src/main/java/com/fulmicoton/multiregexp/MultiPatternSearcher.java
+++ b/src/main/java/com/fulmicoton/multiregexp/MultiPatternSearcher.java
@@ -6,12 +6,16 @@
import dk.brics.automaton.StatePair;
import dk.brics.automaton.Transition;
+import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-public class MultiPatternSearcher {
+public class MultiPatternSearcher
+ implements Serializable {
+
+ private static final long serialVersionUID = -1812442985139693661L;
private final MultiPatternAutomaton automaton;
private final List individualAutomatons;
@@ -19,25 +23,31 @@ public class MultiPatternSearcher {
MultiPatternSearcher(final MultiPatternAutomaton automaton,
final List individualAutomatons) {
+ this(automaton, individualAutomatons, true);
+ }
+
+ MultiPatternSearcher(final MultiPatternAutomaton automaton,
+ final List individualAutomatons,
+ boolean tableize) {
this.automaton = automaton;
- this.individualAutomatons = new ArrayList<>();
- for (final Automaton individualAutomaton: individualAutomatons) {
- this.individualAutomatons.add(new RunAutomaton(individualAutomaton));
+ this.individualAutomatons = new ArrayList<>(individualAutomatons.size());
+ for (final Automaton individualAutomaton : individualAutomatons) {
+ this.individualAutomatons.add(new RunAutomaton(individualAutomaton, tableize));
}
- this.inverseAutomatons = new ArrayList<>(this.individualAutomatons.size());
- for (final Automaton individualAutomaton: individualAutomatons) {
+ this.inverseAutomatons = new ArrayList<>(individualAutomatons.size());
+ for (final Automaton individualAutomaton : individualAutomatons) {
final Automaton inverseAutomaton = inverseAutomaton(individualAutomaton);
- this.inverseAutomatons.add(new RunAutomaton(inverseAutomaton));
+ this.inverseAutomatons.add(new RunAutomaton(inverseAutomaton, tableize));
}
}
static Automaton inverseAutomaton(final Automaton automaton) {
- final Map stateMapping = new HashMap<>();
- for (final State state: automaton.getStates()) {
+ final Map stateMapping = new HashMap<>(automaton.getStates().size());
+ for (final State state : automaton.getStates()) {
stateMapping.put(state, new State());
}
- for (final State state: automaton.getStates()) {
- for (final Transition transition: state.getTransitions()) {
+ for (final State state : automaton.getStates()) {
+ for (final Transition transition : state.getTransitions()) {
final State invDest = stateMapping.get(state);
final State invOrig = stateMapping.get(transition.getDest());
invOrig.addTransition(new Transition(transition.getMin(), transition.getMax(), invDest));
@@ -47,8 +57,8 @@ static Automaton inverseAutomaton(final Automaton automaton) {
stateMapping.get(automaton.getInitialState()).setAccept(true);
final State initialState = new State();
inverseAutomaton.setInitialState(initialState);
- final List epsilons = new ArrayList<>();
- for (final State acceptState: automaton.getAcceptStates()) {
+ final List epsilons = new ArrayList<>(automaton.getAcceptStates().size());
+ for (final State acceptState : automaton.getAcceptStates()) {
final State invOrigState = stateMapping.get(acceptState);
final StatePair statePair = new StatePair(initialState, invOrigState);
epsilons.add(statePair);
@@ -67,30 +77,105 @@ public Cursor search(CharSequence s, int position) {
public class Cursor {
private final CharSequence seq;
- private int matchingPattern = -1;
- private int end = 0;
- private int start = -1;
+ private int[] matchingPatterns = null;
+ private int[] matchingPatternsStart = null;
+ private int[] matchingPatternsEnd = null;
+ private int currentPosition = 0;
Cursor(CharSequence seq, int position) {
this.seq = seq;
- this.end = position;
+ this.currentPosition = position;
}
public int start() {
- return this.start;
+ return start(0);
+ }
+
+ public int start(int patternIndex) {
+ if (this.matchingPatterns == null) {
+ return -1;
+ } else if (this.matchingPatternsStart == null) {
+ this.matchingPatternsStart = new int[this.matchingPatterns.length];
+ for (int i = 0; i < this.matchingPatterns.length; i++) {
+ this.matchingPatternsStart[i] = -1;
+ }
+ }
+
+ if (this.matchingPatternsStart[patternIndex] == -1) {
+ // we rewind using the backward automaton to find the start of the pattern.
+ final RunAutomaton backwardAutomaton = inverseAutomatons.get(this.matchingPatterns[patternIndex]);
+ int state = backwardAutomaton.getInitialState();
+ for (int pos = this.currentPosition - 1; pos >= 0; pos--) {
+ final char c = this.seq.charAt(pos);
+ state = backwardAutomaton.step(state, c);
+ if (state == -1) {
+ break;
+ }
+ if (backwardAutomaton.isAccept(state)) {
+ this.matchingPatternsStart[patternIndex] = pos;
+ }
+ }
+ }
+
+ return this.matchingPatternsStart[patternIndex];
}
public int end() {
- return this.end;
+ return end(0);
+ }
+
+ public int end(int patternIndex) {
+ if (this.matchingPatterns == null) {
+ return -1;
+ } else if (this.matchingPatternsEnd == null) {
+ this.matchingPatternsEnd = new int[this.matchingPatterns.length];
+ }
+
+ if (this.matchingPatternsEnd[patternIndex] == 0) {
+ final int seqLength = this.seq.length();
+ final int start = start(patternIndex);
+ // we go forward again using the forward automaton to find the end of the pattern.
+ final RunAutomaton forwardAutomaton = individualAutomatons.get(this.matchingPatterns[patternIndex]);
+ int state = forwardAutomaton.getInitialState();
+ for (int pos = start; pos < seqLength; pos++) {
+ final char c = this.seq.charAt(pos);
+ state = forwardAutomaton.step(state, c);
+ if (state == -1) {
+ break;
+ }
+ if (forwardAutomaton.isAccept(state)) {
+ this.matchingPatternsEnd[patternIndex] = pos + 1;
+ }
+ }
+ }
+
+ return this.matchingPatternsEnd[patternIndex];
}
public int match() {
- return this.matchingPattern;
+ return match(0);
+ }
+
+ public int match(int patternIndex) {
+ return this.matchingPatterns == null ? -1: this.matchingPatterns[patternIndex];
+ }
+
+ public String pattern() {
+ return pattern(0);
+ }
+
+ public String pattern(int patternIndex) {
+ final RunAutomaton automaton = individualAutomatons.get(this.matchingPatterns[patternIndex]);
+ return automaton.toString();
+ }
+
+ public int[] matches() {
+ return this.matchingPatterns;
}
public boolean found() {
- return this.matchingPattern >= 0;
+ return this.matchingPatterns != null;
}
@@ -114,58 +199,23 @@ public boolean found() {
* If no match is found the function return false.
*/
public boolean next() {
- this.start = -1;
- this.matchingPattern = -1;
+ this.matchingPatterns = null;
+ this.matchingPatternsStart = null;
+ this.matchingPatternsEnd = null;
final int seqLength = this.seq.length();
- { // first find a match and "choose the pattern".
- int state = 0;
- for (int pos=this.end; pos < seqLength; pos++) {
- final char c = this.seq.charAt(pos);
- state = automaton.step(state, c);
- if (automaton.atLeastOneAccept[state]) {
- // We found a match!
- this.matchingPattern = automaton.accept[state][0];
- this.end = pos;
- break;
- }
- }
- if (this.matchingPattern == -1) {
- return false;
- }
- }
- { // we rewind using the backward automaton to find the start of the pattern.
- final RunAutomaton backwardAutomaton = inverseAutomatons.get(this.matchingPattern);
- int state = backwardAutomaton.getInitialState();
- for (int pos = this.end; pos >= 0; pos--) {
- final char c = this.seq.charAt(pos);
- state = backwardAutomaton.step(state, c);
- if (state == -1) {
- break;
- }
- if (backwardAutomaton.isAccept(state)) {
- start = pos;
- }
+ // first find a match and "choose the pattern".
+ for (int state = 0, pos = this.currentPosition; pos < seqLength; pos++) {
+ final char c = this.seq.charAt(pos);
+ state = automaton.step(state, c);
+ if (automaton.atLeastOneAccept[state]) {
+ // We found a match!
+ this.matchingPatterns = automaton.accept[state];
+ this.currentPosition = pos + 1;
+ break;
}
}
- { // we go forward again using the forward automaton to find the end of the pattern.
- final RunAutomaton forwardAutomaton = individualAutomatons.get(this.matchingPattern);
- int state = forwardAutomaton.getInitialState();
- for (int pos = this.start; pos < seqLength; pos++) {
- final char c = this.seq.charAt(pos);
- state = forwardAutomaton.step(state, c);
- if (state == -1) {
- break;
- }
- if (forwardAutomaton.isAccept(state)) {
- this.end = pos + 1;
- }
- }
- }
-
- return true;
+ return this.matchingPatterns != null;
}
-
-
}
}