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.svi.uzabase.logic; import com.softcorporation.suggester.BasicSuggester; import com.softcorporation.suggester.dictionary.BasicDictionary; import com.softcorporation.suggester.tools.SpellCheck; import com.softcorporation.suggester.util.Constants; import com.softcorporation.suggester.util.SpellCheckConfiguration; import com.softcorporation.suggester.util.SuggesterException; import com.svi.uzabase.frames.MainFrame; import com.svi.uzabase.objects.FS; import com.svi.uzabase.objects.Field; import com.svi.uzabase.objects.SchemaFields; import com.svi.uzabase.objects.XMLHolder; import java.io.BufferedReader; import java.util.HashSet; import java.util.Set; import java.util.concurrent.Callable; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import static java.lang.String.format; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; import java.util.logging.Logger; import static javax.management.Query.value; import org.apache.commons.lang3.StringUtils; import org.apache.commons.validator.routines.EmailValidator; import org.apache.commons.validator.routines.UrlValidator; import org.apache.poi.EncryptedDocumentException; import org.jsoup.Jsoup; import org.jsoup.parser.Parser; /** * * @author Daniel */ public class ValidationProcess implements Callable<Map<String, List<?>>> { private final String inputStr; private final MainFrame mf; ArrayList<String> xmlHolder = new ArrayList<>(); List<String> companyList = new ArrayList<>(); List<String> cityList = new ArrayList<>(); List<String> provinceList = new ArrayList<>(); List<String> nationalityList = new ArrayList<>(); List<SchemaFields> schemaFieldsList = new ArrayList<>(); AtomicInteger total; AtomicInteger progress; List<FS> fsData = new ArrayList<>(); ValidationProcess(MainFrame mf, String inputStr, ArrayList<String> xmlHolder, List<String> companyList, List<String> cityList, List<String> provinceList, List<SchemaFields> schemaFieldsList, List<String> nationalityList) { this.inputStr = inputStr; this.mf = mf; this.xmlHolder = xmlHolder; this.companyList = companyList; this.cityList = cityList; this.provinceList = provinceList; this.schemaFieldsList = schemaFieldsList; this.nationalityList = nationalityList; } private Map<String, List<?>> getLists() { Map<String, List<?>> map = new HashMap(); map.put("xmlHolder", extractXML()); map.put("fsData", extractFSErrors()); return map; } private List<XMLHolder> extractXML() { System.out.println("in extracting xml"); BufferedReader brInput; String sCurrentLineInput; String[] fieldNo; String[] splitter; String toValidate; String str; List<XMLHolder> xmlBatchHolder = new ArrayList<>(); XMLHolder xmlFileHolder; Field xmlField; org.jsoup.nodes.Document doc; progress = new AtomicInteger(0); total = new AtomicInteger(xmlHolder.size()); mf.setJprogressValues(total, progress); for (String xmlPath : xmlHolder) { mf.loader("Extracting XML: ", false); xmlFileHolder = new XMLHolder(); xmlFileHolder.setFileName(xmlPath); try { brInput = new BufferedReader(new FileReader(xmlPath)); while ((sCurrentLineInput = brInput.readLine()) != null) { str = sCurrentLineInput; if (str.contains("field no=\"")) { xmlField = new Field(); sCurrentLineInput = brInput.readLine(); fieldNo = str.split("\""); str = sCurrentLineInput; doc = Jsoup.parse(str, "", Parser.xmlParser()); toValidate = doc.select("value").text(); if (fieldNo.length < 1) { xmlField.setFieldNo(0); } else { xmlField.setFieldNo(Integer.parseInt(fieldNo[1])); } xmlField.setValue(toValidate); if (!toValidate.isEmpty()) { xmlFileHolder.add(xmlField); } } } brInput.close(); xmlBatchHolder.add(xmlFileHolder); } catch (FileNotFoundException ex) { Logger.getLogger(ValidationProcess.class.getName()).log(Level.SEVERE, null, ex); } catch (IOException ex) { Logger.getLogger(ValidationProcess.class.getName()).log(Level.SEVERE, null, ex); } } //Set field name based on the schema for (XMLHolder h : xmlBatchHolder) { for (Field f : h) { for (SchemaFields s : schemaFieldsList) { if (f.getFieldNo() == s.getFieldNo()) { f.setFieldName(s.getFieldName()); break; } } } } //Set field types for validation FS fs; for (XMLHolder h : xmlBatchHolder) { fs = new FS(); fs.setFileName(h.getFileName()); for (Field f : h) { if (f.getFieldName().toLowerCase().contains("nationality")) { f.setType("nationality"); f.setColumnHeader(f.getFieldName()); } else if (f.getFieldName().toLowerCase().contains("company name")) { f.setType("corporation"); } else if (f.getFieldName().toLowerCase().contains("position")) { f.setType("position"); } else if (f.getFieldName().toLowerCase().contains("directors/officers") && f.getFieldName().toLowerCase().contains("name")) { f.setType("name"); f.setColumnHeader(f.getFieldName()); } else if (f.getFieldName().toLowerCase().contains("directors/officers") && f.getFieldName().toLowerCase().contains("tin")) { f.setType("tin"); f.setColumnHeader(f.getFieldName()); } else if (f.getFieldName().toLowerCase().contains("directors/officers") && f.getFieldName().toLowerCase().contains("stockholder")) { f.setType("stockholder"); f.setColumnHeader(f.getFieldName()); } else if (f.getFieldName().toLowerCase().contains("directors/officers") && !f.getFieldName().toLowerCase().contains("how many")) { splitter = f.getFieldName().split(":", 2); if (splitter[1].trim().equals("Board") || splitter[1].trim().equals("Officer")) { f.setType("board"); } else { f.setType("none"); } f.setColumnHeader(f.getFieldName()); // } else if (f.getFieldName().equalsIgnoreCase("address")) { } else if (f.getFieldNo() == 31) { f.setType("city"); f.setColumnHeader(f.getFieldName()); } else if (f.getFieldNo() == 32) { f.setType("province"); f.setColumnHeader(f.getFieldName()); } else if (f.getFieldName().toLowerCase().contains("phone number")) { f.setType("tel"); f.setColumnHeader(f.getFieldName()); } else if (f.getFieldName().toLowerCase().contains("fax number")) { f.setType("fax"); f.setColumnHeader(f.getFieldName()); } else if (f.getFieldName().toLowerCase().contains("contact person")) { f.setType("person"); f.setColumnHeader(f.getFieldName()); } else if (f.getFieldName().toLowerCase().contains("e-mail address")) { f.setType("email"); f.setColumnHeader(f.getFieldName()); } else if (f.getFieldName().toLowerCase().contains("website/url address")) { f.setType("website"); f.setColumnHeader(f.getFieldName()); } else if (f.getFieldName().toLowerCase().contains("tin/passport no.")) { f.setType("tin"); f.setColumnHeader(f.getFieldName()); } else if (f.getFieldName().toLowerCase().contains("% ownership")) { f.setType("ownership"); f.setColumnHeader(f.getFieldName()); } else if (f.getFieldName().toLowerCase().contains("share type")) { f.setType("shareType"); f.setColumnHeader(f.getFieldName()); } else if (f.getFieldNo() == 1) { f.setType("sec"); f.setColumnHeader(f.getFieldName()); } else if (f.getFieldNo() == 1207) { f.setType("tin"); f.setColumnHeader(f.getFieldName()); } else if (f.getFieldNo() == 7) { if (!f.getValue().equals("*N/A")) { fs.setTotalAssets(Double.parseDouble(f.getValue().replaceAll(",", ""))); } f.setType("assets"); f.setColumnHeader(f.getFieldName()); } else if (f.getFieldNo() == 9) { f.setType("liabilities"); if (!f.getValue().equals("*N/A")) { fs.setTotalLiabilities(Double.parseDouble(f.getValue().replaceAll(",", ""))); } f.setColumnHeader(f.getFieldName()); } else if (f.getFieldNo() == 11) { f.setType("balanceSheet"); if (!f.getValue().equals("*N/A")) { fs.setTotalShareholderEquity(Double.parseDouble(f.getValue().replaceAll(",", ""))); } f.setColumnHeader(f.getFieldName()); } else if (f.getFieldNo() == 3) { f.setType("grossc"); if (!f.getValue().equals("*N/A")) { fs.setGrossRevenue(Double.parseDouble(f.getValue().replaceAll(",", ""))); } f.setColumnHeader(f.getFieldName()); } else if (f.getFieldNo() == 4) { f.setType("gross"); if (!f.getValue().equals("*N/A")) { fs.setGrossRevenueP(Double.parseDouble(f.getValue().replaceAll(",", ""))); } f.setColumnHeader(f.getFieldName()); } else if (f.getFieldNo() == 5) { f.setType("netIncome"); if (!f.getValue().equals("*N/A")) { fs.setNetIncome(Double.parseDouble(f.getValue().replaceAll(",", ""))); } f.setColumnHeader(f.getFieldName()); } else if (f.getFieldNo() == 6) { f.setType("netIncomeP"); if (!f.getValue().equals("*N/A")) { fs.setNetIncomeP(Double.parseDouble(f.getValue().replaceAll(",", ""))); } f.setColumnHeader(f.getFieldName()); } else if (f.getFieldNo() == 1201) { f.setType("purpose"); } else if (f.getFieldNo() == 1206) { f.setType("periodCovered"); } else if (f.getFieldNo() == 1209) { f.setType("fiscalYear"); } else { f.setType("none"); } } fsData.add(fs); } List<String> foundDupAlready = new ArrayList<>(); String tempFoundDup = ""; int dupCtr = 0; for (XMLHolder h : xmlBatchHolder) { for (Field f1 : h) { if (f1.getValue().equals("*N/A")) { //skip if value is *N/A continue; } for (Field f2 : h) { if (f2.getValue().equals("*N/A")) { //skip if value is *N/A continue; } if (f1.getValue().equals(f2.getValue()) && !f1.getFieldName().toLowerCase().contains("company") && !f2.getFieldName().toLowerCase().contains("company") && f1.getFieldName().toLowerCase().contains("name") && f2.getFieldName().toLowerCase().contains("name") //Check if board or stockholder only && ((f1.getFieldName().toLowerCase().contains("directors/officers") && f2.getFieldName().toLowerCase().contains("directors/officers")) || ((f1.getFieldName().toLowerCase().contains("name") && !f1.getFieldName().toLowerCase().contains("directors/officers")) && (f2.getFieldName().toLowerCase().contains("name") && !f2 .getFieldName().toLowerCase().contains("directors/officers")))) //end of condition to check && !f1.getValue().isEmpty() && !tempFoundDup.equals(f2.getValue())) { dupCtr++; if (dupCtr == 2 && foundDupAlready.indexOf(f1.getValue()) < 0) { if (f2.getFieldName().toLowerCase().contains("former") || f2.getFieldName().toLowerCase().contains("building")) { continue; } System.out.println("2 " + f2.getValue() + f2.getFieldName()); foundDupAlready.add(f2.getValue()); tempFoundDup = f2.getValue(); f2.add("Duplicate entry"); } } } dupCtr = 0; } } List<Field> dupeHolder = new ArrayList<>(); for (XMLHolder z : xmlBatchHolder) { for (Field f1 : z) { for (String s : f1) { if (s.equals("Duplicate entry")) { dupeHolder.add(f1); } } } } for (XMLHolder z : xmlBatchHolder) { for (Field f1 : z) { for (Field fd : dupeHolder) { if (fd.getFieldNo() != f1.getFieldNo() && fd.getValue().equals(f1.getValue())) { f1.add("Duplicate entry"); } } } } return validateData(xmlBatchHolder); } private List<XMLHolder> validateData(List<XMLHolder> xmlBatchHolder) { try { int totalCounter = 0; //Initialize dictionary String dictFileName = "file://./dic/english.jar"; String configFile = "file://./classes/spellCheck.config"; BasicDictionary dictionary = new BasicDictionary(dictFileName); SpellCheckConfiguration configuration = new SpellCheckConfiguration(configFile); BasicSuggester suggester = new BasicSuggester(configuration); suggester.attach(dictionary); // create SpellCheck object based on configuration and specify Suggester SpellCheck spellCheck = new SpellCheck(configuration); spellCheck.setSuggester(suggester); //set for jprogress bar for (XMLHolder h : xmlBatchHolder) { totalCounter += h.size(); } progress = new AtomicInteger(0); total = new AtomicInteger(totalCounter); mf.setJprogressValues(total, progress); //validation process begins here String[] invalidWords = { "corporation", "inc.", "city", "corp.", "st.", "co.", "ltd." }; String[] invalidBoardWords = { "other", "oth" }; String[] validWords = { "loc", "to", "ext", "local" }; String[] invalidCharacters = { ",", "/", "\\", "[", "]", "\"", ":", "^", "{", "}", "%", "+", "#", "(", ")" }; String[] splitter; String tempURL; SimpleDateFormat sdf = new SimpleDateFormat("YYYY/MM/DD"); SimpleDateFormat fiscalYear = new SimpleDateFormat("MM/DD"); sdf.setLenient(false); Set<String> officerList = new HashSet<>(); List<Double> percentOwnership = new ArrayList<>(); UrlValidator urlValidator = new UrlValidator(); Date date = null; for (XMLHolder h : xmlBatchHolder) { for (Field f : h) { mf.loader("Validating fields: ", false); if (!f.getType().equals("none") && !f.getValue().equals("*N/A")) { switch (f.getType()) { case "city": if (f.getValue().isEmpty() || f.getValue().equals("")) { f.add("Address is empty"); } else { if (hasWhiteSpaceTrailing(f.getValue())) { f.add("has trailing white space "); } if (cityList.indexOf(f.getValue()) < 0) { f.add("City not found on list!"); } } break; case "province": if (f.getValue().isEmpty() || f.getValue().equals("")) { f.add("Address is empty"); } else { if (hasWhiteSpaceTrailing(f.getValue())) { f.add("has trailing white space "); } if (provinceList.indexOf(f.getValue()) < 0) { f.add("Province not found on list!"); } } break; case "tel": if (!f.getValue().isEmpty() || !f.getValue().equals("")) { // if (f.getValue().matches("[a-z A-Z]+")) { if (f.getValue().matches(".*[a-zA-Z]+.*")) { for (String s : validWords) { if (!f.getValue().contains(s)) { f.add("Invalid telephone number"); } } } if (f.getValue().replace(" ", "").replace("-", "").length() < 7 || f.getValue().replace(" ", "").replace("-", "").length() > 8) { f.add("Invalid telephone number length"); } if (hasWhiteSpaceTrailing(f.getValue())) { f.add("has trailing white space "); } if (StringUtils.countMatches(f.getValue(), "-") > 2) { f.add("Invalid telephone number"); } for (String c : invalidCharacters) { if (f.getValue().contains(c)) { f.add("Contains invalid character [ " + c + " ]"); break; } } } break; case "fax": if (!f.getValue().isEmpty() || !f.getValue().equals("")) { // if (f.getValue().matches("[a-z A-Z]+")) { if (f.getValue().matches(".*[a-zA-Z]+.*")) { for (String s : validWords) { if (!f.getValue().contains(s)) { f.add("Invalid fax number"); } } } if (f.getValue().replace(" ", "").length() < 6) { f.add("Invalid fax number"); } if (StringUtils.countMatches(f.getValue(), "-") > 1) { f.add("Invalid fax number"); } if (hasWhiteSpaceTrailing(f.getValue())) { f.add("has trailing white space "); } for (String c : invalidCharacters) { if (f.getValue().contains(c)) { f.add("Contains invalid character [ " + c + " ]"); break; } } } break; case "person": if (!f.getValue().isEmpty() || !f.getValue().equals("")) { if (!f.getValue().matches("[a-zA-Z\\.,\\- ()]+")) { f.add("Invalid name"); } if (f.getValue().matches("[a-z ]+")) { f.add("All small caps"); } if (f.getValue().matches("\\w+")) { f.add("Only one word"); } if (f.getValue().replace(" ", "").length() > 30) { f.add("More than 30 characters."); } if (f.getValue().replace(" ", "").length() < 2) { f.add("Invalid name."); } if (hasWhiteSpaceTrailing(f.getValue())) { f.add("has trailing white space "); } } break; case "email": if (!f.getValue().isEmpty() || !f.getValue().equals("")) { if (!EmailValidator.getInstance(true).isValid(f.getValue())) { f.add("Invalid email"); } } break; case "website": if (!f.getValue().isEmpty() || !f.getValue().equals("")) { if (!f.getValue().contains("http")) { tempURL = "http://" + f.getValue(); } else { tempURL = f.getValue(); } if (!urlValidator.isValid(tempURL)) { f.add("Invalid website"); } if (hasWhiteSpaceTrailing(f.getValue())) { f.add("has trailing white space "); } } break; case "name": officerList.add(f.getValue()); if (!f.getValue().isEmpty() || !f.getValue().equals("")) { if (!f.getValue().matches("[a-zA-Z\\.,\\-() ]+")) { f.add("Invalid name"); } if (f.getValue().replace(" ", "").length() > 30) { f.add("More than 50 characters."); } if (f.getValue().matches("[a-z ]+")) { f.add("All small caps"); } if (f.getValue().matches("\\w+")) { f.add("Only one word"); } if (f.getValue().replace(" ", "").length() < 2) { f.add("Invalid name."); } if (hasWhiteSpaceTrailing(f.getValue())) { f.add("has trailing white space "); } for (String s : invalidWords) { if (f.getValue().contains(s)) { f.add("Contains invalid word: " + s); break; } } } break; case "stockholder": officerList.add(f.getValue()); if (!f.getValue().isEmpty() || !f.getValue().equals("")) { if (!f.getValue().matches("[a-zA-Z\\.,\\-() ]+")) { f.add("Invalid name"); } if (f.getValue().replace(" ", "").length() > 30) { f.add("More than 50 characters."); } if (f.getValue().matches("[a-z ]+")) { f.add("All small caps"); } if (f.getValue().matches("\\w+")) { f.add("Only one word"); } if (f.getValue().replace(" ", "").length() < 2) { f.add("Invalid name."); } if (hasWhiteSpaceTrailing(f.getValue())) { f.add("has trailing white space "); } for (String s : invalidWords) { if (f.getValue().contains(s)) { f.add("Contains invalid word: " + s); break; } } } break; case "board": if (!f.getValue().isEmpty() || !f.getValue().equals("")) { if (!f.getValue().matches("[a-zA-Z\\.,\\-() ]+")) { f.add("Invalid position"); } for (String c : invalidCharacters) { if (f.getValue().contains(c)) { f.add("Contains invalid character [ " + c + " ]"); break; } } for (String c : invalidBoardWords) { if (f.getValue().contains(c)) { f.add("Contains invalid word [ " + c + " ]"); break; } } if (f.getValue().equalsIgnoreCase("N") || f.getValue().equalsIgnoreCase("Y")) { f.add("is letter " + f.getValue() + " only"); } if (Character.isLowerCase(f.getValue().charAt(0))) { f.add("starts with a lower case letter"); } spellCheck.setText(f.getValue(), Constants.DOC_TYPE_TEXT, "en"); spellCheck.check(); if (spellCheck.hasMisspelt()) { f.add("word is misspelled."); } } break; case "corporation": if (companyList.indexOf(f.getValue().toUpperCase()) < 0) { f.add("Company name not found on table."); } break; case "sec": if (StringUtils.countMatches(f.getValue(), "-") > 1) { f.add("Invalid SEC number"); } if (f.getValue().replace(" ", "").length() > 9) { f.add("SEC number more than 9 digits."); } if (hasWhiteSpaceTrailing(f.getValue())) { f.add("SEC has trailing white space."); } for (String c : invalidCharacters) { if (f.getValue().contains(c)) { f.add("Contains invalid character [ " + c + " ]"); break; } } break; case "tin": if (f.getValue().isEmpty() || f.getValue().equals("")) { f.add("TIN is empty"); } if (hasWhiteSpaceTrailing(f.getValue())) { f.add("TIN has trailing white space."); } if (!f.getValue().matches("[0-9]+")) { f.add("invalid TIN number"); } if (f.getValue().replace(" ", "").replace("-", "").length() > 12 || f.getValue().replace(" ", "").replace("-", "").length() < 9) { f.add("TIN number invalid length."); } if (StringUtils.countMatches(f.getValue(), "-") > 1) { f.add("Invalid TIN number"); } for (String c : invalidCharacters) { if (f.getValue().contains(c)) { f.add("Contains invalid character [ " + c + " ]"); break; } } break; case "nationality": if (!f.getValue().isEmpty() || !f.getValue().equals("")) { if (nationalityList.indexOf(f.getValue()) < 0) { f.add("nationality is misspelled."); } } break; case "purpose": splitter = f.getValue().split(" "); for (int i = 0; i < splitter.length; i++) { spellCheck.setText(splitter[i], Constants.DOC_TYPE_TEXT, "en"); spellCheck.check(); if (spellCheck.hasMisspelt()) { f.add("word is misspelled. ( " + spellCheck.getMisspelt() + " )"); } } break; case "periodCovered": try { date = sdf.parse(f.getValue()); if (!f.getValue().equals(sdf.format(date))) { f.add("Invalid date format"); } } catch (ParseException ex) { f.add("Invalid date format"); } break; case "fiscalYear": try { date = fiscalYear.parse(f.getValue()); if (!f.getValue().equals(sdf.format(date))) { f.add("Invalid date format"); } } catch (ParseException ex) { f.add("Invalid date format"); } break; case "position": if (f.getValue().contains("\\d+")) { f.add("Invalid position/designation"); } if (f.getValue().replace(" ", "").length() > 10 || f.getValue().replace(" ", "").length() < 3) { f.add("More than 30 characters."); } break; case "shareType": if (f.getValue().toLowerCase().contains("total")) { f.add("Share type contains total."); } if (f.getValue().replace(" ", "").length() > 20) { f.add("Share type More than 20 characters."); } break; case "ownership": percentOwnership.add(Double.parseDouble(f.getValue())); if (Double.parseDouble(f.getValue()) > 100) { f.add("Percent ownership more than 100%"); } break; default: break; } } else if (f.getType().equals("tin") && f.getValue().equals("*N/A")) { f.add("TIN is N/A"); } } } } catch (EncryptedDocumentException | SuggesterException ex) { Logger.getLogger(ValidationProcess.class.getName()).log(Level.SEVERE, null, ex); } return xmlBatchHolder; } private List<FS> extractFSErrors() { for (FS f : fsData) { if ((f.getTotalAssets() - f.getTotalLiabilities() - f.getTotalShareholderEquity()) != 0) { f.add("Total Assets not equal to Total Liabilities and Shareholder Equity\t" + (f.getTotalAssets() - f.getTotalLiabilities() - f.getTotalShareholderEquity())); } if (((f.getGrossRevenue() / f.getGrossRevenueP()) * 100 > 500)) { f.add("Year-on-year turnover rate is higher than 500%\t" + ((f.getGrossRevenue() / f.getGrossRevenueP()) * 100)); } else if (((f.getGrossRevenueP() / f.getGrossRevenue()) * 100 > 500)) { f.add("Year-on-year turnover rate is higher than 500%\t" + ((f.getGrossRevenueP() / f.getGrossRevenue()) * 100)); } if (((f.getGrossRevenue() / f.getGrossRevenueP()) * 100 < 0.002)) { f.add("Year-on-year turnover rate is higher than 500%\t" + ((f.getGrossRevenue() / f.getGrossRevenueP()) * 100)); } else if (((f.getGrossRevenueP() / f.getGrossRevenue()) * 100 < 0.002)) { f.add("Year-on-year turnover rate is higher than 500%\t" + ((f.getGrossRevenueP() / f.getGrossRevenue()) * 100)); } if ((((f.getNetIncome() / f.getGrossRevenue()) * 100) > 1000)) { f.add("Net Profit Margin higher than 1000%\t" + (((f.getNetIncome() / f.getGrossRevenue()) * 100))); } else if ((((f.getNetIncomeP() / f.getGrossRevenueP()) * 100) > 1000)) { f.add("Net Profit Margin higher than 1000%\t" + (((f.getNetIncomeP() / f.getGrossRevenueP()) * 100))); } } return fsData; } private boolean hasWhiteSpaceTrailing(String str) { if (Character.isWhitespace(str.charAt(str.length() - 1))) { return true; } return false; } private static String toAlphabetic(int i) { if (i < 0) { return "-" + toAlphabetic(-i - 1); } int quot = i / 26; int rem = i % 26; char letter = (char) ((int) 'A' + rem); if (quot == 0) { return "" + letter; } else { return toAlphabetic(quot - 1) + letter; } } @Override public Map<String, List<?>> call() throws Exception { return getLists(); } }