/*
This file belongs to the Servoy development and deployment environment, Copyright (C) 1997-2010 Servoy BV
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU Affero General Public License as published by the Free
Software Foundation; either version 3 of the License, or (at your option) any
later version.
This program 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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License along
with this program; if not, see http://www.gnu.org/licenses or write to the Free
Software Foundation,Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
*/
package com.servoy.j2db.dataprocessing;
import java.sql.Types;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.StringTokenizer;
import com.servoy.j2db.IServiceProvider;
import com.servoy.j2db.persistence.Column;
import com.servoy.j2db.persistence.IColumnTypes;
import com.servoy.j2db.persistence.IDataProvider;
import com.servoy.j2db.persistence.ValueList;
import com.servoy.j2db.util.Debug;
import com.servoy.j2db.util.FormatParser.ParsedFormat;
import com.servoy.j2db.util.SafeArrayList;
import com.servoy.j2db.util.ScopesUtils;
import com.servoy.j2db.util.Utils;
import com.servoy.j2db.util.model.OptimizedDefaultListModel;
/**
* user defined fixed valuelist.
*
* The {@link #indexOf(Object)} and {@link #realValueIndexOf(Object)} can return values between -2 and greater negative values
* if this valuelist has a fallback valuelist.
*
* Then {@link #getElementAt(int)} and {@link #getRealElementAt(int)} do support those value to get the value from the fallback valuelist.
*
*
* @author jblok
*/
public class CustomValueList extends OptimizedDefaultListModel implements IValueList
{
public static class DisplayString
{
private final List<String> strings;
private final String separator;
private String completeString;
private DisplayString(String separator)
{
this.separator = separator;
strings = new ArrayList<String>();
}
private DisplayString append(String displayString)
{
completeString = null;
strings.add(displayString);
return this;
}
private DisplayString set(String displayString)
{
completeString = null;
strings.clear();
strings.add(displayString);
return this;
}
private void makeString()
{
if (completeString == null)
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < strings.size(); i++)
{
sb.append(strings.get(i));
if (i < strings.size() - 1) sb.append(separator);
}
completeString = sb.toString();
}
}
/**
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj)
{
makeString();
if (obj instanceof DisplayString)
{
return completeString.equals(obj.toString());
}
return completeString.equals(obj);
}
/**
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode()
{
makeString();
return completeString.hashCode();
}
/**
* @see java.lang.Object#toString()
*/
@Override
public String toString()
{
makeString();
return completeString;
}
/**
* @param txt
* @return
*/
public boolean startsWith(String txt)
{
if (strings.size() == 1)
{
return strings.get(0).toLowerCase().startsWith(txt);
}
String[] displayValues = Utils.stringSplit(txt, separator);
for (int i = 0; i < strings.size(); i++)
{
String str = strings.get(i);
for (String displayValue : displayValues)
{
if (str.toLowerCase().startsWith(displayValue)) return true;
}
}
if (txt.length() > strings.get(0).length())
{
makeString();
return completeString.toLowerCase().startsWith(txt);
}
return false;
}
}
protected final ValueList valueList;
protected final IServiceProvider application;
protected List<Object> realValues; //optional can be null;
protected boolean allowEmptySelection = false;
private int valueType;
private List<String> dataproviders;
private ParsedFormat format;
protected IValueList fallbackValueList;
/*
* _____ Declaration and definition of constructors
*/
CustomValueList(IServiceProvider application, ValueList valueList)
{
this.valueList = valueList;
this.application = application;
}
public boolean getAllowEmptySelection()
{
return allowEmptySelection;
}
public CustomValueList(IServiceProvider application, ValueList valueList, String values, boolean addEmpty, int valueType, ParsedFormat format)
{
this(application, valueList);
this.valueType = valueType;
allowEmptySelection = addEmpty;
// always use default format for numbers
if (Column.mapToDefaultType(valueType) != IColumnTypes.NUMBER && Column.mapToDefaultType(valueType) != IColumnTypes.INTEGER) this.format = format;
else this.format = null;
firstFill(values, false);
}
public void setFallbackValueList(IValueList list)
{
fallbackValueList = list;
}
/**
* @see com.servoy.j2db.dataprocessing.IValueList#getFallbackValueList()
*/
public IValueList getFallbackValueList()
{
return fallbackValueList;
}
/**
* @see com.servoy.j2db.dataprocessing.IValueList#getRelationName()
*/
public String getRelationName()
{
return null;
}
public void deregister()
{
// nothing
}
@SuppressWarnings("nls")
private void firstFill(String values, boolean displayValuesOnly)
{
if (values != null && super.getSize() == 0 && SEPARATOR_DESIGN_VALUE.equals(values.toString()))
{
values = null;
}
try
{
startBundlingEvents();
//add empty row
if (allowEmptySelection)
{
super.addElement(""); //$NON-NLS-1$
}
if (values != null)
{
if (values.startsWith("\n") || values.startsWith("\r")) //$NON-NLS-1$ //$NON-NLS-2$
{
super.addElement(""); //$NON-NLS-1$
}
StringTokenizer tk = new StringTokenizer(values.trim(), "\r\n"); //$NON-NLS-1$
while (tk.hasMoreTokens())
{
String line = tk.nextToken();
String[] str = Utils.stringSplit(line, '|', '\\');
// in case we are dealing with a valuelist with display & real values, consider \- display value not to be a separator (normally you could use \\- but it's hard to figure out)
if (SEPARATOR_DESIGN_VALUE.equals(str[0])) str[0] = Utils.stringSplit(line, "|")[0];
if ((SEPARATOR_DESIGN_VALUE.equals(str[0]) || str[1] != null) && !displayValuesOnly)
{
super.addElement(application.getI18NMessageIfPrefixed(str[0]));
if (realValues == null)
{
realValues = new SafeArrayList<Object>();
// add empty values ignore last one
for (int i = 1; i < getSize(); i++)
{
realValues.add(null);
}
}
if (valueType == Types.OTHER)
{
realValues.add(str[1]);
}
else
{
Object realValue = null;
// ^ is null value
if (!"^".equals(str[1]) && str[1] != null)
{
String sRealValue = str[1];
if (sRealValue.startsWith("%%") && ScopesUtils.isVariableScope(sRealValue.substring(2)) && sRealValue.endsWith("%%")) //$NON-NLS-1$//$NON-NLS-2$
{
realValue = application.getFoundSetManager().getScopesScopeProvider().getDataProviderValue(
sRealValue.substring(2, sRealValue.length() - 2));
}
else
{
realValue = str[1];
}
try
{
realValue = Column.getAsRightType(valueType, Column.NORMAL_COLUMN, realValue,
format == null ? null : format.getDisplayFormat(), Integer.MAX_VALUE, null, true);
}
catch (RuntimeException ex)
{
Debug.error("Value List '" + getName() + "' has real value '" + str[1] + "' which cannot be converted to type:" + //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
Column.getDisplayTypeString(valueType), ex);
}
// check if it is a global var
}
realValues.add(realValue);
}
}
else
{
if (realValues == null)
{
displayValuesOnly = true;
String strval = application.getI18NMessageIfPrefixed(line);
if (valueType == Types.OTHER)
{
super.addElement(strval);
}
else
{
if (SEPARATOR_DESIGN_VALUE.equals(strval) || ESCAPED_SEPARATOR_DESIGN_VALUE.equals(strval))
{
super.addElement(strval);
}
else
{
Object value = strval;
if (strval.startsWith("%%") && ScopesUtils.isVariableScope(strval.substring(2)) && strval.endsWith("%%")) //$NON-NLS-1$//$NON-NLS-2$
{
value = application.getFoundSetManager().getScopesScopeProvider().getDataProviderValue(
strval.substring(2, strval.length() - 2));
}
try
{
value = Column.getAsRightType(valueType, Column.NORMAL_COLUMN, value, format == null ? null : format.getDisplayFormat(),
Integer.MAX_VALUE, null, true);
}
catch (Exception e)
{
Debug.error("Could not convert custom value list value '" + strval + "' to type " +
Column.getDisplayTypeString(valueType) + " for value list " + getName() + " -- skipped: " + e.getMessage());
continue;
}
super.addElement(value);
}
}
}
else
{
// Line found without a '|'
realValues = null;
stopBundlingEvents();
removeAllElements();
firstFill(values, true);
break;
}
}
}
}
}
finally
{
stopBundlingEvents();
}
}
private void fillWithArrayValuesImpl(Object[] display_val_array, List<Object> newRealValues)
{
if (display_val_array != null && display_val_array.length == 1 && display_val_array[0] != null &&
SEPARATOR_DESIGN_VALUE.equals(display_val_array[0].toString().trim()))
{
//do not show "-" as only value in valuelist, makes no sense
display_val_array = new Object[0];
newRealValues = new ArrayList<Object>();
}
// make realValues empty first, they may be used in callbacks
realValues = null;
super.removeAllElements();
realValues = newRealValues;
try
{
startBundlingEvents();
//add empty row
if (allowEmptySelection)
{
super.addElement(""); //$NON-NLS-1$
}
if (display_val_array != null)
{
for (Object o : display_val_array)
{
if (o instanceof String)
{
o = application.getI18NMessageIfPrefixed((String)o);
}
if (realValues == null)
{
o = Column.getAsRightType(valueType, Column.NORMAL_COLUMN, o, format == null ? null : format.getDisplayFormat(), Integer.MAX_VALUE,
null, true);
}
else
{
// if we have real values all display values should be strings
o = (o != null) ? o.toString() : null;
}
super.addElement(o);
}
}
}
finally
{
stopBundlingEvents();
}
fireContentsChanged(this, 0, getSize() - 1);
}
public void fillWithArrayValues(Object[] display_val_array, Object[] allRealValues)
{
List<Object> newRrealValues = null;
if (allRealValues != null)
{
newRrealValues = new SafeArrayList<Object>(allRealValues.length);
//add empty row
if (allowEmptySelection)
{
newRrealValues.add(null);
}
for (Object obj : allRealValues)
{
if (valueType != Types.OTHER && Column.mapToDefaultType(valueType) != IColumnTypes.MEDIA)
{
obj = Column.getAsRightType(valueType, Column.NORMAL_COLUMN, obj, format == null ? null : format.getDisplayFormat(), Integer.MAX_VALUE,
null, false);
}
newRrealValues.add(obj);
}
}
fillWithArrayValuesImpl(display_val_array, newRrealValues);
}
/*
* @see com.servoy.j2db.dataprocessing.IValueList#hasRealValues()
*/
public boolean hasRealValues()
{
return realValues != null;
}
public int realValueIndexOf(Object obj)
{
if (realValues != null)
{
int i = realValues.indexOf(obj);
if (i == -1 && fallbackValueList != null)
{
i = fallbackValueList.realValueIndexOf(obj);
if (i != -1)
{
i = (i + 2) * -1; // all values range from -2 > N
}
}
return i;
}
int ret = -1;
if (obj == null) ret = indexOf(null);
else
{
if (obj instanceof Date && format != null && valueType == IColumnTypes.DATETIME)
{
SimpleDateFormat sfsd = new SimpleDateFormat(format.getDisplayFormat());
String selectedFormat = sfsd.format(obj);
for (int i = 0; i < size(); i++)
{
try
{
Object element = getElementAt(i);
if (!(element instanceof Date)) continue;
String elementFormat = sfsd.format(element);
if (Utils.equalObjects(selectedFormat, elementFormat))
{
return i;
}
}
catch (RuntimeException e)
{
Debug.error(e);
}
}
}
else for (int i = 0; i < size(); i++)
{
if (Utils.equalObjects(obj, getElementAt(i))) return i;
}
}
if (ret == -1 && fallbackValueList != null)
{
ret = fallbackValueList.realValueIndexOf(obj);
if (ret != -1)
{
ret = (ret + 2) * -1; // all values range from -2 > N
}
}
return ret;
}
/**
* Gets the real element of this custom valuelist,
* a negative value (excluding -1) will be the fallback valuelist realvalue.
*
* @see com.servoy.j2db.dataprocessing.IValueList#getRealElementAt(int)
*/
//real value, getElementAt is display value
public Object getRealElementAt(int row)
{
if (row < -1 && fallbackValueList != null)
{
return fallbackValueList.getRealElementAt((row * -1) - 2);
}
if (realValues != null)
{
return realValues.get(row);
}
if (row >= 0 && row < size())
{
return getElementAt(row);
}
return null;
}
/**
* If a this valuelist has a fallback valuelist and the value is not found in this list
* then the fallback valuelist will be queried and if found a value between -2 and greater negative value.
*
* @see javax.swing.DefaultListModel#indexOf(java.lang.Object)
*/
@Override
public int indexOf(Object elem)
{
int ret = super.indexOf(elem);
if (ret == -1 && fallbackValueList != null)
{
ret = fallbackValueList.indexOf(elem);
if (ret != -1)
{
ret = (ret + 2) * -1; // all values range from -2 > N
}
}
return ret;
}
/**
* If a this valuelist has a fallback valuelist and the value is not found in this list
* then the fallback valuelist will be queried and if found a value between -2 and greater negative value.
* @see javax.swing.DefaultListModel#getElementAt(int)
*/
@Override
public Object getElementAt(int index)
{
if (index < -1 && fallbackValueList != null)
{
return fallbackValueList.getElementAt((index * -1) - 2);
}
return super.getElementAt(index);
}
public void fill(IRecordInternal parentState)
{
if (fallbackValueList != null) fallbackValueList.fill(parentState);
}
public static Object[] processRow(Object[] row, int showValues, int returnValues)
{
Object[] returnValue = row;
boolean appendFirstRow = (showValues & 1) == 0 && (returnValues & 1) == 0;
boolean appendSecondRow = (showValues & 2) == 0 && (returnValues & 2) == 0;
boolean appendThirdRow = (showValues & 4) == 0 && (returnValues & 4) == 0;
if (!appendFirstRow && appendSecondRow && appendThirdRow)
{
returnValue = new Object[] { row[0], null, null };
}
else if (appendFirstRow && appendThirdRow)
{
returnValue = new Object[] { null, row[0], null };
}
else if (appendFirstRow && appendSecondRow)
{
returnValue = new Object[] { null, null, row[0] };
}
else if (appendFirstRow)
{
returnValue = new Object[] { null, row[0], row[1] };
}
else if (appendSecondRow)
{
returnValue = new Object[] { row[0], null, row[1] };
}
else if (appendThirdRow)
{
returnValue = new Object[] { row[0], row[1], null };
}
return returnValue;
}
public static Object handleRowData(ValueList vl, String[] displayFormats, boolean concat, int bitset, IRecordInternal row, IServiceProvider application)
{
Object[] args = new Object[3];
if ((bitset & 1) != 0) args[0] = row.getValue(vl.getDataProviderID1());
if ((bitset & 2) != 0) args[1] = row.getValue(vl.getDataProviderID2());
if ((bitset & 4) != 0) args[2] = row.getValue(vl.getDataProviderID3());
if (displayFormats != null) return handleDisplayData(vl, displayFormats, concat, bitset, args, application).toString();
else return handleRowData(vl, concat, bitset, args, application);
}
public static Object handleRowData(ValueList vl, boolean concat, int bitset, Object[] row, IServiceProvider application)
{
Object anObject = null;
StringBuffer showVal = null;
if (concat) showVal = new StringBuffer();
if ((bitset & 1) != 0)
{
Object val = row[0];
if (concat)
{
showVal.append(convertToString(val, application));
}
else
{
anObject = val;
}
}
if ((bitset & 2) != 0)
{
Object val = row[1];
if (concat)
{
if (showVal.length() != 0) showVal.append(vl.getSeparator());
showVal.append(convertToString(val, application));
}
else
{
anObject = val;
}
}
if ((bitset & 4) != 0)
{
Object val = row[2];
if (concat)
{
if (showVal.length() != 0) showVal.append(vl.getSeparator());
showVal.append(convertToString(val, application));
}
else
{
anObject = val;
}
}
if (concat)
{
return showVal.toString();
}
else
{
return anObject;
}
}
public static DisplayString handleDisplayData(ValueList vl, String[] displayFormat, boolean concat, int bitset, Object[] row, IServiceProvider application)
{
DisplayString showVal = new DisplayString(vl.getSeparator());
if ((bitset & 1) != 0)
{
String str = convertToString(row[0], displayFormat[0], application);
if (concat)
{
showVal.append(str);
}
else
{
showVal.set(str);
}
}
if ((bitset & 2) != 0)
{
String str = convertToString(row[1], displayFormat[1], application);
if (concat)
{
showVal.append(str);
}
else
{
showVal.set(str);
}
}
if ((bitset & 4) != 0)
{
String str = convertToString(row[2], displayFormat[2], application);
if (concat)
{
showVal.append(str);
}
else
{
showVal.set(str);
}
}
return showVal;
}
public static String convertToString(Object obj, IServiceProvider application)
{
return convertToString(obj, null, application);
}
public static String convertToString(Object obj, String format, IServiceProvider application)
{
if (obj == null)
{
return ""; //$NON-NLS-1$
}
return TagResolver.formatObject(obj, format, application.getLocale(), application.getSettings());
}
public String getName()
{
return valueList == null ? "<unknown>" : valueList.getName();
}
public ParsedFormat getFormat()
{
return format;
}
public int getValueType()
{
return valueType;
}
public void setValueType(int t)
{
this.valueType = t;
}
@Override
public void setElementAt(Object obj, int index)
{
stopBundlingEvents(); // just to be sure
super.setElementAt(obj, index);
}
@Override
public Object set(int index, Object element)
{
stopBundlingEvents(); // just to be sure
return super.set(index, element);
}
/**
* @param dataprovider
*/
public void addDataProvider(String dataprovider)
{
if (dataproviders == null) dataproviders = new ArrayList<String>(3);
dataproviders.add(dataprovider);
}
public List<String> getDataProviders()
{
return (dataproviders == null ? Collections.<String> emptyList() : dataproviders);
}
public ValueList getValueList()
{
return valueList;
}
@Override
public IDataProvider[] getDependedDataProviders()
{
return null;
}
}