use of org.apache.sis.util.resources.Vocabulary in project sis by apache.
the class ParameterFormat method format.
/**
* Implementation of public {@code format(…)} methods for all content levels except {@code NAME_SUMMARY}.
*
* @param name the group name, usually {@code descriptor.getName().getCode()}.
* @param group the parameter descriptor, usually {@code values.getDescriptor()}.
* @param values the parameter values, or {@code null} if none.
* @throws IOException if an error occurred while writing to the given appendable.
*/
private void format(final String name, final ParameterDescriptorGroup group, final ParameterValueGroup values, final Appendable out) throws IOException {
final boolean isBrief = (contentLevel == ContentLevel.BRIEF);
final boolean showObligation = !isBrief || (values == null);
final boolean hasColors = (colors != null);
final String lineSeparator = this.lineSeparator;
final Map<String, Integer> remarks = new LinkedHashMap<>();
final ParameterTableRow header = new ParameterTableRow(group, displayLocale, preferredCodespaces, remarks, isBrief);
final String groupCodespace = header.getCodeSpace();
/*
* Prepares the informations to be printed later as table rows. We scan all rows before to print them
* in order to compute the width of codespaces. During this process, we split the objects to be printed
* later in two collections: simple parameters are stored as (descriptor,value) pairs, while groups are
* stored in an other collection for deferred formatting after the simple parameters.
*/
int codespaceWidth = 0;
final Collection<?> elements = (values != null) ? values.values() : group.descriptors();
final Map<GeneralParameterDescriptor, ParameterTableRow> descriptorValues = new LinkedHashMap<>(hashMapCapacity(elements.size()));
// To be created only if needed (it is usually not).
List<Object> deferredGroups = null;
for (final Object element : elements) {
final GeneralParameterValue parameter;
final GeneralParameterDescriptor descriptor;
if (values != null) {
parameter = (GeneralParameterValue) element;
descriptor = parameter.getDescriptor();
} else {
parameter = null;
descriptor = (GeneralParameterDescriptor) element;
}
if (descriptor instanceof ParameterDescriptorGroup) {
if (deferredGroups == null) {
deferredGroups = new ArrayList<>(4);
}
deferredGroups.add(element);
continue;
}
/*
* In the vast majority of cases, there is only one value for each parameter. However
* if we find more than one value, we will append all extra occurrences in a "multiple
* values" list to be formatted in the same row.
*/
Object value = null;
Unit<?> unit = null;
if (parameter instanceof ParameterValue<?>) {
final ParameterValue<?> p = (ParameterValue<?>) parameter;
value = p.getValue();
unit = p.getUnit();
} else if (descriptor instanceof ParameterDescriptor<?>) {
final ParameterDescriptor<?> p = (ParameterDescriptor<?>) descriptor;
value = p.getDefaultValue();
unit = p.getUnit();
}
ParameterTableRow row = descriptorValues.get(descriptor);
if (row == null) {
row = new ParameterTableRow(descriptor, displayLocale, preferredCodespaces, remarks, isBrief);
descriptorValues.put(descriptor, row);
if (row.codespaceWidth > codespaceWidth) {
codespaceWidth = row.codespaceWidth;
}
}
row.addValue(value, unit);
}
/*
* Finished to collect the values. Now transform the values:
*
* - Singleton value of array types (either primitive or not) are wrapped into a list.
* - Values are formatted.
* - Value domains are formatted.
* - Position of the character on which to do the alignment are remembered.
*/
int unitWidth = 0;
int valueDomainAlignment = 0;
boolean writeCodespaces = (groupCodespace == null);
final StringBuffer buffer = new StringBuffer();
final FieldPosition dummyFP = new FieldPosition(-1);
for (final Map.Entry<GeneralParameterDescriptor, ParameterTableRow> entry : descriptorValues.entrySet()) {
final GeneralParameterDescriptor descriptor = entry.getKey();
if (descriptor instanceof ParameterDescriptor<?>) {
final ParameterTableRow row = entry.getValue();
/*
* Verify if all rows use the same codespace than the header, in which case we can omit
* row codespace formatting.
*/
if (!writeCodespaces && !groupCodespace.equals(entry.getValue().getCodeSpace())) {
writeCodespaces = true;
}
/*
* Format the value domain, so we can compute the character position on which to perform alignment.
*/
final Range<?> valueDomain = Parameters.getValueDomain((ParameterDescriptor<?>) descriptor);
if (valueDomain != null) {
final int p = row.setValueDomain(valueDomain, getFormat(Range.class), buffer);
if (p > valueDomainAlignment) {
valueDomainAlignment = p;
}
}
/*
* Singleton array conversion. Because it may be an array of primitive types, we can not just
* cast to Object[]. Then formats the units, with a space before the unit if the symbol is a
* letter or digit (i.e. we do not put a space in front of ° symbol for instance).
*/
row.expandSingleton();
final int length = row.units.size();
for (int i = 0; i < length; i++) {
final Object unit = row.units.get(i);
if (unit != null) {
if (getFormat(Unit.class).format(unit, buffer, dummyFP).length() != 0) {
if (Character.isLetterOrDigit(buffer.codePointAt(0))) {
buffer.insert(0, ' ');
}
}
final String symbol = buffer.toString();
row.units.set(i, symbol);
buffer.setLength(0);
final int p = symbol.length();
if (p > unitWidth) {
unitWidth = p;
}
}
}
}
}
/*
* Finished to prepare information. Now begin the actual writing.
* First, formats the table header (i.e. the column names).
*/
final Vocabulary resources = Vocabulary.getResources(displayLocale);
header.writeIdentifiers(out, true, colors, false, lineSeparator);
out.append(lineSeparator);
final char horizontalBorder = isBrief ? '─' : '═';
final TableAppender table = (isBrief || !columnSeparator.equals(SEPARATOR)) ? new TableAppender(out, columnSeparator) : new TableAppender(out);
table.setMultiLinesCells(true);
table.nextLine(horizontalBorder);
int numColumnsBeforeValue = 0;
for (int i = 0; ; i++) {
boolean end = false;
final short key;
switch(i) {
case 0:
{
key = Vocabulary.Keys.Name;
break;
}
case 1:
{
key = Vocabulary.Keys.Type;
break;
}
case 2:
{
if (!showObligation) {
continue;
}
key = Vocabulary.Keys.Obligation;
break;
}
case 3:
{
key = Vocabulary.Keys.ValueDomain;
break;
}
case 4:
{
key = (values == null) ? Vocabulary.Keys.DefaultValue : Vocabulary.Keys.Value;
end = true;
break;
}
default:
throw new AssertionError(i);
}
if (hasColors)
table.append(X364.BOLD.sequence());
table.append(resources.getString(key));
if (hasColors)
table.append(X364.NORMAL.sequence());
if (!writeCodespaces && i == 0) {
table.append(" (").append(groupCodespace).append(')');
}
if (end)
break;
nextColumn(table);
numColumnsBeforeValue++;
}
table.nextLine();
/*
* Now process to the formatting of (descriptor,value) pairs. Each descriptor's alias
* will be formatted on its own line in a table row. If there is more than one value,
* then each value will be formatted on its own line as well. Note that the values may
* be null if there is none.
*/
char horizontalLine = horizontalBorder;
for (final Map.Entry<GeneralParameterDescriptor, ParameterTableRow> entry : descriptorValues.entrySet()) {
if (horizontalLine != 0) {
table.nextLine('─');
}
horizontalLine = isBrief ? 0 : '─';
final ParameterTableRow row = entry.getValue();
row.codespaceWidth = codespaceWidth;
row.writeIdentifiers(table, writeCodespaces, null, hasColors, lineSeparator);
nextColumn(table);
final GeneralParameterDescriptor generalDescriptor = entry.getKey();
if (generalDescriptor instanceof ParameterDescriptor<?>) {
/*
* Writes value type.
*/
final ParameterDescriptor<?> descriptor = (ParameterDescriptor<?>) generalDescriptor;
final Class<?> valueClass = descriptor.getValueClass();
if (valueClass != null) {
// Should never be null, but let be safe.
table.append(getFormat(Class.class).format(valueClass, buffer, dummyFP).toString());
}
nextColumn(table);
buffer.setLength(0);
/*
* Writes the obligation (mandatory or optional).
*/
if (showObligation) {
final int minimumOccurs = descriptor.getMinimumOccurs();
final int maximumOccurs = descriptor.getMaximumOccurs();
if (maximumOccurs == 1) {
table.append(resources.getString(minimumOccurs == 0 ? Vocabulary.Keys.Optional : Vocabulary.Keys.Mandatory));
} else {
final Format f = getFormat(Integer.class);
table.append(f.format(minimumOccurs, buffer, dummyFP).toString()).append(" … ");
buffer.setLength(0);
if (maximumOccurs == Integer.MAX_VALUE) {
table.append('∞');
} else {
table.append(f.format(maximumOccurs, buffer, dummyFP).toString());
buffer.setLength(0);
}
}
nextColumn(table);
}
/*
* Writes minimum and maximum values, together with the unit of measurement (if any).
*/
final String valueDomain = row.valueDomain;
if (valueDomain != null) {
table.append(CharSequences.spaces(valueDomainAlignment - row.valueDomainAlignment)).append(valueDomain);
}
nextColumn(table);
/*
* Writes the values, each on its own line, together with their unit of measurement.
*/
final byte alignment = Number.class.isAssignableFrom(valueClass) ? TableAppender.ALIGN_RIGHT : TableAppender.ALIGN_LEFT;
table.setCellAlignment(alignment);
final int length = row.values.size();
for (int i = 0; i < length; i++) {
Object value = row.values.get(i);
if (value != null) {
if (i != 0) {
/*
* If the same parameter is repeated more than once (not allowed by ISO 19111,
* but this extra flexibility is allowed by Apache SIS), write the ditto mark
* in all previous columns (name, type, etc.) on a new row.
*/
final String ditto = resources.getString(Vocabulary.Keys.DittoMark);
table.nextLine();
table.setCellAlignment(TableAppender.ALIGN_CENTER);
for (int j = 0; j < numColumnsBeforeValue; j++) {
table.append(ditto);
nextColumn(table);
}
table.setCellAlignment(alignment);
}
/*
* Format the value followed by the unit of measure, or followed by spaces if there is no unit
* for this value. The intent is the right align the numerical value rather than the numerical
* + unit tupple.
*/
final Format format = getFormat(value.getClass());
if (format != null) {
if (format instanceof NumberFormat && value instanceof Number) {
configure((NumberFormat) format, Math.abs(((Number) value).doubleValue()));
}
value = format.format(value, buffer, dummyFP);
}
table.append(value.toString());
buffer.setLength(0);
int pad = unitWidth;
final String unit = (String) row.units.get(i);
if (unit != null) {
table.append(unit);
pad -= unit.length();
}
table.append(CharSequences.spaces(pad));
}
}
}
table.nextLine();
table.setCellAlignment(TableAppender.ALIGN_LEFT);
}
table.nextLine(horizontalBorder);
table.flush();
/*
* Write remarks, if any.
*/
for (final Map.Entry<String, Integer> remark : remarks.entrySet()) {
ParameterTableRow.writeFootnoteNumber(out, remark.getValue());
out.append(' ').append(remark.getKey()).append(lineSeparator);
}
/*
* Now formats all groups deferred to the end of this table, with recursive calls to
* this method (recursive calls use their own TableWriter instance, so they may result
* in a different cell layout). Most of the time, there is no such additional group.
*/
if (deferredGroups != null) {
for (final Object element : deferredGroups) {
final ParameterValueGroup value;
final ParameterDescriptorGroup descriptor;
if (element instanceof ParameterValueGroup) {
value = (ParameterValueGroup) element;
descriptor = value.getDescriptor();
} else {
value = null;
descriptor = (ParameterDescriptorGroup) element;
}
out.append(lineSeparator);
format(name + '/' + descriptor.getName().getCode(), descriptor, value, out);
}
}
}
use of org.apache.sis.util.resources.Vocabulary in project sis by apache.
the class StatisticsFormat method format.
/**
* Formats the given statistics in a tabular format. This method does not check
* for the statistics on {@linkplain Statistics#differences() differences} - if
* such statistics are wanted, they must be included in the given array.
*
* @param stats the statistics to format.
* @param toAppendTo where to format the statistics.
* @throws IOException if an error occurred while writing to the given appendable.
*/
public void format(final Statistics[] stats, final Appendable toAppendTo) throws IOException {
/*
* Inspect the given statistics in order to determine if we shall omit the headers,
* and if we shall omit the count of NaN values.
*/
final String[] headers = new String[stats.length];
boolean showHeaders = false;
boolean showNaNCount = false;
for (int i = 0; i < stats.length; i++) {
final Statistics s = stats[i];
showNaNCount |= (s.countNaN() != 0);
final InternationalString header = s.name();
if (header != null) {
headers[i] = header.toString(headerLocale);
showHeaders |= (headers[i] != null);
}
}
char horizontalLine = 0;
String separator = columnSeparator;
switch(borderWidth) {
case 1:
horizontalLine = '─';
separator += "│ ";
break;
case 2:
horizontalLine = '═';
separator += "║ ";
break;
}
final TableAppender table = new TableAppender(toAppendTo, separator);
final Vocabulary resources = Vocabulary.getResources(headerLocale);
/*
* If there is a header for at least one statistics, write the full headers row.
*/
if (horizontalLine != 0) {
table.nextLine(horizontalLine);
}
if (showHeaders) {
table.nextColumn();
for (final String header : headers) {
if (header != null) {
table.append(header);
table.setCellAlignment(TableAppender.ALIGN_CENTER);
}
table.nextColumn();
}
table.append(lineSeparator);
if (horizontalLine != 0) {
table.nextLine(horizontalLine);
}
}
/*
* Initialize the NumberFormat for formatting integers without scientific notation.
* This is necessary since the format may have been modified by a previous execution
* of this method.
*/
final Format format = getFormat(Double.class);
if (format instanceof DecimalFormat) {
// Also disable scientific notation.
((DecimalFormat) format).applyPattern("#0");
} else if (format instanceof NumberFormat) {
setFractionDigits((NumberFormat) format, 0);
}
/*
* Iterates over the rows to format (count, minimum, maximum, mean, RMS, standard deviation),
* then iterate over columns (statistics on sample values, on the first derivatives, etc.)
* The NumberFormat configuration may be different for each column, but we can skip many
* reconfiguration in the common case where there is only one column.
*/
boolean needsConfigure = false;
for (int i = 0; i < KEYS.length; i++) {
switch(i) {
case 1:
if (!showNaNCount)
continue;
else
break;
// Case 3 and others need reconfiguration only if there is more than one column.
case 2:
needsConfigure = true;
break;
case 3:
needsConfigure = (stats[0].differences() != null);
break;
}
table.setCellAlignment(TableAppender.ALIGN_LEFT);
table.append(resources.getString(KEYS[i])).append(':');
for (final Statistics s : stats) {
final Number value;
switch(i) {
case 0:
value = s.count();
break;
case 1:
value = s.countNaN();
break;
case 2:
value = s.minimum();
break;
case 3:
value = s.maximum();
break;
case 4:
value = s.mean();
break;
case 5:
value = s.rms();
break;
case 6:
value = s.standardDeviation(allPopulation);
break;
default:
throw new AssertionError(i);
}
if (needsConfigure) {
configure(format, s);
}
table.append(beforeFill);
table.nextColumn(fillCharacter);
table.append(format.format(value));
table.setCellAlignment(TableAppender.ALIGN_RIGHT);
}
table.append(lineSeparator);
}
if (horizontalLine != 0) {
table.nextLine(horizontalLine);
}
/*
* TableAppender needs to be explicitly flushed in order to format the values.
*/
table.flush();
}
use of org.apache.sis.util.resources.Vocabulary in project sis by apache.
the class Exceptions method formatChainedMessages.
/**
* Returns a string which contain the given message on the first line, followed by the
* {@linkplain #getLocalizedMessage(Throwable, Locale) localized message} of the given exception
* on the next line. If the exception has a {@linkplain Throwable#getCause() causes}, then
* the class name and the localized message of the cause are formatted on the next line
* and the process is repeated for the whole cause chain, omitting duplicated messages.
*
* <p>{@link SQLException} is handled especially in order to process the
* {@linkplain SQLException#getNextException() next exception} instead than the cause.</p>
*
* <p>This method does not format the stack trace.</p>
*
* @param locale the preferred locale for the exception message, or {@code null}.
* @param header the message to insert on the first line, or {@code null} if none.
* @param cause the exception, or {@code null} if none.
* @return the formatted message, or {@code null} if both the header was {@code null}
* and no exception provide a message.
*/
public static String formatChainedMessages(final Locale locale, final String header, Throwable cause) {
final List<String> previousLines = new ArrayList<>();
StringBuilder buffer = null;
Vocabulary resources = null;
while (cause != null) {
final String message = CharSequences.trimWhitespaces(getLocalizedMessage(cause, locale));
if (message != null && !message.isEmpty()) {
if (buffer == null) {
buffer = new StringBuilder(128);
if (header != null) {
final int length = CharSequences.skipTrailingWhitespaces(header, 0, header.length());
if (length > 0) {
buffer.append(header, 0, length);
}
previousLines.add(header);
}
}
if (!contains(previousLines, message)) {
previousLines.add(message);
if (buffer.length() != 0) {
if (resources == null) {
resources = Vocabulary.getResources(locale);
}
buffer.append(System.lineSeparator()).append(resources.getString(Vocabulary.Keys.CausedBy_1, cause.getClass())).append(": ");
}
buffer.append(message);
}
}
if (cause instanceof SQLException) {
final SQLException next = ((SQLException) cause).getNextException();
if (next != null) {
cause = next;
continue;
}
}
cause = cause.getCause();
}
return (buffer != null) ? buffer.toString() : header;
}
Aggregations