blob: 20d9048e887aa5c4fba1ba432417d7858fc7964b [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2010, 2021 Stephan Wahlbrink and others.
#
# This program and the accompanying materials are made available under the
# terms of the Eclipse Public License 2.0 which is available at
# https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
# which is available at https://www.apache.org/licenses/LICENSE-2.0.
#
# SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
#
# Contributors:
# Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation
#=============================================================================*/
package org.eclipse.statet.internal.r.ui.dataeditor;
import java.util.Date;
import java.util.Locale;
import com.ibm.icu.math.BigDecimal;
import com.ibm.icu.math.MathContext;
import com.ibm.icu.text.DateFormat;
import com.ibm.icu.text.DecimalFormat;
import com.ibm.icu.text.DecimalFormatSymbols;
import com.ibm.icu.text.SimpleDateFormat;
import com.ibm.icu.util.Calendar;
import com.ibm.icu.util.TimeZone;
import com.ibm.icu.util.ULocale;
import org.eclipse.statet.ecommons.waltable.data.ControlData;
import org.eclipse.statet.rj.data.RCharacterStore;
public class RDataFormatter {
private static final char[] HEX_CHARS= new char[] {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
};
public static final int MILLIS_PER_SECOND= 1000;
public static final int MILLIS_PER_MINUTE= 60 * MILLIS_PER_SECOND;
public static final int MILLIS_PER_HOUR= 60 * MILLIS_PER_MINUTE;
public static final int MILLIS_PER_DAY= 24 * MILLIS_PER_HOUR;
private final StringBuilder fCurrentText= new StringBuilder();
private DecimalFormat fNumFormat;
private int fNumMaxExpDigits;
private MathContext fMathContext;
private DateFormat fDateFormat;
private long fDateValueMillis;
private RCharacterStore fFactorLevels;
private int fAutoWidth= -1;
public RDataFormatter() {
}
protected void clean() {
this.fNumFormat= null;
this.fMathContext= null;
this.fDateFormat= null;
this.fFactorLevels= null;
}
protected void appendByteHexFormat(final int b) {
this.fCurrentText.append(HEX_CHARS[((b >> 2) & 0xf)]);
this.fCurrentText.append(HEX_CHARS[(b & 0xf)]);
}
protected void appendNum(final double num) {
if (Double.isNaN(num) || Double.isInfinite(num)) {
this.fCurrentText.append(this.fNumFormat.format(num));
return;
}
BigDecimal decimal= new BigDecimal(num);
if (this.fMathContext != null) {
decimal= decimal.multiply(BigDecimal.ONE, this.fMathContext);
}
this.fCurrentText.append(this.fNumFormat.format(decimal));
}
public Object modelToDisplayValue(Object modelValue) {
if (modelValue == null) {
return AbstractRDataProvider.NA;
}
final Class<?> clazz= modelValue.getClass();
if (clazz == ControlData.class) {
return modelValue;
}
if (clazz == Double.class) {
if (this.fNumFormat != null) {
this.fCurrentText.setLength(0);
appendNum((Double) modelValue);
return this.fCurrentText.toString();
}
if (this.fDateFormat != null) {
return this.fDateFormat.format(new Date((long)
((Double) modelValue).doubleValue() * this.fDateValueMillis) );
}
}
if (clazz == Boolean.class) {
return ((Boolean) modelValue).booleanValue() ? "TRUE" : "FALSE";
}
INT: if (clazz == Integer.class){
if (this.fFactorLevels != null) {
final int value= ((Integer) modelValue).intValue() - 1;
if (value >= 0 && value < this.fFactorLevels.getLength()) {
modelValue= this.fFactorLevels.getChar(value);
break INT;
}
else {
return new ControlData(ControlData.ERROR, "?" + modelValue + "?");
}
}
if (this.fDateFormat != null) {
return this.fDateFormat.format(new Date(
((Integer) modelValue).intValue() * this.fDateValueMillis) );
}
return modelValue.toString();
}
if (clazz == String.class) {
final String text= (String) modelValue;
this.fCurrentText.setLength(0);
int beginIdx= 0;
int i= 0;
final int length= text.length();
while (i < length) {
final char c= text.charAt(i);
switch (c) {
case 10:
if (i > beginIdx) {
this.fCurrentText.append(text, beginIdx, i);
}
this.fCurrentText.append("\\n");
beginIdx= ++i;
continue;
case 13:
if (i > beginIdx) {
this.fCurrentText.append(text, beginIdx, i);
}
this.fCurrentText.append("\\r");
beginIdx= ++i;
continue;
case 9:
if (i > beginIdx) {
this.fCurrentText.append(text, beginIdx, i);
}
this.fCurrentText.append("\\t");
beginIdx= ++i;
continue;
case 8:
if (i > beginIdx) {
this.fCurrentText.append(text, beginIdx, i);
}
this.fCurrentText.append("\\b");
beginIdx= ++i;
continue;
case 7:
if (i > beginIdx) {
this.fCurrentText.append(text, beginIdx, i);
}
this.fCurrentText.append("\\a");
beginIdx= ++i;
continue;
case 12:
if (i > beginIdx) {
this.fCurrentText.append(text, beginIdx, i);
}
this.fCurrentText.append("\\f");
beginIdx= ++i;
continue;
case 11:
if (i > beginIdx) {
this.fCurrentText.append(text, beginIdx, i);
}
this.fCurrentText.append("\\v");
beginIdx= ++i;
continue;
case 92:
if (i > beginIdx) {
this.fCurrentText.append(text, beginIdx, i);
}
this.fCurrentText.append("\\\\");
beginIdx= ++i;
continue;
default:
if (c < 0x20) {
if (i > beginIdx) {
this.fCurrentText.append(text, beginIdx, i);
}
this.fCurrentText.append("\\0x");
appendByteHexFormat(c);
beginIdx= ++i;
continue;
}
i++;
continue;
}
}
if (beginIdx > 0) {
if (beginIdx < length) {
this.fCurrentText.append(text, beginIdx, length);
}
return this.fCurrentText.toString();
}
else {
return text;
}
}
if (clazz == Byte.class) {
this.fCurrentText.setLength(0);
appendByteHexFormat(((Byte) modelValue).intValue());
return this.fCurrentText.toString();
}
return modelValue;
}
public boolean hasNumFormat() {
return (this.fNumFormat != null);
}
public int getMaxFractionalDigits() {
return this.fNumFormat.getMaximumFractionDigits();
}
public int getMaxExponentDigits() {
return this.fNumMaxExpDigits;
}
public void initNumFormat(final int maxFractionalDigits, final int maxExponentDigits) {
clean();
this.fNumMaxExpDigits= maxExponentDigits;
final DecimalFormatSymbols symbols= new DecimalFormatSymbols(Locale.ENGLISH);
symbols.setExponentSeparator("e");
symbols.setNaN("NaN");
symbols.setInfinity("Inf");
final DecimalFormat decimalFormat= new DecimalFormat("0.", symbols);
this.fNumFormat= decimalFormat;
decimalFormat.setDecimalSeparatorAlwaysShown(false);
decimalFormat.setGroupingUsed(false);
decimalFormat.setExponentSignAlwaysShown(true);
decimalFormat.setSignificantDigitsUsed(false);
decimalFormat.setMinimumFractionDigits(maxFractionalDigits);
decimalFormat.setMaximumFractionDigits(maxFractionalDigits);
if (maxExponentDigits > 0) {
this.fMathContext= new MathContext(maxFractionalDigits+1, MathContext.SCIENTIFIC, false, MathContext.ROUND_HALF_UP);
decimalFormat.setScientificNotation(true);
decimalFormat.setMinimumExponentDigits((byte) maxExponentDigits);
}
else {
decimalFormat.setScientificNotation(false);
decimalFormat.setRoundingMode(MathContext.ROUND_HALF_UP);
decimalFormat.setRoundingIncrement(BigDecimal.valueOf(1L, maxFractionalDigits));
}
}
public void initDateFormat(final int millis) {
clean();
final SimpleDateFormat dateFormat= new SimpleDateFormat("yyyy-MM-dd"); //$NON-NLS-1$
this.fDateFormat= dateFormat;
this.fDateValueMillis= millis;
this.fDateFormat.setCalendar(Calendar.getInstance(TimeZone.getTimeZone("UTC"), ULocale.ENGLISH)); //$NON-NLS-1$
}
public void initDateTimeFormat(final int millis) {
clean();
final SimpleDateFormat dateFormat= new SimpleDateFormat("yyyy-MM-dd HH:mm:ss zz"); //$NON-NLS-1$
this.fDateFormat= dateFormat;
this.fDateValueMillis= millis;
}
public void setDateTimeZone(final TimeZone zone) {
if (this.fDateFormat == null) {
throw new IllegalStateException();
}
this.fDateFormat.setTimeZone(zone);
}
public void initFactorLevels(final RCharacterStore levels) {
clean();
this.fFactorLevels= levels;
}
public RCharacterStore getFactorLevels() {
return this.fFactorLevels;
}
public void setAutoWidth(final int width) {
this.fAutoWidth= width;
}
public int getAutoWidth() {
return this.fAutoWidth;
}
}