Although the precedent is convenient for some projects that involve word processing, the following project is immediately useful because it performs a style check to ensure that our capitalization conforms to the "de facto" Java style standard. It opens each. java file in the current directory and extracts all the class names and identifiers. If you find a situation that does not conform to the Java style, report to us.
In order for this program to work correctly, you first have to build a class name as a "warehouse" that holds all the class names in the standard Java library. To do this, traverse all the source subdirectories for the standard Java library and run Classscanner in each subdirectory. As for parameters, the name of the warehouse file (with the same path and first name each time) and the command-line switch-A is provided, indicating that the class name should be added to the warehouse file.
to check your code with a program, you need to run it and pass it the path and name of the warehouse file that you want to use. It examines all the classes and identifiers in the current directory and tells us which ones do not follow the typical Java capitalization write specification.
Be aware that this program is not perfect. Sometimes, it may report itself to a problem. But when we examine the code carefully, we find that there is nothing to change. Although this is a bit annoying, it's much better than checking all the bugs in the code yourself.
The source code is listed below, followed by a detailed explanation:
: Classscanner.java//Scans All files in directory for classes//and identifiers, to check capitalization.
Assumes properly compiling code listings.
doesn ' t do everything right, but is a very//useful aid.
Import java.io.*;
Import java.util.*; Class Multistringmap extends Hashtable {public void Add (string key, String value) {if (!containskey (key)) put
(Key, New Vector ());
((Vector) get (key)). AddElement (value); Public Vector Getvector (String key) {if (!containskey (key)) {System.err.println ("Error:can ' t find
Key: "+ key";
System.exit (1);
Return (Vector) get (key);
public void Printvalues (PrintStream p) {Enumeration k = keys ();
while (K.hasmoreelements ()) {String Onekey = (string) k.nextelement ();
Vector val = getvector (Onekey);
for (int i = 0; i < val.size (); i++) P.println ((String) Val.elementat (i));
}} public class Classscanner {private File path; Private String[] filelist;
Private Properties Classes = new properties ();
Private Multistringmap Classmap = new Multistringmap (), Identmap = new Multistringmap ();
Private Streamtokenizer in;
Public Classscanner () {path = new File (".");
FileList = path.list (New Javafilter ());
for (int i = 0; i < filelist.length i++) {System.out.println (filelist[i]);
Scanlisting (Filelist[i]);
} void Scanlisting (String fname) {try {in = new Streamtokenizer (New BufferedReader (
New FileReader (fname)));
Doesn ' t seem to work://In.slashstarcomments (TRUE);
In.slashslashcomments (TRUE);
In.ordinarychar ('/');
In.ordinarychar ('. ');
In.wordchars (' _ ', ' _ ');
In.eolissignificant (TRUE);
while (In.nexttoken ()!= streamtokenizer.tt_eof) {if (In.ttype = = ") eatcomments (); else if (In.ttype = = Streamtokenizer.tt_word) {if in.svAl.equals ("class") | |
In.sval.equals ("interface")) {//Get class Name:while (In.nexttoken ()!=
streamtokenizer.tt_eof && in.ttype!= Streamtokenizer.tt_word)
;
Classes.put (In.sval, in.sval);
Classmap.add (fname, in.sval);
} if (In.sval.equals ("import") | |
In.sval.equals ("package")) Discardline ();
else//It ' s an identifier or keyword Identmap.add (fname, in.sval);
A catch (IOException e) {e.printstacktrace ()); } void Discardline () {try {while in.nexttoken ()!= streamtokenizer.tt_eof & & In.ttype!= Streamtokenizer.tt_eol);
Throw away tokens to end of line} catch (IOException e) {e.printstacktrace (); }//Streamtokenizer ' s comment removal sEemed//To be broken.
This extracts them:void eatcomments () {try {if (In.nexttoken ()!= streamtokenizer.tt_eof) {
if (In.ttype = = '/') discardline ();
else if (in.ttype!= ' * ') in.pushback ();
else while (true) {if (In.nexttoken () = = streamtokenizer.tt_eof) break;
if (In.ttype = = ' * ') if (In.nexttoken ()!= streamtokenizer.tt_eof
&& In.ttype = = '/') break;
A catch (IOException e) {e.printstacktrace ());
} public string[] Classnames () {string[] result = new string[classes.size ()];
Enumeration E = Classes.keys ();
int i = 0;
while (E.hasmoreelements ()) result[i++] = (String) e.nextelement ();
return result;
public void Checkclassnames () {Enumeration files = Classmap.keys (); while (Files.hasmoreelements ()) {String File = (String) files.nextelement ();
Vector cls = classmap.getvector (file);
for (int i = 0; i < cls.size (); i++) {String className = (string) cls.elementat (i); if (Character.islowercase (Classname.charat (0))) System.out.println ("Class Capitalizati
On error, File: "+ file +", class: "+ ClassName";
}} public void Checkidentnames () {Enumeration files = Identmap.keys ();
Vector reportset = new vector ();
while (Files.hasmoreelements ()) {String file = (String) files.nextelement ();
Vector ids = identmap.getvector (file);
for (int i = 0; i < ids.size (); i++) {String id = (string) ids.elementat (i);
if (!classes.contains (ID)) {//Ignore identifiers of length 3 or//longer that are all uppercase (probably static final values): if (Id.length () >= 3 && id.equALS (Id.touppercase ()) continue; Check to Upper:if (character.isuppercase (Id.charat (0))) {if (Reportset.indexo
F (file + id) = = 1) {//not reported yet reportset.addelement (file + ID);
System.out.println ("Ident capitalization error in:" + file + ", Ident:" + ID); }}}} static final String usage = "Usage: \ n" + "Classscanner classname s-a\n "+" \tadds all of the class names in this \ n "+" \tdirectory to the repository file \ n "+" \tcalled "class Names ' \ n "+" Classscanner classnames\n "+" \tchecks all the Java files in this \ n "+" \tdirectory for Capitali
zation errors, \ n "+" \tusing the repository file ' Classnames ';
private static void Usage () {System.err.println (usage);
System.exit (1);
public static void Main (string[] args) { if (Args.length < 1 | | | args.length > 2) usage ();
Classscanner C = new Classscanner ();
File old = new file (Args[0]); if (old.exists ()) {try {//try to open a existing//properties File:inputstream oldlist
= new Bufferedinputstream (new FileInputStream (old));
C.classes.load (oldlist);
Oldlist.close ();
catch (IOException e) {System.err.println ("could not open" + old + "for reading");
System.exit (1);
} if (Args.length = 1) {c.checkclassnames ();
C.checkidentnames (); //Write The class names to a repository:if (args.length = = 2) {if (!args[1].equals ("a")) usage ()
;
try {bufferedoutputstream out = new Bufferedoutputstream (new FileOutputStream (args[0));
C.classes.save (out, "classes found by Classscanner.java");
Out.close (); catch (IoexcePtion e) {System.err.println ("could not write" + args[0]);
System.exit (1); Class Javafilter implements FilenameFilter {public boolean accept (File dir, String name) {//Strip
Path information:string f = new File (name). GetName ();
Return F.trim (). EndsWith (". Java"); }
} ///:~
The Multistringmap class is a special tool that allows us to correspond (map) a set of strings to each key item. As in the previous case, a hash table (Hashtable) was used, but this time the inheritance was set. The hash table treats the key as a single string that is mapped to a vector value. The Add () method is very simple and is responsible for checking whether a key exists in the hash table. If it does not exist, place one in it. The Getvector () method produces a vector for a particular key, and printvalues () prints all the values in vector, which is useful for debugging the program.
To simplify the program, the class names from the standard Java library are all placed in a Properties object (from the standard Java library). Remember that the properties object is actually a hash table that holds only the string object for key and value items. However, just one method call, we can save it to disk or recover from disk. In fact, we only need a list of names, so the same object is used for both the key and the value.
For files in a particular directory, we used two multistringmap:classmap and identmap to find the appropriate class and identifier. In addition, when the program starts, it loads the standard class name warehouse into the properties object named classes. Once a new class name is found in the local directory, it is added to classes and Classmap. In this way, Classmap can be used to traverse all the classes in the local directory, and can be classes to check whether the current tag is a class name (it marks the beginning of the object or method definition, so collect the next token-until a semicolon is encountered – and place them in the Identmap).
The default builder for Classscanner creates a list of file names (the Javafilter implementation form of FilenameFilter, see chap. 10th). Scanlisting () is then invoked for each file name.
Inside Scanlisting (), open source code files are typed and converted to a streamtokenizer. According to the Java Help documentation, the intent to pass true to Slashstartcomments () and slashslashcomments () should be to strip those annotation content, but there seems to be some problem (almost invalid in Java 1.0). So instead, those lines are labeled as comments, and another method is used to extract the annotations. To achieve this, '/' must be captured as a raw character, rather than let streamtokeinzer treat it as part of a comment. The Ordinarychar () method is used to instruct the Streamtokenizer to take the correct action. The same applies to the point number ('. ') Because we want the method call to separate the identifiers. But for underscores, it is initially treated as a single character by Streamtokenizer, but it should be left as part of the identifier because it is used in the static final value as tt_eof, and so on. Of course, this is only true for the current special procedure. The Wordchars () method needs to take a series of characters we want to add, leaving them in the same notation as a word. Finally, when parsing a single-line comment or discarding a row, we need to know when a change action occurs. So by calling Eollssignificant (true), the newline character (EOL) is displayed instead of being absorbed by Streamtokenizer.
The remainder of the scanlisting () will be read into and checked for signs until the end of the file. Once Nexttoken () returns a final static value of--streamtokenizer.tt_eof, it indicates that it has arrived at the end of the file.
If the notation is a '/', which means it may be a comment, call eatcomments () to handle the situation. The only other thing we're interested in here is whether it's a word, and of course there may be other special cases.
If the word is Class (class) or interface (interface), then the following notation should represent a class or interface name and place it in classes and Classmap. If the word is import or package, then we have little interest in the rest of the line. Everything else is definitely an identifier (which we're interested in), or a keyword (not interested in it, but they're definitely in lowercase, so you don't have to check them selectmen). They will be added to the identmap.
The Discardline () method is a simple tool for finding the end of a line. Note that each time you get a new token, you must check the end of the line.
The Eatcomments () method is invoked whenever a forward slash is encountered in the primary parsing loop. However, this does not mean that a comment must have been encountered, so it is necessary to extract the next token, check if it is a forward slash (then the line is discarded), or an asterisk. But if both are not, it means you have to send back the token you just took out in the main parsing loop! Fortunately, the pushback () method allows us to "press back" the current notation to enter the data stream. So when the main parsing loop calls Nexttoken (), it gets exactly what it has just sent back.
For convenience, the Classnames () method produces an array that contains all the names in the Classes collection. This method is not used in a program, but is useful for debugging code.
The next two methods are the actual places to check. In Checkclassnames (), the class name is extracted from the Classmap (remember, Classmap contains only the names within the directory, they are organized by file name, so the file name may be printed with the wrong class name). To do this, you need to take out each associated vector and iterate through it to check whether the first character is lowercase. If it is lowercase, print out the appropriate error message.
In Checkidentnames (), we take a similar approach: each identifier name is extracted from the identmap. If the name is not in the classes list, it is considered an identifier or keyword. A special case is checked: If the identifier has a length equal to 3 or longer and all characters are uppercase, this identifier is ignored because it may be a static final value, such as tt_eof. Of course, this is not a perfect algorithm, but it assumes that we will eventually notice that any uppercase identifiers are not appropriate.
This method does not report each identifier that starts with an uppercase character, but rather tracks those that have been reported in a vector named Reportset (). It treats vectors as a "set" and tells us if an item is already in that collection. The project is generated by connecting the file name and identifier. If the element is not in the collection, add it, and then produce a report.
The remainder of the list of programs is composed of main (), which controls command-line arguments and determines whether we are prepared to build a "warehouse" of a series of class names on the basis of a standard Java library, or to check the correctness of the code that has been written. In either case, a Classscanner object is created.
Whether you are ready to build a "warehouse" or are ready to use a ready-made one, you must try to open an existing warehouse. By creating a file object and testing whether it exists, you can decide whether to open it and load classes This properties list in Classscanner (using Load ()). Classes from the warehouse are appended to the class found by the Classscanner builder, not overwritten. If you provide only one command-line argument, it means you want to check the class name and identifier name one at a time. However, if you provide two parameters (the second is "-a"), you indicate that you want to form a class name warehouse. In this case, you need to open an output file and use the Properties.save () method to write the list to a file and provide file header information with a string.