JTopas - Java tokenizer and parser toolsVersion 0.6 Contents: |
|||||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||||
What is this?As the title indicates, this is software written in Java and designed for being used in Java applications, libraries and other Java environments. In this version, the following features are provided:
This list will be frequently extended by new features, implementations of standard Java API's, and, last but not least, extensions to existing features. Currently, JTopas consists of:
NOTE: The java package root is "de.susebox" according to our registered internet domain, not jtopas or similar. |
|||||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||||
The generic tokenizerIf You need to parse more sophisticated texts than can easily be handled using In the following example, we extract the contents from a HTML file to produce a roughly formatted text file. A more sophisticated version can be found in one of the JUnit tests (see
// Imports
import java.io.FileInputStream;
import java.io.InputStreamReader;
import de.susebox.jtopas.Token;
import de.susebox.jtopas.Tokenizer;
import de.susebox.jtopas.TokenizerProperties;
import de.susebox.jtopas.StandardTokenizer;
import de.susebox.jtopas.StandardTokenizerProperties;
import de.susebox.jtopas.ReaderSource;
// class to hold main method
public class ContentsExtractor {
// Main method. Supply a HTML file name as argument
public static void main(String[] args) {
FileInputStream stream = new FileInputStream(args[0]);
InputStreamReader reader = new InputStreamReader(stream);
TokenizerProperties props = new StandardTokenizerProperties();
Tokenizer tokenizer = new StandardTokenizer();
Token token;
int len;
int caseFlags;
// setup the tokenizer
props.setParseFlags( TokenizerProperties.F_NO_CASE
| TokenizerProperties.F_TOKEN_POS_ONLY
| TokenizerProperties.F_RETURN_WHITESPACES);
caseFlags = props.getParseFlags() & ~Tokenizer.F_NO_CASE;
props.setSeparators(null);
props.addBlockComment("<", ">");
props.addBlockComment("<HEAD>", "</HEAD>");
props.addBlockComment("<!--", "-->");
props.addSpecialSequence("<", "<");
props.addSpecialSequence(">", ">");
props.addSpecialSequence("ä", "ä", caseFlags);
props.addSpecialSequence("Ä", "Ä", caseFlags);
props.addSpecialSequence("ö", "ö", caseFlags);
props.addSpecialSequence("Ö", "Ö", caseFlags);
props.addSpecialSequence("ü", "ü", caseFlags);
props.addSpecialSequence("Ü", "Ü", caseFlags);
props.addSpecialSequence("<b>", "");
props.addSpecialSequence("</b>", "");
props.addSpecialSequence("<i>", "");
props.addSpecialSequence("</i>", "");
props.addSpecialSequence("<code>", "");
props.addSpecialSequence("</code>", "");
tokenizer.setTokenizerProperties(props);
tokenizer.setSource(new ReaderSource(reader));
// tokenize the file and print basically
// formatted context to stdout
len = 0;
while (tokenizer.hasMoreToken()) {
token = tokenizer.nextToken();
switch (token.getType()) {
case Token.NORMAL:
System.out.print(tokenizer.currentImage());
len += token.getLength();
break;
case Token.SPECIAL_SEQUENCE:
System.out.print((String)token.getCompanion());
break;
case Token.BLOCK_COMMENT:
if (len > 0) {
System.out.println();
len = 0;
}
break;
case Token.WHITESPACE:
if (len > 75) {
System.out.println();
len = 0;
} else if (len > 0) {
System.out.print(' ');
len++;
}
break;
}
}
}
}
There are two core interfaces involved in the above example: The generic tokenizer supports the following features, some of them can be found in the example above:
More examples can be found in the various JUnit tests for the The basic behaviour of the tokenizer can be configured with various flags, that may control the parsing process as a whole, but also the handling of single comments, keywords or special sequences. This is shown in the example above: HTML is generally not case-sensitive, but some special characters are, like ä (ä) and Ä (Ä). Currently, the tokenizer recognizes the following parse flags:
The mentioned flags can also be set for a single comment, keyword or special sequence. Currently, only the The more advanced If a specific flag can be set or cleared while the parsing process is under way, depends highly on the implementation. Our basic version supports dynamic change for the following flags:
The results of the Tokenizers parse operations a delivered as objects of class With version 0.6, pattern handling is added to the basic tokenizer operations. Using this new feature requires a backing library for regular expression support. Since JDK 1.4 the Java standard edition contains the package Java classes and documentation:
|
|||||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||||
Working with embedded tokenizersBeside the implementation of the Two examples for embedded tokenizers can be found in one of the JUnit test cases, The idea behind the embedded tokenizers is that one master tokenizer contains the input buffer and controls the read operation while the slave tokenizers share the input buffer. The user switches actively between the tokenizers when boundaries between different parts are reached (end of block comment in javadoc comments, for instance). The following code snippet shows the principle:
StandardTokenizerProperties prop1 = new StandardTokenizerProperties();
StandardTokenizerProperties prop2 = new StandardTokenizerProperties();
StandardTokenizer base = new StandardTokenizer(prop1);
StandardTokenizer embedded = new StandardTokenizer(prop2);
// setting properties (comments, keywords etc.)
prop1.setSpecialSequence("/**", embedded);
prop2.setSpecialSequence("*/", base);
// embedding a tokenizer
base.addTokenizer(embedded);
// tokenizing with base
StandardTokenizer current = base;
while (current.hasMoreToken()) {
Token token = current.nextToken();
if ( token.getType == Token.SPECIAL_SEQUENCE
&& token.getCompanion() instanceof StandardTokenizer) {
current.switchTo((StandardTokenizer)token.getCompanion());
current = token.getCompanion();
}
}
|
|||||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||||
The Service Provider InterfaceIn former versions of JTopas, there was the so-called plugin tokenizer. The concept a bit complicated and only an add-on to the (old version) of the core tokenizer classes. With version 0.6, the old design was completely renovated and the plugin tokenizer has been replaced by a Service Provider Interface (SPI). The SPI is used by the StandardTokenizer to communicate with the associated TokenizerProperties object. The tokenizer is therefore independend from the actual implementation of the TokenizerProperties interface. Moreover, the functionality of this interface can be partially or even completely shifted to peer classes implementing various interfaces in the The SPI can basically be used in the following ways:
Here is an example there a lookup structure for a set of keywords already exists. The situation is a bit artificial, but its the principle that counts :-)
// Imports
import java.util.Collection;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import de.susebox.jtopas.Token;
import de.susebox.jtopas.TokenizerProperty;
import de.susebox.jtopas.ReaderSource;
import de.susebox.jtopas.spi.KeywordHandler;
import de.susebox.jtopas.spi.DataProvider;
// class to hold main method
public class MyKeywordHandler
implements de.susebox.jtopas.spi.KeywordHandler {
/** the keyword collection */
private Collection _keywords = null;
/** Constructor taking the existing keyword collection */
public MyKeywordHandler(Collection keywords) {
_keywords = keywords;
}
/** Method from the KeywordHandler interface */
public TokenizerProperty isKeyword(DataProvider dataProvider) {
if (_keywords != null) {
String keyword = new String(dataProvider.getData(),
dataProvider.getStartPosition(),
dataProvider.getLength());
if(_keywords.contains(keyword)) {
return new TokenizerProperty(Token.KEYWORD,
new String[] { keyword },
null,
TokenizerProperties.F_CASE);
} else {
return null;
}
} else {
return false;
}
}
}
/**
* Class searching for keywords in an input stream. The files
* may contain line comments starting with the '#' character
* and strings encapsulated in '"' or ''' pairs
*/
public class KeywordFinder {
/**
* Main method taking file names as data sources.
* With the '-k' option keywords can be specified, that should
* be found in the files.
*/
public void main(String[] args) {
// Setting up the keyword structure
HashSet keywords = new HashSet();
for (int index = 0; index < args.length; ++index) {
String arg = args[index];
if (arg.length() > 2 && arg.charAt(0) == '-' && arg.charAt(1) == 'k') {
keywords.add(arg.substring(2);
}
}
// Setting up the tokenizer
StandardTokenizerProperties props = new StandardTokenizerProperties();
StandardTokenizer tokenizer = new StandardTokenizer(props);
MyKeywordHandler handler = new MyKeywordHandler(keywords);
props.setParseFlags(TokenizerProperties.F_COUNT_LINES);
props.addLineComment("#");
props.addString("\"", "\"", "\\");
props.addString("'", "'", "\\");
tokenizer.setKeywordHandler(handler);
// tokenizing the given files
for (int index = 0; index < args.length; ++index) {
String arg = args[index];
if (arg.length() > 1 && arg.charAt(0) != '-') {
FileInputStream is = new FileInputStream(arg);
tokenizer.setSource(new ReaderSource(new InputStreamReader(is)));
while (tokenizer.hasMoreToken()) {
Token token = tokenizer.nextToken();
switch (token.getType()) {
case Token.KEYWORD:
System.out.println(arg
+ ": at " + token.getStartLine()
+ "/" + token.getStartColumn()
+ ": " + token.getImage());
break;
}
}
is.close();
}
}
}
}
The example could be altered, so that the keyword handler class reads keywords from a file that changes frequently during runtime of the tokenizer. Ot where the currently active set of keywords is read from a database. Equivalent to the shown keyword handling, there are interfaces to control separator, whitespace, comment, pattern and special sequence detection separately. Java classes and documentation:
|
|||||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||||
Using exception stacksIn order to see, whats going wrong in one's Java application, But the following problems occur while working with exceptions (Objects of type
We use the interface
We use either exceptions derived from the standard JDK exceptions or our own ones. Both support the Exception messages can be provided in the usual way by composing the message when constructing the exception. Another way is the usage of a format string and a array of parameters. The latter approach avoids the costly formatting when an exception is thrown (nobody knows, if the message is actually shown somewhere). Another aspect is the separation of language-depending format and the actual parameters. Beside the methods of the Future releases of JTopas will reflect the new changed exception facility of JDK 1.4. In particular, Java classes and documentation:
|
|||||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||||
Using the
|
|||||||||||||||||||||||||||||||||
Class / Interface |
Doc. |
Remarks |
|---|---|---|
A substitute for |
||
This class acompanies the |
After extracting files from the JTopas tar'ed and gzip'ed archive jtopas-0.1.tar.gz, the following directories can be found:
Directory |
Contents |
|---|---|
contains the sources for the |
|
contains various JUnit test cases and suites for the packages in |
|
configuration files ... (empty, so far). |
|
the contents of the JTopas website http://jtopas.sourceforge.net. |
|
This folder is filled by the ant build process, controlled by the ant "Makefile" build.xml. It contains everything that can be made out of the sources: the archives jtopas.jar and jtopas-jt.jar, the compiled classes, and the java documentation. |
|
the contents of our project website http://jtopas.sourceforge.net |
The Java package structure follows the usual policy. The root for our package hierarchy is derived from our web domain: de.susebox. For application-independed interfaces and classes that are extended from or related to JDK (J2SE, J2EE) API's, we use the equivalent structure. If, for instance, we would implement a java.io.InputStream, it would appear in the package de.susebox.java.io.
We use the popular JUnit framework for testing our classes. The tests are composed hierarchically by the (static) suite method of JUnit. The test class de.susebox.java.JavaTestSuite assembles a suite of all subpackage test suites. Part of it is, for instance, de.susebox.java.util.UtilTestSuite. The root test class is de.susebox.SuseboxTestSuite.
While some test cases are typical unit tests as JUnit suggests, there are others, that can - and need to - be configured using a configuration file. Currently, these configuration files contain entries that can be read by the java.lang.Class.getResourceAsStream method. To run the tests successfully, You should:
$JTOPAS_HOME/junit directory and add "." (current working directory) to the classpath. That way, the class loader will eventually find the configuration files.It is also possible to put all *.conf files into a separate directory tree, provided they have the same relative paths as in the $JTOPAS_HOME/junit subtree
The configuration files contain several test data sets. Each set is a collection of properties defined in the test case class using the configuration file, followed by the number of the test data set:
# first test set Path1=README.html # second test set Path2=build.xml
Line comments may be used as shown above. The property base names ("Path" in the example) are defined in the test case Java classes. You will find the declaration at the beginning of the class sources. See for instance TestInputStreamTokenizer.java.
To compile, use and run sources and / or archives of JTopas, You need at least a Java runtime environment (JRE), that can be obtained - among others - from Sun. For debugging purposes or a look inside the JDK sources, the full JDK is a better choice. For Linux users: there should be at least one JDK in Your distribution.
Since Version 0.3 of JTopas, a Java 2 environment (at least JDK 1.2) is nessecary. The older java.util classes like Vector, Hashtable and especially Enumeration are abandoned in favour of ArrayList, HashMap and Iterator.
For development, we use the Netbeans IDE with Sun's JDK 1.4.1 on a SuSE Linux 8.1. The Jakarta Ant build tool is used to compile, archive, javadoc and package the whole thing. CVS does the version control. There are some ant targets for CVS.
Our development platform is Linux, therefore we provide the usual zipped tar archive jtopas-version.tar.gz. The version tag has the form major.minor[.maintenance], for instance 0.1 or 149.73.3
Installation is straightforward:
me@ours: > cd <parent_dir>
me@ours: > tar xvzf <download_dir/>jtopas-0.6.tar.gz
For the various Microsoft operating systems, programs like Winzip or Power Archiver are able to extract the tgz-archive. With the Cygwin bash shell for Windows, one may install the archive like in Unix environment.
JTopas requires JDK 1.2 or higher to run (mainly, we use the collection classes not available in JDK 1.1). Recommended and - for the use of pattern matching - is JDK 1.4. For the JUnit tests, JUnit 3.7 or higher is nessecary. All archives have been tested against Sun's JDK 1.3.1 and JDK 1.4.1 on a SuSE Linux 8.1. Currently, IBM's JDK 1.3.0 refuses to run on my Linux, but there shouldn't be a problem building JTopas with it ;-)
The Jakarta Ant tool uses a build file, written in an XML dialect, as an equivalent to the classical Makefile.
The default name is build.xml and such a file exists for JTopas (look here).