Java tutorial
/* * To change this license header, choose License Headers in Project Properties. * To change this template file, choose Tools | Templates * and open the template in the editor. */ package com.mycompany.trafficimportfileconverter2; import com.sun.deploy.uitoolkit.impl.fx.HostServicesFactory; import com.sun.javafx.application.HostServicesDelegate; import java.io.BufferedReader; import java.io.File; import java.io.FileFilter; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.io.PrintWriter; import java.net.URL; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; import static java.nio.file.StandardWatchEventKinds.*; import java.nio.file.WatchEvent; import java.nio.file.WatchKey; import java.nio.file.WatchService; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatterBuilder; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.Optional; import java.util.ResourceBundle; import java.util.StringTokenizer; import java.util.function.Consumer; import java.util.logging.Level; import java.util.logging.Logger; import java.util.prefs.Preferences; import java.util.stream.Collectors; import javafx.application.Platform; import javafx.beans.property.ObjectProperty; import javafx.beans.property.Property; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.event.ActionEvent; import javafx.event.Event; import javafx.event.EventHandler; import javafx.fxml.FXML; import javafx.fxml.Initializable; import javafx.scene.control.Alert; import javafx.scene.control.Button; import javafx.scene.control.ButtonBar; import javafx.scene.control.ButtonBar.ButtonData; import javafx.scene.control.ButtonType; import javafx.scene.control.CheckBox; import javafx.scene.control.DatePicker; import javafx.scene.control.Label; import javafx.scene.control.TextArea; import javafx.scene.input.MouseEvent; import javafx.scene.input.TouchEvent; import javafx.scene.layout.AnchorPane; import javafx.scene.layout.BackgroundFill; import javafx.stage.DirectoryChooser; import javafx.stage.FileChooser; import javafx.stage.FileChooser.ExtensionFilter; import javafx.util.converter.LocalDateTimeStringConverter; import javax.swing.event.DocumentEvent; import org.apache.commons.io.FilenameUtils; /** * FXML Controller class * * @author Paul */ public class Main2Controller implements Initializable { @FXML private Label lblOutputLoc; @FXML private Label lblInputFile; @FXML private Label lblInfo; @FXML private Button btnSelect; @FXML private Button btnOutputLocation; private final ObjectProperty<File> outputDir = new SimpleObjectProperty<>(); private final ObjectProperty<File> inputFile = new SimpleObjectProperty<>(); @FXML private Button btnGo; private Preferences prefs; private final static String INPUT_DIR_LOC = "INPUT_DIR_LOC"; private final static String OUTPUT_DIR_LOC = "OUTPUT_DIR_LOC"; private final static String EXTENSION = "EXTENSION"; private boolean lineBreaksMatter = true; @FXML private TextArea txtAreaOutput; @FXML private DatePicker date1; @FXML private DatePicker date2; @FXML private DatePicker date3; @FXML private DatePicker date4; @FXML private DatePicker date5; @FXML private DatePicker date6; @FXML private DatePicker date7; @FXML private CheckBox chkDay1; @FXML private CheckBox chkDay2; @FXML private CheckBox chkDay3; @FXML private CheckBox chkDay4; @FXML private CheckBox chkDay5; @FXML private CheckBox chkDay6; @FXML private CheckBox chkDay7; @FXML private CheckBox chkAutoInc; public CheckBox[] dateChecks = new CheckBox[7]; public DatePicker[] datePickers = new DatePicker[7]; private Label[] dayLabels = new Label[7]; @FXML private CheckBox chkSelectAll; @FXML private Label lbl1; @FXML private Label lbl2; @FXML private Label lbl3; @FXML private Label lbl4; @FXML private Label lbl5; @FXML private Label lbl6; @FXML private Label lbl7; @FXML private Label lblSelectAll; private ArrayList<LocalDate> dates = new ArrayList<>(7); private ArrayList<String> data = new ArrayList<>(7); private Thread myThread; private static ArrayList<String> myfiles = new ArrayList<>(); private static final Object lock = new Object(); public static String helpurl = "https://github.com/KJHK-Technology/Traffic-Importing-File-Converter/tree/master"; public static void addToFiles(ArrayList<String> newstuff) { synchronized (lock) { myfiles.addAll(newstuff); } } public static void removeFromFiles(String fname) { synchronized (lock) { myfiles.remove(fname); } } public File getInputFile() { return inputFile.get(); } public void setInputFile(File value) { inputFile.set(value); } public ObjectProperty inputFileProperty() { return inputFile; } FileChooser filechooser; DirectoryChooser dirchooser; @FXML private AnchorPane BigPane; public File getOutputDir() { return outputDir.get(); } public void setOutputDir(File value) { outputDir.set(value); } public ObjectProperty outputDirProperty() { return outputDir; } /** * Initializes the controller class. */ @Override public void initialize(URL url, ResourceBundle rb) { prefs = Preferences.userNodeForPackage(this.getClass()); initDateArrays(); dirchooser = new DirectoryChooser(); outputDir.addListener(new ChangeListener<File>() { @Override public void changed(ObservableValue<? extends File> observable, File oldValue, File newValue) { lblOutputLoc.setText(newValue.getAbsolutePath()); prefs.put(OUTPUT_DIR_LOC, newValue.getAbsolutePath()); } }); setOutputDir(safeFileSet(new File(prefs.get(OUTPUT_DIR_LOC, "./")))); try { dirchooser.setInitialDirectory(getOutputDir()); } catch (Exception e) { System.out.println("Error setting init dir: " + e); e.printStackTrace(); } filechooser = new FileChooser(); try { filechooser.setInitialDirectory(new File(prefs.get(INPUT_DIR_LOC, "./"))); } catch (Exception e) { System.out.println("Error setting init directory of file chooser: " + e); e.printStackTrace(); } setExtension(prefs.get(EXTENSION, ".gen")); /* Save in preferences default getting file location every time the input file is reselected. */ inputFile.addListener(new ChangeListener<File>() { @Override public void changed(ObservableValue<? extends File> observable, File oldValue, File newValue) { lblInputFile.setText(newValue.getAbsolutePath()); prefs.put(INPUT_DIR_LOC, newValue.getParent()); reloadFileData(newValue); } }); txtAreaOutput.textProperty().addListener(new ChangeListener<Object>() { @Override public void changed(ObservableValue<?> observable, Object oldValue, Object newValue) { txtAreaOutput.setScrollTop(Double.MAX_VALUE); //this will scroll to the bottom //use Double.MIN_VALUE to scroll to the top } }); filechooser.getExtensionFilters().add(new ExtensionFilter("Tab Separated Values", "*.tsv", "*.TSV")); autoSearch(); log("Help document located:"); log(helpurl); } private void initDateArrays() { //populate arrays; datePickers[0] = date1; datePickers[1] = date2; datePickers[2] = date3; datePickers[3] = date4; datePickers[4] = date5; datePickers[5] = date6; datePickers[6] = date7; dateChecks[0] = chkDay1; dateChecks[1] = chkDay2; dateChecks[2] = chkDay3; dateChecks[3] = chkDay4; dateChecks[4] = chkDay5; dateChecks[5] = chkDay6; dateChecks[6] = chkDay7; dayLabels[0] = lbl1; dayLabels[1] = lbl2; dayLabels[2] = lbl3; dayLabels[3] = lbl4; dayLabels[4] = lbl5; dayLabels[5] = lbl6; dayLabels[6] = lbl7; //set property to icrement all others if selected. datePickers[0].setOnAction(new EventHandler() { public void handle(Event t) { System.out.println("I've been touched!!!"); if (chkAutoInc.isSelected() && datePickers[0].getValue() != null) { for (int i = 1; i < dateChecks.length; i++) { datePickers[i].setValue(datePickers[i - 1].getValue().plusDays(1)); } } } }); //if the checkbox is checked after setting the date, trigger event. chkAutoInc.selectedProperty().addListener(new ChangeListener<Boolean>() { @Override public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) { if (newValue) { datePickers[0].fireEvent(new ActionEvent()); datePickers[0].setValue(datePickers[0].getValue()); } } }); for (int i = 0; i < datePickers.length; i++) { final int x = i; datePickers[i].valueProperty().addListener(new ChangeListener<LocalDate>() { @Override public void changed(ObservableValue<? extends LocalDate> observable, LocalDate oldValue, LocalDate newValue) { dates.set(x, newValue); dayLabels[x].setText(newValue.getDayOfWeek().name()); datePickers[x].setStyle("-fx-background-color: red"); dayLabels[x].setStyle("-fx-background-color: red"); } }); } //set select all functionality chkSelectAll.selectedProperty().addListener(new ChangeListener<Boolean>() { @Override public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) { for (int i = 0; i < dateChecks.length; i++) { dateChecks[i].setSelected(newValue); } lblSelectAll.setText(newValue ? "Uncheck to: Unselect All" : "Check to: Select All"); } }); //default to include everything. chkSelectAll.setSelected(true); } @FXML private void onBtnSelect(ActionEvent event) { File sel = filechooser.showOpenDialog(BigPane.getScene().getWindow()); setInputFile(sel); } @FXML private void onBtnOutputLocation(ActionEvent event) { File selectedDirectory = dirchooser.showDialog(BigPane.getScene().getWindow()); setOutputDir(selectedDirectory); } private void reloadFileData(File newValue) { loadInInfo(newValue); //display it all. for (int i = 0; i < datePickers.length; i++) { try { datePickers[i].setValue(dates.get(i)); } catch (Exception e) { System.out.println("failed to set datePicker:" + i + " " + e); log("File appears invalid. Please select a different file and try again."); e.printStackTrace(); return; } } } private void loadInInfo(File file) { BufferedReader TSVFile = null; //get rid of any dates and data already there. dates.clear(); data.clear(); try { TSVFile = new BufferedReader(new FileReader(file.getAbsoluteFile())); String dataRow = TSVFile.readLine(); while (dataRow != null) { String[] row = dataRow.split("\t"); System.out.println(row[1] + "Will be:\n" + row[2]); dates.add(LocalDate.parse("20" + row[0])); data.add(getData(row)); dataRow = TSVFile.readLine(); } TSVFile.close(); log("Days in file: " + dates.size()); if (dates.size() > datePickers.length) { log("WARNING: More days found in file than can be displayed!\n" + "Only the first " + datePickers.length + " " + "can be displayed and configurable.\nThe last " + (dates.size() - datePickers.length) + " " + "dates in your file will be\nimported with" + " the dates untouched!\n\n"); log("Unconfigurable days that will be imported:"); for (int i = datePickers.length; i < dates.size(); i++) { log(dates.get(i).format(DateTimeFormatter.ISO_DATE)); } } System.out.println(); } catch (FileNotFoundException ex) { Logger.getLogger(Main2Controller.class.getName()).log(Level.SEVERE, null, ex); } catch (IOException ex) { Logger.getLogger(Main2Controller.class.getName()).log(Level.SEVERE, null, ex); } catch (ArrayIndexOutOfBoundsException e) { Logger.getLogger(Main2Controller.class.getName()).log(Level.SEVERE, null, e); String str = "It appears like the file contents were invalid.\n" + "Please try again or a different file."; System.out.println(str); log(str); } finally { try { TSVFile.close(); } catch (IOException ex) { Logger.getLogger(Main2Controller.class.getName()).log(Level.SEVERE, null, ex); } } } @FXML private void onBtnGo(ActionEvent event) { if (null == outputDir.getValue()) { Alert alert = new Alert(Alert.AlertType.ERROR); alert.setContentText("Select Output Directory!"); alert.showAndWait(); return; } if (null == inputFile.getValue()) { Alert alert = new Alert(Alert.AlertType.ERROR); alert.setContentText("Select Input File!"); alert.showAndWait(); return; } //threadedHandledFileConversion(getInputFile()); handleFileWriting(); if (null == myThread || !myThread.isAlive()) { myThread = (new Thread(() -> { try { watchForConsumption(); } catch (IOException ex) { Logger.getLogger(Main2Controller.class.getName()).log(Level.SEVERE, null, ex); } })); myThread.setDaemon(true); myThread.start(); } } private void autoSearch() { new Thread(() -> { File[] files = filechooser.getInitialDirectory().listFiles(new FileFilter() { @Override public boolean accept(File pathname) { String x = FilenameUtils.getExtension(pathname.getAbsolutePath()); return x.compareToIgnoreCase("tsv") == 0; } }); File mostRecent = Arrays.stream(files).max((x, y) -> Long.compare(x.lastModified(), y.lastModified())) .orElse(null); if (mostRecent == null) { return; } Platform.runLater(() -> { Alert alert = new Alert(Alert.AlertType.CONFIRMATION); alert.setContentText("We found the following TSV file in your default location:\n\"" + mostRecent.getName() + "\"\n" + "Modified: " + new Date(mostRecent.lastModified()) + "\n\n" + "Would you like load this file?"); Optional<ButtonType> answer = alert.showAndWait(); if (answer.isPresent() && answer.get().getButtonData().equals(ButtonData.OK_DONE)) { System.out.println(answer.get()); setInputFile(mostRecent); //setting the input file is enough to trigger the load! // loadInInfo(mostRecent); //not safe, just load the file in for you. // onBtnGo(null); } else { } }); }).start(); } // private void threadedHandledFileConversion(File file) { // new Thread(() -> hanldeFileConversion(file)).start(); // } /** * * @param file The location of the file downloaded from the google sheet. * @return The dates the scheduled traffic. */ // private ArrayList<LocalDate> parseDates(File file) { // BufferedReader TSVFile = null; // ArrayList<LocalDate> answer = new ArrayList<>(7); // try { // TSVFile = new BufferedReader(new FileReader(file.getAbsoluteFile())); // String dataRow = TSVFile.readLine(); // while (dataRow != null) { // String[] row = dataRow.split("\t"); // System.out.println("I think the day is " + row[0]); // answer.add(LocalDate.parse("20" + row[0])); // dataRow = TSVFile.readLine(); // } // TSVFile.close(); //// return answer; // } catch (FileNotFoundException ex) { // Logger.getLogger(Main2Controller.class.getName()).log(Level.SEVERE, null, ex); // } catch (IOException ex) { // Logger.getLogger(Main2Controller.class.getName()).log(Level.SEVERE, null, ex); // } finally { // try { // TSVFile.close(); // } catch (IOException ex) { // Logger.getLogger(Main2Controller.class.getName()).log(Level.SEVERE, null, ex); // } // // } // return answer; // } private void handleFileWriting() { //ArrayList<String> filenames = new ArrayList<>(dates.size()); dates.parallelStream().forEach(x -> { int index = dates.indexOf(x); if (!dateChecks[index].isSelected()) { return; } //Am i already imported? File alreadyThereFile = isPresentForDate(x); if (null != alreadyThereFile) { UI(() -> { Alert alert = new Alert(Alert.AlertType.CONFIRMATION); alert.setTitle("Re-import?"); alert.setContentText("Hey there, just thought I'd let you know " + "that you've already imported a file for this day: " + x.format(DateTimeFormatter.ISO_DATE) + ". Because I found this file: " + alreadyThereFile.getName() + "\nAre you sure you want to do this?"); Optional<ButtonType> answer = alert.showAndWait(); if (answer.isPresent() && answer.get().getButtonData().equals(ButtonData.OK_DONE)) { writeFile(toWideOrbitTitle(x), data.get(index)); UI(() -> { datePickers[index].setStyle("-fx-background-color: yellow"); dayLabels[index].setStyle("-fx-background-color: yellow"); }); synchronized (lock) { myfiles.add(toWideOrbitTitle(x)); System.out.println("Adding: " + toWideOrbitTitle(x)); System.out.println(myfiles.toString()); } } else { log("Skipping: " + alreadyThereFile.getName()); } }); } else { writeFile(toWideOrbitTitle(x), data.get(index)); UI(() -> { datePickers[index].setStyle("-fx-background-color: yellow"); dayLabels[index].setStyle("-fx-background-color: yellow"); }); synchronized (lock) { myfiles.add(toWideOrbitTitle(x)); } } }); } // private void hanldeFileConversion(File file) { // BufferedReader TSVFile = null; // try { // TSVFile = new BufferedReader(new FileReader(file.getAbsoluteFile())); // String dataRow = TSVFile.readLine(); // while (dataRow != null) { // String[] row = dataRow.split("\t"); // System.out.println(row[1] + "Will be:\n" + row[2]); // handleImport(row); // dataRow = TSVFile.readLine(); // } // TSVFile.close(); // // System.out.println(); // } catch (FileNotFoundException ex) { // Logger.getLogger(Main2Controller.class.getName()).log(Level.SEVERE, null, ex); // } catch (IOException ex) { // Logger.getLogger(Main2Controller.class.getName()).log(Level.SEVERE, null, ex); // } finally { // try { // TSVFile.close(); // } catch (IOException ex) { // Logger.getLogger(Main2Controller.class.getName()).log(Level.SEVERE, null, ex); // } // } // } // private final StringProperty extension = new SimpleStringProperty(); public String getExtension() { return extension.get(); } public void setExtension(String value) { extension.set(value); } public StringProperty extensionProperty() { return extension; } // private TrafficDay parseRow(String[] row) { // TrafficDay t = new TrafficDay(); // t.setDay(LocalDate.now()); // String dateStr = ""; // for (String string : row) { // if (string.matches("\\d\\d-\\d\\d-\\d\\d")) { // dateStr = string; // break; // } // } // //yy-mm-dd. // String[] parts = dateStr.split("-"); // t.getDay().withYear(2000 + Integer.parseInt(parts[0])); // t.getDay().withDayOfMonth(Integer.parseInt(parts[1])); // t.getDay().withMonth(Integer.parseInt(parts[2])); // t.setContent(row[row.length - 1]); // return t; // } // private void handleImport(TrafficDay td){ // chkAlreadyImported(row[0], (fileName) // -> { // //on yes, let's import // try { // PrintWriter writer = new PrintWriter(fileName, "UTF-8"); // if (lineBreaksMatter) { // writer.write(mkLineBreaks(row[2])); // } else { // writer.write(row[2]); // // } // writer.close(); // String newFileName = getOutputDir().getAbsolutePath() + java.io.File.separator + fileName + getExtension(); // if (rename(fileName, newFileName)) { // log("Success writing: " + newFileName); // } else { // log("failed to write:" + newFileName); // }; // } catch (IOException e) { // e.printStackTrace(); // // do something // } // }); // // } private String writeFile(String fileName, String data) { try { PrintWriter writer = new PrintWriter(fileName, "UTF-8"); writer.write(data); writer.close(); String newFileName = getOutputDir().getAbsolutePath() + java.io.File.separator + fileName + getExtension(); if (rename(fileName, newFileName)) { log("Success writing: " + newFileName); } else { log("failed to write:" + newFileName); } ; return newFileName; } catch (IOException e) { e.printStackTrace(); // do something } return null; } // private void handleImport(String[] row) { // // chkAlreadyImported(row[0], (fileName) // -> { // //on yes, let's import // try { // PrintWriter writer = new PrintWriter(fileName, "UTF-8"); // if (lineBreaksMatter) { // writer.write(mkLineBreaks(row[2])); // } else { // writer.write(row[2]); // // } // writer.close(); // String newFileName = getOutputDir().getAbsolutePath() + java.io.File.separator + fileName + getExtension(); // if (rename(fileName, newFileName)) { // log("Success writing: " + newFileName); // } else { // log("failed to write:" + newFileName); // }; // } catch (IOException e) { // e.printStackTrace(); // // do something // } // }); // // } public void log(String str) { Platform.runLater(() -> { txtAreaOutput.appendText("\n" + str); }); } public static boolean rename(String oldFileName, String newFileName) { new File(newFileName).delete(); File oldFile = new File(oldFileName); return oldFile.renameTo(new File(newFileName)); } public static String toWideOrbitTitle(LocalDate ld) { return ld.format(DateTimeFormatter.ofPattern("yyMMdd")); } public File isPresentForDate(LocalDate ld) { //the date/file name File[] files = getOutputDir().listFiles(); String fileName = toWideOrbitTitle(ld); Optional<File> opSome = Arrays.stream(files).parallel().filter(x -> x.getName().contains(fileName)) .findAny(); if (opSome.isPresent()) { return opSome.get(); } else { return null; } } // private void chkAlreadyImported(String datestr, Consumer<String> onDoImport) { // //the date/file name // File[] files = getOutputDir().listFiles(); // String fileName = Arrays.stream(datestr.split("-")).collect(Collectors.joining()); // Optional<File> opSome = Arrays.stream(files).parallel().filter(x -> x.getName().contains(fileName)).findAny(); // if (opSome.isPresent()) { // //oh snap, looks like one's here already! // UI(() -> { // Alert alert = new Alert(Alert.AlertType.CONFIRMATION); // alert.setTitle("Re-import?"); // alert.setContentText("Hey there, just thought I'd let you know " // + "that you've already imported a file for this day: " // + datestr + ". Because I found this file: " + opSome.get().getName() // + "\nAre you sure you want to do this?"); // Optional<ButtonType> answer = alert.showAndWait(); // if (answer.isPresent() && answer.get().getButtonData().equals(ButtonData.OK_DONE)) { // onDoImport.accept(fileName); // } else { // log("Skipping: " + fileName); // } // // return; // }); // } else { // //then of course we can import. // onDoImport.accept(fileName); // } // } public void UI(Runnable f) { Platform.runLater(f); } private String mkLineBreaks(String string) { return Arrays.stream(string.split("(?=\\d\\d:\\d\\d:\\d\\d)")).collect(Collectors.joining("\n")); } private File safeFileSet(File file) { return safeFileSetHelper(file, 17); } private File safeFileSetHelper(File file, int i) { if (file.exists()) { return file; } else if (i > 0) { return safeFileSetHelper(file.getParentFile(), i - 1); } else { return (new DirectoryChooser()).getInitialDirectory(); } } @FXML private void selectAllClicked(MouseEvent event) { chkSelectAll.setSelected(!chkSelectAll.isSelected()); } private String getData(String[] row) { return lineBreaksMatter ? mkLineBreaks(row[2]) : row[2]; } private void watchForConsumption() throws IOException { WatchService watcher = FileSystems.getDefault().newWatchService(); try { Path dir = getOutputDir().toPath(); WatchKey key = dir.register(watcher, ENTRY_DELETE); for (;;) { if (Thread.interrupted()) { key.cancel(); return; } try { key = watcher.take(); } catch (InterruptedException x) { return; } for (WatchEvent<?> event : key.pollEvents()) { WatchEvent.Kind<?> kind = event.kind(); // This key is registered only // for ENTRY_CREATE events, // but an OVERFLOW event can // occur regardless if events // are lost or discarded. if (kind == OVERFLOW) { continue; } // // The filename is the // // context of the event. WatchEvent<Path> ev = (WatchEvent<Path>) event; Path filepath = ev.context(); String filename = filepath.toString(); System.out.println("the filename was: " + filename); System.out.println(kind); Optional<String> res = findFile(filename); if (res.isPresent()) { System.out.println("BEFORE REMOVAL: " + myfiles.toString()); System.out.println("removing: " + res.get()); removeFromFiles(res.get()); System.out.println("Removed. Now: " + myfiles.toString()); int dpi = findThisDP(res.get()); if (-1 != dpi) { UI(() -> { datePickers[dpi].setStyle("-fx-background-color: lightgreen"); dayLabels[dpi].setStyle("-fx-background-color: lightgreen"); }); } log("Wide Orbit CONSUMED: " + filename); } else { System.out.println("is present was false for: " + filename); System.out.println(myfiles.toString()); } // Reset the key -- this step is critical if you want to // receive further watch events. If the key is no longer valid, // the directory is inaccessible so exit the loop. boolean valid = key.reset(); if (!valid) { return; } if (myfiles.isEmpty()) { key.cancel(); log("ALL WRITTEN FILES CONSUMED."); System.out.println("\n\n\n"); return; } } //end of events } //end of infinite loop } catch (IOException x) { System.err.println(x); } finally { Thread.currentThread().interrupt(); } } private int findThisDP(String str) { for (int i = 0; i < datePickers.length; i++) { if (toWideOrbitTitle(datePickers[i].getValue()).compareTo(str) == 0) { return i; } } return -1; } private Optional<String> findFile(String filename) { synchronized (lock) { return myfiles.parallelStream().filter(str -> { return filename.contains(str); }).findAny(); } } }