Java tutorial
/* * JasperReports - Free Java Reporting Library. * Copyright (C) 2001 - 2019 TIBCO Software Inc. All rights reserved. * http://www.jaspersoft.com * * Unless you have purchased a commercial license agreement from Jaspersoft, * the following license terms apply: * * This program is part of JasperReports. * * JasperReports is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * JasperReports is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with JasperReports. If not, see <http://www.gnu.org/licenses/>. */ package net.sf.jasperreports.engine.export; import java.io.IOException; import java.io.InputStream; import java.io.Writer; import java.text.DateFormat; import java.text.ParseException; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Scanner; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.io.JsonStringEncoder; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import net.sf.jasperreports.annotations.properties.Property; import net.sf.jasperreports.annotations.properties.PropertyScope; import net.sf.jasperreports.engine.DefaultJasperReportsContext; import net.sf.jasperreports.engine.JRAbstractExporter; import net.sf.jasperreports.engine.JRException; import net.sf.jasperreports.engine.JRGenericPrintElement; import net.sf.jasperreports.engine.JRPrintElement; import net.sf.jasperreports.engine.JRPrintFrame; import net.sf.jasperreports.engine.JRPrintHyperlink; import net.sf.jasperreports.engine.JRPrintPage; import net.sf.jasperreports.engine.JRPrintText; import net.sf.jasperreports.engine.JRPropertiesMap; import net.sf.jasperreports.engine.JRPropertiesUtil; import net.sf.jasperreports.engine.JRPropertiesUtil.PropertySuffix; import net.sf.jasperreports.engine.JRRuntimeException; import net.sf.jasperreports.engine.JasperReportsContext; import net.sf.jasperreports.engine.export.data.BooleanTextValue; import net.sf.jasperreports.engine.export.data.DateTextValue; import net.sf.jasperreports.engine.export.data.NumberTextValue; import net.sf.jasperreports.engine.export.data.StringTextValue; import net.sf.jasperreports.engine.export.data.TextValue; import net.sf.jasperreports.engine.export.data.TextValueHandler; import net.sf.jasperreports.engine.type.EnumUtil; import net.sf.jasperreports.engine.type.NamedEnum; import net.sf.jasperreports.engine.util.JRDataUtils; import net.sf.jasperreports.engine.util.JRStyledText; import net.sf.jasperreports.export.ExportInterruptedException; import net.sf.jasperreports.export.ExporterInputItem; import net.sf.jasperreports.export.JsonExporterConfiguration; import net.sf.jasperreports.export.JsonMetadataReportConfiguration; import net.sf.jasperreports.export.WriterExporterOutput; import net.sf.jasperreports.properties.PropertyConstants; /** * @author Narcis Marcu (narcism@users.sourceforge.net) */ public class JsonMetadataExporter extends JRAbstractExporter<JsonMetadataReportConfiguration, JsonExporterConfiguration, WriterExporterOutput, JsonExporterContext> { private static final Log log = LogFactory.getLog(JsonMetadataExporter.class); public static final String JSON_EXPORTER_KEY = JRPropertiesUtil.PROPERTY_PREFIX + "json"; protected static final String JSON_EXPORTER_PROPERTIES_PREFIX = JRPropertiesUtil.PROPERTY_PREFIX + "export.json."; protected static final String EXCEPTION_MESSAGE_KEY_INVALID_JSON_OBJECT = "export.json.invalid.json.object"; protected static final String EXCEPTION_MESSAGE_KEY_INVALID_JSON_OBJECT_SEMANTIC = EXCEPTION_MESSAGE_KEY_INVALID_JSON_OBJECT + ".semantic"; protected static final String EXCEPTION_MESSAGE_KEY_INVALID_JSON_OBJECT_ARRAY_FOUND = EXCEPTION_MESSAGE_KEY_INVALID_JSON_OBJECT + ".array.found"; @Property(category = PropertyConstants.CATEGORY_EXPORT, scopes = { PropertyScope.ELEMENT }, sinceVersion = PropertyConstants.VERSION_6_0_0) public static final String JSON_EXPORTER_PATH_PROPERTY = JSON_EXPORTER_PROPERTIES_PREFIX + "path"; @Property(category = PropertyConstants.CATEGORY_EXPORT, defaultValue = PropertyConstants.BOOLEAN_FALSE, scopes = { PropertyScope.ELEMENT }, sinceVersion = PropertyConstants.VERSION_6_0_0, valueType = Boolean.class) public static final String JSON_EXPORTER_REPEAT_VALUE_PROPERTY = JSON_EXPORTER_PROPERTIES_PREFIX + "repeat.value"; @Property(category = PropertyConstants.CATEGORY_EXPORT, scopes = { PropertyScope.ELEMENT }, sinceVersion = PropertyConstants.VERSION_6_0_0) public static final String JSON_EXPORTER_DATA_PROPERTY = JSON_EXPORTER_PROPERTIES_PREFIX + "data"; @Property(name = "net.sf.jasperreports.export.json.repeat.{path}", category = PropertyConstants.CATEGORY_EXPORT, defaultValue = PropertyConstants.BOOLEAN_FALSE, scopes = { PropertyScope.ELEMENT }, sinceVersion = PropertyConstants.VERSION_6_1_0, valueType = Boolean.class) public static final String JSON_EXPORTER_REPEAT_PROPERTIES_PREFIX = JSON_EXPORTER_PROPERTIES_PREFIX + "repeat."; @Property(name = "net.sf.jasperreports.export.json.number.{path}", category = PropertyConstants.CATEGORY_EXPORT, scopes = { PropertyScope.ELEMENT }, sinceVersion = PropertyConstants.VERSION_6_1_0) public static final String JSON_EXPORTER_NUMBER_PROPERTIES_PREFIX = JSON_EXPORTER_PROPERTIES_PREFIX + "number."; @Property(name = "net.sf.jasperreports.export.json.date.{path}", category = PropertyConstants.CATEGORY_EXPORT, scopes = { PropertyScope.ELEMENT }, sinceVersion = PropertyConstants.VERSION_6_1_0) public static final String JSON_EXPORTER_DATE_PROPERTIES_PREFIX = JSON_EXPORTER_PROPERTIES_PREFIX + "date."; @Property(name = "net.sf.jasperreports.export.json.boolean.{path}", category = PropertyConstants.CATEGORY_EXPORT, scopes = { PropertyScope.ELEMENT }, sinceVersion = PropertyConstants.VERSION_6_1_0) public static final String JSON_EXPORTER_BOOLEAN_PROPERTIES_PREFIX = JSON_EXPORTER_PROPERTIES_PREFIX + "boolean."; @Property(name = "net.sf.jasperreports.export.json.string.{path}", category = PropertyConstants.CATEGORY_EXPORT, scopes = { PropertyScope.ELEMENT }, sinceVersion = PropertyConstants.VERSION_6_1_0) public static final String JSON_EXPORTER_STRING_PROPERTIES_PREFIX = JSON_EXPORTER_PROPERTIES_PREFIX + "string."; private static final String JSON_SCHEMA_ROOT_NAME = "___root"; protected final DateFormat isoDateFormat = JRDataUtils.getIsoDateFormat(); protected Writer writer; protected int reportIndex; protected int pageIndex; private Map<String, SchemaNode> pathToValueNode = new HashMap<String, SchemaNode>(); private Map<String, SchemaNode> pathToObjectNode = new HashMap<String, SchemaNode>(); private Map<SchemaNode, ArrayList<String>> visitedMembers = new HashMap<SchemaNode, ArrayList<String>>(); private ArrayList<SchemaNode> openedSchemaNodes = new ArrayList<SchemaNode>(); private String jsonSchema; private String previousPath; private boolean escapeMembers; private boolean gotSchema; public void validateSchema(String jsonSchema) throws JRException { ObjectMapper mapper = new ObjectMapper(); // relax the JSON rules mapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true); mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true); mapper.configure(JsonParser.Feature.ALLOW_COMMENTS, true); try { JsonNode root = mapper.readTree(jsonSchema); if (root.isObject()) { pathToValueNode = new HashMap<String, SchemaNode>(); pathToObjectNode = new HashMap<String, SchemaNode>(); previousPath = null; if (!isValid((ObjectNode) root, JSON_SCHEMA_ROOT_NAME, "", null)) { throw new JRException(EXCEPTION_MESSAGE_KEY_INVALID_JSON_OBJECT_SEMANTIC, (Object[]) null); } } else { throw new JRException(EXCEPTION_MESSAGE_KEY_INVALID_JSON_OBJECT_ARRAY_FOUND, (Object[]) null); } } catch (IOException e) { throw new JRException(EXCEPTION_MESSAGE_KEY_INVALID_JSON_OBJECT, (Object[]) null); } } private boolean isValid(ObjectNode objectNode, String objectName, String currentPath, SchemaNode parent) { String nodeTypeValue = null; JsonNode typeNode = objectNode.path("_type"); if (typeNode.isMissingNode()) { nodeTypeValue = "object"; } else if (!typeNode.isTextual()) { return false; } if (nodeTypeValue == null) { nodeTypeValue = typeNode.asText(); } NodeTypeEnum nodeType = NodeTypeEnum.getByName(nodeTypeValue); // enforce type "object" or "array" with "_children" if (!(NodeTypeEnum.OBJECT.equals(nodeType) || (NodeTypeEnum.ARRAY.equals(nodeType) && objectNode.has("_children")))) { return false; } // enforce "_children" of type Object when typeNode is "array" if (NodeTypeEnum.ARRAY.equals(nodeType) && !objectNode.path("_children").isObject()) { return false; } boolean result = true; String availablePath = currentPath; SchemaNode schemaNode; availablePath = availablePath.length() > 0 ? (availablePath.endsWith(".") ? availablePath : availablePath + ".") + objectName : objectName; // _children properties are passed to the parent array if (parent != null) { schemaNode = parent; } else { int level; if (JSON_SCHEMA_ROOT_NAME.equals(objectName)) { level = 0; } else if (availablePath.length() > 0 && availablePath.indexOf(".") > 0) { level = availablePath.split("\\.").length - 1; } else { level = 1; } schemaNode = new SchemaNode(level, objectName, nodeType, currentPath.endsWith(".") ? currentPath.substring(0, currentPath.length() - 2) : currentPath); pathToObjectNode.put(availablePath, schemaNode); } Iterator<String> it = objectNode.fieldNames(); while (it.hasNext()) { String field = it.next(); JsonNode node = objectNode.path(field); String localPath = availablePath; if (!field.startsWith("_")) { schemaNode.addMember(field); if (node.isTextual() && node.asText().equals("value")) { localPath = localPath.length() > 0 ? (localPath.endsWith(".") ? localPath : localPath + ".") + field : field; pathToValueNode.put(localPath, schemaNode); } else if ((node.isObject() && !isValid((ObjectNode) node, field, availablePath, null)) || !node.isObject()) { result = false; break; } } else if (field.equals("_children") && !isValid((ObjectNode) node, "", availablePath, schemaNode)) { result = false; break; } } if (log.isDebugEnabled()) { log.debug("object is valid: " + objectNode); log.debug("objectName: " + objectName); log.debug("currentPath: " + currentPath); } return result; } public JsonMetadataExporter() { this(DefaultJasperReportsContext.getInstance()); } public JsonMetadataExporter(JasperReportsContext jasperReportsContext) { super(jasperReportsContext); exporterContext = new ExporterContext(); } @Override protected Class<JsonExporterConfiguration> getConfigurationInterface() { return JsonExporterConfiguration.class; } @Override protected Class<JsonMetadataReportConfiguration> getItemConfigurationInterface() { return JsonMetadataReportConfiguration.class; } @Override @SuppressWarnings("deprecation") protected void ensureOutput() { if (exporterOutput == null) { exporterOutput = new net.sf.jasperreports.export.parameters.ParametersWriterExporterOutput( getJasperReportsContext(), getParameters(), getCurrentJasperPrint()); } } @Override public String getExporterKey() { return JSON_EXPORTER_KEY; } @Override public String getExporterPropertiesPrefix() { return JSON_EXPORTER_PROPERTIES_PREFIX; } @Override public void exportReport() throws JRException { /* */ ensureJasperReportsContext(); ensureInput(); //FIXMENOW check all exporter properties that are supposed to work at report level initExport(); ensureOutput(); writer = getExporterOutput().getWriter(); try { exportReportToWriter(); } catch (IOException e) { throw new JRException(EXCEPTION_MESSAGE_KEY_OUTPUT_WRITER_ERROR, new Object[] { jasperPrint.getName() }, e); } finally { getExporterOutput().close(); resetExportContext();//FIXMEEXPORT check if using same finally is correct; everywhere } } @Override protected void initExport() { super.initExport(); } @Override protected void initReport() { super.initReport(); } protected void exportReportToWriter() throws JRException, IOException { List<ExporterInputItem> items = exporterInput.getItems(); for (reportIndex = 0; reportIndex < items.size(); reportIndex++)//FIXMEJSONMETA deal with batch export { ExporterInputItem item = items.get(reportIndex); setCurrentExporterInputItem(item); JsonMetadataReportConfiguration currentItemConfiguration = getCurrentItemConfiguration(); escapeMembers = currentItemConfiguration.isEscapeMembers(); String jsonSchemaResource = currentItemConfiguration.getJsonSchemaResource(); if (jsonSchemaResource != null) { InputStream is = null; try { is = getRepository().getInputStreamFromLocation(jsonSchemaResource); jsonSchema = new Scanner(is, "UTF-8").useDelimiter("\\A").next(); } finally { if (is != null) { try { is.close(); } catch (IOException e) { } } } validateSchema(jsonSchema); gotSchema = true; } else { if (log.isWarnEnabled()) { log.warn("No JSON Schema provided!"); } } List<JRPrintPage> pages = jasperPrint.getPages(); if (pages != null && pages.size() > 0) { PageRange pageRange = getPageRange(); int startPageIndex = (pageRange == null || pageRange.getStartPageIndex() == null) ? 0 : pageRange.getStartPageIndex(); int endPageIndex = (pageRange == null || pageRange.getEndPageIndex() == null) ? (pages.size() - 1) : pageRange.getEndPageIndex(); JRPrintPage page = null; for (pageIndex = startPageIndex; pageIndex <= endPageIndex; pageIndex++) { if (Thread.interrupted()) { throw new ExportInterruptedException(); } page = pages.get(pageIndex); exportPage(page); } closeOpenNodes(); } if (log.isDebugEnabled()) { for (Map.Entry<String, SchemaNode> entry : pathToValueNode.entrySet()) { log.debug("pathToValueNode: path: " + entry.getKey() + "; node: " + entry.getValue()); } for (Map.Entry<String, SchemaNode> entry : pathToObjectNode.entrySet()) { log.debug("pathToObjectNode: path: " + entry.getKey() + "; node: " + entry.getValue()); } } } boolean flushOutput = getCurrentConfiguration().isFlushOutput(); if (flushOutput) { writer.flush(); } } protected void exportPage(JRPrintPage page) throws IOException { Collection<JRPrintElement> elements = page.getElements(); exportElements(elements); JRExportProgressMonitor progressMonitor = getCurrentItemConfiguration().getProgressMonitor(); if (progressMonitor != null) { progressMonitor.afterPageExport(); } } protected void exportElements(Collection<JRPrintElement> elements) throws IOException { if (elements != null && elements.size() > 0) { for (Iterator<JRPrintElement> it = elements.iterator(); it.hasNext();) { JRPrintElement element = it.next(); if (filter == null || filter.isToExport(element)) { exportElement(element); if (element instanceof JRGenericPrintElement) { //exportElement(element); } else if (element instanceof JRPrintFrame) { exportElements(((JRPrintFrame) element).getElements()); } } } } } protected void exportElement(JRPrintElement element) throws IOException { JRPropertiesMap propMap = element.getPropertiesMap(); List<PropertySuffix> properties = JRPropertiesUtil.getProperties(element, JSON_EXPORTER_PROPERTIES_PREFIX); for (PropertySuffix property : properties) { String propertyPath = null; boolean repeatValue = false; Object value = null; boolean legacyPathProperty = false; String propertyName = property.getKey(); if (propertyName.equals(JSON_EXPORTER_PATH_PROPERTY)) { legacyPathProperty = true; propertyPath = property.getValue(); repeatValue = getPropertiesUtil().getBooleanProperty(propMap, JSON_EXPORTER_REPEAT_VALUE_PROPERTY, false); } else if (propertyName.startsWith(JSON_EXPORTER_STRING_PROPERTIES_PREFIX)) { propertyPath = propertyName.substring(JSON_EXPORTER_STRING_PROPERTIES_PREFIX.length()); repeatValue = getPropertiesUtil().getBooleanProperty(propMap, JSON_EXPORTER_REPEAT_PROPERTIES_PREFIX + propertyPath, false); value = property.getValue(); } else if (propertyName.startsWith(JSON_EXPORTER_NUMBER_PROPERTIES_PREFIX)) { propertyPath = propertyName.substring(JSON_EXPORTER_NUMBER_PROPERTIES_PREFIX.length()); repeatValue = getPropertiesUtil().getBooleanProperty(propMap, JSON_EXPORTER_REPEAT_PROPERTIES_PREFIX + propertyPath, false); value = Double.parseDouble(property.getValue()); } else if (propertyName.startsWith(JSON_EXPORTER_DATE_PROPERTIES_PREFIX)) { propertyPath = propertyName.substring(JSON_EXPORTER_DATE_PROPERTIES_PREFIX.length()); repeatValue = getPropertiesUtil().getBooleanProperty(propMap, JSON_EXPORTER_REPEAT_PROPERTIES_PREFIX + propertyPath, false); try { value = isoDateFormat.parse(property.getValue()); } catch (ParseException e) { throw new JRRuntimeException(e); } } else if (propertyName.startsWith(JSON_EXPORTER_BOOLEAN_PROPERTIES_PREFIX)) { propertyPath = propertyName.substring(JSON_EXPORTER_BOOLEAN_PROPERTIES_PREFIX.length()); repeatValue = getPropertiesUtil().getBooleanProperty(propMap, JSON_EXPORTER_REPEAT_PROPERTIES_PREFIX + propertyPath, false); value = Boolean.parseBoolean(property.getValue()); } if (propertyPath != null && propertyPath.length() > 0) { String absolutePath = JSON_SCHEMA_ROOT_NAME + "." + propertyPath; // we have a mapped node for this path if (gotSchema) { if (pathToValueNode.containsKey(absolutePath)) { if (log.isDebugEnabled()) { log.debug("found element with path: " + propertyPath); } if (legacyPathProperty) { value = getValue(element); } processElement(value, absolutePath, repeatValue); } } else { prepareSchema(absolutePath); if (log.isDebugEnabled()) { log.debug("found element with path: " + propertyPath); } if (legacyPathProperty) { value = getValue(element); } processElement(value, absolutePath, repeatValue); } } } } private void prepareSchema(String absolutePath) { if (!pathToValueNode.containsKey(absolutePath)) { String valueProperty = absolutePath.substring(absolutePath.lastIndexOf(".") + 1); String[] objectPathSegments = absolutePath.substring(0, absolutePath.lastIndexOf(".")).split("\\."); SchemaNode node = null; for (int i = 0; i < objectPathSegments.length; i++) { StringBuilder objectPath = new StringBuilder(objectPathSegments[0]); for (int j = 1; j <= i; j++) { objectPath.append(".").append(objectPathSegments[j]); } if (!pathToObjectNode.containsKey(objectPath.toString())) { String schemaNodePath = ""; for (int k = 0; k < i; k++) { schemaNodePath += schemaNodePath.length() > 0 ? "." + objectPathSegments[k] : objectPathSegments[k]; } node = new SchemaNode(i, objectPathSegments[i], NodeTypeEnum.ARRAY, schemaNodePath); pathToObjectNode.put(objectPath.toString(), node); } else { node = pathToObjectNode.get(objectPath.toString()); } if (i < objectPathSegments.length - 1 && node.getMember(objectPathSegments[i + 1]) == null) { node.addMember(objectPathSegments[i + 1]); } } node.addMember(valueProperty); pathToValueNode.put(absolutePath, node); } } private Object getValue(JRPrintElement element) throws IOException { Object value; final String textStr; final boolean hasDataProp; if (element.getPropertiesMap().containsProperty(JSON_EXPORTER_DATA_PROPERTY)) { hasDataProp = true; textStr = element.getPropertiesMap().getProperty(JSON_EXPORTER_DATA_PROPERTY); } else { hasDataProp = false; if (element instanceof JRPrintText) { JRPrintText printText = (JRPrintText) element; JRStyledText styledText = getStyledText(printText); if (styledText != null) { textStr = styledText.getText(); } else { textStr = null; } } else { textStr = null; } } if (element instanceof JRPrintText) { JRPrintText printText = (JRPrintText) element; TextValue textValue = getTextValue(printText, textStr); LocalTextValueHandler handler = new LocalTextValueHandler(hasDataProp, textStr); try { textValue.handle(handler); } catch (JRException e) { throw new JRRuntimeException(e); } value = handler.getValue(); } else { value = textStr; } return value; } private void processElement(Object value, String absolutePath, boolean repeatValue) throws IOException { if (openedSchemaNodes.size() == 0) { // initialize the json for the first time initJson(absolutePath, value, repeatValue); } else { String valueProperty = absolutePath.substring(absolutePath.lastIndexOf(".") + 1); String[] curSegments = absolutePath.substring(0, absolutePath.lastIndexOf(".")).split("\\."); String[] prevSegments = previousPath.substring(0, previousPath.lastIndexOf(".")).split("\\."); int ln = Math.min(curSegments.length, prevSegments.length); int lastCommonIndex = -1; for (int i = 0; i < ln; i++) { if (curSegments[i].equals(prevSegments[i])) { lastCommonIndex = i; } else { break; } } int commonSegmentsNo = lastCommonIndex + 1; // compared to previous path, we have different path with common segments if (commonSegmentsNo < prevSegments.length) { if (log.isDebugEnabled()) { log.debug("\tgot different path with common segments"); } // close the extra path segments of the previous path closeExtraPathSegments(prevSegments, lastCommonIndex); // open new path segments for the current path openPathSegments(curSegments, lastCommonIndex + 1); } // we have a longer path that extends previous path else if (commonSegmentsNo == prevSegments.length && curSegments.length > prevSegments.length) { if (log.isDebugEnabled()) { log.debug("\tgot longer path than previous one"); } // open new paths openPathSegments(curSegments, lastCommonIndex + 1); } SchemaNode currentNode = pathToValueNode.get(absolutePath); if (log.isDebugEnabled()) { log.debug("\tcurrent node is: " + currentNode.getType().getName()); } if (currentNode.isArray()) { writePathProperty(currentNode, valueProperty, value, repeatValue); } // just write the value for property, no repeat else { writePathProperty(currentNode, valueProperty, value, false); } } previousPath = absolutePath; } private void writePathProperty(SchemaNode node, String valueProperty, Object value, boolean repeatValue) throws IOException { if (log.isDebugEnabled()) { log.debug("\twriting property: " + valueProperty); } ArrayList<String> vizMembers = visitedMembers.get(node); String lastProp = null; int lastPropIdx = -1; int valPropIdx = node.indexOfMember(valueProperty); if (vizMembers != null && vizMembers.size() > 0) { lastProp = vizMembers.get(vizMembers.size() - 1); lastPropIdx = node.indexOfMember(lastProp); } else { vizMembers = new ArrayList<String>(); visitedMembers.put(node, vizMembers); } boolean foundPreviousRepeated = false; // if property of the same object if (lastProp == null || valPropIdx > lastPropIdx) { if (log.isDebugEnabled()) { log.debug("\tgot property of the same object"); } // check for repeated values, if any, before writing current if (lastProp != null) { foundPreviousRepeated = writeReapeatedValues(node, lastPropIdx + 1, valPropIdx); } else { foundPreviousRepeated = writeReapeatedValues(node, 0, valPropIdx); } if (foundPreviousRepeated || vizMembers.size() > 0) { writer.write(",\n"); } writeEscaped(node, valueProperty, value, repeatValue); // mark visited property for current node visitedMembers.get(node).add(valueProperty); } // create new object else { if (log.isDebugEnabled()) { log.debug("\tgot property of a new object"); } // before closing current object, write the repeated values, if any, from last accessed property until the end is reached writeReapeatedValues(node, lastPropIdx + 1, node.getMembers().size()); // close existing object writer.write("},\n{"); // check for repeated values, if any, before writing current foundPreviousRepeated = writeReapeatedValues(node, 0, valPropIdx); if (foundPreviousRepeated) { writer.write(","); } writeEscaped(node, valueProperty, value, repeatValue); // mark visited property for current node visitedMembers.get(node).clear(); visitedMembers.get(node).add(valueProperty); } } private boolean writeReapeatedValues(SchemaNode node, int from, int to) throws IOException { return writeReapeatedValues(node, from, to, true); } private boolean writeReapeatedValues(SchemaNode node, int from, int to, boolean startWithComma) throws IOException { boolean found = false; SchemaNodeMember member; for (int i = from; i < to; i++) { member = node.getMember(i); if (member.isRepeatValue() && member.getPreviousValue() != null) { found = true; if (i != 0 && startWithComma) { writer.write(","); } if (escapeMembers) { writer.write("\"" + member.getName() + "\":"); } else { writer.write(member.getName() + ":"); } writeValue(member.getPreviousValue()); if (log.isDebugEnabled()) { log.debug("\t\twriting repeated value for member: " + member.getName()); } } } return found; } private void writeEscaped(SchemaNode node, String valueProperty, Object value, boolean repeatValue) throws IOException { // write current value if (escapeMembers) { writer.write("\"" + valueProperty + "\":"); } else { writer.write(valueProperty + ":"); } writeValue(value); // mark repeated value if (repeatValue) { SchemaNodeMember nodeMember = node.getMember(valueProperty); nodeMember.setRepeatValue(true); nodeMember.setPreviousValue(value); } } private void closeExtraPathSegments(String[] prevSegments, int lastCommonIndex) throws IOException { for (int i = prevSegments.length - 1; i > lastCommonIndex; i--) { StringBuilder sb = new StringBuilder(prevSegments[0]); for (int j = 1; j <= i; j++) { sb.append(".").append(prevSegments[j]); } SchemaNode toClose = pathToObjectNode.get(sb.toString()); if (openedSchemaNodes.get(openedSchemaNodes.size() - 1).equals(toClose)) { openedSchemaNodes.remove(openedSchemaNodes.size() - 1); } else if (log.isWarnEnabled()) { log.warn("unexpected"); } // write previous repeated before closing if (toClose.isArray()) { List<String> vizMembers = visitedMembers.get(toClose); String lastProp = vizMembers.get(vizMembers.size() - 1); int lastPropIdx = toClose.indexOfMember(lastProp); writeReapeatedValues(toClose, lastPropIdx + 1, toClose.getMembers().size()); // clear visited member cache for closed node vizMembers.clear(); } if (toClose.isObject()) { writer.write("}\n"); } else { writer.write("}]\n"); } if (log.isDebugEnabled()) { log.debug("\t\tclosing " + toClose.getType().getName() + " path: " + sb.toString()); } } } private void openPathSegments(String[] pathSegments, int from) throws IOException { for (int i = from; i < pathSegments.length; i++) { StringBuilder sb = new StringBuilder(pathSegments[0]); StringBuilder parentPath = new StringBuilder(pathSegments[0]); for (int j = 1; j <= i; j++) { sb.append(".").append(pathSegments[j]); if (j < i) { parentPath.append(".").append(pathSegments[j]); } } SchemaNode parent = pathToObjectNode.get(parentPath.toString()); String currentProperty = pathSegments[i]; boolean foundPreviousRepeated = false; ArrayList<String> vizMembers = visitedMembers.get(parent); String lastVisitedProp = null; int lastVisitedPropIdx = -1; int currentPropIdx = parent.indexOfMember(currentProperty); if (vizMembers != null && vizMembers.size() > 0) { lastVisitedProp = vizMembers.get(vizMembers.size() - 1); lastVisitedPropIdx = parent.indexOfMember(lastVisitedProp); } // before opening new path, check if previous has repeated values to be written if (parent.isArray()) { if (lastVisitedProp != null) { foundPreviousRepeated = writeReapeatedValues(parent, lastVisitedPropIdx + 1, currentPropIdx, false); } else { vizMembers = new ArrayList<String>(); visitedMembers.put(parent, vizMembers); } vizMembers.add(currentProperty); } if (foundPreviousRepeated || // got another property of the same object (lastVisitedPropIdx != -1 && currentPropIdx > lastVisitedPropIdx)) { writer.write(","); } if (escapeMembers) { writer.write("\"" + currentProperty + "\":"); } else { writer.write(currentProperty + ":"); } SchemaNode toOpen = pathToObjectNode.get(sb.toString()); openedSchemaNodes.add(toOpen); if (toOpen.isObject()) { writer.write("{"); } else { writer.write("[{"); } if (log.isDebugEnabled()) { log.debug("\t\topening " + toOpen.getType().getName() + " path: " + sb.toString()); } } } private void closeOpenNodes() throws IOException { if (openedSchemaNodes.size() == 0) { return; } SchemaNode toClose; for (int i = openedSchemaNodes.size() - 1; i >= 0; i--) { toClose = openedSchemaNodes.get(i); if (toClose.isArray()) { // write previous repeated before closing List<String> vizMembers = visitedMembers.get(toClose); String lastProp = vizMembers.get(vizMembers.size() - 1); int lastPropIdx = toClose.indexOfMember(lastProp); writeReapeatedValues(toClose, lastPropIdx + 1, toClose.getMembers().size()); // clear visited member cache for closed node vizMembers.clear(); writer.write("}]"); } else { writer.write("}"); } if (log.isDebugEnabled()) { log.debug("closing " + toClose.getType().getName() + " path: " + (toClose.path.length() > 0 ? toClose.path + "." : "") + toClose.name); } } } private void initJson(String firstPath, Object firstValue, boolean repeatValue) throws IOException { if (log.isDebugEnabled()) { log.debug("Initializing JSON with first absolute path: " + firstPath); } String[] segments = firstPath.split("\\."); String currentPath = ""; SchemaNode schemaNode = null; int i; for (i = 0; i < segments.length - 1; i++) { currentPath = currentPath.length() > 0 ? currentPath + "." + segments[i] : segments[i]; schemaNode = pathToObjectNode.get(currentPath); openedSchemaNodes.add(schemaNode); if (i == 0) { // got root node if (schemaNode.isObject()) { writer.write("{"); } else { writer.write("[{"); } } else { String parentPath = currentPath.substring(0, currentPath.lastIndexOf(".")); SchemaNode parent = pathToObjectNode.get(parentPath); String currentProperty = segments[i]; ArrayList<String> vizMembers = new ArrayList<String>(); vizMembers.add(currentProperty); visitedMembers.put(parent, vizMembers); if (schemaNode.isObject()) { if (escapeMembers) { writer.write("\"" + currentProperty + "\": {"); } else { writer.write(currentProperty + ": {"); } } else { if (escapeMembers) { writer.write("\"" + currentProperty + "\": [{"); } else { writer.write(currentProperty + ": [{"); } } } } if (escapeMembers) { writer.write("\"" + segments[i] + "\": "); } else { writer.write(segments[i] + ": "); } writeValue(firstValue); // mark repeated value if (schemaNode != null && repeatValue) { SchemaNodeMember nodeMember = schemaNode.getMember(segments[i]); nodeMember.setRepeatValue(true); nodeMember.setPreviousValue(firstValue); } // mark visited property for current node ArrayList<String> members = new ArrayList<String>(); members.add(segments[i]); visitedMembers.put(schemaNode, members); } private void writeValue(Object value) throws IOException { if (value != null) { if (value instanceof Number || value instanceof Boolean) { writer.write(value.toString()); } else if (value instanceof Date) { writer.write("\""); writer.write(isoDateFormat.format((Date) value)); writer.write("\""); } else { writer.write("\""); writer.write(JsonStringEncoder.getInstance().quoteAsString(value.toString())); writer.write("\""); } } else { writer.write("null"); // FIXMEJSONMETA: how to treat null values? } } @Override protected JRStyledText getStyledText(JRPrintText textElement) { return textElement.getFullStyledText(noneSelector); } protected class ExporterContext extends BaseExporterContext implements JsonExporterContext { @Override public String getHyperlinkURL(JRPrintHyperlink link) { return ""; // FIXMEJSONMETA should we treat hyperlinks? } } private class SchemaNode { private int level; private String name; private NodeTypeEnum type; private String path; private List<SchemaNodeMember> members; private List<String> memberNames; public SchemaNode(int _level, String _name, NodeTypeEnum _type, String _path) { level = _level; name = _name; type = _type; path = _path; members = new ArrayList<SchemaNodeMember>(); memberNames = new ArrayList<String>(); } public NodeTypeEnum getType() { return type; } public void addMember(String memberName) { members.add(new SchemaNodeMember(memberName)); memberNames.add(memberName); } public boolean isObject() { return NodeTypeEnum.OBJECT.equals(type); } public boolean isArray() { return NodeTypeEnum.ARRAY.equals(type); } public int indexOfMember(String memberName) { return memberNames.indexOf(memberName); } public SchemaNodeMember getMember(int i) { return members.get(i); } public SchemaNodeMember getMember(String memberName) { if (indexOfMember(memberName) != -1) { return members.get(indexOfMember(memberName)); } else { return null; } } public List<SchemaNodeMember> getMembers() { return members; } @Override public String toString() { StringBuilder out = new StringBuilder("{"); boolean isArray = NodeTypeEnum.ARRAY.equals(type); out.append("level: ").append(level).append(", "); out.append("name: \"").append(name).append("\", "); out.append("type: \"").append(type.getName()).append("\", "); out.append("path: \"").append(path).append("\", "); out.append("members: ["); if (isArray) { out.append("{"); } for (int i = 0, ln = members.size(); i < ln; i++) { out.append("\"").append(members.get(i).getName()).append("\""); if (i < ln - 1) { out.append(", "); } } if (isArray) { out.append("}"); } out.append("]}"); return out.toString(); } @Override public boolean equals(Object obj) { return this.level == ((SchemaNode) obj).level && this.name.equals(((SchemaNode) obj).name) && this.type.equals(((SchemaNode) obj).type) && this.path.equals(((SchemaNode) obj).path); } @Override public int hashCode() { int hash = level != 0 ? level : 41; hash = hash * 41 + name.hashCode(); hash = hash * 41 + type.hashCode(); hash = hash * 41 + path.hashCode(); return hash; } } private class SchemaNodeMember { private boolean repeatValue; private Object previousValue; private String name; public SchemaNodeMember(String _name) { name = _name; } public String getName() { return name; } public boolean isRepeatValue() { return repeatValue; } public void setRepeatValue(boolean _repeatValue) { repeatValue = _repeatValue; } public Object getPreviousValue() { return previousValue; } public void setPreviousValue(Object _previousValue) { previousValue = _previousValue; } } private enum NodeTypeEnum implements NamedEnum { /** * */ OBJECT("object"), /** * */ ARRAY("array"); private final String name; private NodeTypeEnum(String _name) { name = _name; } @Override public String getName() { return name; } public static NodeTypeEnum getByName(String name) { return EnumUtil.getEnumByName(values(), name); } } private class LocalTextValueHandler implements TextValueHandler { Object value; boolean hasDataProp; String textStr; public LocalTextValueHandler(boolean hasDataProp, String textStr) { this.hasDataProp = hasDataProp; this.textStr = textStr; } public Object getValue() { return value; } @Override public void handle(StringTextValue textValue) { value = textValue.getText(); } @Override public void handle(NumberTextValue textValue) { if (hasDataProp) { if (textStr != null) { try { value = Double.parseDouble(textStr); } catch (NumberFormatException nfe) { throw new JRRuntimeException(nfe); } } } else { value = textValue.getValue(); } } @Override public void handle(DateTextValue textValue) { if (hasDataProp) { if (textStr != null) { try { value = new Date(Long.parseLong(textStr)); } catch (NumberFormatException nfe) { try { value = isoDateFormat.parse(textStr); } catch (ParseException pe) { throw new JRRuntimeException(pe); } } } } else { value = textValue.getValue(); } } @Override public void handle(BooleanTextValue textValue) { value = hasDataProp ? Boolean.valueOf(textStr) : textValue.getValue(); } } }