Книга е още в много ранна фаза на написване



страница66/73
Дата25.07.2016
Размер13.53 Mb.
#6732
1   ...   62   63   64   65   66   67   68   69   ...   73

Проверка на стила


В тази секция ще погледнем по-завършен пример за използване на Java IO. Този про­ект е пряко полезен понеже изпълнява проверка дали вашите файлове от­го­ва­рят на Java стила на писане както може да се намери на www.JavaSoft.com. Отва­ря всеки .java файл в текущата директория и извлича ивената на класове и иден­тификаторите, после показва ако нещо не отговаря на Java стила.

За да работи програмата коректно, трябва първо да построите хранилище за имена на класове, което да съдържа всички имена от Java библиотеката. Това се пра­ви чрез преминаване на всичкия сорс във всичките поддиректории на Java би­блиотеката и пускане на ClassScanner за всяка поддиректория. Давайки като ар­гументи имената на файловете-хранилища (с един и същ път и име всеки път) и -a опция на командния ред за отбелязване че имената на класове ще се до­ба­вят в хранилището.

За да се използва програмата за проверка на код, дайте име на файл и хранилище за използване и я пуснете. Тя ще провери всички имена на файлове и идентификатори в текущата директория и ще ви каже кои не следват ти­пич­ния за Java стил на капитализация.

Трябва да знаете, че програмата не е перфектна; понякога ще показва че има проблеми, но при преглед ще установите, че няма такива. Това е малко ядо­сва­що, но е много по-добре като цяло, отколкото да се намерят всички тези имена не­посредствено от кода.

Обяснението непосредствено следва листинга:

//: c10: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 HashMap {

public void add(String key, String value) {

if(!containsKey(key))

put(key, new ArrayList());

((ArrayList)get(key)).add(value);

}

public ArrayList getArrayList(String key) {



if(!containsKey(key)) {

System.err.println(

"ERROR: can't find key: " + key);

System.exit(1);

}

return (ArrayList)get(key);



}

public void printValues(PrintStream p) {

Iterator k = keySet().iterator();

while(k.hasNext()) {

String oneKey = (String)k.next();

ArrayList val = getArrayList(oneKey);

for(int i = 0; i < val.size(); i++)

p.println((String)val.get(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);

}

}

} 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;


}

}

} catch(IOException e) {



e.printStackTrace();

}

}



public String[] classNames() {

String[] result = new String[classes.size()];

Iterator e = classes.keySet().iterator();

int i = 0;

while(e.hasNext())

result[i++] = (String)e.next();

return result;

}

public void checkClassNames() {



Iterator files = classMap.keySet().iterator();

while(files.hasNext()) {

String file = (String)files.next();

ArrayList cls = classMap.getArrayList(file);

for(int i = 0; i < cls.size(); i++) {

String className =

(String)cls.get(i);

if(Character.isLowerCase(

className.charAt(0)))

System.out.println(

"class capitalization error, file: "

+ file + ", class: "

+ className);

}

}



}

public void checkIdentNames() {

Iterator files = identMap.keySet().iterator();

ArrayList reportSet = new ArrayList();

while(files.hasNext()) {

String file = (String)files.next();

ArrayList ids = identMap.getArrayList(file);

for(int i = 0; i < ids.size(); i++) {

String id =

(String)ids.get(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 see if first char is upper:

if(Character.isUpperCase(id.charAt(0))){

if(reportSet.indexOf(file + id)

== -1){ // Not reported yet

reportSet.add(file + id);

System.out.println(

"Ident capitalization error in:"

+ file + ", ident: " + id);

}

}

}



}

}

}



static final String usage =

"Usage: \n" +

"ClassScanner classnames -a\n" +

"\tAdds all the class names in this \n" +

"\tdirectory to the repository file \n" +

"\tcalled 'classnames'\n" +

"ClassScanner classnames\n" +

"\tChecks all the java files in this \n" +

"\tdirectory for capitalization 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 an 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");

}

} ///:~



Класът MultiStringMap'>MultiStringMap е инструмент който позволява да се проектира група стрин­гове върху отделен ключ. Както в предишния пример, използва се HashMap (този път с наследяване) с ключа като единствен стринг който се проек­тира в ArrayList стойност. Методът add( ) просто проверява дали вече има такъв ключ в HashMap, ако няма го слага там. Методът getArrayList( ) да­ва ArrayList за конкретен ключ, а printValues( ), който основно е полезен за тест­ване, извежда всички стойности ArrayList по ArrayList.

За да се опрости живота, имената от стандартните Java библиотеки са пъхнати в Properties обект (от стандартната Java библиотека). Помнете че Properties обек­тът е HashMap който съдържа само String обекти и за ключа, и за стой­ност­та. Обаче той може да бъде запазван на диск и възстановяван от там с едно из­викване на метод, така че е идеален за склад на имена. Фактически се нуж­даем само от списък с имена и HashMap не може да приеме null за ключ или стой­ност. Така че един и същ обек ще се използва и за стойностите, и за клю­чо­ве­те.

За класовете във файловете от конкретна директория се използват два, two MultiStringMapа: classMap и identMap. Също когато програмата тръгва тя то­ва­ри склада за имена в Properties обект наречен classes, а когато се намери но­во име на клас, то се добавя също към classes както и към classMap. По този на­чин classMap може да бъде използван за преминаване по всички класове в ло­калната директория, а classes може да бъде използван за проверка дали те­ку­щият токен е име на клас (което показва че започва дефиниция на обект или ме­тод, така че се грабват следващите токени – до точка и запетая – и се слагат в identMap).

Конструкторът по подразбиране на ClassScanner създава списък от файлови имена (чрез JavaFilter реализацията на FilenameFilter, както е описано в глава 10). После вика scanListing( ) за всяко класово име.

Вътре в scanListing( ) сорсовия файл е отворен и се превръща в Stream­Tokenizer. По документация чрез даване на true на slashStarComments( ) и slashSlashComments( ) се очаква да се махнат тези коментари, но това май не е точ­но така (не работи в Java 1.0). Вместо това тези редове се изкоментират и се из­вличат от друг метод. За да стане това ‘/’ трябва да бъде хванато като обик­но­вен знак вместо да се остави StreamTokenizer да го погълне като част от комен­тар, а ordinaryChar( ) методът казва на StreamTokenizer да направи това. То­ва също е вярно за точки (‘.’), понеже искаме извикванията на методи да са раз­паднати на отделни идентификатори. Обаче подчертаващото тире, което обик­новено се третира от StreamTokenizer като отделен знак, ще се остави като част от идентификатора, понеже се появява в такива static final стойности като TT_EOF и т.н., използвани в същата тази програма. Методът wordChars( ) взе­ма количество знаци които искате да се добавят към онези които се оставят въ­тре в обработвания токен като една дума. Накрая, когато обработваме ед­но­ре­дов коментар или пренебрегваме ред трябва да знаем къде е знакът за край на ред, та чрез викане на eolIsSignificant(true) eol ще се покаже наместо да бъде по­гълнат от StreamTokenizer.

Останалото от scanListing( ) чете и реагира на токените до края на файла, означаванс връщане от nextToken( ) на final static стойност StreamTokenizer.TT_EOF.

Ако токенът е / това потенциално е коментар, така че eatComments( ) се вика да се разправя с него. Единствената друга ситуация от която сме заинтере­со­ва­ни тук е когато е в дума, като има няколко специални случая.

Ако думата е class или interface тогава следващият токен представя име на клас или интерфейс и се слага в classes и classMap. Ако думата е import или package, не искаме останалата част от реда. Всичко друго трябва да е иденти­фи­катор (от който се интересуваме) или ключова дума (от които не се ин­те­ре­су­ваме, но в редки случаи са изцяло с малки букви, така че не е лошо да се вка­рат, за да не развалят работата). Всички се добавят в identMap.

Методът discardLine( ) е прост инструмент който следи за край на ред. За­бе­ле­же­те че всеки път когато имате нов токен трябва да проверите за край на файла.

Методът eatComments( ) се вика винаги когато в основния цикъл на преглеждане се срещне наклонена черта. Това обаче не значи непременно че е на­мерен коментар, така че трябва да се види следващия токен за да се види да­ли е наклонена черта (в който случай редът се пренебрегва) или звезда. Но ако не е от тези, това значи че чертата трябва да се вкара обратно в главния цикъл! За щастие методът pushBack( ) позволява да “се вкара обратно” те­ку­щия токен на входния поток така че когато главният цикъл вика nextToken( ) той ще го намери бутнат обратно.

За удобство методът classNames( ) дава масив от всичките имена в колекцията classes. Този метод не се използва в програмата но е полезен при тестване.

Следващите два метода са където фактически се прави проверката. В checkClassNames( ) имената на класове се вземат от classMap (който, помнете, съ­държа имената само от тази директория, организирани по файловото име та­ка, че то може да бъде изведено заедно с грешното име на клас). Това се прави чрез слагане на всеки асоцииран ArrayList и преглеждането му, за да се види да­ли първият знак е малка буква. Ако да, извежда се съответното съобщение за греш­ка.

В checkIdentNames( ) има подобен подход: всяко име на идентификатор се взе­ма от identMap. Ако името не е в списъка на classes, счита се че е иден­ти­фи­ка­тор или ключова дума. Проверява се един специален случай: ако дължината на иден­тификатора е 3 или повече и всичките знаци са главни букви, този иден­ти­фи­катор се игнорира понеже вероятно е static final стойност като например TT_EOF. Разбира се, това не е перфектен алгоритъм, но той предполага че ще за­бележите в края на краищата всички думи само от главни букви.

Вместо да докладва всеки идентификатор който започва с главна буква, този метод следи кои вече са били докладвани в ArrayList наречен reportSet( ). Това третира ArrayList като “множество” което ви казва дали елементът вече е в мно­жеството. Елементът се получава чрез конкатенация на файловото име и идентификатора. Ако елементът не е в множеството, добавя се и после се до­клад­ва.

Останалата част от листинга включва main( ), който е зает с обработката на аргу­ментите на командния ред и установяване дали искате да правите склад за име­на на класове от стандартна Java библиотека или да проверявате ва­лид­ност­та на написан от вас код. В двата случая се прави ClassScanner обект.

Дали правите склад или използвате такъв, трябва да отворите съществуващия склад. Чрез провене на File обект и проверка за съществуване може да решите да­ли да отворите файла и load( ) списъка Properties classes вътре в ClassScanner. (Класовете от склада се добавят към, а не подтискат класовете от кон­структора на ClassScanner.) Ако дадете един аргумент на командния ред то­ва значи че искате да направите проверка на имената на класове и иден­ти­фи­ка­тори, но ако дадете два аргумента (вторият “-a”) правите склад за имена на кла­сове. В този случай се отваря файл за изход и Properties.save( ) се използва за писане на списъка във файл, заедно със стринг, който съдържа заглавна ин­фор­мация за файла.





Сподели с приятели:
1   ...   62   63   64   65   66   67   68   69   ...   73




©obuch.info 2024
отнасят до администрацията

    Начална страница