1. Wyszukiwanie
plików
Należało zastosować rekurencję. Wygodniejsze jest przy tym
listFiles(...) niż list(..). Do wyboru plików - FileFilter. Co to są pliki html
- tak naprawdę nie jest to jednoznaczne. Wydaje się wystarczające zastosowanie
zestawu rozszerzeń, zadawanego przez użytkownika. Przy czym należy uwzględnić,
że wielkość liter nie ma znaczenia.
2. Komunikacja między wątkami
Najprostsza
wydaje się komunikacja przez potok. Przy buforowaniu trzeba dobrać rozmiar
bufora tak, by wątki działały "wizualnie" równolegle (przy dużym, niewymiatanym
buforze, wątki mogą działać "napzremiennie" - np. wyszukiwacz znajduje najpierw
200 plików, po czym konwerter je przetwarza, po czym wyszukiwacz znowu szuka
itd.)
3. Test
Dostarczyłem klasy testowej, która w GUI
opartym na AWT pokazuje postępy szukania i przetwarzania. To nie jest oczywiście
prawdziwe GUI dla takiego zadania, ale przydaje się przy
testowaniiu.
Poniżej fragmenty kodu.
// Klasa dziedziczona przez inne klasy wątków // Daje jednolity sposób kończenia pracy wątku. // Proszę zwrócić uwagę na synchronizowanie metod class StopableThread extends Thread { private boolean stopped = false; public synchronized boolean isStopped() { return stopped; } public synchronized void finish() { stopped = true; } }
import java.util.*; import java.io.*; class FileSearcher extends StopableThread { private String[] ext; // tablica rozszerzeń plików private File initDir; // katalog początkowy private BufferedWriter out; // buforowane wyjście private int count; // liczba odnalezionych plików Test gui; // Gui do pokazywania postępów public FileSearcher(String beg, String[] exts, Writer outPipe, Test gui) { ext = new String[exts.length]; for (int i=0; i< exts.length; i++) ext[i] = exts[i]; initDir = new File(beg); out = new BufferedWriter(outPipe, 256); this.gui = gui; } public void run() { try { srchFiles(initDir); // szukamy plików out.write("***FINISH"); // gdy szukanie skończone - zapis FINISH do potoku out.newLine(); out.close(); } catch(IOException exc) { String msg = exc.getMessage(); // Wymienione wyjątki oznaczają wcześniejsze zakończenie // konwertera i powinny spowodowac łagodne zkończenie wyszukiwania if (!msg.equals("Read end dead") && !msg.equals("Pipe broken")) { exc.printStackTrace(); System.exit(1); } } System.out.println("Koniec FileSrch"); } // Metoda szukania plików - rekurencja! private void srchFiles(File srchDir) throws IOException { if (isStopped()) return; File[] files = srchDir.listFiles(new FileFilter() { // akceptujemy katalogi i pliki o rozszerzeniach z tablicy public boolean accept(File file) { if (file.isDirectory()) return true; String fname = file.toString(); int dot = fname.lastIndexOf("."); String extension = fname.substring(dot+1).toLowerCase(); for (int i=0; i<ext.length; i++) { if (extension.equals(ext[i])) return true; } return false; } }); if (files != null) { // null może być wtedy, gdy mamy wadliwy katalog inicj. // lub gdy nastąpił błąd I/) // np. sięgamy do ukrytego katalogu for (int i=0; i < files.length && !isStopped(); i++) { // Jeśli katalog - przeszukujemy go if (files[i].isDirectory()) srchFiles(files[i]); else { // plik HTML gui.showFound("Found: " + ++count); // info do GUI out.write(files[i].toString()); // zapis nazwy do potoku out.newLine(); yield(); } } } } }
import java.util.regex.*; import java.io.*; import java.nio.*; import java.nio.channels.*; import java.nio.charset.*; class HtmlConverter extends StopableThread { private Test gui; // gui pokazujące postępy private BufferedReader inpipe; // potok wejściowy (buforowany) private int count = 0; // liczba przetworzonych plików private String destDir; // katalog docelowy do zapisu skonwertowanych // Konstruktor public HtmlConverter(Test t, Reader pipeIn, String dir) { gui = t; inpipe = new BufferedReader(pipeIn,256); destDir = dir; } // Kod wątku public void run() { String line = ""; while (!isStopped()) { try { line = inpipe.readLine(); if (line == null) break; if (line.equals("***FINISH")) break; if (convert(line)) gui.show("Converted: " + line); gui.showProcessed("Processed: " + ++count); } catch(IOException exc) { exc.printStackTrace(); break; } } System.out.println("Koniec convert"); } // ---- Zmienne ważne dla metody convert (podanej dalej) // Separator plików (/ lub \) na danej platformie private String fileSep = System.getProperty("file.separator"); // Wzorce będą kompilowane raz przy tworzeniu obiektu // wzorzec do podziału pliku na nagłówek i resztę private Pattern splitPatt = Pattern.compile("</head>", Pattern.CASE_INSENSITIVE); // wzorzec do odnalezienia charset win 1250 w nagłówku // i podziału nagłowka na trzy częsci, by podmienić "charset=..." private Pattern winPatt = Pattern.compile( "(.+?)(charset[ ]*?=[ ]*?windows[ ]*?-[ ]*?1250)(.*?)", Pattern.DOTALL | Pattern.CASE_INSENSITIVE); // Strony kodowe private Charset inCharset = Charset.forName("Cp1250"); private Charset outCh = Charset.forName("ISO-8859-2"); // Metoda konwertująca plik o podanej nazwie private boolean convert(String fname) throws IOException { // Wczytanie pliku FileInputStream instream = new FileInputStream(fname); FileChannel in = instream.getChannel(); ByteBuffer buf = ByteBuffer.allocate((int) in.size()); in.read(buf); in.close(); instream.close(); // Dekodowanie buf.flip(); CharBuffer cb = inCharset.decode(buf); // Wzorzec </head> będzie zastosowany raz do wyodrębnienia // nagłówka String[] part = splitPatt.split(cb,2); // part[0] - nagłówek if (part.length != 2) return false; // brak tagu </head> String body = part[1]; // reszta pliku HTML // czy w nagłówku znajdziemy określenie strony kodowej win-1250 Matcher m = winPatt.matcher(part[0]); boolean result = m.matches(); if (result) { // jeżeli znaleziony // Odtworzenie nagłówka // z zamianą "charset =" // korzystamy z grup zapamiętanych przy "pasowaniu" wyrażenia regularnego StringBuffer header = new StringBuffer(m.group(1)); header.append("charset=ISO-8859-2"); header.append(m.group(3)); // Utworzenie nowego obiektu plikowego (będzie w katalogu destDir) // z nazwą, która ew. będzie zawierać numerację dla odróżnienia // istniejących plików int p = fname.lastIndexOf(fileSep); int dot = fname.lastIndexOf("."); String ext = fname.substring(dot); StringBuffer outFn = new StringBuffer(destDir); outFn.append("/"); outFn.append(fname.substring(p+1, dot)); File outf = new File(outFn+ext); int nr = 1; while (outf.exists()) { outf = new File(outFn + "_" + (nr++) + ext); } // Enkodowanie ByteBuffer[] outBuff = { outCh.encode(CharBuffer.wrap(header)), outCh.encode(CharBuffer.wrap(body)) }; // Zapis - gathering write! FileChannel out = new FileOutputStream(outf).getChannel(); out.write(outBuff); out.force(false); out.close(); } return result; } }
import java.awt.*; import java.awt.event.*; import java.io.*; class Test extends Frame implements ActionListener { // Parametry do zmiany // --- rozszerzenia plików branych pod uwagę private static final String[] EXTS = { "htm", "html", "shtml" }; // --- katalog w którym zaczyna się rekursywne wyszukiwanie private static final String INIT_DIR = "G:/"; // --- katalog docelowy (do zapisu skonwertowanych plików) private static final String DEST_DIR = "G:/temp"; private TextArea ta = new TextArea(25, 30); // obszar komunikatów private FileSearcher fs; // wyszukiwacz private HtmlConverter hc; // konwerter private TextField found = new TextField(12); // pola informacyjne private TextField proc = new TextField(12); public Test() { PipedWriter pipeOut = new PipedWriter(); PipedReader pipeIn = null; try { pipeIn = new PipedReader(pipeOut); } catch(IOException exc) { exc.printStackTrace(); System.exit(1); } // Wyszukiwacz: // - startuje od ktalogu INIT_DIR // - podaje pliki o rozszerzeniach z tablicy EXTS // - do potoku pipeOut // - pokazując postępy w tym GUI (this) fs = new FileSearcher(INIT_DIR, EXTS, pipeOut, this); // Konwerter // - pokaże postępy tu (this) // - odczytuje nazwy plików do konwersji z potoku pipeIn // zapisze nowe wersje plikow w katalogu DEST_DIR hc = new HtmlConverter(this, pipeIn, DEST_DIR); Button b = new Button("Stop"); // tym przyciskiem można zakończyć wątki b.addActionListener(this); add(ta, "Center"); Panel p = new Panel(new FlowLayout()); p.add(b); p.add(found); p.add(proc); add(p, "South"); // Zamknięcie okna - zakończenie aplikacji addWindowListener( new WindowAdapter() { public void windowClosing(WindowEvent e) { dispose(); System.exit(0); } }); // Start wątków fs.start(); hc.start(); // Okno pack(); show(); } // Metoda, która może być używana w ątkach // wypisuje info w obsarze komunikatów public synchronized void show(String s) { ta.append(s+ '\n'); } // Metodw wołana z wątku wyszukiwania // pokazuje postępy w polu informacyjnym public synchronized void showFound(String s) { found.setText(s); } // Metodw wołana z wątku konwersji // pokazuje postępy w polu informacyjnym public synchronized void showProcessed(String s) { proc.setText(s); } // Obsługa wyboru przycisku STOP // kończy oba wątki public void actionPerformed(ActionEvent e) { fs.finish(); hc.finish(); } public static void main(String args[]) { new Test(); } }