Java tutorial
/* * Copyright 2014-2018 JKOOL, LLC. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.jkoolcloud.tnt4j.streams.inputs; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.text.DateFormat; import java.text.DecimalFormat; import java.text.SimpleDateFormat; import java.util.*; import java.util.regex.Pattern; import javax.xml.parsers.ParserConfigurationException; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.poi.hssf.eventusermodel.FormatTrackingHSSFListener; import org.apache.poi.hssf.eventusermodel.HSSFEventFactory; import org.apache.poi.hssf.eventusermodel.HSSFListener; import org.apache.poi.hssf.eventusermodel.HSSFRequest; import org.apache.poi.hssf.record.*; import org.apache.poi.hssf.record.crypto.Biff8EncryptionKey; import org.apache.poi.hssf.usermodel.HSSFDateUtil; import org.apache.poi.openxml4j.exceptions.OpenXML4JException; import org.apache.poi.openxml4j.opc.OPCPackage; import org.apache.poi.openxml4j.opc.PackageAccess; import org.apache.poi.poifs.filesystem.DirectoryNode; import org.apache.poi.poifs.filesystem.DocumentFactoryHelper; import org.apache.poi.poifs.filesystem.FileMagic; import org.apache.poi.poifs.filesystem.NPOIFSFileSystem; import org.apache.poi.ss.usermodel.*; import org.apache.poi.ss.util.CellAddress; import org.apache.poi.ss.util.CellReference; import org.apache.poi.util.SAXHelper; import org.apache.poi.xssf.eventusermodel.ReadOnlySharedStringsTable; import org.apache.poi.xssf.eventusermodel.XSSFReader; import org.apache.poi.xssf.eventusermodel.XSSFSheetXMLHandler; import org.apache.poi.xssf.eventusermodel.XSSFSheetXMLHandler.SheetContentsHandler; import org.apache.poi.xssf.model.StylesTable; import org.apache.poi.xssf.streaming.SXSSFCell; import org.apache.poi.xssf.streaming.SXSSFRow; import org.apache.poi.xssf.streaming.SXSSFSheet; import org.apache.poi.xssf.streaming.SXSSFWorkbook; import org.apache.poi.xssf.usermodel.XSSFComment; import org.xml.sax.ContentHandler; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.XMLReader; import com.jkoolcloud.tnt4j.core.OpLevel; import com.jkoolcloud.tnt4j.sink.DefaultEventSinkFactory; import com.jkoolcloud.tnt4j.sink.EventSink; import com.jkoolcloud.tnt4j.streams.configure.MsOfficeStreamProperties; import com.jkoolcloud.tnt4j.streams.configure.StreamProperties; import com.jkoolcloud.tnt4j.streams.utils.IntRange; import com.jkoolcloud.tnt4j.streams.utils.MsOfficeStreamConstants; import com.jkoolcloud.tnt4j.streams.utils.StreamsResources; import com.jkoolcloud.tnt4j.streams.utils.Utils; /** * Implements a MS Excel {@link org.apache.poi.xssf.streaming.SXSSFWorkbook} stored activity stream, where each workbook * sheet {@link SXSSFRow} is assumed to represent a single activity or event which should be recorded. * <p> * NOTE: to read MS Excel file Apache POI SXSSF API is used to optimize memory consumption. But in some cases cell value * formatting and formula value evaluation may be limited! If you notice any critical inaccuracies, use * {@link com.jkoolcloud.tnt4j.streams.inputs.ExcelRowStream} instead. * <p> * This activity stream requires parsers that can support {@link org.apache.poi.ss.usermodel.Row} data. * <p> * This activity stream supports the following configuration properties (in addition to those supported by * {@link AbstractBufferedStream}): * <ul> * <li>FileName - the system-dependent file name of MS Excel document. (Required)</li> * <li>SheetsToProcess - defines workbook sheets name filter mask (wildcard or RegEx) to process only sheets which names * matches this mask. (Optional)</li> * <li>WorkbookPassword - excel workbook password. (Optional)</li> * <li>RangeToStream - defines streamed data rows index range. Default value - {@code 1:}. (Optional)</li> * </ul> * * @version $Revision: 1 $ * * @see com.jkoolcloud.tnt4j.streams.parsers.ActivityParser#isDataClassSupported(Object) */ public class ExcelSXSSFRowStream extends AbstractBufferedStream<Row> { private static final EventSink LOGGER = DefaultEventSinkFactory.defaultEventSink(ExcelSXSSFRowStream.class); /** * Stream attribute defining file name. */ private String fileName = null; private String sheetName = null; private Pattern sheetNameMatcher = null; private String wbPass = null; private String rangeValue = "1:"; // NON-NLS private IntRange rowRange; private boolean ended = false; /** * Constructs a new ExcelSXSSFRowStream. Requires configuration settings to set input stream source. */ public ExcelSXSSFRowStream() { super(); } @Override protected EventSink logger() { return LOGGER; } @Override public void setProperties(Collection<Map.Entry<String, String>> props) { super.setProperties(props); if (CollectionUtils.isNotEmpty(props)) { for (Map.Entry<String, String> prop : props) { String name = prop.getKey(); String value = prop.getValue(); if (StreamProperties.PROP_FILENAME.equalsIgnoreCase(name)) { fileName = value; } else if (MsOfficeStreamProperties.PROP_SHEETS.equalsIgnoreCase(name)) { sheetName = value; if (StringUtils.isNotEmpty(sheetName)) { sheetNameMatcher = Pattern.compile(Utils.wildcardToRegex2(sheetName)); } } else if (StreamProperties.PROP_RANGE_TO_STREAM.equalsIgnoreCase(name)) { if (StringUtils.isNotEmpty(value)) { rangeValue = value; } } else if (MsOfficeStreamProperties.PROP_WORKBOOK_PASS.equalsIgnoreCase(name)) { if (StringUtils.isNotEmpty(value)) { wbPass = value; } } } } } @Override public Object getProperty(String name) { if (StreamProperties.PROP_FILENAME.equalsIgnoreCase(name)) { return fileName; } if (MsOfficeStreamProperties.PROP_SHEETS.equalsIgnoreCase(name)) { return sheetName; } if (StreamProperties.PROP_RANGE_TO_STREAM.equalsIgnoreCase(name)) { return rangeValue; } if (MsOfficeStreamProperties.PROP_WORKBOOK_PASS.equalsIgnoreCase(name)) { return wbPass; } return super.getProperty(name); } @Override protected long getActivityItemByteSize(Row activityItem) { return activityItem == null ? 0 : getRowByteSize(activityItem); } private static long getRowByteSize(Row activityItem) { Iterator<Cell> cells = activityItem.cellIterator(); if (cells == null) { return 0; } long rowSize = 0; while (cells.hasNext()) { Cell cell = cells.next(); rowSize += cell.toString().getBytes().length; } return rowSize; } @Override protected boolean isInputEnded() { return ended; } /** * Checks if row is within stream defined range and adds it to stream input buffer. * * @param row * row instance to add to buffer * @param rowNumber * row number to check if it is in range * @return {@code true} if row was added to input buffer, {@code false} = otherwise * * @throws IllegalStateException * if buffer queue is not initialized */ protected boolean checkAndAddToInputToBuffer(Row row, int rowNumber) throws IllegalStateException { // NOTE: adding 1, since rowNumber is 0 based while for user convenience range starts from 1. int rRowNum = rowNumber + 1; // must pass rowNumber cause inputData.getRowNum() iterates whole sheet if (IntRange.inRange(rowRange, rRowNum)) { return addInputToBuffer(row); } else { skipFilteredActivities(); } if (rowRange.getTo().compareTo(rRowNum) < 0) { offerDieMarker(); } return false; } @Override protected void applyProperties() throws Exception { super.applyProperties(); if (StringUtils.isEmpty(fileName)) { throw new IllegalStateException( StreamsResources.getStringFormatted(StreamsResources.RESOURCE_BUNDLE_NAME, "TNTInputStream.property.undefined", StreamProperties.PROP_FILENAME)); } rowRange = IntRange.getRange(rangeValue); } @Override protected void initialize() throws Exception { super.initialize(); Thread excelFileReader = new Thread(new Runnable() { @Override public void run() { try { File inputFile = new File(fileName); InputStream is = new FileInputStream(inputFile); is = FileMagic.prepareToCheckMagic(is); FileMagic fm = FileMagic.valueOf(is); Utils.close(is); if (fm == FileMagic.OOXML) { readXLXS(inputFile); } else if (fm == FileMagic.OLE2) { readXLS(inputFile); } else { throw new IOException( StreamsResources.getStringFormatted(MsOfficeStreamConstants.RESOURCE_BUNDLE_NAME, "ExcelSXSSFRowStream.unsupported.format", fileName)); } } catch (Exception e) { Utils.logThrowable(LOGGER, OpLevel.ERROR, StreamsResources.getBundle(MsOfficeStreamConstants.RESOURCE_BUNDLE_NAME), "ExcelSXSSFRowStream.file.read.failed", fileName, e); } ended = true; } }); excelFileReader.start(); } /** * Reads HSSF (XLS) format excel file using Apache POI streaming SXSSF API. * * @param xlsFile * excel HSSF format file to read * * @throws IOException * if excel file or workbook can't be read */ protected void readXLS(File xlsFile) throws IOException { NPOIFSFileSystem fs = null; InputStream dis = null; boolean passwordSet = false; try { fs = new NPOIFSFileSystem(xlsFile, true); DirectoryNode root = fs.getRoot(); if (root.hasEntry("EncryptedPackage")) { // NON-NLS dis = DocumentFactoryHelper.getDecryptedStream(fs, wbPass); } else { if (wbPass != null) { Biff8EncryptionKey.setCurrentUserPassword(wbPass); passwordSet = true; } dis = fs.createDocumentInputStream("Workbook"); // NON-NLS } HSSFRequest req = new HSSFRequest(); XLSEventListener listener = new XLSEventListener(this); FormatTrackingHSSFListener formatsListener = new FormatTrackingHSSFListener(listener, Locale.getDefault()); listener.setFormatListener(formatsListener); req.addListenerForAllRecords(formatsListener); HSSFEventFactory factory = new HSSFEventFactory(); factory.processEvents(req, dis); } finally { if (passwordSet) { Biff8EncryptionKey.setCurrentUserPassword((String) null); } Utils.close(fs); Utils.close(dis); } } /** * Reads XSSF (XLXS) format excel file using Apache POI streaming SXSSF API. * * @param xlsxFile * excel XSSF format file to read * * @throws IOException * if excel file or workbook can't be read * @throws SAXException * if file contained XML reading fails * @throws OpenXML4JException * if file contained XML reading fails */ protected void readXLXS(File xlsxFile) throws IOException, SAXException, OpenXML4JException { try (OPCPackage xlsxPackage = OPCPackage.open(xlsxFile, PackageAccess.READ)) { ReadOnlySharedStringsTable strings = new ReadOnlySharedStringsTable(xlsxPackage); XSSFReader xssfReader = new XSSFReader(xlsxPackage); StylesTable styles = xssfReader.getStylesTable(); XSSFReader.SheetIterator sIter = (XSSFReader.SheetIterator) xssfReader.getSheetsData(); while (sIter.hasNext()) { try (InputStream sStream = sIter.next()) { String sheetName = sIter.getSheetName(); boolean match = sheetNameMatcher == null || sheetNameMatcher.matcher(sheetName).matches(); if (!match) { continue; } SXSSFSheet sheet = new SXSSFSheet(new SXSSFWorkbook(), null); processSXSSFSheet(styles, strings, new XLSXSheetContentHandler(this, sheet), sStream); } } } } /** * Parses the content of one sheet using the specified styles and shared-strings tables. * * @param styles * the table of styles that may be referenced by cells in the sheet * @param strings * the table of strings that may be referenced by cells in the sheet * @param sheetHandler * the sheet handler * @param sheetInputStream * the input stream to read the sheet-data from * * @throws IOException * if sheet input stream read fails * @throws SAXException * if sheet input stream provided XML can't be parsed */ public static void processSXSSFSheet(StylesTable styles, ReadOnlySharedStringsTable strings, SheetContentsHandler sheetHandler, InputStream sheetInputStream) throws IOException, SAXException { DataFormatter formatter = new DataFormatter(); InputSource sheetSource = new InputSource(sheetInputStream); try { XMLReader sheetParser = SAXHelper.newXMLReader(); ContentHandler handler = new XSSFSheetXMLHandler(styles, null, strings, sheetHandler, formatter, false); sheetParser.setContentHandler(handler); sheetParser.parse(sheetSource); } catch (ParserConfigurationException exc) { throw new RuntimeException( StreamsResources.getStringFormatted(MsOfficeStreamConstants.RESOURCE_BUNDLE_NAME, "ExcelSXSSFRowStream.sax.cfg.error", Utils.getExceptionMessages(exc))); } } private static class XLSXSheetContentHandler implements SheetContentsHandler { private ExcelSXSSFRowStream stream; private SXSSFSheet sheet; private SXSSFRow currentRow = null; private int currentRowNum = -1; private int currentColNum = -1; /** * Constructs a new XLSXSheetContentHandler. * * @param stream * referenced stream instance * @param sheet * sheet instance to process */ XLSXSheetContentHandler(ExcelSXSSFRowStream stream, SXSSFSheet sheet) { this.stream = stream; this.sheet = sheet; } @Override public void startRow(int rowNum) { LOGGER.log(OpLevel.TRACE, StreamsResources.getBundle(MsOfficeStreamConstants.RESOURCE_BUNDLE_NAME), "ExcelSXSSFRowStream.starting.xssf.row", rowNum); this.currentRowNum = rowNum; currentColNum = -1; currentRow = new SXSSFRow(sheet); } @Override public void endRow(int rowNum) { LOGGER.log(OpLevel.TRACE, StreamsResources.getBundle(MsOfficeStreamConstants.RESOURCE_BUNDLE_NAME), "ExcelSXSSFRowStream.ending.xssf.row", rowNum); stream.checkAndAddToInputToBuffer(currentRow, rowNum); currentRow = null; } @Override public void cell(String cellReference, String formattedValue, XSSFComment comment) { LOGGER.log(OpLevel.TRACE, StreamsResources.getBundle(MsOfficeStreamConstants.RESOURCE_BUNDLE_NAME), "ExcelSXSSFRowStream.got.xssf.cell", cellReference, formattedValue, comment == null ? null : comment.getString()); // gracefully handle missing CellRef here in a similar way as XSSFCell does if (cellReference == null) { cellReference = new CellAddress(currentRowNum, currentColNum).formatAsString(); } currentColNum = new CellReference(cellReference).getCol(); try { double v = Double.parseDouble(formattedValue); Cell cell = currentRow.createCell(currentColNum, CellType.NUMERIC); cell.setCellValue(v); } catch (NumberFormatException e) { Cell cell = currentRow.createCell(currentColNum, CellType.STRING); cell.setCellValue(formattedValue); } } @Override public void headerFooter(String text, boolean isHeader, String tagName) { } } private static class XLSEventListener implements HSSFListener { private ExcelSXSSFRowStream stream; private SXSSFSheet sheet; private SXSSFRow currentRow = null; private int currentRowNum = -1; private int currentColNum = -1; private Queue<String> sheets = new LinkedList<>(); private String currentSheet; private SSTRecord sstRec; private FormatTrackingHSSFListener formatListener; /** * Constructs a new XLSEventListener. * * @param stream * referenced stream instance */ public XLSEventListener(ExcelSXSSFRowStream stream) throws IOException { this.stream = stream; this.sheet = new SXSSFSheet(new SXSSFWorkbook(), null); } /** * Sets format tracking listener to format cell values. * * @param formatListener * format tracking listener instance */ void setFormatListener(FormatTrackingHSSFListener formatListener) { this.formatListener = formatListener; } /** * Handles processing of provided HSSF records. * * @param record * the record to be processed */ @Override public void processRecord(Record record) { boolean currentSheetMatches = currentSheet == null || stream.sheetNameMatcher == null || stream.sheetNameMatcher.matcher(currentSheet).matches(); switch (record.getSid()) { case BOFRecord.sid: BOFRecord bof = (BOFRecord) record; if (bof.getType() == bof.TYPE_WORKBOOK) { LOGGER.log(OpLevel.TRACE, StreamsResources.getBundle(MsOfficeStreamConstants.RESOURCE_BUNDLE_NAME), "ExcelSXSSFRowStream.hssf.workbook"); } else if (bof.getType() == bof.TYPE_WORKSHEET) { currentSheet = sheets.poll(); LOGGER.log(OpLevel.TRACE, StreamsResources.getBundle(MsOfficeStreamConstants.RESOURCE_BUNDLE_NAME), "ExcelSXSSFRowStream.hssf.sheet", currentSheet); } break; case SSTRecord.sid: sstRec = (SSTRecord) record; for (int i = 0; i < sstRec.getNumUniqueStrings(); i++) { LOGGER.log(OpLevel.TRACE, StreamsResources.getBundle(MsOfficeStreamConstants.RESOURCE_BUNDLE_NAME), "ExcelSXSSFRowStream.hssf.sstRecord", i, sstRec.getString(i)); } break; case BoundSheetRecord.sid: BoundSheetRecord bsr = (BoundSheetRecord) record; sheets.add(bsr.getSheetname()); LOGGER.log(OpLevel.TRACE, StreamsResources.getBundle(MsOfficeStreamConstants.RESOURCE_BUNDLE_NAME), "ExcelSXSSFRowStream.hssf.bsRecord", bsr.getSheetname(), bsr.getPositionOfBof()); break; case EOFRecord.sid: if (currentRow != null && currentSheetMatches) { LOGGER.log(OpLevel.TRACE, StreamsResources.getBundle(MsOfficeStreamConstants.RESOURCE_BUNDLE_NAME), "ExcelSXSSFRowStream.hssf.sheets.end"); stream.checkAndAddToInputToBuffer(currentRow, currentRowNum); } LOGGER.log(OpLevel.TRACE, StreamsResources.getBundle(MsOfficeStreamConstants.RESOURCE_BUNDLE_NAME), "ExcelSXSSFRowStream.hssf.workbook.end"); break; case RowRecord.sid: RowRecord rowRec = (RowRecord) record; LOGGER.log(OpLevel.TRACE, StreamsResources.getBundle(MsOfficeStreamConstants.RESOURCE_BUNDLE_NAME), "ExcelSXSSFRowStream.hssf.row", currentSheet, rowRec.getRowNumber(), rowRec.getFirstCol(), rowRec.getLastCol()); break; case LabelSSTRecord.sid: LabelSSTRecord lRec = (LabelSSTRecord) record; LOGGER.log(OpLevel.TRACE, StreamsResources.getBundle(MsOfficeStreamConstants.RESOURCE_BUNDLE_NAME), "ExcelSXSSFRowStream.hssf.string.cell", sstRec.getString(lRec.getSSTIndex()), lRec.getRow(), lRec.getColumn()); if (!currentSheetMatches) { LOGGER.log(OpLevel.TRACE, StreamsResources.getBundle(MsOfficeStreamConstants.RESOURCE_BUNDLE_NAME), "ExcelSXSSFRowStream.hssf.record.skip", currentSheet); return; } processCell(lRec); SXSSFCell cell = currentRow.createCell(lRec.getColumn(), CellType.STRING); cell.setCellValue(String.valueOf(sstRec.getString(lRec.getSSTIndex()))); break; case NumberRecord.sid: NumberRecord numRec = (NumberRecord) record; if (!currentSheetMatches) { LOGGER.log(OpLevel.TRACE, StreamsResources.getBundle(MsOfficeStreamConstants.RESOURCE_BUNDLE_NAME), "ExcelSXSSFRowStream.hssf.record.skip", currentSheet); return; } processCell(numRec); LOGGER.log(OpLevel.TRACE, StreamsResources.getBundle(MsOfficeStreamConstants.RESOURCE_BUNDLE_NAME), "ExcelSXSSFRowStream.hssf.number.cell", numRec.getValue(), numRec.getRow(), numRec.getColumn()); creteCell(CellType.NUMERIC, formatNumberValue(numRec, numRec.getValue()), numRec); break; case FormulaRecord.sid: FormulaRecord fr = (FormulaRecord) record; if (!currentSheetMatches) { LOGGER.log(OpLevel.TRACE, StreamsResources.getBundle(MsOfficeStreamConstants.RESOURCE_BUNDLE_NAME), "ExcelSXSSFRowStream.hssf.record.skip", currentSheet); return; } processCell(fr); LOGGER.log(OpLevel.TRACE, StreamsResources.getBundle(MsOfficeStreamConstants.RESOURCE_BUNDLE_NAME), "ExcelSXSSFRowStream.hssf.formula.cell", fr.getValue(), fr.getRow(), fr.getColumn()); creteCell(CellType.NUMERIC, formatNumberValue(fr, fr.getValue()), fr); break; case BoolErrRecord.sid: BoolErrRecord bErrRec = (BoolErrRecord) record; if (!currentSheetMatches) { LOGGER.log(OpLevel.TRACE, StreamsResources.getBundle(MsOfficeStreamConstants.RESOURCE_BUNDLE_NAME), "ExcelSXSSFRowStream.hssf.record.skip", currentSheet); return; } processCell(bErrRec); LOGGER.log(OpLevel.TRACE, StreamsResources.getBundle(MsOfficeStreamConstants.RESOURCE_BUNDLE_NAME), "ExcelSXSSFRowStream.hssf.error.cell", bErrRec.getErrorValue(), bErrRec.getRow(), bErrRec.getColumn()); cell = currentRow.createCell(bErrRec.getColumn(), CellType.ERROR); cell.setCellErrorValue(bErrRec.getErrorValue()); break; default: LOGGER.log(OpLevel.TRACE, StreamsResources.getBundle(MsOfficeStreamConstants.RESOURCE_BUNDLE_NAME), "ExcelSXSSFRowStream.unhandled.hssf.record", record); } } private void creteCell(CellType type, Object value, CellRecord cellRec) { SXSSFCell cell = currentRow.createCell(cellRec.getColumn(), type); if (value instanceof Date) { cell.setCellValue((Date) value); CellStyle cellStyle = sheet.getWorkbook().createCellStyle(); String formatString = formatListener.getFormatString(cellRec); CreationHelper createHelper = sheet.getWorkbook().getCreationHelper(); cellStyle.setDataFormat(createHelper.createDataFormat().getFormat(formatString)); cell.setCellStyle(cellStyle); } else if (value instanceof Double) { cell.setCellValue((Double) value); } else { cell.setCellValue(value.toString()); } } private void processCell(CellRecord cellRec) { if (currentRowNum != cellRec.getRow()) { if (currentRowNum != -1) { LOGGER.log(OpLevel.TRACE, StreamsResources.getBundle(MsOfficeStreamConstants.RESOURCE_BUNDLE_NAME), "ExcelSXSSFRowStream.hssf.rows.end"); stream.checkAndAddToInputToBuffer(currentRow, cellRec.getRow()); } currentRow = new SXSSFRow(sheet); currentRowNum = cellRec.getRow(); currentColNum = cellRec.getColumn(); } } /** * Formats provided <tt>value</tt> as date when <tt>cell</tt> has date formatting. If <tt>cell</tt> has number * formatting, formats <tt>value</tt> as decimal number. In all other cases returns <tt>value</tt> without * changes. * * @param cell * cell instance containing value * @param value * number/date value * @return date string built from <tt>value</tt> if <tt>cell</tt> has date formatting, formatted <tt>value</tt> * number string if <tt>cell</tt> has number formatting, plain <tt>value</tt> - otherwise. */ private Object formatNumberValue(CellValueRecordInterface cell, double value) { if (formatListener == null) { return value; } int formatIndex = formatListener.getFormatIndex(cell); String formatString = formatListener.getFormatString(cell); if (formatString == null) { return value; } if (HSSFDateUtil.isADateFormat(formatIndex, formatString) && HSSFDateUtil.isValidExcelDate(value)) { // Java wants M not m for month formatString = formatString.replace('m', 'M'); // Change \- into -, if it's there formatString = formatString.replaceAll("\\\\-", "-"); // NON-NLS Date d = HSSFDateUtil.getJavaDate(value, false); DateFormat df = new SimpleDateFormat(formatString); return df.format(d); } if (formatString == "General") { // NON-NLS return value; } DecimalFormat df = new DecimalFormat(formatString); return df.format(value); } } }