"Fossies" - the Fresh Open Source Software Archive  

Source code changes of the file "hsqldb/src/org/hsqldb/lib/KMPSearchAlgorithm.java" between
hsqldb-2.7.1.zip and hsqldb-2.7.2.zip

About: HSQLDB (HyperSQL DataBase) is a SQL relational database engine written in Java. It supports nearly full ANSI-92 SQL (BNF format) and full core SQL:2008.

KMPSearchAlgorithm.java  (hsqldb-2.7.1):KMPSearchAlgorithm.java  (hsqldb-2.7.2)
skipping to change at line 61 skipping to change at line 61
* <li>does not need to employ reverse scan order * <li>does not need to employ reverse scan order
* <li>does not need to perform effectively random access lookups against * <li>does not need to perform effectively random access lookups against
* the searched data or pattern * the searched data or pattern
* </ul> * </ul>
* *
* it also has: * it also has:
* *
* <ul> * <ul>
* <li>a very simple, highly predictable behavior * <li>a very simple, highly predictable behavior
* <li>an O(n) complexity once the a search pattern is preprocessed * <li>an O(n) complexity once the a search pattern is preprocessed
* <li>an O(m) complexity for preprocessing search patterns * <li>an O(m) complexity for pre-processing search patterns
* <li>a worst case performance characteristic of only 2n * <li>a worst case performance characteristic of only 2n
* <li>a typical performance characteristic that is deemed to be * <li>an average case performance characteristic that is deemed to be
* 2-3 times better than the naive search algorithm employed by * 2-3 times better than the naive search algorithm employed by
* {@link String#indexOf(java.lang.String,int)}. * {@link String#indexOf(java.lang.String,int)}.
* </ul> * </ul>
* *
* Note that the Boyer-Moore algorithm is generally considered to be the better * Note that the Boyer-Moore algorithm is generally considered to be the better
* practical, all-round exact sub-string search algorithm, but due to its * practical, all-round exact sub-string search algorithm, but due to its
* reverse pattern scan order, performance considerations dictate that it * reverse pattern scan order, performance considerations dictate that it
* requires more space and that is somewhat more complex to implement * requires more space and that is somewhat more complex to implement
* efficiently for searching forward-only access streams. <p> * efficiently for searching forward-only access streams. <p>
* *
* In particular, its higher average performance is biased toward larger * In particular, its higher average performance is biased toward larger
* search patterns, due to its ability to skip ahead further and with fewer * search patterns, due to its ability to skip ahead further and with fewer
* tests under reverse pattern scan. But when searching forward-only access * tests under reverse pattern scan. But when searching forward-only access
* streams, overall performance considerations require the use a circular buffer * streams, overall performance considerations require the use a circular buffer
* of the same size as the search pattern to hold data from the searched stream * of the same size as the search pattern to hold data from the searched stream
* as it is being compared in reverse order to the search pattern. Hence, * as it is being compared in reverse order to the search pattern. Hence,
* Boyer-Moore requires at minimum twice the memory required by Knuth-Morris-Pra * Boyer-Moore requires at minimum twice the memory required by
tt * Knuth-Morris-Pratt to search for the same pattern and that factor has the
* to search for the same pattern and that factor has the greatest impact * greatest impact precisely on the same class of patterns (larger) for which it
* precisely on the same class of patterns (larger) for which it is most * is most outperforms Knuth-Morris-Pratt.
* outperforms Knuth-Morris-Pratt.
* *
* @author Campbell Burnet (campbell-burnet@users dot sourceforge.net) * @author Campbell Burnet (campbell-burnet@users dot sourceforge.net)
* @version 2.1 * @version 2.7.x
* @since 2.1 * @since 2.1
* @see <a href="http://en.wikipedia.org/wiki/Knuth%E2%80%93Morris%E2%80%93Pratt * @see
_algorithm">Knuth-Morris-Pratt algorithm</a> * <a href="http://en.wikipedia.org/wiki/Knuth%E2%80%93Morris%E2%80%93Pratt_algo
rithm">
* Knuth-Morris-Pratt algorithm</a>
*/ */
public class KMPSearchAlgorithm { public class KMPSearchAlgorithm {
// @todo 2.7.x - performance optimizations for backend use
/**
* computes the table used to optimize octet pattern search
*
* @param pattern for which to compute the table.
* @return the table computed from the octet pattern.
* @throws IllegalArgumentException if {@code pattern == null
* || pattern.length < 2}.
*/
public static int[] computeTable(final byte[] pattern) {
if (pattern == null) {
throw new IllegalArgumentException("pattern must not be null.");
} else if (pattern.length < 2) {
throw new IllegalArgumentException("pattern.length must be > 1.");
}
//
final int[] table = new int[pattern.length];
int i = 2;
int j = 0;
//
table[0] = -1;
table[1] = 0;
//
while (i < pattern.length) {
if (pattern[i - 1] == pattern[j]) {
table[i] = j + 1;
j++;
i++;
} else if (j > 0) {
j = table[j];
} else {
table[i] = 0;
i++;
j = 0;
}
}
//
return table;
}
/**
* computes the table used to optimize octet pattern search
*
* @param pattern for which to compute the table.
* @return the table computed from the character pattern.
* @throws IllegalArgumentException if {@code pattern == null
* || pattern.length < 2}.
*/
public static int[] computeTable(final char[] pattern) {
if (pattern == null) {
throw new IllegalArgumentException("pattern must not be null.");
} else if (pattern.length < 2) {
throw new IllegalArgumentException("pattern.length must be > 1.");
}
int[] table = new int[pattern.length];
int i = 2;
int j = 0;
table[0] = -1;
table[1] = 0;
while (i < pattern.length) {
if (pattern[i - 1] == pattern[j]) {
table[i] = j + 1;
j++;
i++;
} else if (j > 0) {
j = table[j];
} else {
table[i] = 0;
i++;
j = 0;
}
}
return table;
}
/**
* computes the table used to optimize octet pattern search
*
* @param pattern for which to compute the table.
* @return the table computed from the String pattern.
* @throws IllegalArgumentException if {@code pattern == null
* || pattern.length < 2}.
*/
public static int[] computeTable(final String pattern) {
if (pattern == null) {
throw new IllegalArgumentException("Pattern must not be null.");
} else if (pattern.length() < 2) {
throw new IllegalArgumentException("Pattern length must be > 1.");
}
final int patternLength = pattern.length();
//
int[] table = new int[patternLength];
int i = 2;
int j = 0;
table[0] = -1;
table[1] = 0;
while (i < patternLength) {
if (pattern.charAt(i - 1) == pattern.charAt(j)) {
table[i] = j + 1;
j++;
i++;
} else if (j > 0) {
j = table[j];
} else {
table[i] = 0;
i++;
j = 0;
}
}
return table;
}
/** /**
* Searches the given octet stream for the given octet pattern * Searches the given octet stream for the given octet pattern
* returning the zero-based offset from the initial stream position * returning the zero-based offset from the initial stream position
* at which the first match is detected. <p> * at which the first match is detected.
* <p>
* *
* Note that the signature includes a slot for the table so that * Note that the signature includes a slot for the table so that
* searches for a pattern can be performed multiple times without * searches for a pattern can be performed multiple times without
* incurring the overhead of computing the table each time. * incurring the overhead of computing the table each time.
* *
* @param inputStream in which to search * @param inputStream in which to search
* @param pattern for which to search * @param pattern for which to search
* @param table computed from the pattern that optimizes the search. * @param table computed from the pattern that optimizes the search.
* If null, automatically computed. * If null, automatically computed.
* @return zero-based offset of first match; -1 if no match found. * @return zero-based offset of first match; -1 if {@code inputStream == null
* || pattern == null} or no match found, ; zero (0) if
* {@code pattern.length == 0}.
* @throws IOException when an error occurs accessing the input stream. * @throws IOException when an error occurs accessing the input stream.
*/ */
public static long search(final InputStream inputStream, public static long search(final InputStream inputStream,
final byte[] pattern, final byte[] pattern,
int[] table) throws IOException { int[] table) throws IOException {
if (inputStream == null || pattern == null || pattern.length == 0) { if (inputStream == null || pattern == null) {
return -1; return -1;
} }
// //
final int patternLength = pattern.length; final int patternLength = pattern.length;
// as per SQL LOCATE, INSTR, POSITION( IN )
if (patternLength == 0) {
return 0;
}
// //
long streamIndex = -1; long streamIndex = -1;
int currentByte; int currentByte;
if (patternLength == 1) { if (patternLength == 1) {
final int byteToFind = pattern[0]; final int byteToFind = pattern[0];
while (-1 != (currentByte = inputStream.read())) { currentByte = inputStream.read();
while (-1 != currentByte) {
streamIndex++; streamIndex++;
if (currentByte == byteToFind) { if (currentByte == byteToFind) {
return streamIndex; return streamIndex;
} }
currentByte = inputStream.read();
} }
return -1; return -1;
} }
int patternIndex = 0; int patternIndex = 0;
final int[] localTable = table == null ? computeTable(pattern)
: table;
currentByte = inputStream.read();
if (table == null) { while (-1 != currentByte) {
table = computeTable(pattern);
}
while (-1 != (currentByte = inputStream.read())) {
streamIndex++; streamIndex++;
if (currentByte == pattern[patternIndex]) { if (currentByte == pattern[patternIndex]) {
patternIndex++; patternIndex++;
} else if (patternIndex > 0) { } else if (patternIndex > 0) {
patternIndex = table[patternIndex]; patternIndex = localTable[patternIndex];
patternIndex++; patternIndex++;
} }
if (patternIndex == patternLength) { if (patternIndex == patternLength) {
return streamIndex - (patternLength - 1); return streamIndex - (patternLength - 1);
} }
currentByte = inputStream.read();
} }
return -1; return -1;
} }
/** /**
* Searches the given character stream for the given character pattern * Searches the given character stream for the given character pattern
* returning the zero-based offset from the initial stream position * returning the zero-based offset from the initial stream position
* at which the first match is detected. <p> * at which the first match is detected. <p>
* *
* Note that the signature includes a slot for the table so that * Note that the signature includes a slot for the table so that
* searches for a pattern can be performed multiple times without * searches for a pattern can be performed multiple times without
* incurring the overhead of computing the table each time. * incurring the overhead of computing the table each time.
* *
* @param reader in which to search * @param reader in which to search
* @param pattern for which to search * @param pattern for which to search
* @param table computed from the pattern that optimizes the search * @param table computed from the pattern that optimizes the search
* If null, automatically computed. * If null, automatically computed.
* @return zero-based offset of first match; -1 if no match found. * @return zero-based offset of first match; -1 if {@code reader == null
* || pattern == null} or no match found, ; zero (0) if
* {@code pattern.length == 0}.
* @throws IOException when an error occurs accessing the input stream. * @throws IOException when an error occurs accessing the input stream.
*/ */
@SuppressWarnings({"NestedAssignment", "AssignmentToMethodParameter"})
public static long search(final Reader reader, final char[] pattern, public static long search(final Reader reader, final char[] pattern,
int[] table) throws IOException { int[] table) throws IOException {
if (reader == null || pattern == null || pattern.length == 0) { if (reader == null || pattern == null) {
return -1; return -1;
} }
// //
final int patternLength = pattern.length; final int patternLength = pattern.length;
// as per SQL LOCATE, INSTR, POSITION( IN )
if (patternLength == 0) {
return 0;
}
// //
long streamIndex = -1; long streamIndex = -1;
int currentCharacter; int currentCharacter;
if (patternLength == 1) { if (patternLength == 1) {
final int characterToFind = pattern[0]; final int characterToFind = pattern[0];
while (-1 != (currentCharacter = reader.read())) { currentCharacter = reader.read();
while (-1 != currentCharacter) {
streamIndex++; streamIndex++;
if (currentCharacter == characterToFind) { if (currentCharacter == characterToFind) {
return streamIndex; return streamIndex;
} }
currentCharacter = reader.read();
} }
return -1; return -1;
} }
final int[] localTable = table == null ? computeTable(pattern)
: table;
//
int patternIndex = 0; int patternIndex = 0;
currentCharacter = reader.read();
while (-1 != currentCharacter) {
streamIndex++;
if (currentCharacter == pattern[patternIndex]) {
patternIndex++;
} else if (patternIndex > 0) {
patternIndex = localTable[patternIndex];
patternIndex++;
}
if (patternIndex == patternLength) {
return streamIndex - (patternLength - 1);
}
currentCharacter = reader.read();
}
return -1;
}
/**
* Searches the given character stream for the given character pattern
* returning the zero-based offset from the initial stream position
* at which the first match is detected.
* <p>
*
* Note that the signature includes a slot for the table so that
* searches for a pattern can be performed multiple times without
* incurring the overhead of computing the table each time.
*
* @param reader in which to search
* @param pattern for which to search
* @param table computed from the pattern that optimizes the search
* If null, automatically computed.
* @return zero-based offset of first match; -1 if {@code reader == null
* || pattern == null} or no match found, ; zero (0) if
* {@code pattern.length() == 0}.
* @throws IOException when an error occurs accessing the input stream.
*/
@SuppressWarnings({"NestedAssignment", "AssignmentToMethodParameter"})
public static long search(final Reader reader, final String pattern,
int[] table) throws IOException {
if (table == null) { if (reader == null || pattern == null) {
table = computeTable(pattern); return -1;
}
//
final int patternLength = pattern.length();
// as per SQL LOCATE, INSTR, POSITION( IN )
if (patternLength == 0) {
return 0;
} }
//
long streamIndex = -1;
int currentCharacter;
if (patternLength == 1) {
final int characterToFind = pattern.charAt(0);
currentCharacter = reader.read();
while (-1 != (currentCharacter = reader.read())) { while (-1 != currentCharacter) {
streamIndex++;
if (currentCharacter == characterToFind) {
return streamIndex;
}
currentCharacter = reader.read();
}
return -1;
}
final int[] localTable = table == null ? computeTable(pattern)
: table;
//
int patternIndex = 0;
currentCharacter = reader.read();
while (-1 != currentCharacter) {
streamIndex++; streamIndex++;
if (currentCharacter == pattern[patternIndex]) { if (currentCharacter == pattern.charAt(patternIndex)) {
patternIndex++; patternIndex++;
} else if (patternIndex > 0) { } else if (patternIndex > 0) {
patternIndex = table[patternIndex]; patternIndex = localTable[patternIndex];
patternIndex++; patternIndex++;
} }
if (patternIndex == patternLength) { if (patternIndex == patternLength) {
return streamIndex - (patternLength - 1); return streamIndex - (patternLength - 1);
} }
currentCharacter = reader.read();
} }
return -1; return -1;
} }
/** /**
* Searches the given octet string for the given octet pattern * Searches the given octet string for the given octet pattern
* returning the zero-based offset from given start position * returning the zero-based offset from given start position
* at which the first match is detected. <p> * at which the first match is detected. <p>
* *
* Note that the signature includes a slot for the table so that * Note that the signature includes a slot for the table so that
* searches for a pattern can be performed multiple times without * searches for a pattern can be performed multiple times without
* incurring the overhead of computing the table each time. * incurring the overhead of computing the table each time.
* *
* @param source array in which to search * @param source array in which to search
* @param pattern to be matched * @param pattern to be matched
* @param table computed from the pattern that optimizes the search * @param table computed from the pattern that optimizes the search
* If null, automatically computed. * If null, automatically computed.
* @param start position in source at which to start the search * @param start position in source at which to start the search
* @return zero-based offset of first match; -1 if no match found. * @return zero-based offset of first match; -1 if {@code source == null
* || pattern == null} or no match found, ; start if
* {@code pattern.length == 0 and start <= source.length}.
*/ */
public static int search(final byte[] source, final byte[] pattern, public static int search(final byte[] source, final byte[] pattern,
int[] table, final int start) { int[] table, final int start) {
if (source == null || pattern == null || pattern.length == 0) { if (source == null || pattern == null) {
return -1; return -1;
} }
// //
final int sourceLength = source.length; final int sourceLength = source.length;
final int patternLength = pattern.length; final int patternLength = pattern.length;
// as per SQL LOCATE, INSTR, POSITION( IN )
if (patternLength == 0) {
return start > sourceLength ? -1 : start;
}
// //
int sourceIndex = start; int sourceIndex = start;
if (patternLength == 1) { if (patternLength == 1) {
final int byteToFind = pattern[0]; final int byteToFind = pattern[0];
for (; sourceIndex < sourceLength; sourceIndex++) { for (; sourceIndex < sourceLength; sourceIndex++) {
if (source[sourceIndex] == byteToFind) { if (source[sourceIndex] == byteToFind) {
return sourceIndex; return sourceIndex;
} }
} }
return -1; return -1;
} }
// //
int matchStart = start; int matchStart = start;
int patternIndex = 0; int patternIndex = 0;
// //
if (table == null) { final int[] localTable = table == null ? computeTable(pattern)
table = computeTable(pattern); : table;
}
// //
while ((sourceIndex < sourceLength) while ((sourceIndex < sourceLength)
&& (patternIndex < patternLength)) { && (patternIndex < patternLength)) {
if (source[sourceIndex] == pattern[patternIndex]) { if (source[sourceIndex] == pattern[patternIndex]) {
patternIndex++; patternIndex++;
} else { } else {
final int tableValue = table[patternIndex]; final int tableValue = localTable[patternIndex];
matchStart += (patternIndex - tableValue); matchStart += (patternIndex - tableValue);
if (patternIndex > 0) { if (patternIndex > 0) {
patternIndex = tableValue; patternIndex = tableValue;
} }
patternIndex++; patternIndex++;
} }
skipping to change at line 316 skipping to change at line 565
} }
} }
/** /**
* Searches the given character array for the given character pattern * Searches the given character array for the given character pattern
* returning the zero-based offset from given start position * returning the zero-based offset from given start position
* at which the first match is detected. * at which the first match is detected.
* *
* @param source array in which to search * @param source array in which to search
* @param pattern to be matched * @param pattern to be matched
* @param table computed from the pattern that optimizes the search * @param table computed from the pattern that optimizes the search
* If null, automatically computed. * If null, automatically computed.
* @param start position in source at which to start the search * @param start position in source at which to start the search
* @return zero-based offset of first match; -1 if no match found. * @return zero-based offset of first match; -1 if {@code source == null
* || pattern == null} or no match found, ; start if
* {@code pattern.length == 0 and start <= source.length}.
*/ */
public static int search(final char[] source, final char[] pattern, public static int search(final char[] source, final char[] pattern,
int[] table, final int start) { int[] table, final int start) {
if (source == null || pattern == null || pattern.length == 0) { if (source == null || pattern == null) {
return -1; return -1;
} }
final int sourceLength = source.length; final int sourceLength = source.length;
final int patternLength = pattern.length; final int patternLength = pattern.length;
int sourceIndex = start;
// as per SQL LOCATE, INSTR, POSITION( IN )
if (patternLength == 0) {
return start > sourceLength ? -1 : start;
}
int sourceIndex = start;
if (patternLength == 1) { if (patternLength == 1) {
final int characterToFind = pattern[0]; final int characterToFind = pattern[0];
for (; sourceIndex < sourceLength; sourceIndex++) { for (; sourceIndex < sourceLength; sourceIndex++) {
if (source[sourceIndex] == characterToFind) { if (source[sourceIndex] == characterToFind) {
return sourceIndex; return sourceIndex;
} }
} }
return -1; return -1;
} }
// //
int matchStart = start; int matchStart = start;
int patternIndex = 0; int patternIndex = 0;
// //
if (table == null) { final int[] localTable = table == null ? computeTable(pattern)
table = computeTable(pattern); : table;
}
// //
while ((sourceIndex < sourceLength) while ((sourceIndex < sourceLength)
&& (patternIndex < patternLength)) { && (patternIndex < patternLength)) {
if (source[sourceIndex] == pattern[patternIndex]) { if (source[sourceIndex] == pattern[patternIndex]) {
patternIndex++; patternIndex++;
} else { } else {
final int tableValue = table[patternIndex]; final int tableValue = localTable[patternIndex];
matchStart += (patternIndex - tableValue); matchStart += (patternIndex - tableValue);
if (patternIndex > 0) { if (patternIndex > 0) {
patternIndex = tableValue; patternIndex = tableValue;
} }
patternIndex++; patternIndex++;
} }
skipping to change at line 387 skipping to change at line 642
} }
} }
/** /**
* Searches the given String object for the given character pattern * Searches the given String object for the given character pattern
* returning the zero-based offset from given start position * returning the zero-based offset from given start position
* at which the first match is detected. * at which the first match is detected.
* *
* @param source array to be searched * @param source array to be searched
* @param pattern to be matched * @param pattern to be matched
* @param table computed from the pattern that optimizes the search * @param table computed from the pattern that optimizes the search
* @param start position in source at which to start the search * @param start position in source at which to start the search
* @return zero-based offset of first match; -1 if no match found. * @return zero-based offset of first match; -1 if {@code source == null
* || pattern == null} or no match found, ; start if
* {@code pattern.length == 0 and start <= source.length}.
*/ */
public static int search(final String source, final String pattern, public static int search(final String source, final String pattern,
int[] table, final int start) { int[] table, final int start) {
if (source == null || pattern == null || pattern.isEmpty()) { if (source == null || pattern == null) {
return -1; return -1;
} }
final int patternLength = pattern.length(); final int patternLength = pattern.length();
final int sourceLength = source.length();
// // as per SQL LOCATE, INSTR, POSITION( IN )
if (patternLength == 0) {
return start > sourceLength ? -1 : start;
}
if (patternLength == 1) { if (patternLength == 1) {
return source.indexOf(pattern, start); return source.indexOf(pattern, start);
} }
// //
final int sourceLength = source.length();
//
int matchStart = start; int matchStart = start;
int sourceIndex = start; int sourceIndex = start;
int patternIndex = 0; int patternIndex = 0;
// //
if (table == null) { final int[] localTable = table == null ? computeTable(pattern)
table = computeTable(pattern); : table;
}
// //
while ((sourceIndex < sourceLength) while ((sourceIndex < sourceLength)
&& (patternIndex < patternLength)) { && (patternIndex < patternLength)) {
if (source.charAt(sourceIndex) == pattern.charAt(patternIndex)) { if (source.charAt(sourceIndex) == pattern.charAt(patternIndex)) {
patternIndex++; patternIndex++;
} else { } else {
final int tableValue = table[patternIndex]; final int tableValue = localTable[patternIndex];
matchStart += (patternIndex - tableValue); matchStart += (patternIndex - tableValue);
if (patternIndex > 0) { if (patternIndex > 0) {
patternIndex = tableValue; patternIndex = tableValue;
} }
patternIndex++; patternIndex++;
} }
sourceIndex = matchStart + patternIndex; sourceIndex = matchStart + patternIndex;
} }
if (patternIndex == patternLength) { if (patternIndex == patternLength) {
return matchStart; return matchStart;
} else { } else {
return -1; return -1;
} }
} }
/** private KMPSearchAlgorithm() {
* computes the table used to optimize octet pattern search assert false : "Pure Utility Class";
*
* @param pattern for which to compute the table.
* @return the table computed from the octet pattern.
*/
public static int[] computeTable(final byte[] pattern) {
if (pattern == null) {
throw new IllegalArgumentException("Pattern must not be null.");
} else if (pattern.length < 2) {
throw new IllegalArgumentException("Pattern length must be > 1.");
}
//
final int[] table = new int[pattern.length];
int i = 2;
int j = 0;
//
table[0] = -1;
table[1] = 0;
//
while (i < pattern.length) {
if (pattern[i - 1] == pattern[j]) {
table[i] = j + 1;
j++;
i++;
} else if (j > 0) {
j = table[j];
} else {
table[i] = 0;
i++;
j = 0;
}
}
//
return table;
} }
public static int[] computeTable(final char[] pattern) {
if (pattern == null) {
throw new IllegalArgumentException("Pattern must not be null.");
} else if (pattern.length < 2) {
throw new IllegalArgumentException("Pattern length must be > 1.");
}
int[] table = new int[pattern.length];
int i = 2;
int j = 0;
table[0] = -1;
table[1] = 0;
while (i < pattern.length) {
if (pattern[i - 1] == pattern[j]) {
table[i] = j + 1;
j++;
i++;
} else if (j > 0) {
j = table[j];
} else {
table[i] = 0;
i++;
j = 0;
}
}
return table;
}
public static int[] computeTable(final String pattern) {
if (pattern == null) {
throw new IllegalArgumentException("Pattern must not be null.");
} else if (pattern.length() < 2) {
throw new IllegalArgumentException("Pattern length must be > 1.");
}
final int patternLength = pattern.length();
//
int[] table = new int[patternLength];
int i = 2;
int j = 0;
table[0] = -1;
table[1] = 0;
while (i < patternLength) {
if (pattern.charAt(i - 1) == pattern.charAt(j)) {
table[i] = j + 1;
j++;
i++;
} else if (j > 0) {
j = table[j];
} else {
table[i] = 0;
i++;
j = 0;
}
}
return table;
}
} }
 End of changes. 52 change blocks. 
185 lines changed or deleted 328 lines changed or added

Home  |  About  |  Features  |  All  |  Newest  |  Dox  |  Diffs  |  RSS Feeds  |  Screenshots  |  Comments  |  Imprint  |  Privacy  |  HTTP(S)