/**
* Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.financial.security.equity;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Objects;
/**
* The description of a GICS code.
* <p>
* This provides a description for {@link GICSCode}.
* <p>
* This is an effective singleton.
*
* S&P provides an Excel file of GICS code mappings. To load support for these mappings, the file provided by S&P must be renamed 'gics_map.xls'
* and be located in the classpath. If the file does not have this name or the Classloader cannot find it, an error will be logged
* and GICS code mapping will not be available.
*
* @see <a href="http://www.standardandpoors.com/indices/gics/en/us">Standard and Poors</a>
*/
final class GICSCodeDescription {
/** Logger. */
public static final GICSCodeDescription INSTANCE = new GICSCodeDescription();
/** Logger. */
private static final Logger s_logger = LoggerFactory.getLogger(GICSCodeDescription.class);
private static final String GICS_FILE_NAME = "gics_map.xls";
/**
* The descriptions.
*/
private static final Map<String, String> DESCRIPTIONS = new HashMap<String, String>();
static {
InputStream xlsStream = GICSCodeDescription.class.getClassLoader().getResourceAsStream(GICS_FILE_NAME);
processGICSExcelWorkbook(xlsStream, DESCRIPTIONS);
}
/**
* Load S&P GICS code mappings from an Excel file stream
* @param inputStream opened stream based on Excel file
* @param gicsMap Map to add mappings to
*/
static void processGICSExcelWorkbook(InputStream inputStream, Map<String, String> gicsMap) {
Workbook workbook;
try {
workbook = new HSSFWorkbook(new BufferedInputStream(inputStream));
} catch (IOException e) {
s_logger.warn("Unable to find S&P GICS Code Mapping file '" + GICS_FILE_NAME +
"' in classpath; unable to use GICS Codes: " + e);
return;
}
processGICSExcelWorkbook(workbook, gicsMap);
}
/**
* Load S&P GICS code mappings from an Apace POI HSSFWorkbook
* @param workbook HSSFWorkbook to parse S&P GCIS Excel
* @param gicsMap Map to add mappings to
*/
static void processGICSExcelWorkbook(Workbook workbook, Map<String, String> gicsMap) {
//Assume 1 sheet
Sheet sheet = workbook.getSheetAt(0);
if (sheet == null) {
return;
}
for (int rowNum = sheet.getFirstRowNum(); rowNum <= sheet.getLastRowNum(); rowNum++) {
Row row = sheet.getRow(rowNum);
if (row == null) {
continue;
}
for (int cellNum = 0; cellNum < row.getPhysicalNumberOfCells(); cellNum++) {
Cell cell = row.getCell(cellNum, Row.CREATE_NULL_AS_BLANK);
if (isNumeric(cell)) {
//worst case if the Excel file is in an incorrect (or updated) format
// is that number -> random or empty string mappings will be created
gicsMap.put(getGICSCellValue(cell), getGICSCellValue(row, cellNum + 1));
}
}
}
}
/**
* Get the value of the Apache POI Cell as a String. If the Cell type is numeric (always a double with POI),
* the value is converted to an integer. The GCIS file does not contain any floating point values so (at this time)
* this is a valid operation
*
* @param cell Apache POI Cell
* @return String value
*/
static String getGICSCellValue(Cell cell) {
if (cell == null) {
return "";
}
switch (cell.getCellType()) {
case Cell.CELL_TYPE_NUMERIC:
return Integer.valueOf((int) cell.getNumericCellValue()).toString();
case Cell.CELL_TYPE_STRING:
return cell.getStringCellValue();
case Cell.CELL_TYPE_BOOLEAN:
return Boolean.toString(cell.getBooleanCellValue());
case Cell.CELL_TYPE_BLANK:
return "";
default:
return "null";
}
}
/**
* Get the value of the Apache POI Cell specified by the row and cell num (column) as a String.
* If row,cellNum defines a null or blank cell, an empty String is returned
* @param row Apace POI Row
* @param cellNum cell number in Row
* @return String value of specified cell, or empty String if invalid cell
*/
static String getGICSCellValue(Row row, int cellNum) {
return getGICSCellValue(row.getCell(cellNum, Row.CREATE_NULL_AS_BLANK));
}
/**
* Determine if specfied Cell contains a number or something else based on the cell
* type defined in the source Excel file.
* @param cell Apace POI Cell
* @return true if numeric, false if any other type
*/
static boolean isNumeric(Cell cell) {
return cell.getCellType() == Cell.CELL_TYPE_NUMERIC;
}
/**
* Creates an instance.
*/
private GICSCodeDescription() {
}
//-------------------------------------------------------------------------
/**
* Gets the description for the code.
*
* @param code the code to lookup, not null
* @return the description, "Unknown" if not found
*/
String getDescription(String code) {
String desc = DESCRIPTIONS.get(code);
return Objects.firstNonNull(desc, "Unknown");
}
/**
* Gets all the sector descriptions
* @return a collection of all the sector description strings
*/
Collection<String> getAllSectorDescriptions() {
return getAllDescriptions(2);
}
/**
* Gets all the industry group descriptions
* @return a collection of all the industry group description strings
*/
Collection<String> getAllIndustryGroupDescriptions() {
return getAllDescriptions(4);
}
/**
* Gets all the industry descriptions
* @return a collection of all the industry description strings
*/
Collection<String> getAllIndustryDescriptions() {
return getAllDescriptions(6);
}
/**
* Gets all the sub-industry descriptions
* @return a collection of all the sub-industry description strings
*/
Collection<String> getAllSubIndustryDescriptions() {
return getAllDescriptions(8);
}
/**
* Get all descriptions with a particular code length
* @param codeLength the number of digits in the code
* @return a collection of all the description strings
*/
private Collection<String> getAllDescriptions(int codeLength) {
Collection<String> results = new ArrayList<String>();
for (Map.Entry<String, String> entry : DESCRIPTIONS.entrySet()) {
if (entry.getKey().length() == codeLength) {
results.add(entry.getValue());
}
}
return results;
}
//-------------------------------------------------------------------------
/**
* Gets a simple string description for the class.
*
* @return the string, not null
*/
@Override
public String toString() {
return getClass().getSimpleName();
}
/**
* Output the current contents of the GICS map to the log
* @param gicsMap Map of GICS code -> description
*/
static void dumpGICSMap(Map<String, String> gicsMap) {
for (Map.Entry<String, String> entry : gicsMap.entrySet()) {
s_logger.info(" {} -> {} ", entry.getKey(), entry.getValue());
}
}
/**
* For testing. Logs the contents of the GICS code->description map that is loaded
* statically
* @param args
* @return
*/
private boolean run(String[] args) {
s_logger.info(this.toString() + " is initialising...");
s_logger.info("Current working directory is " + System.getProperty("user.dir"));
dumpGICSMap(DESCRIPTIONS);
s_logger.info(this.toString() + " is finished.");
return true;
}
/**
* For standalone testing
* @param args command line arguments
*/
public static void main(String[] args) { // CSIGNORE
boolean success = new GICSCodeDescription().run(args);
System.exit(success ? 0 : 1);
}
}