package gui; import alg.XML_processing; import data.*; import javafx.application.HostServices; import javafx.beans.InvalidationListener; import javafx.beans.Observable; import javafx.beans.property.ReadOnlyDoubleWrapper; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.collections.FXCollections; import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; import javafx.concurrent.Task; import javafx.fxml.FXML; import javafx.scene.control.*; import javafx.scene.layout.AnchorPane; import javafx.scene.layout.Pane; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.controlsfx.control.CheckComboBox; import java.io.File; import java.io.UnsupportedEncodingException; import java.util.*; import java.util.regex.Pattern; import static alg.XML_processing.readXML; import static gui.GUIController.showAlert; @SuppressWarnings("Duplicates") public class CharacterAnalysisTab { public final static Logger logger = LogManager.getLogger(CharacterAnalysisTab.class); @FXML public AnchorPane characterAnalysisTab; @FXML public Label selectedFiltersLabel; @FXML public Label stringLengthL; @FXML public Label calculateForL; @FXML public Label displayTaxonomyL; @FXML public Label dataLimitL; @FXML public Label msdL; @FXML public Label taxonomyL; @FXML public Label minimalOccurrencesL; @FXML public Label minimalTaxonomyL; @FXML public Label stringLengthLH; @FXML public Label calculateForLH; @FXML public Label displayTaxonomyLH; @FXML public Label msdLH; @FXML public Label taxonomyLH; @FXML public Label minimalOccurrencesLH; @FXML public Label minimalTaxonomyLH; @FXML public Label solarFilters; @FXML private TextField msdTF; private ArrayList msd; private ArrayList msdStrings; @FXML private CheckComboBox taxonomyCCB; private ArrayList taxonomy; @FXML private CheckBox displayTaxonomyChB; private boolean displayTaxonomy; @FXML private CheckBox calculatecvvCB; private boolean calculateCvv; @FXML private TextField stringLengthTF; private Integer stringLength; @FXML private TextField minimalOccurrencesTF; private Integer minimalOccurrences; @FXML private TextField minimalTaxonomyTF; private Integer minimalTaxonomy; // @FXML // private ToggleGroup calculateForRB; // private CalculateFor calculateFor; @FXML private ComboBox calculateForCB; private CalculateFor calculateFor; @FXML private RadioButton lemmaRB; @FXML private RadioButton varietyRB; @FXML private Pane paneLetters; @FXML private Button computeNgramsB; @FXML private Button cancel; @FXML private Button changeLanguageB; @FXML public ProgressBar ngramProgressBar; @FXML public Label progressLabel; @FXML private Hyperlink helpH; private enum MODE { LETTER } private MODE currentMode; private Corpus corpus; private HashMap> solarFiltersMap; private Filter filter; private boolean useDb; private HostServices hostService; private ListChangeListener taxonomyListener; private static final String [] N_GRAM_COMPUTE_FOR_LETTERS_ARRAY = {"calculateFor.WORD", "calculateFor.LEMMA"}; private static final ArrayList N_GRAM_COMPUTE_FOR_LETTERS = new ArrayList<>(Arrays.asList(N_GRAM_COMPUTE_FOR_LETTERS_ARRAY)); // private static final ObservableList N_GRAM_COMPUTE_FOR_LETTERS = FXCollections.observableArrayList("različnica", "lema"); private static final ObservableList N_GRAM_COMPUTE_FOR_WORDS_ORTH = FXCollections.observableArrayList("različnica"); // TODO: pass observables for taxonomy based on header scan // after header scan private ObservableList taxonomyCCBValues; private CorpusType currentCorpusType; public void init() { characterAnalysisTab.getStylesheets().add("style.css"); characterAnalysisTab.getStyleClass().add("root"); manageTranslations(); currentMode = MODE.LETTER; toggleMode(currentMode); // calculateForRB.selectedToggleProperty().addListener(new ChangeListener() { // @Override // public void changed(ObservableValue observable, Toggle oldValue, Toggle newValue) { // //logger.info("calculateForRB:", newValue.toString()); // RadioButton chk = (RadioButton)newValue.getToggleGroup().getSelectedToggle(); // Cast object to radio button // calculateFor = CalculateFor.factory(chk.getText()); // logger.info("calculateForRB:", chk.getText()); // //System.out.println("Selected Radio Button - "+chk.getText()); // } // }); calculateForCB.valueProperty().addListener((observable, oldValue, newValue) -> { if(newValue == null){ newValue = I18N.getTranslatedValue(oldValue, N_GRAM_COMPUTE_FOR_LETTERS); calculateForCB.getSelectionModel().select(newValue); } // System.out.println(oldValue); // System.out.println(newValue); calculateFor = CalculateFor.factory(newValue); logger.info("calculateForCB:", calculateFor.toString()); }); calculateForCB.getSelectionModel().select(0); // msd msdTF.focusedProperty().addListener((observable, oldValue, newValue) -> { if (!newValue) { // focus lost String value = msdTF.getText(); logger.info("msdTf: ", value); if (!ValidationUtil.isEmpty(value)) { ArrayList msdTmp = new ArrayList<>(Arrays.asList(value.split(" "))); int nOfRequiredMsdTokens = 1; if (msdTmp.size() != nOfRequiredMsdTokens) { String msg = String.format(I18N.get("message.WARNING_MISMATCHED_NGRAM_AND_TOKENS_VALUES"), nOfRequiredMsdTokens, msdTmp.size()); logAlert(msg); showAlert(Alert.AlertType.ERROR, msg); } msd = new ArrayList<>(); msdStrings = new ArrayList<>(); for (String msdToken : msdTmp) { msd.add(Pattern.compile(msdToken)); msdStrings.add(msdToken); } logger.info(String.format("msd accepted (%d)", msd.size())); } else if (!ValidationUtil.isEmpty(newValue)) { msd = new ArrayList<>(); msdStrings = new ArrayList<>(); } } }); msdTF.setText(""); msd = new ArrayList<>(); // taxonomy if (Tax.getCorpusTypesWithTaxonomy().contains(corpus.getCorpusType()) && corpus.getObservableListTaxonomy().size() > 0) { taxonomyCCB.setDisable(false); } else { taxonomyCCB.setDisable(true); } if (taxonomyListener != null){ taxonomyCCB.getCheckModel().getCheckedItems().removeListener(taxonomyListener); } taxonomyListener = new ListChangeListener() { boolean changing = true; @Override public void onChanged(ListChangeListener.Change c){ if(changing) { ObservableList checkedItems = taxonomyCCB.getCheckModel().getCheckedItems(); // ArrayList checkedItemsTaxonomy = Taxonomy.convertStringListToTaxonomyList(checkedItems); ArrayList checkedItemsTaxonomy = Taxonomy.modifyingTaxonomy(taxonomy, checkedItems, corpus); taxonomy = new ArrayList<>(); taxonomy.addAll(checkedItemsTaxonomy); taxonomyCCB.getItems().removeAll(); taxonomyCCB.getItems().setAll(corpus.getObservableListTaxonomy()); // taxonomyCCB.getCheckModel().clearChecks(); changing = false; taxonomyCCB.getCheckModel().clearChecks(); for (Taxonomy t : checkedItemsTaxonomy) { taxonomyCCB.getCheckModel().check(t.toLongNameString()); } changing = true; logger.info(String.format("Selected taxonomy: %s", StringUtils.join(checkedItems, ","))); } } }; taxonomyCCB.getCheckModel().clearChecks(); taxonomyCCB.getItems().removeAll(); taxonomyCCB.getItems().setAll(corpus.getObservableListTaxonomy()); taxonomyCCB.getCheckModel().getCheckedItems().addListener(taxonomyListener); displayTaxonomy = false; displayTaxonomyChB.setSelected(false); // set if (Tax.getCorpusTypesWithTaxonomy().contains(corpus.getCorpusType()) && corpus.getObservableListTaxonomy().size() > 0) { displayTaxonomyChB.setDisable(false); displayTaxonomyChB.selectedProperty().addListener((observable, oldValue, newValue) -> { displayTaxonomy = newValue; if (displayTaxonomy) { minimalTaxonomyTF.setDisable(false); } else { minimalTaxonomyTF.setDisable(true); minimalTaxonomyTF.setText("1"); minimalTaxonomy = 1; } logger.info("display taxonomy: ", displayTaxonomy); }); displayTaxonomyChB.setTooltip(new Tooltip(I18N.get("message.TOOLTIP_readDisplayTaxonomyChB"))); } else { displayTaxonomyChB.setDisable(true); } // cvv calculatecvvCB.selectedProperty().addListener((observable, oldValue, newValue) -> { calculateCvv = newValue; logger.info("calculate cvv: " + calculateCvv); }); // string length stringLengthTF.focusedProperty().addListener((observable, oldValue, newValue) -> { if (!newValue) { // focus lost String value = stringLengthTF.getText(); if (!ValidationUtil.isEmpty(value)) { if (!ValidationUtil.isNumber(value)) { logAlert("stringlengthTf: " + I18N.get("message.WARNING_ONLY_NUMBERS_ALLOWED")); GUIController.showAlert(Alert.AlertType.ERROR, I18N.get("message.WARNING_ONLY_NUMBERS_ALLOWED")); } stringLength = Integer.parseInt(value); } else { GUIController.showAlert(Alert.AlertType.ERROR, I18N.get("message.WARNING_MISSING_STRING_LENGTH")); stringLengthTF.setText("1"); logAlert(I18N.get("message.WARNING_MISSING_STRING_LENGTH")); } } }); // set default values minimalOccurrencesTF.setText("1"); minimalOccurrences = 1; minimalTaxonomyTF.setText("1"); minimalTaxonomy = 1; minimalTaxonomyTF.setDisable(true); minimalOccurrencesTF.focusedProperty().addListener((observable, oldValue, newValue) -> { if (!newValue) { // focus lost String value = minimalOccurrencesTF.getText(); if (!ValidationUtil.isEmpty(value)) { if (!ValidationUtil.isNumber(value)) { logAlert("minimalOccurrencesTF: " + I18N.get("message.WARNING_ONLY_NUMBERS_ALLOWED")); GUIController.showAlert(Alert.AlertType.ERROR, I18N.get("message.WARNING_ONLY_NUMBERS_ALLOWED")); } else { minimalOccurrences = Integer.parseInt(value); } } else { minimalOccurrencesTF.setText("1"); minimalOccurrences = 1; } } }); minimalTaxonomyTF.focusedProperty().addListener((observable, oldValue, newValue) -> { if (!newValue) { // focus lost String value = minimalTaxonomyTF.getText(); if (!ValidationUtil.isEmpty(value)) { if (!ValidationUtil.isNumber(value)) { logAlert("minimalTaxonomyTF: " + I18N.get("message.WARNING_ONLY_NUMBERS_ALLOWED")); GUIController.showAlert(Alert.AlertType.ERROR, I18N.get("message.WARNING_ONLY_NUMBERS_ALLOWED")); } else { minimalTaxonomy = Integer.parseInt(value); } } else { minimalTaxonomyTF.setText("1"); minimalTaxonomy = 1; } } }); computeNgramsB.setOnAction(e -> { compute(); logger.info("compute button"); }); 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(); Messages.updateChooseCorpusL(); logger.info("change language"); }); helpH.setOnAction(e -> openHelpWebsite()); cancel.setVisible(false); } /** * case a: values for combo boxes can change after a corpus change *
    *
  • different corpus type - reset all fields so no old values remain
  • *
  • same corpus type, different subset - keep
  • *
*

* case b: values for combo boxes can change after a header scan *

    *
  • at first, fields are populated by corpus type defaults
  • *
  • after, with gathered data
  • *
*

* ngrams: 1 * calculateFor: word * msd: * taxonomy: * skip: 0 * iscvv: false * string length: 1 */ // public void populateFields() { // // corpus changed if: current one is null (this is first run of the app) // // or if currentCorpus != gui's corpus // boolean corpusChanged = currentCorpusType == null // || currentCorpusType != corpus.getCorpusType(); // // // TODO: check for GOS, GIGAFIDA, SOLAR... // // refresh and: // // TODO if current value != null && is in new calculateFor ? keep : otherwise reset //// if (calculateFor == null) { //// calculateForRB.selectToggle(lemmaRB); //// calculateFor = CalculateFor.factory(calculateForRB.getSelectedToggle().toString()); //// } // // if (!filter.hasMsd()) { // // if current corpus doesn't have msd data, disable this field // msd = new ArrayList<>(); // msdTF.setText(""); // msdTF.setDisable(true); // logger.info("no msd data"); // } else { // if (ValidationUtil.isEmpty(msd) // || (!ValidationUtil.isEmpty(msd) && corpusChanged)) { // // msd has not been set previously // // or msd has been set but the corpus changed -> reset // msd = new ArrayList<>(); // msdTF.setText(""); // msdTF.setDisable(false); // logger.info("msd reset"); // } else if (!ValidationUtil.isEmpty(msd) && !corpusChanged) { // // if msd has been set, but corpus type remained the same, we can keep any set msd value // msdTF.setText(StringUtils.join(msdStrings, " ")); // msdTF.setDisable(false); // logger.info("msd kept"); // } // } // // // TODO: taxonomy: refresh and keep if in new taxonomy, otherwise empty (no selection) // // // keep calculateCvv // calculatecvvCB.setSelected(calculateCvv); // // // keep string length if set // if (stringLength != null) { // stringLengthTF.setText(String.valueOf(stringLength)); // } else { // stringLengthTF.setText("1"); // stringLength = 1; // } // // // TODO: trigger on rescan // if ((currentCorpusType != null && currentCorpusType != corpus.getCorpusType())) { // // user changed corpus (by type) or by selection & triggered a rescan of headers // // see if we read taxonomy from headers, otherwise use default values for given corpus // ObservableList tax = corpus.getObservableListTaxonomy(); // taxonomyCCBValues = tax != null ? tax : Taxonomy.getDefaultForComboBox(corpus.getCorpusType()); // // currentCorpusType = corpus.getCorpusType(); // // setTaxonomyIsDirty(false); // } else { // // } // // // see if we read taxonomy from headers, otherwise use default values for given corpus // ObservableList tax = corpus.getObservableListTaxonomy(); // taxonomyCCBValues = tax != null ? tax : Taxonomy.getDefaultForComboBox(corpus.getCorpusType()); // taxonomyCCB.getItems().addAll(taxonomyCCBValues); // // } private void manageTranslations(){ helpH.textProperty().bind(I18N.createStringBinding("hyperlink.help")); changeLanguageB.textProperty().bind(I18N.createStringBinding("button.language")); computeNgramsB.textProperty().bind(I18N.createStringBinding("button.computeNgrams")); cancel.textProperty().bind(I18N.createStringBinding("button.cancel")); stringLengthL.textProperty().bind(I18N.createStringBinding("label.stringLength")); calculateForL.textProperty().bind(I18N.createStringBinding("label.calculateFor")); displayTaxonomyL.textProperty().bind(I18N.createStringBinding("label.displayTaxonomy")); dataLimitL.textProperty().bind(I18N.createStringBinding("label.dataLimit")); msdL.textProperty().bind(I18N.createStringBinding("label.msd")); taxonomyL.textProperty().bind(I18N.createStringBinding("label.taxonomy")); minimalOccurrencesL.textProperty().bind(I18N.createStringBinding("label.minimalOccurrences")); minimalTaxonomyL.textProperty().bind(I18N.createStringBinding("label.minimalTaxonomy")); stringLengthLH.textProperty().bind(I18N.createStringBinding("label.letter.stringLengthH")); calculateForLH.textProperty().bind(I18N.createStringBinding("label.letter.calculateForH")); displayTaxonomyLH.textProperty().bind(I18N.createStringBinding("label.letter.displayTaxonomyH")); msdLH.textProperty().bind(I18N.createStringBinding("label.letter.msdH")); taxonomyLH.textProperty().bind(I18N.createStringBinding("label.letter.taxonomyH")); minimalOccurrencesLH.textProperty().bind(I18N.createStringBinding("label.letter.minimalOccurrencesH")); minimalTaxonomyLH.textProperty().bind(I18N.createStringBinding("label.letter.minimalTaxonomyH")); solarFilters.textProperty().bind(I18N.createStringBinding("label.solarFilters")); calculateForCB.itemsProperty().bind(I18N.createObjectBinding(N_GRAM_COMPUTE_FOR_LETTERS)); } /** * Toggles visibility for panes which hold fields for skipgram value (not applicable when calculating for letters) etc., * sets combobox values to what is applicable ... * * @param mode */ public void toggleMode(MODE mode) { if (mode == null) { mode = currentMode; } logger.info("mode: ", mode.toString()); if (mode == MODE.LETTER) { paneLetters.setVisible(true); // populate with default cvv length value if (stringLength == null) { stringLengthTF.setText("1"); stringLength = 1; } else { stringLengthTF.setText(String.valueOf(stringLength)); } // if calculateFor was selected for something other than a word or a lemma -> reset if (!(calculateFor == CalculateFor.WORD || calculateFor == CalculateFor.LEMMA)) { // if the user selected something else before selecting ngram for letters, reset that choice calculateFor = CalculateFor.WORD; calculateForCB.getSelectionModel().select(0); } } // override if orth mode, allow only word if (corpus.isGosOrthMode()) { // TODO change to // varietyRB.setDisable(true); msdTF.setDisable(true); } else { msdTF.setDisable(false); // varietyRB.setDisable(false); } } private void compute() { Filter filter = new Filter(); filter.setNgramValue(0); filter.setCalculateFor(calculateFor); filter.setMultipleKeys(new ArrayList<>()); filter.setMsd(msd); filter.setTaxonomy(taxonomy); filter.setDisplayTaxonomy(displayTaxonomy); filter.setAl(AnalysisLevel.STRING_LEVEL); filter.setSkipValue(0); filter.setIsCvv(calculateCvv); filter.setSolarFilters(solarFiltersMap); filter.setStringLength(stringLength); filter.setMinimalOccurrences(minimalOccurrences); filter.setMinimalTaxonomy(minimalTaxonomy); String message = Validation.validateForStringLevel(filter); if (message == null) { // no errors logger.info("Executing: ", filter.toString()); StatisticsNew statistic = new StatisticsNew(corpus, filter, useDb); execute(statistic); } else { logAlert(message); showAlert(Alert.AlertType.ERROR, "Prosim izpolnite polja:", message); } } private void openHelpWebsite(){ hostService.showDocument(Messages.HELP_URL); } private void logAlert(String alert) { logger.info("alert: " + alert); } public Corpus getCorpus() { return corpus; } public void setCorpus(Corpus corpus) { this.corpus = corpus; if (corpus.getCorpusType() != CorpusType.SOLAR) { setSelectedFiltersLabel(null); } else { setSelectedFiltersLabel("/"); } } public void setSelectedFiltersLabel(String content) { if (content != null) { solarFilters.setVisible(true); selectedFiltersLabel.setVisible(true); selectedFiltersLabel.setText(content); } else { solarFilters.setVisible(false); selectedFiltersLabel.setVisible(false); } } private void execute(StatisticsNew statistic) { logger.info("Started execution: ", statistic.getFilter()); Collection corpusFiles = statistic.getCorpus().getDetectedCorpusFiles(); final Task task = new Task() { @SuppressWarnings("Duplicates") @Override protected Void call() throws Exception { final boolean multipleFiles = CorpusType.multipleFilesCorpuses().contains(statistic.getCorpus().getCorpusType()); if(multipleFiles){ cancel.setVisible(true); } int i = 0; // DateFormat df = new SimpleDateFormat("hh:mm:ss"); Date startTime = new Date(); Date previousTime = new Date(); int remainingSeconds = -1; for (File f : corpusFiles) { final int iFinal = i; XML_processing xml_processing = new XML_processing(); xml_processing.isCancelled = false; i++; if (isCancelled()) { updateMessage(I18N.get("message.CANCELING_NOTIFICATION")); break; } if(xml_processing.progressBarListener != null) { xml_processing.progressProperty().removeListener(xml_processing.progressBarListener); } if (multipleFiles) { if ((new Date()).getTime() - previousTime.getTime() > 500 || remainingSeconds == -1){ remainingSeconds = (int) (((new Date()).getTime() - startTime.getTime()) * (1.0/i) * (corpusFiles.size() - i) / 1000); previousTime = new Date(); } this.updateProgress(i, corpusFiles.size()); this.updateMessage(String.format(I18N.get("message.ONGOING_NOTIFICATION_ANALYZING_FILE_X_OF_Y"), i, corpusFiles.size(), f.getName(), remainingSeconds)); } else { xml_processing.progressBarListener = new InvalidationListener() { int remainingSeconds = -1; Date previousTime = new Date(); @Override public void invalidated(Observable observable) { cancel.setVisible(true); if ((new Date()).getTime() - previousTime.getTime() > 500 || remainingSeconds == -1){ remainingSeconds = (int) (((new Date()).getTime() - xml_processing.startTime.getTime()) * (1.0/(iFinal * 100 + ((ReadOnlyDoubleWrapper) observable).get() + 1)) * ((corpusFiles.size() - iFinal - 1) * 100 + 100 - ((ReadOnlyDoubleWrapper) observable).get()) / 1000); previousTime = new Date(); } xml_processing.isCancelled = isCancelled(); updateProgress((iFinal * 100) + ((ReadOnlyDoubleWrapper) observable).get() + 1, corpusFiles.size() * 100); updateMessage(String.format(I18N.get("message.ONGOING_NOTIFICATION_ANALYZING_FILE_X_OF_Y"), iFinal + 1, corpusFiles.size(), f.getName(), remainingSeconds)); // updateProgress((iFinal * 100) + (double) observable, corpusFiles.size() * 100); } }; // this.updateMessage(String.format(I18N.get("message.ONGOING_NOTIFICATION_ANALYZING_FILE_X_OF_Y"), i, corpusFiles.size(), f.getName(), remainingSeconds)); xml_processing.progressProperty().addListener(xml_processing.progressBarListener); // xml_processing.progressProperty().addListener((obs, oldProgress, newProgress) -> // updateProgress((iFinal * 100) + newProgress.doubleValue(), corpusFiles.size() * 100)); } xml_processing.readXML(f.toString(), statistic); if (isCancelled()) { updateMessage(I18N.get("message.CANCELING_NOTIFICATION")); break; } // readXML(f.toString(), statistic, this, corpusFiles.size(), startTime, previousTime, i); } return null; } }; ngramProgressBar.progressProperty().bind(task.progressProperty()); progressLabel.textProperty().bind(task.messageProperty()); task.setOnSucceeded(e -> { try { boolean successullySaved = statistic.saveResultToDisk(); if (successullySaved) { showAlert(Alert.AlertType.INFORMATION, I18N.get("message.NOTIFICATION_ANALYSIS_COMPLETED")); } else { showAlert(Alert.AlertType.INFORMATION, I18N.get("message.NOTIFICATION_ANALYSIS_COMPLETED_NO_RESULTS")); } } catch (UnsupportedEncodingException e1) { showAlert(Alert.AlertType.ERROR, I18N.get("message.ERROR_WHILE_SAVING_RESULTS_TO_CSV")); logger.error("Error while saving", e1); } ngramProgressBar.progressProperty().unbind(); ngramProgressBar.setStyle(Settings.FX_ACCENT_OK); progressLabel.textProperty().unbind(); progressLabel.setText(""); cancel.setVisible(false); }); task.setOnFailed(e -> { showAlert(Alert.AlertType.ERROR, I18N.get("message.ERROR_WHILE_EXECUTING")); logger.error("Error while executing", e); ngramProgressBar.progressProperty().unbind(); ngramProgressBar.setProgress(0.0); ngramProgressBar.setStyle(Settings.FX_ACCENT_NOK); progressLabel.textProperty().unbind(); progressLabel.setText(""); cancel.setVisible(false); }); task.setOnCancelled(e -> { showAlert(Alert.AlertType.INFORMATION, I18N.get("message.NOTIFICATION_ANALYSIS_CANCELED")); ngramProgressBar.progressProperty().unbind(); ngramProgressBar.setProgress(0.0); ngramProgressBar.setStyle(Settings.FX_ACCENT_OK); progressLabel.textProperty().unbind(); progressLabel.setText(""); cancel.setVisible(false); }); // When cancel button is pressed cancel analysis cancel.setOnAction(e -> { task.cancel(); logger.info("cancel button"); }); final Thread thread = new Thread(task, "task"); thread.setDaemon(true); thread.start(); } public void setSolarFiltersMap(HashMap> solarFiltersMap) { this.solarFiltersMap = solarFiltersMap; } public void setHostServices(HostServices hostServices){ this.hostService = hostServices; } }