package gui; import static data.CorpusType.*; import static gui.GUIController.*; import static gui.Messages.*; import static util.Util.*; import java.io.File; import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.util.*; import javafx.scene.layout.AnchorPane; import javafx.util.Duration; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOCase; import org.apache.commons.io.LineIterator; import org.apache.commons.io.filefilter.FileFilterUtils; import org.apache.commons.io.filefilter.TrueFileFilter; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import alg.XML_processing; import data.Corpus; import data.CorpusType; import data.Enums.solar.SolarFilters; import data.Tax; import javafx.collections.ObservableList; import javafx.concurrent.Task; import javafx.fxml.FXML; import javafx.scene.control.*; import javafx.scene.layout.Pane; import javafx.stage.DirectoryChooser; import javafx.stage.Stage; import javafx.application.HostServices; public class CorpusTab { public final static Logger logger = LogManager.getLogger(CorpusTab.class); public Pane setCorpusWrapperP; private Stage stage; @FXML private AnchorPane corpusTabPane; @FXML private Button chooseCorpusLocationB; private File chosenCorpusLocation; @FXML private CheckBox readHeaderInfoChB; private boolean readHeaderInfo; // @FXML // private CheckBox gosUseOrthChB; // private boolean gosUseOrth; @FXML private Button chooseResultsLocationB; @FXML private Button changeLanguageB; @FXML private Label chooseCorpusL; private String chooseCorpusLabelContent; @FXML private Label chooseResultsL; private String chooseResultsLabelContent; @FXML private ProgressIndicator locationScanPI; @FXML private Hyperlink helpH; // *** shared *** private Corpus corpus; private CorpusType corpusType; // tabs - used to enable/disable private Tab stringLevelTabNew2; private Tab oneWordAnalysisTab; private Tab characterLevelTab; private Tab wordFormationTab; private Tab wordLevelTab; private Tab filterTab; private TabPane tabPane; private StringAnalysisTabNew2 satNew2Controller; private OneWordAnalysisTab oneWordTabController; private CharacterAnalysisTab catController; private FiltersForSolar ffsController; private WordFormationTab wfController; private WordLevelTab wlController; private HostServices hostService; private String corpusLocation; private String corpusFilesSize; /** * Hack allowing to modify the default behavior of the tooltips. * @param openDelay The open delay, knowing that by default it is set to 1000. * @param visibleDuration The visible duration, knowing that by default it is set to 5000. * @param closeDelay The close delay, knowing that by default it is set to 200. * @param hideOnExit Indicates whether the tooltip should be hide on exit, * knowing that by default it is set to false. */ private static void updateTooltipBehavior(double openDelay, double visibleDuration, double closeDelay, boolean hideOnExit) { try { // Get the non public field "BEHAVIOR" Field fieldBehavior = Tooltip.class.getDeclaredField("BEHAVIOR"); // Make the field accessible to be able to get and set its value fieldBehavior.setAccessible(true); // Get the value of the static field Object objBehavior = fieldBehavior.get(null); // Get the constructor of the private static inner class TooltipBehavior Constructor constructor = objBehavior.getClass().getDeclaredConstructor( Duration.class, Duration.class, Duration.class, boolean.class ); // Make the constructor accessible to be able to invoke it constructor.setAccessible(true); // Create a new instance of the private static inner class TooltipBehavior Object tooltipBehavior = constructor.newInstance( new Duration(openDelay), new Duration(visibleDuration), new Duration(closeDelay), hideOnExit ); // Set the new instance of TooltipBehavior fieldBehavior.set(null, tooltipBehavior); } catch (Exception e) { throw new IllegalStateException(e); } } public void initialize() { updateTooltipBehavior(0.0, 30000.0,0.0, true); // add CSS style corpusTabPane.getStylesheets().add("style.css"); corpusTabPane.getStyleClass().add("root"); stage = new Stage(); manageTranslations(); // add listeners chooseCorpusLocationB.setOnAction(e -> chooseCorpusLocation()); chooseCorpusLocationB.setTooltip(new Tooltip(I18N.get("message.TOOLTIP_chooseCorpusLocationB"))); helpH.setOnAction(e -> openHelpWebsite()); readHeaderInfoChB.selectedProperty().addListener((observable, oldValue, newValue) -> { readHeaderInfo = newValue; logger.info("read headers: ", readHeaderInfo); }); readHeaderInfoChB.setTooltip(new Tooltip(I18N.get("message.TOOLTIP_readHeaderInfoChB"))); // gosUseOrthChB.selectedProperty().addListener((observable, oldValue, newValue) -> { // gosUseOrth = newValue; // corpus.setGosOrthMode(gosUseOrth); //// wordFormationTab.setDisable(gosUseOrth); // satNew2Controller.toggleMode(null); // oneWordTabController.toggleMode(null); // catController.toggleMode(null); // // logger.info("gosUseOrth: ", gosUseOrth); // }); chooseResultsLocationB.setOnAction(e -> chooseResultsLocation(null)); changeLanguageB.setOnAction(e -> { if (I18N.getLocale() == new Locale.Builder().setLanguage("sl").setRegion("SI").build()){ I18N.setLocale(Locale.ENGLISH); } else { I18N.setLocale(new Locale.Builder().setLanguage("sl").setRegion("SI").build()); } Messages.reload(); // StringBuilder sb = new StringBuilder(); // sb.append(corpusLocation) // .append("\n") // .append(String.format(I18N.get("message.NOTIFICATION_FOUND_X_FILES"), corpusFilesSize)) // .append("\n") // .append(String.format(I18N.get("message.NOTIFICATION_CORPUS"), corpusType.toString())); // // chooseCorpusLabelContent = sb.toString(); // chooseCorpusL.textProperty().unbind(); // chooseCorpusL.setText(chooseCorpusLabelContent); Messages.updateChooseCorpusL(); logger.info("change language"); }); // set labels and toggle visibility // toggleGosChBVisibility(); // chooseCorpusLabelContent = Messages.LABEL_CORPUS_LOCATION_NOT_SET; // chooseCorpusL.setText(chooseCorpusLabelContent); // // chooseResultsLabelContent = Messages.LABEL_RESULTS_LOCATION_NOT_SET; // chooseResultsL.setText(chooseResultsLabelContent); togglePiAndSetCorpusWrapper(false); } private void manageTranslations(){ chooseCorpusLocationB.textProperty().bind(I18N.createStringBinding("button.setCorpusLocation")); readHeaderInfoChB.textProperty().bind(I18N.createStringBinding("checkBox.readHeaderInfo")); chooseResultsLocationB.textProperty().bind(I18N.createStringBinding("button.chooseResultsLocation")); helpH.textProperty().bind(I18N.createStringBinding("hyperlink.help")); changeLanguageB.textProperty().bind(I18N.createStringBinding("button.language")); chooseCorpusL.textProperty().bind(I18N.createStringBinding("message.LABEL_CORPUS_LOCATION_NOT_SET")); chooseResultsL.textProperty().bind(I18N.createStringBinding("message.LABEL_RESULTS_LOCATION_NOT_SET")); } private void togglePiAndSetCorpusWrapper(boolean piIsActive) { locationScanPI.setVisible(piIsActive); setCorpusWrapperP.setLayoutX(piIsActive ? 100.0 : 10.0); } private void openHelpWebsite(){ hostService.showDocument(Messages.HELP_URL); } /** * In order for a directory to pass as a valid corpus location, following criteria has to be met: * *

* Additionally, if the user checks to read taxonomy/filters from the corpus files, that read * has to produce a non-empty list results list */ private void chooseCorpusLocation() { File selectedDirectory = directoryChooser(); if (selectedDirectory != null && ValidationUtil.isReadableDirectory(selectedDirectory)) { logger.info("selected corpus dir: ", selectedDirectory.getAbsolutePath()); // scan for xml files Collection corpusFiles = FileUtils.listFiles(selectedDirectory, FileFilterUtils.suffixFileFilter("xml", IOCase.INSENSITIVE), TrueFileFilter.INSTANCE); // make sure there are corpus files in selected directory or notify the user about it if (corpusFiles.size() == 0) { // try .vert corpusFiles = FileUtils.listFiles(selectedDirectory, FileFilterUtils.suffixFileFilter("vert", IOCase.INSENSITIVE), TrueFileFilter.INSTANCE); Collection corpusFilesRegi = FileUtils.listFiles(selectedDirectory, FileFilterUtils.suffixFileFilter("regi", IOCase.INSENSITIVE), TrueFileFilter.INSTANCE); // if (!checkRegiFile(corpusFilesRegi)){ // return; // } if (corpusFiles.size() == 0){ logger.info("alert: ", I18N.get("message.WARNING_CORPUS_NOT_FOUND")); showAlert(Alert.AlertType.ERROR, I18N.get("message.WARNING_CORPUS_NOT_FOUND"), null); } else if (corpusFilesRegi.size() == 0){ GUIController.showAlert(Alert.AlertType.ERROR, String.format(I18N.get("message.ERROR_NO_REGI_FILE_FOUND"), selectedDirectory.getAbsolutePath())); } else { corpusLocation = selectedDirectory.getAbsolutePath(); corpusFilesSize = String.valueOf(corpusFiles.size()); Messages.setChooseCorpusProperties(corpusLocation, corpusFilesSize, corpusType != null ? corpusType.toString() : null); corpusType = VERT; corpus.setCorpusType(corpusType); Messages.setChooseCorpusProperties(corpusLocation, corpusFilesSize, corpusType.toString()); StringBuilder sb = new StringBuilder(); sb.append(corpusLocation) .append("\n") .append(String.format(I18N.get("message.NOTIFICATION_FOUND_X_FILES"), corpusFiles.size())) .append("\n") .append(String.format(I18N.get("message.NOTIFICATION_CORPUS"), corpusType.toString())); String result = sb.toString(); logger.debug(result); initNewCorpus(selectedDirectory, corpusFiles); Messages.setChooseCorpusProperties(corpusLocation, corpusFilesSize, corpusType.toString()); corpus.setChosenCorpusLocation(selectedDirectory); corpus.setDetectedCorpusFiles(corpusFiles); chooseCorpusLabelContent = result; if (readHeaderInfo) { logger.info("reading header info..."); readHeaderInfo(); } else { setResults(); setCorpusForAnalysis(); } } } else { corpusLocation = selectedDirectory.getAbsolutePath(); corpusFilesSize = String.valueOf(corpusFiles.size()); Messages.setChooseCorpusProperties(corpusLocation, corpusFilesSize, corpusType != null ? corpusType.toString() : null); String chooseCorpusLabelContentTmp = detectCorpusType(corpusFiles); if (chooseCorpusLabelContentTmp == null) { logger.info("alert: ", I18N.get("message.WARNING_CORPUS_NOT_FOUND")); showAlert(Alert.AlertType.ERROR, I18N.get("message.WARNING_CORPUS_NOT_FOUND"), null); } else { initNewCorpus(selectedDirectory, corpusFiles); Messages.setChooseCorpusProperties(corpusLocation, corpusFilesSize, corpusType.toString()); corpus.setChosenCorpusLocation(selectedDirectory); corpus.setDetectedCorpusFiles(corpusFiles); chooseCorpusLabelContent = chooseCorpusLabelContentTmp; logger.info("corpus dir: ", corpus.getChosenCorpusLocation().getAbsolutePath()); if (readHeaderInfo) { logger.info("reading header info..."); readHeaderInfo(); } else { setResults(); setCorpusForAnalysis(); } } } } } /** * If a user selects a valid corpus location, we define a new corpus (so none of the old data gets carried over) * * @param selectedDirectory * @param corpusFiles */ private void initNewCorpus(File selectedDirectory, Collection corpusFiles) { corpus = new Corpus(); corpus.setCorpusType(corpusType); corpus.setDetectedCorpusFiles(corpusFiles); corpus.setChosenCorpusLocation(selectedDirectory); chooseResultsLocation(selectedDirectory); } private void chooseResultsLocation(File dir) { // results location can be set either to default value (after selecting valid corpus location) - dir attribute // or to a dir picked via directoryChooser (when dir == null File selectedDirectory = dir == null ? directoryChooser() : dir; if (selectedDirectory != null) { String resultsLocationPath = selectedDirectory.getAbsolutePath().concat(File.separator); File chosenResultsLocationTmp = new File(resultsLocationPath); if (!ValidationUtil.isValidDirectory(chosenResultsLocationTmp)) { showAlert(Alert.AlertType.ERROR, I18N.get("message.WARNING_RESULTS_DIR_NOT_VALID")); logger.info("alert: ", I18N.get("message.WARNING_RESULTS_DIR_NOT_VALID")); } else { corpus.setChosenResultsLocation(chosenResultsLocationTmp); chooseResultsLabelContent = corpus.getChosenResultsLocation().getAbsolutePath(); chooseResultsL.textProperty().unbind(); chooseResultsL.setText(chooseResultsLabelContent); logger.info("results dir: " + chooseResultsLabelContent); } } } private void setResults() { // if everything is ok // check and enable checkbox if GOS // toggleGosChBVisibility(); // set default results location String defaultResultsLocationPath = corpus.getChosenCorpusLocation().getAbsolutePath(); logger.info("setting default results location to: ", defaultResultsLocationPath); Messages.setChooseCorpusL(chooseCorpusL, chooseCorpusLabelContent); } private boolean checkRegiFile(Collection corpusFiles) { // CorpusType corpusType = corpus.getCorpusType(); // Collection corpusFiles = corpus.getDetectedCorpusFiles(); for (File file : corpusFiles) { // try to open .regi file String regiPath = file.getAbsolutePath().substring(0, file.getAbsolutePath().length() - 4) + "regi"; LineIterator regiIt; try { // read regi file regiIt = FileUtils.lineIterator(new File(regiPath), "UTF-8"); LineIterator.closeQuietly(regiIt); } catch (IOException e) { GUIController.showAlert(Alert.AlertType.ERROR, String.format(I18N.get("message.ERROR_NO_REGI_FILE_FOUND"), regiPath)); return false; } } return true; } private void readHeaderInfo() { CorpusType corpusType = corpus.getCorpusType(); Collection corpusFiles = corpus.getDetectedCorpusFiles(); togglePiAndSetCorpusWrapper(true); chooseCorpusL.textProperty().unbind(); chooseCorpusL.setText(I18N.get("message.LABEL_SCANNING_CORPUS")); logger.info("reading header data for ", corpusType.toString()); if (corpusType == CorpusType.GIGAFIDA || corpusType == CorpusType.GOS || corpusType == CorpusType.CCKRES || corpusType == CorpusType.SSJ500K || corpusType == CorpusType.GIGAFIDA2) { boolean corpusIsSplit = corpusFiles.size() > 1; final Task> task = new Task>() { @Override protected HashSet call() throws Exception { HashSet values = new HashSet<>(); long i = 0; if (!corpusIsSplit) { updateProgress(-1.0f, -1.0f); } for (File file : corpusFiles) { values.addAll((Collection) XML_processing.readXmlHeaderTaxonomyAndFilters(file.getAbsolutePath(), corpusIsSplit, corpusType)); i++; if (corpusIsSplit) { // System.out.println(i); // System.out.println(corpusFiles.size()); updateProgress(i, corpusFiles.size()); } } updateProgress(1.0f, 1.0f); return values; } }; locationScanPI.progressProperty().bind(task.progressProperty()); task.setOnSucceeded(e -> { ObservableList readTaxonomy = Tax.getTaxonomyForComboBox(corpusType, task.getValue()); if (ValidationUtil.isEmpty(readTaxonomy)) { // if no taxonomy found alert the user and keep other tabs disabled logger.info("No taxonomy found in headers."); GUIController.showAlert(Alert.AlertType.ERROR, I18N.get("message.WARNING_NO_TAXONOMY_FOUND")); } else { // set taxonomy, update label corpus.setTaxonomy(readTaxonomy); corpus.setHeaderRead(true); Messages.setChooseCorpusL(chooseCorpusL, chooseCorpusLabelContent); setResults(); setCorpusForAnalysis(); } togglePiAndSetCorpusWrapper(false); }); task.setOnCancelled(e -> togglePiAndSetCorpusWrapper(false)); task.setOnFailed(e -> togglePiAndSetCorpusWrapper(false)); final Thread thread = new Thread(task, "task"); thread.setDaemon(true); thread.start(); } else if (corpusType == CorpusType.SOLAR) { // many many fields boolean corpusIsSplit = corpusFiles.size() > 1; final Task>> task = new Task>>() { @Override protected HashMap> call() throws Exception { HashMap> values = new HashMap<>(); long i = 0; if (!corpusIsSplit) { updateProgress(-1.0f, -1.0f); } for (File file : corpusFiles) { HashMap> tmpvalues = (HashMap>) XML_processing.readXmlHeaderTaxonomyAndFilters(file.getAbsolutePath(), corpusIsSplit, corpusType); // update final results for (Map.Entry> entry : tmpvalues.entrySet()) { if (values.containsKey(entry.getKey())) { values.get(entry.getKey()).addAll(entry.getValue()); } else { values.put(entry.getKey(), entry.getValue()); } } i++; if (corpusIsSplit) { updateProgress(i, corpusFiles.size()); } } updateProgress(1.0f, 1.0f); return values; } }; locationScanPI.progressProperty().bind(task.progressProperty()); task.setOnSucceeded(e -> { HashMap> values = task.getValue(); if (ValidationUtil.isEmpty(values)) { // if no taxonomy found alert the user and keep other tabs disabled logger.info("No solar filters found in headers."); GUIController.showAlert(Alert.AlertType.ERROR, I18N.get("message.WARNING_NO_SOLAR_FILTERS_FOUND")); } else { HashMap> filtersForComboBoxes = SolarFilters.getFiltersForComboBoxes(values); // set taxonomy, update label corpus.setSolarFiltersForXML(values); corpus.setSolarFilters(filtersForComboBoxes); corpus.setHeaderRead(true); Messages.setChooseCorpusL(chooseCorpusL, chooseCorpusLabelContent); setResults(); setCorpusForAnalysis(); } togglePiAndSetCorpusWrapper(false); }); task.setOnCancelled(e -> togglePiAndSetCorpusWrapper(false)); task.setOnFailed(e -> togglePiAndSetCorpusWrapper(false)); final Thread thread = new Thread(task, "task"); thread.setDaemon(true); thread.start(); } else if (corpusType == CorpusType.VERT) { // many many fields boolean corpusIsSplit = corpusFiles.size() > 1; final Task> task = new Task>() { @Override protected HashSet call() throws Exception { HashSet values = new HashSet<>(); long i = 0; if (!corpusIsSplit) { updateProgress(-1.0f, -1.0f); } for (File file : corpusFiles) { HashSet tmpvalues = XML_processing.readVertHeaderTaxonomyAndFilters(file.getAbsolutePath(), corpusIsSplit, corpusType); // update final results for (String entry : tmpvalues) { if (!entry.equals("-")) { values.add(entry); } } i++; if (corpusIsSplit) { updateProgress(i, corpusFiles.size()); } } updateProgress(1.0f, 1.0f); return values; } }; locationScanPI.progressProperty().bind(task.progressProperty()); task.setOnSucceeded(e -> { ObservableList readTaxonomy = Tax.getTaxonomyForComboBox(corpusType, task.getValue()); // if (ValidationUtil.isEmpty(readTaxonomy)) { // // if no taxonomy found alert the user and keep other tabs disabled // logger.info("No vert filters found in headers."); // GUIController.showAlert(Alert.AlertType.ERROR, I18N.get("message.WARNING_NO_SOLAR_FILTERS_FOUND")); // } else { // set taxonomy, update label corpus.setTaxonomy(readTaxonomy); corpus.setHeaderRead(true); Messages.setChooseCorpusL(chooseCorpusL, chooseCorpusLabelContent); setResults(); setCorpusForAnalysis(); // } togglePiAndSetCorpusWrapper(false); }); task.setOnCancelled(e -> togglePiAndSetCorpusWrapper(false)); task.setOnFailed(e -> togglePiAndSetCorpusWrapper(false)); final Thread thread = new Thread(task, "task"); thread.setDaemon(true); thread.start(); } } private void setCorpusForAnalysis() { if (corpus.validate()) { // new statistic, enable tabs... stringLevelTabNew2.setDisable(false); satNew2Controller.setCorpus(corpus); satNew2Controller.init(); oneWordAnalysisTab.setDisable(false); oneWordTabController.setCorpus(corpus); oneWordTabController.init(); characterLevelTab.setDisable(false); catController.setCorpus(corpus); catController.init(); //wordFormationTab.setDisable(false); wordLevelTab.setDisable(false); //wfController.setCorpus(corpus); //wfController.init(); wlController.setCorpus(corpus); wlController.init(); if (corpus.getCorpusType() == CorpusType.SOLAR) { filterTab.setDisable(false); tabPane.getTabs().add(1, filterTab); ffsController.setCorpus(corpus); ffsController.initFilters(); } else { filterTab.setDisable(true); tabPane.getTabs().removeAll(filterTab); } } else { GUIController.showAlert(Alert.AlertType.ERROR, corpus.getValidationErrorsToString()); } } private File directoryChooser() { DirectoryChooser directoryChooser = new DirectoryChooser(); // open in the folder where the jar is located if possible File workingDir = getWorkingDirectory(); if (workingDir != null) { directoryChooser.setInitialDirectory(workingDir); } return directoryChooser.showDialog(stage); } /** * Hides GOS related checkbox until needed. */ // private void toggleGosChBVisibility() { // gosUseOrthChB.setVisible(corpus != null && corpus.getCorpusType() != null && corpus.getCorpusType() == CorpusType.GOS); // } private String detectCorpusType(Collection corpusFiles) { // check that we recognize this corpus // read first file only, maybe later do all, if toll on resources is acceptable File f = corpusFiles.iterator().next(); String title = XML_processing.readXMLHeaderTag(f.getAbsolutePath(), "title").toLowerCase(); String attrib = XML_processing.readXMLHeaderAttribute(f.getAbsolutePath(), "body", "base").toLowerCase(); String test = CCKRES.getNameLowerCase(); String debug = ""; // check if XML file's title contains any of recognized corpus titles corpusType = null; if (title.contains(SOLAR.getNameLowerCase())) { corpusType = SOLAR; } else if (title.contains(GIGAFIDA.getNameLowerCase())) { String edition = XML_processing.readXMLHeaderTag(f.getAbsolutePath(), "edition").toLowerCase(); if (Double.valueOf(edition) < 2.0) { corpusType = GIGAFIDA; } else { corpusType = GIGAFIDA2; } } else if (title.contains(CCKRES.getNameLowerCase())) { corpusType = CCKRES; } else if (title.contains(GOS.getNameLowerCase())) { corpusType = GOS; } else if (attrib.contains(SSJ500K.getNameLowerCase())) { corpusType = SSJ500K; } if (corpusType == null) { return null; } else { corpus.setCorpusType(corpusType); Messages.setChooseCorpusProperties(corpusLocation, corpusFilesSize, corpusType.toString()); StringBuilder sb = new StringBuilder(); sb.append(corpusLocation) .append("\n") .append(String.format(I18N.get("message.NOTIFICATION_FOUND_X_FILES"), corpusFiles.size())) .append("\n") .append(String.format(I18N.get("message.NOTIFICATION_CORPUS"), corpusType.toString())); String result = sb.toString(); logger.debug(result); return result; } } public Corpus getCorpus() { return corpus; } public void setCorpus(Corpus corpus) { this.corpus = corpus; } public void setStringLevelTabNew2(Tab stringLevelTabNew2) { this.stringLevelTabNew2 = stringLevelTabNew2; } public void setOneWordAnalysisTab(Tab oneWordAnalysisTab) { this.oneWordAnalysisTab = oneWordAnalysisTab; } public void setCharacterLevelTab(Tab characterLevelTab) { this.characterLevelTab = characterLevelTab; } public void setWordLevelTab(Tab wordLevelTab) { this.wordLevelTab = wordLevelTab; } public void setFilterTab(Tab filterTab) { this.filterTab = filterTab; } public void setFfsController(FiltersForSolar ffsController) { this.ffsController = ffsController; } public void setTabPane(TabPane tabPane) { this.tabPane = tabPane; } public void setSatNew2Controller(StringAnalysisTabNew2 satNew2Controller) { this.satNew2Controller = satNew2Controller; } public void setOneWordTabController(OneWordAnalysisTab oneWordTabController) { this.oneWordTabController = oneWordTabController; } public void setCatController(CharacterAnalysisTab catController) { this.catController = catController; } /*public void setWfController(WordFormationTab wfController) { this.wfController = wfController; }*/ public void setWlController(WordLevelTab wlController) { this.wlController = wlController; } public void setWordFormationTab(Tab wordFormationTab) { this.wordFormationTab = wordFormationTab; } public void setHostServices(HostServices hostServices){ this.hostService = hostServices; } }