org.codehaus.mojo.license.extended.spreadsheet.CalcFileWriter Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of license-maven-plugin Show documentation
Show all versions of license-maven-plugin Show documentation
Maven plugin to download and collect license files from project dependencies.
The newest version!
package org.codehaus.mojo.license.extended.spreadsheet;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.BiConsumer;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.maven.model.Developer;
import org.apache.maven.model.Organization;
import org.apache.maven.model.Scm;
import org.codehaus.mojo.license.download.ProjectLicense;
import org.codehaus.mojo.license.download.ProjectLicenseInfo;
import org.codehaus.mojo.license.extended.ExtendedInfo;
import org.codehaus.mojo.license.extended.InfoFile;
import org.odftoolkit.odfdom.doc.OdfSpreadsheetDocument;
import org.odftoolkit.odfdom.doc.table.OdfTable;
import org.odftoolkit.odfdom.doc.table.OdfTableCell;
import org.odftoolkit.odfdom.doc.table.OdfTableCellRange;
import org.odftoolkit.odfdom.doc.table.OdfTableColumn;
import org.odftoolkit.odfdom.doc.table.OdfTableRow;
import org.odftoolkit.odfdom.dom.OdfContentDom;
import org.odftoolkit.odfdom.dom.OdfSettingsDom;
import org.odftoolkit.odfdom.dom.element.config.ConfigConfigItemElement;
import org.odftoolkit.odfdom.dom.element.config.ConfigConfigItemMapEntryElement;
import org.odftoolkit.odfdom.dom.element.style.StyleParagraphPropertiesElement;
import org.odftoolkit.odfdom.dom.element.style.StyleTableCellPropertiesElement;
import org.odftoolkit.odfdom.dom.element.style.StyleTextPropertiesElement;
import org.odftoolkit.odfdom.dom.element.text.TextAElement;
import org.odftoolkit.odfdom.dom.style.OdfStyleFamily;
import org.odftoolkit.odfdom.dom.style.props.OdfTableColumnProperties;
import org.odftoolkit.odfdom.incubator.doc.office.OdfOfficeStyles;
import org.odftoolkit.odfdom.incubator.doc.style.OdfStyle;
import org.odftoolkit.odfdom.type.Color;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import static org.codehaus.mojo.license.extended.spreadsheet.SpreadsheetUtil.COPYRIGHT_JOIN_SEPARATOR;
import static org.codehaus.mojo.license.extended.spreadsheet.SpreadsheetUtil.CurrentRowData;
import static org.codehaus.mojo.license.extended.spreadsheet.SpreadsheetUtil.DEVELOPERS_COLUMNS;
import static org.codehaus.mojo.license.extended.spreadsheet.SpreadsheetUtil.DEVELOPERS_END_COLUMN;
import static org.codehaus.mojo.license.extended.spreadsheet.SpreadsheetUtil.DEVELOPERS_START_COLUMN;
import static org.codehaus.mojo.license.extended.spreadsheet.SpreadsheetUtil.EXTENDED_INFO_END_COLUMN;
import static org.codehaus.mojo.license.extended.spreadsheet.SpreadsheetUtil.EXTENDED_INFO_START_COLUMN;
import static org.codehaus.mojo.license.extended.spreadsheet.SpreadsheetUtil.GAP_WIDTH;
import static org.codehaus.mojo.license.extended.spreadsheet.SpreadsheetUtil.GENERAL_END_COLUMN;
import static org.codehaus.mojo.license.extended.spreadsheet.SpreadsheetUtil.GENERAL_START_COLUMN;
import static org.codehaus.mojo.license.extended.spreadsheet.SpreadsheetUtil.INCEPTION_YEAR_WIDTH;
import static org.codehaus.mojo.license.extended.spreadsheet.SpreadsheetUtil.INFO_LICENSES_COLUMNS;
import static org.codehaus.mojo.license.extended.spreadsheet.SpreadsheetUtil.INFO_LICENSES_END_COLUMN;
import static org.codehaus.mojo.license.extended.spreadsheet.SpreadsheetUtil.INFO_LICENSES_START_COLUMN;
import static org.codehaus.mojo.license.extended.spreadsheet.SpreadsheetUtil.INFO_NOTICES_COLUMNS;
import static org.codehaus.mojo.license.extended.spreadsheet.SpreadsheetUtil.INFO_NOTICES_END_COLUMN;
import static org.codehaus.mojo.license.extended.spreadsheet.SpreadsheetUtil.INFO_NOTICES_START_COLUMN;
import static org.codehaus.mojo.license.extended.spreadsheet.SpreadsheetUtil.INFO_SPDX_COLUMNS;
import static org.codehaus.mojo.license.extended.spreadsheet.SpreadsheetUtil.INFO_SPDX_END_COLUMN;
import static org.codehaus.mojo.license.extended.spreadsheet.SpreadsheetUtil.INFO_SPDX_START_COLUMN;
import static org.codehaus.mojo.license.extended.spreadsheet.SpreadsheetUtil.LICENSES_COLUMNS;
import static org.codehaus.mojo.license.extended.spreadsheet.SpreadsheetUtil.LICENSES_END_COLUMN;
import static org.codehaus.mojo.license.extended.spreadsheet.SpreadsheetUtil.LICENSES_START_COLUMN;
import static org.codehaus.mojo.license.extended.spreadsheet.SpreadsheetUtil.MANIFEST_END_COLUMN;
import static org.codehaus.mojo.license.extended.spreadsheet.SpreadsheetUtil.MANIFEST_START_COLUMN;
import static org.codehaus.mojo.license.extended.spreadsheet.SpreadsheetUtil.MAVEN_END_COLUMN;
import static org.codehaus.mojo.license.extended.spreadsheet.SpreadsheetUtil.MAVEN_START_COLUMN;
import static org.codehaus.mojo.license.extended.spreadsheet.SpreadsheetUtil.MISC_COLUMNS;
import static org.codehaus.mojo.license.extended.spreadsheet.SpreadsheetUtil.MISC_END_COLUMN;
import static org.codehaus.mojo.license.extended.spreadsheet.SpreadsheetUtil.MISC_START_COLUMN;
import static org.codehaus.mojo.license.extended.spreadsheet.SpreadsheetUtil.PLUGIN_ID_END_COLUMN;
import static org.codehaus.mojo.license.extended.spreadsheet.SpreadsheetUtil.PLUGIN_ID_START_COLUMN;
import static org.codehaus.mojo.license.extended.spreadsheet.SpreadsheetUtil.TABLE_NAME;
import static org.codehaus.mojo.license.extended.spreadsheet.SpreadsheetUtil.TIMEZONE_WIDTH;
import static org.codehaus.mojo.license.extended.spreadsheet.SpreadsheetUtil.getDownloadColumn;
/**
* Writes LibreOffices Calc ODS file.
*/
public class CalcFileWriter {
private static final Logger LOG = LoggerFactory.getLogger(CalcFileWriter.class);
private static final String HEADER_CELL_STYLE = "headerCellStyle";
private static final String HYPERLINK_NORMAL_STYLE = "hyperlinkNormalStyle";
private static final String HYPERLINK_GRAY_STYLE = "hyperlinkGrayStyle";
private static final String GRAY_CELL_STYLE = "grayCellStyle";
private static final String NORMAL_CELL_STYLE = "normalCellStyle";
private static final int DOWNLOAD_COLUMN_WIDTH = 6_000;
private static final String VALUE_TYPE_STRING = "string";
private static final String CONFIG_TYPE_SHORT = "short";
private CalcFileWriter() {}
public static void write(List projectLicenseInfos, final File licensesCalcOutputFile) {
if (CollectionUtils.isEmpty(projectLicenseInfos)) {
LOG.debug("Nothing to write to excel, no project data.");
return;
}
LOG.debug("Write LibreOffice Calc file {}", licensesCalcOutputFile);
try (OdfSpreadsheetDocument spreadsheet = OdfSpreadsheetDocument.newSpreadsheetDocument()) {
List tableList = spreadsheet.getTableList();
final OdfTable table;
if (!tableList.isEmpty()) {
table = tableList.get(0);
} else {
table = OdfTable.newTable(spreadsheet);
}
table.setTableName(TABLE_NAME);
createHeaderStyle(spreadsheet);
createHeader(projectLicenseInfos, spreadsheet, table);
writeData(
projectLicenseInfos, spreadsheet, table, convertToOdfColor(SpreadsheetUtil.ALTERNATING_ROWS_COLOR));
try (OutputStream fileOut = Files.newOutputStream(licensesCalcOutputFile.toPath())) {
spreadsheet.save(fileOut);
LOG.debug("Written LibreOffice Calc file {}", licensesCalcOutputFile);
} catch (IOException e) {
LOG.error("Error on storing LibreOffice Calc file with license and other information", e);
}
} catch (Exception e) {
LOG.error("Error on creating LibreOffice Calc file with license and other information", e);
}
}
private static Color convertToOdfColor(final int[] color) {
return new Color(color[0], color[1], color[2]);
}
@SuppressWarnings("checkstyle:MethodLength")
private static void createHeader(
List projectLicenseInfos, OdfSpreadsheetDocument spreadsheet, OdfTable table) {
boolean hasExtendedInfo = false;
for (ProjectLicenseInfo projectLicenseInfo : projectLicenseInfos) {
if (projectLicenseInfo.getExtendedInfo() != null) {
hasExtendedInfo = true;
break;
}
}
/*
All rows must be added before any cell merges, or merges on new lines will be merged like the previous
rows.
*/
// Create 1st header row. The Maven/JAR header row
OdfTableRow mavenJarRow = table.getRowByIndex(0);
// Create 2nd header row
OdfTableRow secondHeaderRow = table.appendRow();
// Create 3rd header row
OdfTableRow thirdHeaderRow = table.appendRow();
// Create Maven header cell
createMergedCellsInRow(
table, MAVEN_START_COLUMN, MAVEN_END_COLUMN, mavenJarRow, "Maven information", 0, HEADER_CELL_STYLE);
if (hasExtendedInfo) {
// Create JAR header cell
createMergedCellsInRow(
table,
EXTENDED_INFO_START_COLUMN,
EXTENDED_INFO_END_COLUMN,
mavenJarRow,
"JAR Content",
0,
HEADER_CELL_STYLE);
}
// Create Maven "General" header
createMergedCellsInRow(
table, GENERAL_START_COLUMN, GENERAL_END_COLUMN, secondHeaderRow, "General", 1, HEADER_CELL_STYLE);
// Create Maven "Plugin ID" header
createMergedCellsInRow(
table,
PLUGIN_ID_START_COLUMN,
PLUGIN_ID_END_COLUMN,
secondHeaderRow,
"Plugin ID",
1,
HEADER_CELL_STYLE);
// Gap "General" <-> "Plugin ID".
setColumnWidth(table, GENERAL_END_COLUMN, GAP_WIDTH);
// Create Maven "Licenses" header
createMergedCellsInRow(
table, LICENSES_START_COLUMN, LICENSES_END_COLUMN, secondHeaderRow, "Licenses", 1, HEADER_CELL_STYLE);
// Gap "Plugin ID" <-> "Licenses".
setColumnWidth(table, PLUGIN_ID_END_COLUMN, GAP_WIDTH);
// Create Maven "Developers" header
createMergedCellsInRow(
table,
DEVELOPERS_START_COLUMN,
DEVELOPERS_END_COLUMN,
secondHeaderRow,
"Developers",
1,
HEADER_CELL_STYLE);
// Gap "Licenses" <-> "Developers".
setColumnWidth(table, LICENSES_END_COLUMN, GAP_WIDTH);
// Create Maven "Miscellaneous" header
createMergedCellsInRow(
table, MISC_START_COLUMN, MISC_END_COLUMN, secondHeaderRow, "Miscellaneous", 1, HEADER_CELL_STYLE);
// Gap "Developers" <-> "Miscellaneous".
setColumnWidth(table, DEVELOPERS_END_COLUMN, GAP_WIDTH);
if (hasExtendedInfo) {
createMergedCellsInRow(
table,
MANIFEST_START_COLUMN,
MANIFEST_END_COLUMN,
secondHeaderRow,
"MANIFEST.MF",
1,
HEADER_CELL_STYLE);
// Gap "Miscellaneous" <-> "MANIFEST.MF".
setColumnWidth(table, DEVELOPERS_END_COLUMN, GAP_WIDTH);
createMergedCellsInRow(
table,
INFO_NOTICES_START_COLUMN,
INFO_NOTICES_END_COLUMN,
secondHeaderRow,
"Notices text files",
1,
HEADER_CELL_STYLE);
// Gap "MANIFEST.MF" <-> "Notice text files".
setColumnWidth(table, MANIFEST_END_COLUMN, GAP_WIDTH);
createMergedCellsInRow(
table,
INFO_LICENSES_START_COLUMN,
INFO_LICENSES_END_COLUMN,
secondHeaderRow,
"License text files",
1,
HEADER_CELL_STYLE);
// Gap "Notice text files" <-> "License text files".
setColumnWidth(table, INFO_NOTICES_END_COLUMN, GAP_WIDTH);
createMergedCellsInRow(
table,
INFO_SPDX_START_COLUMN,
INFO_SPDX_END_COLUMN,
secondHeaderRow,
"SPDX license id matched",
1,
HEADER_CELL_STYLE);
// Gap "License text files" <-> "SPDX license matches".
setColumnWidth(table, INFO_LICENSES_END_COLUMN, GAP_WIDTH);
}
// sheet.setColumnGroupCollapsed();
setColumnWidth(table, getDownloadColumn(hasExtendedInfo) - 1, GAP_WIDTH);
setColumnWidth(table, getDownloadColumn(hasExtendedInfo), DOWNLOAD_COLUMN_WIDTH);
// General
createCellsInRow(thirdHeaderRow, GENERAL_START_COLUMN, HEADER_CELL_STYLE, "Name");
// Plugin ID
createCellsInRow(
thirdHeaderRow, PLUGIN_ID_START_COLUMN, HEADER_CELL_STYLE, "Group ID", "Artifact ID", "Version");
// Licenses
createCellsInRow(
thirdHeaderRow,
LICENSES_START_COLUMN,
HEADER_CELL_STYLE,
"Name",
"URL",
"Distribution",
"Comments",
"File");
// Developers
createCellsInRow(
thirdHeaderRow,
DEVELOPERS_START_COLUMN,
HEADER_CELL_STYLE,
"Id",
"Email",
"Name",
"Organization",
"Organization URL",
"URL",
"Timezone");
// Miscellaneous
createCellsInRow(
thirdHeaderRow, MISC_START_COLUMN, HEADER_CELL_STYLE, "Inception Year", "Organization", "SCM", "URL");
int headerLineCount = 3;
if (hasExtendedInfo) {
// MANIFEST.MF
createCellsInRow(
thirdHeaderRow,
MANIFEST_START_COLUMN,
HEADER_CELL_STYLE,
"Bundle license",
"Bundle vendor",
"Implementation vendor");
// 3 InfoFile groups: Notices, Licenses and SPDX-Licenses.
createInfoFileCellsInRow(
thirdHeaderRow,
HEADER_CELL_STYLE,
INFO_NOTICES_START_COLUMN,
INFO_LICENSES_START_COLUMN,
INFO_SPDX_START_COLUMN);
createFreezePane(spreadsheet, table, getDownloadColumn(true) - 1, headerLineCount);
} else {
createFreezePane(spreadsheet, table, getDownloadColumn(false) - 1, headerLineCount);
}
createFreezePane(spreadsheet, table, GENERAL_END_COLUMN, headerLineCount);
}
private static void setColumnWidth(OdfTable table, int column, int width) {
table.getColumnByIndex(column)
.getOdfElement()
.setProperty(OdfTableColumnProperties.ColumnWidth, (width / 100) + "mm");
}
private static void createFreezePane(
OdfSpreadsheetDocument spreadsheet, OdfTable table, int column, int lineCount) {
// TODO: Find out why this perfect XML is ignored. Use FreezePane function from ODFToolkit after they add it.
final OdfSettingsDom settingsDom;
try {
settingsDom = spreadsheet.getSettingsDom();
} catch (Exception e) {
throw new RuntimeException(e);
}
NodeList childNodes = settingsDom.getFirstChild().getFirstChild().getChildNodes();
for (int i = 0; i < childNodes.getLength(); i++) {
Node child = childNodes.item(i);
if ("config:config-item-set".equals(child.getNodeName())
&& "ooo:view-settings".equals(((Element) child).getAttribute("config:name"))) {
NodeList subChilds = child.getChildNodes();
for (int j = 0; j < subChilds.getLength(); j++) {
Node subChild = subChilds.item(j);
if ("config:config-item-map-indexed".equals(subChild.getNodeName())
&& "Views".equals(((Element) subChild).getAttribute("config:name"))) {
break;
}
}
break;
}
}
XPath xpath = settingsDom.getXPath();
NodeList list;
try {
list = (NodeList) xpath.evaluate(
"/office:document-settings/" + "office:settings/"
+ "config:config-item-set/"
+ "config:config-item-map-indexed/"
+ "config:config-item-map-entry/"
+ "config:config-item-map-named/"
+ "config:config-item-map-entry",
// "/config:config-item-set[@config:name=\"ooo:view-settings\"]" +
settingsDom,
XPathConstants.NODE);
/*
2
2
1
4
3
0
1
0
3
*/
if (list instanceof ConfigConfigItemMapEntryElement) {
ConfigConfigItemMapEntryElement entryElement = (ConfigConfigItemMapEntryElement) list;
appendConfigItemElement(entryElement, "HorizontalSplitMode", CONFIG_TYPE_SHORT, "2");
appendConfigItemElement(entryElement, "VerticalSplitMode", CONFIG_TYPE_SHORT, "2");
appendConfigItemElement(entryElement, "HorizontalSplitPosition", "int", "1");
appendConfigItemElement(entryElement, "VerticalSplitPosition", "int", "3");
appendConfigItemElement(entryElement, "ActiveSplitRange", CONFIG_TYPE_SHORT, "3");
appendConfigItemElement(entryElement, "PositionLeft", "int", "0");
appendConfigItemElement(entryElement, "PositionRight", "int", "1");
appendConfigItemElement(entryElement, "PositionTop", "int", "0");
appendConfigItemElement(entryElement, "PositionBottom", "int", "3");
appendConfigItemElement(entryElement, "ShowGrid", "boolean", "true");
appendConfigItemElement(entryElement, "AnchoredTextOverflowLegacy", "boolean", "false");
}
} catch (XPathExpressionException e) {
throw new RuntimeException(e);
}
}
private static void appendConfigItemElement(
ConfigConfigItemMapEntryElement entryElement, String configName, String configType, String nodeValue) {
ConfigConfigItemElement horizontalSplitMode = null;
if (entryElement.hasChildNodes()) {
NodeList nodeList = entryElement.getChildNodes();
for (int i = 0; i < nodeList.getLength(); i++) {
Node node = nodeList.item(i);
if (node instanceof ConfigConfigItemElement) {
ConfigConfigItemElement itemElement = (ConfigConfigItemElement) node;
if (configName.equals(itemElement.getConfigNameAttribute())) {
horizontalSplitMode = (ConfigConfigItemElement) node;
break;
}
}
}
}
if (horizontalSplitMode == null) {
horizontalSplitMode = entryElement.newConfigConfigItemElement(configName, configType);
} else {
if (horizontalSplitMode.hasChildNodes()) {
// Find text node and set new value if found.
for (int i = 0; i < horizontalSplitMode.getLength(); i++) {
Node child = horizontalSplitMode.item(i);
if (child.getNodeType() == Node.TEXT_NODE) {
child.setNodeValue(nodeValue);
return;
}
}
}
}
horizontalSplitMode.newTextNode(nodeValue);
}
private static void createHeaderStyle(OdfSpreadsheetDocument spreadsheet) {
OdfOfficeStyles styles = spreadsheet.getOrCreateDocumentStyles();
OdfStyle headerStyle = styles.newStyle(HEADER_CELL_STYLE, OdfStyleFamily.TableCell);
headerStyle.setProperty(StyleTextPropertiesElement.FontFamily, "Arial");
headerStyle.setProperty(StyleTextPropertiesElement.FontWeight, "bold");
headerStyle.setProperty(StyleTableCellPropertiesElement.BackgroundColor, "#CCFFCC");
headerStyle.setProperty(StyleParagraphPropertiesElement.TextAlign, "center");
headerStyle.setProperty(StyleTableCellPropertiesElement.VerticalAlign, "middle");
headerStyle.setProperty(StyleTableCellPropertiesElement.Border, "1.0pt solid #000000");
}
/* Improvement: Clean this method up.
Reduce parameters, complicated parameters/DTO pattern.
But keep it still threadsafe.
*/
@SuppressWarnings("checkstyle:MethodLength")
private static void writeData(
List projectLicenseInfos,
OdfSpreadsheetDocument wb,
OdfTable table,
Color alternatingRowsColor) {
final int firstRowIndex = 3;
int currentRowIndex = firstRowIndex;
final Map rowMap = new HashMap<>();
boolean hasExtendedInfo = false;
final OdfStyle hyperlinkStyleNormal = createHyperlinkStyle(wb, HYPERLINK_NORMAL_STYLE, null);
final OdfStyle hyperlinkStyleGray = createHyperlinkStyle(wb, HYPERLINK_GRAY_STYLE, alternatingRowsColor);
boolean grayBackground = false;
OdfOfficeStyles officeStyles = wb.getOrCreateDocumentStyles();
OdfStyle styleGray = officeStyles.newStyle(GRAY_CELL_STYLE, OdfStyleFamily.TableCell);
styleGray.setProperty(StyleTableCellPropertiesElement.BackgroundColor, alternatingRowsColor.toString());
styleGray.setProperty(OdfTableColumnProperties.UseOptimalColumnWidth, String.valueOf(true));
/* Set own, empty style, instead of leaving the style out,
because otherwise it copies the style of the row above. */
OdfStyle styleNormal = officeStyles.newStyle(NORMAL_CELL_STYLE, OdfStyleFamily.TableCell);
styleNormal.setProperty(OdfTableColumnProperties.UseOptimalColumnWidth, String.valueOf(true));
for (ProjectLicenseInfo projectInfo : projectLicenseInfos) {
final OdfStyle cellStyle, hyperlinkStyle;
LOG.debug(
"Writing {}:{} into LibreOffice calc file", projectInfo.getGroupId(), projectInfo.getArtifactId());
if (grayBackground) {
cellStyle = styleGray;
hyperlinkStyle = hyperlinkStyleGray;
} else {
cellStyle = styleNormal;
hyperlinkStyle = hyperlinkStyleNormal;
}
grayBackground = !grayBackground;
int extraRows = 0;
OdfTableRow currentRow = table.appendRow();
rowMap.put(currentRowIndex, currentRow);
// Plugin ID
createDataCellsInRow(
currentRow,
PLUGIN_ID_START_COLUMN,
cellStyle,
projectInfo.getGroupId(),
projectInfo.getArtifactId(),
projectInfo.getVersion());
// Licenses
final CellListParameter cellListParameter = new CellListParameter(table, rowMap, cellStyle);
CurrentRowData currentRowData = new CurrentRowData(currentRowIndex, extraRows, hasExtendedInfo);
extraRows = addList(
cellListParameter,
currentRowData,
LICENSES_START_COLUMN,
LICENSES_COLUMNS,
projectInfo.getLicenses(),
(OdfTableRow licenseRow, ProjectLicense license) -> {
OdfTableCell[] licenses = createDataCellsInRow(
licenseRow,
LICENSES_START_COLUMN,
cellStyle,
license.getName(),
license.getUrl(),
license.getDistribution(),
license.getComments(),
license.getFile());
addHyperlinkIfExists(table, licenses[1], hyperlinkStyle);
});
final ExtendedInfo extendedInfo = projectInfo.getExtendedInfo();
if (extendedInfo != null) {
hasExtendedInfo = true;
// General
createDataCellsInRow(currentRow, GENERAL_START_COLUMN, cellStyle, extendedInfo.getName());
// Developers
currentRowData = new CurrentRowData(currentRowIndex, extraRows, hasExtendedInfo);
extraRows = addList(
cellListParameter,
currentRowData,
DEVELOPERS_START_COLUMN,
DEVELOPERS_COLUMNS,
extendedInfo.getDevelopers(),
(OdfTableRow developerRow, Developer developer) -> {
OdfTableCell[] licenses = createDataCellsInRow(
developerRow,
DEVELOPERS_START_COLUMN,
cellStyle,
developer.getId(),
developer.getEmail(),
developer.getName(),
developer.getOrganization(),
developer.getOrganizationUrl(),
developer.getUrl(),
developer.getTimezone());
addHyperlinkIfExists(table, licenses[1], hyperlinkStyle, true);
addHyperlinkIfExists(table, licenses[4], hyperlinkStyle);
addHyperlinkIfExists(table, licenses[5], hyperlinkStyle);
});
// Miscellaneous
OdfTableCell[] miscCells = createDataCellsInRow(
currentRow,
MISC_START_COLUMN,
cellStyle,
extendedInfo.getInceptionYear(),
Optional.ofNullable(extendedInfo.getOrganization())
.map(Organization::getName)
.orElse(null),
Optional.ofNullable(extendedInfo.getScm())
.map(Scm::getUrl)
.orElse(null),
extendedInfo.getUrl());
addHyperlinkIfExists(table, miscCells[2], hyperlinkStyle);
addHyperlinkIfExists(table, miscCells[3], hyperlinkStyle);
// MANIFEST.MF
createDataCellsInRow(
currentRow,
MANIFEST_START_COLUMN,
cellStyle,
extendedInfo.getBundleLicense(),
extendedInfo.getBundleVendor(),
extendedInfo.getImplementationVendor());
// Info files
if (!CollectionUtils.isEmpty(extendedInfo.getInfoFiles())) {
// Sort all info files by type into 3 different lists, each list for each of the 3 types.
List notices = new ArrayList<>();
List licenses = new ArrayList<>();
List spdxs = new ArrayList<>();
extendedInfo.getInfoFiles().forEach(infoFile -> {
switch (infoFile.getType()) {
case LICENSE:
licenses.add(infoFile);
break;
case NOTICE:
notices.add(infoFile);
break;
case SPDX_LICENSE:
spdxs.add(infoFile);
break;
default:
break;
}
});
// InfoFile notices text file
currentRowData = new CurrentRowData(currentRowIndex, extraRows, hasExtendedInfo);
extraRows = addInfoFileList(
cellListParameter,
currentRowData,
INFO_NOTICES_START_COLUMN,
INFO_NOTICES_COLUMNS,
notices);
// InfoFile licenses text file
currentRowData = new CurrentRowData(currentRowIndex, extraRows, hasExtendedInfo);
extraRows = addInfoFileList(
cellListParameter,
currentRowData,
INFO_LICENSES_START_COLUMN,
INFO_LICENSES_COLUMNS,
licenses);
// InfoFile spdx licenses text file
currentRowData = new CurrentRowData(currentRowIndex, extraRows, hasExtendedInfo);
extraRows = addInfoFileList(
cellListParameter, currentRowData, INFO_SPDX_START_COLUMN, INFO_SPDX_COLUMNS, spdxs);
} else if (cellListParameter.cellStyle != null) {
setStyleOnEmptyCells(
cellListParameter, currentRowData, INFO_NOTICES_START_COLUMN, INFO_NOTICES_COLUMNS);
setStyleOnEmptyCells(
cellListParameter, currentRowData, INFO_LICENSES_START_COLUMN, INFO_LICENSES_COLUMNS);
setStyleOnEmptyCells(cellListParameter, currentRowData, INFO_SPDX_START_COLUMN, INFO_SPDX_COLUMNS);
}
} else {
createDataCellsInRow(currentRow, GENERAL_START_COLUMN, cellStyle, 1);
createDataCellsInRow(currentRow, DEVELOPERS_START_COLUMN, cellStyle, DEVELOPERS_COLUMNS);
createDataCellsInRow(currentRow, MISC_START_COLUMN, cellStyle, MISC_COLUMNS);
}
final int downloadColumn = getDownloadColumn(hasExtendedInfo);
if (CollectionUtils.isNotEmpty(projectInfo.getDownloaderMessages())) {
currentRowData = new SpreadsheetUtil.CurrentRowData(currentRowIndex, extraRows, hasExtendedInfo);
extraRows = addList(
cellListParameter,
currentRowData,
downloadColumn,
SpreadsheetUtil.DOWNLOAD_MESSAGE_COLUMNS,
projectInfo.getDownloaderMessages(),
(OdfTableRow licenseRow, String message) -> {
OdfTableCell[] licenses =
createDataCellsInRow(licenseRow, downloadColumn, cellStyle, message);
if (message.matches(SpreadsheetUtil.VALID_LINK)) {
addHyperlinkIfExists(table, licenses[0], hyperlinkStyle);
}
});
} else {
// Add empty cell, so it doesn't copy the previous row cell.
OdfTableCell cell = currentRow.getCellByIndex(downloadColumn);
cell.setValueType(VALUE_TYPE_STRING);
cell.getOdfElement().setStyleName(getCellStyleName(cellStyle));
}
currentRowIndex += extraRows + 1;
}
autosizeColumns(table, hasExtendedInfo, currentRowIndex);
}
private static OdfStyle createHyperlinkStyle(OdfSpreadsheetDocument wb, String name, Color backgroundColor) {
OdfOfficeStyles styles = wb.getOrCreateDocumentStyles();
OdfStyle hyperlinkStyle = styles.newStyle(name, OdfStyleFamily.TableCell);
hyperlinkStyle.setProperty(StyleTextPropertiesElement.FontFamily, "Arial");
hyperlinkStyle.setProperty(StyleTextPropertiesElement.Color, Color.BLUE.toString());
hyperlinkStyle.setProperty(StyleParagraphPropertiesElement.TextAlign, "center");
hyperlinkStyle.setProperty(StyleTableCellPropertiesElement.VerticalAlign, "middle");
if (backgroundColor != null) {
hyperlinkStyle.setProperty(StyleTableCellPropertiesElement.BackgroundColor, backgroundColor.toString());
}
return hyperlinkStyle;
}
private static void autosizeColumns(OdfTable table, boolean hasExtendedInfo, int rows) {
autosizeColumns(
table,
rows,
new ImmutablePair<>(GENERAL_START_COLUMN, GENERAL_END_COLUMN),
new ImmutablePair<>(PLUGIN_ID_START_COLUMN, PLUGIN_ID_END_COLUMN),
new ImmutablePair<>(LICENSES_START_COLUMN, LICENSES_END_COLUMN),
new ImmutablePair<>(DEVELOPERS_START_COLUMN, DEVELOPERS_END_COLUMN - 1),
new ImmutablePair<>(MISC_START_COLUMN + 1, MISC_END_COLUMN));
// The column header widths are most likely wider than the actual cells content.
setColumnWidth(table, DEVELOPERS_END_COLUMN - 1, TIMEZONE_WIDTH);
setColumnWidth(table, MISC_START_COLUMN, INCEPTION_YEAR_WIDTH);
if (hasExtendedInfo) {
autosizeColumns(
table,
rows,
new ImmutablePair<>(MANIFEST_START_COLUMN, MANIFEST_END_COLUMN),
new ImmutablePair<>(INFO_NOTICES_START_COLUMN + 2, INFO_NOTICES_END_COLUMN),
new ImmutablePair<>(INFO_LICENSES_START_COLUMN + 2, INFO_LICENSES_END_COLUMN),
new ImmutablePair<>(INFO_SPDX_START_COLUMN + 2, INFO_SPDX_END_COLUMN));
}
}
@SafeVarargs
private static void autosizeColumns(OdfTable sheet, int rows, Pair... ranges) {
for (Pair range : ranges) {
for (int i = range.getLeft(); i < range.getRight(); i++) {
final float sizeFactor = 2.0f;
float size = 25;
// Get max width by taking the max string length multiplied by sizeFactor.
for (int row = 0; row < rows; row++) {
OdfTableCell cell = sheet.getCellByPosition(i, row);
if (VALUE_TYPE_STRING.equals(cell.getValueType())) {
String stringValue = cell.getStringValue();
size = Math.max(stringValue.length() * sizeFactor, size);
}
}
final OdfTableColumn column = sheet.getColumnByIndex(i);
// The attribute is ignored by LibreOffice Calc, set it for other applications.
column.setUseOptimalWidth(true);
column.setWidth((long) size);
}
}
}
private static int addInfoFileList(
CellListParameter cellListParameter,
CurrentRowData currentRowData,
int startColumn,
int columnsToFill,
List infoFiles) {
return addList(
cellListParameter,
currentRowData,
startColumn,
columnsToFill,
infoFiles,
(OdfTableRow infoFileRow, InfoFile infoFile) -> {
final String copyrightLines = Optional.ofNullable(infoFile.getExtractedCopyrightLines())
.map(strings -> String.join(COPYRIGHT_JOIN_SEPARATOR, strings))
.orElse(null);
createDataCellsInRow(
infoFileRow,
startColumn,
cellListParameter.getCellStyle(),
// This would otherwise lead to invalid XML characters in the ODS file content.
infoFile.getContent().replace("\f", "\n"),
copyrightLines,
infoFile.getFileName());
});
}
private static int addList(
CellListParameter cellListParameter,
CurrentRowData currentRowData,
int startColumn,
int columnsToFill,
List list,
BiConsumer biConsumer) {
if (!CollectionUtils.isEmpty(list)) {
for (int i = 0; i < list.size(); i++) {
T type = list.get(i);
Integer index = currentRowData.getCurrentRowIndex() + i;
OdfTableRow row = cellListParameter.getRows().get(index);
if (row == null) {
row = cellListParameter.getSheet().appendRow();
cellListParameter.getRows().put(index, row);
if (cellListParameter.getCellStyle() != null) {
// Style all empty left cells, in the columns left from this
createAndStyleCells(
row,
cellListParameter.getCellStyle(),
new ImmutablePair<>(GENERAL_START_COLUMN, GENERAL_END_COLUMN),
new ImmutablePair<>(PLUGIN_ID_START_COLUMN, PLUGIN_ID_END_COLUMN),
new ImmutablePair<>(LICENSES_START_COLUMN, LICENSES_END_COLUMN));
if (currentRowData.isHasExtendedInfo()) {
createAndStyleCells(
row,
cellListParameter.getCellStyle(),
new ImmutablePair<>(DEVELOPERS_START_COLUMN, DEVELOPERS_END_COLUMN),
new ImmutablePair<>(MISC_START_COLUMN, MISC_END_COLUMN),
// JAR
new ImmutablePair<>(MANIFEST_START_COLUMN, MANIFEST_END_COLUMN),
new ImmutablePair<>(INFO_LICENSES_START_COLUMN, INFO_LICENSES_END_COLUMN),
new ImmutablePair<>(INFO_NOTICES_START_COLUMN, INFO_NOTICES_END_COLUMN),
new ImmutablePair<>(INFO_SPDX_START_COLUMN, INFO_SPDX_END_COLUMN));
}
}
currentRowData.setExtraRows(currentRowData.getExtraRows() + 1);
}
biConsumer.accept(row, type);
}
} else if (cellListParameter.cellStyle != null) {
setStyleOnEmptyCells(cellListParameter, currentRowData, startColumn, columnsToFill);
}
return currentRowData.getExtraRows();
}
/**
* If no cells are set, color at least the background,
* to color concatenated blocks with the same background color.
*
* @param cellListParameter Passes data about sheet, row, cell style.
* @param currentRowData Passes data about the current indices for rows and columns.
* @param startColumn Column where to start setting the style.
* @param columnsToFill How many columns to set the style on, starting from 'startColumn'.
*/
private static void setStyleOnEmptyCells(
CellListParameter cellListParameter, CurrentRowData currentRowData, int startColumn, int columnsToFill) {
OdfTableRow row = cellListParameter.getRows().get(currentRowData.getCurrentRowIndex());
for (int i = 0; i < columnsToFill; i++) {
OdfTableCell cell = row.getCellByIndex(startColumn + i);
cell.setValueType(VALUE_TYPE_STRING);
cell.getOdfElement().setStyleName(getCellStyleName(cellListParameter.getCellStyle()));
}
}
@SafeVarargs
private static void createAndStyleCells(OdfTableRow row, OdfStyle cellStyle, Pair... ranges) {
for (Pair range : ranges) {
for (int i = range.getLeft(); i < range.getRight(); i++) {
OdfTableCell cell = row.getCellByIndex(i);
cell.setValueType(VALUE_TYPE_STRING);
cell.getOdfElement().setStyleName(getCellStyleName(cellStyle));
}
}
}
public static void applyHyperlink(OdfTable table, OdfTableCell cell, String hyperlink, boolean isEmail) {
TextAElement aElement;
aElement = ((OdfContentDom) (table.getOdfElement().getOwnerDocument())).newOdfElement(TextAElement.class);
aElement.setXlinkTypeAttribute("simple");
hyperlink = hyperlink.trim().replace(" dot ", ".");
if (isEmail) {
hyperlink = hyperlink.replace(" at ", "@");
if (hyperlink.contains("@") && hyperlink.matches(".*\\s[a-zA-Z]{2,3}$")) {
hyperlink = hyperlink.replace(" ", ".");
}
}
aElement.setXlinkHrefAttribute(isEmail ? "mailto:" + hyperlink : hyperlink);
aElement.setTextContent(hyperlink);
Node node = cell.getOdfElement().getFirstChild();
node.appendChild(aElement);
}
private static void addHyperlinkIfExists(OdfTable table, OdfTableCell cell, OdfStyle hyperlinkStyle) {
addHyperlinkIfExists(table, cell, hyperlinkStyle, false);
}
private static void addHyperlinkIfExists(
OdfTable table, OdfTableCell cell, OdfStyle hyperlinkStyle, boolean isEmail) {
if (!StringUtils.isEmpty(cell.getStringValue())) {
try {
cell.getOdfElement().setStyleName(getCellStyleName(hyperlinkStyle));
String content = cell.getStringValue();
cell.setStringValue("");
applyHyperlink(table, cell, content, isEmail);
} catch (IllegalArgumentException e) {
LOG.debug(
"Can't set Hyperlink for cell value " + cell.getStringValue() + " it gets rejected as URI", e);
}
}
}
/**
* Create data cells in row.
*
* @param row Row.
* @param startColumn Starting column.
* @param cellStyle Cell style.
* @param names Name of cell values.
* @return Array of created table cells.
*/
private static OdfTableCell[] createDataCellsInRow(
OdfTableRow row, int startColumn, OdfStyle cellStyle, String... names) {
OdfTableCell[] result = new OdfTableCell[names.length];
for (int i = 0; i < names.length; i++) {
OdfTableCell cell = row.getCellByIndex(startColumn + i);
cell.setValueType(VALUE_TYPE_STRING);
if (cellStyle != null) {
cell.getOdfElement().setStyleName(getCellStyleName(cellStyle));
}
if (!StringUtils.isEmpty(names[i])) {
final String value;
final int maxCellStringLength = Short.MAX_VALUE;
if (names[i].length() > maxCellStringLength) {
value = names[i].substring(0, maxCellStringLength - 3) + "...";
} else {
value = names[i];
}
cell.setStringValue(value);
}
result[i] = cell;
}
return result;
}
/**
* Fills cells with empty strings, so they get created and don't copy the previous rows content and style,
* like the header's background color and bold border.
*
* @param row Row.
* @param startColumn Starting column (inclusive).
* @param cellStyle Cell style.
* @param count Number of columns to set.
*/
private static void createDataCellsInRow(OdfTableRow row, int startColumn, OdfStyle cellStyle, int count) {
for (int i = 0; i < count; i++) {
OdfTableCell cell = row.getCellByIndex(startColumn + i);
cell.setValueType(VALUE_TYPE_STRING);
if (cellStyle != null) {
cell.getOdfElement().setStyleName(getCellStyleName(cellStyle));
}
cell.setStringValue("");
}
}
private static String getCellStyleName(OdfStyle cellStyle) {
return cellStyle.getAttributes().item(1).getNodeValue();
}
/**
* Create cells for InfoFile content.
*
* @param row The row to insert cells into.
* @param styleName Name of the style.
* @param startPositions The start position of the 3 columns for an InfoFile.
*/
private static void createInfoFileCellsInRow(OdfTableRow row, String styleName, int... startPositions) {
for (int startPosition : startPositions) {
createCellsInRow(row, startPosition, styleName, "Content", "Extracted copyright lines", "File");
}
}
private static void createCellsInRow(OdfTableRow row, int startColumn, String styleName, String... names) {
for (int i = 0; i < names.length; i++) {
OdfTableCell cell = row.getCellByIndex(startColumn + i);
cell.setValueType(VALUE_TYPE_STRING);
cell.getOdfElement().setStyleName(styleName);
cell.setStringValue(names[i]);
}
}
private static void createMergedCellsInRow(
OdfTable table,
int startColumn,
int endColumn,
OdfTableRow row,
String cellValue,
int rowIndex,
String styleName) {
OdfTableCell cell = createCellsInRow(startColumn, endColumn, row);
if (cell == null) {
return;
}
final boolean merge = endColumn - 1 > startColumn;
if (merge) {
OdfTableCellRange cellRange = table.getCellRangeByPosition(startColumn, rowIndex, endColumn - 1, rowIndex);
cellRange.merge();
}
// Set value and style only after merge
cell.setStringValue(cellValue);
cell.getOdfElement().setStyleName(styleName);
// TODO: Add grouping, with a hierarchy, after ODFToolkit offers it.
}
private static OdfTableCell createCellsInRow(int startColumn, int exclusiveEndColumn, OdfTableRow inRow) {
OdfTableCell firstCell = null;
for (int i = startColumn; i < exclusiveEndColumn; i++) {
OdfTableCell cell = inRow.getCellByIndex(i);
if (i == startColumn) {
firstCell = cell;
}
}
return firstCell;
}
/**
* Parameters for cells which apply to all cells in each loop iteration.
*/
private static class CellListParameter {
private final OdfTable sheet;
private final Map rows;
private final OdfStyle cellStyle;
private CellListParameter(OdfTable sheet, Map rows, OdfStyle cellStyle) {
this.sheet = sheet;
this.rows = rows;
this.cellStyle = cellStyle;
}
OdfTable getSheet() {
return sheet;
}
Map getRows() {
return rows;
}
OdfStyle getCellStyle() {
return cellStyle;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy