/**
* Copyright (C) 2011 rwitzel75@googlemail.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.rwitzel.streamflyer.experimental.regexj6;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.github.rwitzel.streamflyer.internal.thirdparty.ZzzAssert;
import com.github.rwitzel.streamflyer.internal.thirdparty.ZzzReflectionUtils;
import com.github.rwitzel.streamflyer.regex.OnStreamMatcher;
import com.github.rwitzel.streamflyer.regex.OnStreamStandardMatcher;
/**
* Implements {@link OnStreamMatcher} knowing internal fields, methods, and classes of the JDK 6 classes {@link Pattern}
* and {@link Matcher}.
* <p>
* This implementation takes four times as much time as {@link String#replaceAll(String, String)} needs to match and
* replace data in an character stream that is read entirely into a {@link CharSequence}.
*
* @author rwoo
*
* @since 20.06.2011
*/
public class OnStreamJava6Matcher extends OnStreamStandardMatcher {
//
// properties initialized by constructor
//
/**
* The field to access the private property <code>first</code> of {@link Matcher}.
*/
private Field firstField;
private Field lastField;
private Field oldLastField;
private Field groupsField;
private Field fromField;
private Field hitEndField;
private Field requireEndField;
private Field acceptModeField;
private Object matchRoot;
private Method matchMethod;
//
//
//
/**
* @param matcher
*/
public OnStreamJava6Matcher(Matcher matcher) {
super(matcher);
this.firstField = findAccessiblePrivateField("first");
this.lastField = findAccessiblePrivateField("last");
this.oldLastField = findAccessiblePrivateField("oldLast");
this.groupsField = findAccessiblePrivateField("groups");
this.fromField = findAccessiblePrivateField("from");
this.hitEndField = findAccessiblePrivateField("hitEnd");
this.requireEndField = findAccessiblePrivateField("requireEnd");
this.acceptModeField = findAccessiblePrivateField("acceptMode");
findMatchMethodOfRootMatchNode();
}
/**
* Initializes {@link #matchRoot} and {@link #matchMethod}.
* <p>
* <code><pre>
boolean result = parentPattern.matchRoot.match(this, from, text);
</pre></code>
*
* @return Returns the field with the given namen of the class {@link Matcher}.
*/
private void findMatchMethodOfRootMatchNode() {
try {
// find field Matcher.parentPattern
Field parentPatternField = ZzzReflectionUtils.findField(Matcher.class, "parentPattern");
ZzzReflectionUtils.makeAccessible(parentPatternField);
ZzzAssert.notNull(parentPatternField, "field parentPattern must not be null");
Pattern parentPattern = (Pattern) parentPatternField.get(matcher);
ZzzAssert.notNull(parentPattern, "field parentPattern must not be null");
// find field Pattern.matchRoot
Field matchRootField = ZzzReflectionUtils.findField(Pattern.class, "matchRoot");
ZzzReflectionUtils.makeAccessible(matchRootField);
ZzzAssert.notNull(matchRootField, "field matchRoot must not be null");
matchRoot = matchRootField.get(parentPattern);
ZzzAssert.notNull(matchRoot, "matchRoot must not be null");
// find method Node.match()
matchMethod = ZzzReflectionUtils.findMethod(matchRoot.getClass(), "match", Matcher.class, Integer.TYPE,
CharSequence.class);
ZzzAssert.notNull(matchMethod, "method match(..) must not be null");
ZzzReflectionUtils.makeAccessible(matchMethod);
} catch (IllegalAccessException ex) {
throw new IllegalStateException(ex);
}
}
/**
* @param fieldName
* @return Returns the field with the given namen of the class {@link Matcher}.
*/
private Field findAccessiblePrivateField(String fieldName) {
Field field = ZzzReflectionUtils.findField(Matcher.class, fieldName);
ZzzReflectionUtils.makeAccessible(field);
ZzzAssert.notNull(field,
String.format("field with name '%s' of class %s must not be null but was", fieldName, Matcher.class));
return field;
}
private void arrayValuesToMinusOne(Field field) throws IllegalAccessException {
int[] array = (int[]) field.get(matcher);
Arrays.fill(array, -1);
field.set(matcher, array); // <- is this redundant?
}
/**
*
<code><pre>
boolean result = parentPattern.matchRoot.match(this, from, text);
</pre></code>
*
* @see com.github.rwitzel.streamflyer.regex.OnStreamStandardMatcher#findUnlessHitEnd(int, int)
*/
@Override
public boolean findUnlessHitEnd(int minFrom, int maxFrom) {
lastFrom = minFrom;
beforeFind(lastFrom);
boolean result = false;
for (; lastFrom <= maxFrom; lastFrom++) {
result = invoke(lastFrom, input.length());
if (result || matcher.hitEnd()) {
break;
}
}
if (result) {
try {
// this.first = this.lastFrom;
// this.groups[0] = this.first;
// this.groups[1] = this.last;
firstField.setInt(matcher, lastFrom);
int[] array = (int[]) groupsField.get(matcher);
array[0] = lastFrom;
array[1] = lastField.getInt(matcher);
groupsField.set(matcher, array); // <- is this redundant?
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
afterFind(result);
return result;
}
private boolean invoke(int lastFrom_, int to) {
try {
// TODO region should be made redundant!!!
matcher.region(lastFrom, to);
return (Boolean) matchMethod.invoke(matchRoot, matcher, lastFrom_, input);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
/**
* <code><pre>
this.hitEnd = false;
this.requireEnd = false;
from = from < 0 ? 0 : from;
this.first = from;
this.oldLast = oldLast < 0 ? from : oldLast;
for (int i = 0; i < groups.length; i++)
groups[i] = -1;
acceptMode = NOANCHOR;
</pre></code>
*/
private void beforeFind(int from) {
try {
hitEndField.setBoolean(matcher, false);
requireEndField.setBoolean(matcher, false);
if (from < 0) {
from = 0;
}
fromField.setInt(matcher, 0);
firstField.setInt(matcher, from);
if (oldLastField.getInt(matcher) < 0) {
oldLastField.setInt(matcher, from);
}
arrayValuesToMinusOne(groupsField);
acceptModeField.setInt(matcher, 0); // NOANCHOR = 0
} catch (IllegalAccessException ex) {
throw new IllegalStateException(ex);
}
}
/**
* <code><pre>
if (!result)
this.first = -1;
this.oldLast = this.last;
</pre></code>
*/
private void afterFind(boolean result) {
try {
if (!result) {
firstField.setInt(matcher, -1);
}
oldLastField.setInt(matcher, lastField.getInt(matcher));
} catch (IllegalAccessException ex) {
throw new IllegalStateException(ex);
}
}
}