package au.com.vaadinutils.crud;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import javax.persistence.metamodel.SingularAttribute;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.google.common.base.Preconditions;
import com.vaadin.data.Item;
import com.vaadin.server.FontAwesome;
import com.vaadin.shared.ui.label.ContentMode;
import com.vaadin.ui.Label;
import com.vaadin.ui.Table;
import com.vaadin.ui.Table.ColumnCollapseEvent;
import com.vaadin.ui.Table.ColumnCollapseListener;
import com.vaadin.ui.Table.ColumnGenerator;
import com.vaadin.ui.Table.ColumnReorderEvent;
import com.vaadin.ui.Table.ColumnReorderListener;
import com.vaadin.ui.Table.ColumnResizeEvent;
import com.vaadin.ui.Table.ColumnResizeListener;
import au.com.vaadinutils.dao.Path;
import au.com.vaadinutils.user.UserSettingsStorageFactory;
public class HeadingPropertySet
{
private List<HeadingToPropertyId> cols = new LinkedList<HeadingToPropertyId>();
private Logger logger = LogManager.getLogger();
public boolean autoExpandColumns = false;
private boolean eraseSavedConfig = false;
private HeadingPropertySet()
{
// use the builder!
}
public static <E> Builder<E> getBuilder(Class<E> Class)
{
return new Builder<E>();
}
interface Start<E>
{
public AddingColumn<E> createColumn(String heading, String propertyId);
public <T> AddingColumn<E> createColumn(String heading, SingularAttribute<E, T> headingPropertyId);
public HeadingPropertySet build();
}
public interface AddingColumn<E>
{
public AddingColumn<E> setLockedState(boolean lockedState);
public AddingColumn<E> setDefaultVisibleState(boolean defaultVisibleState);
public AddingColumn<E> setWidth(Integer width);
public AddingColumn<E> setColumnGenerator(ColumnGenerator columnGenerator);
}
public static class Builder<E> implements AddingColumn<E>, Start<E>
{
private List<HeadingToPropertyId> cols = new LinkedList<HeadingToPropertyId>();
private boolean autoExpandColumns = false;
private boolean eraseSavedConfig = false;
@Override
public HeadingPropertySet build()
{
addColumn();
final HeadingPropertySet tmp = new HeadingPropertySet();
tmp.cols = this.cols;
tmp.autoExpandColumns = autoExpandColumns;
tmp.eraseSavedConfig = eraseSavedConfig;
return tmp;
}
/* Add column methods */
/**
* Add a new table column
*
* @param heading
* the column heading that will be displayed
* @param headingPropertyId
* the heading property id
* @param defaultVisibleState
* whether the column is visible by default
* @param lockedState
* whether the visibility of a column can be modified
* @param width
* the width of the column
* @return the builder
*/
public Builder<E> addColumn(final String heading, final String headingPropertyId,
final boolean defaultVisibleState, final boolean lockedState, final int width)
{
cols.add(new HeadingToPropertyId(heading, headingPropertyId, null, defaultVisibleState, lockedState, null));
return this;
}
HeadingToPropertyId.Builder columnBuilder = null;
@Override
public AddingColumn<E> createColumn(String heading, String propertyId)
{
addColumn();
columnBuilder = new HeadingToPropertyId.Builder(heading, propertyId);
return this;
}
@Override
public <T> AddingColumn<E> createColumn(String heading, SingularAttribute<E, T> headingPropertyId)
{
addColumn();
columnBuilder = new HeadingToPropertyId.Builder(heading, headingPropertyId.getName());
return this;
}
private Builder<E> addColumn()
{
if (columnBuilder != null)
{
cols.add(columnBuilder.build());
}
// fail fast rather than have weird behaviour
columnBuilder = null;
return this;
}
@Override
public AddingColumn<E> setLockedState(boolean lockedState)
{
columnBuilder.setLockedState(lockedState);
return this;
}
@Override
public AddingColumn<E> setDefaultVisibleState(boolean defaultVisibleState)
{
columnBuilder.setDefaultVisibleState(defaultVisibleState);
return this;
}
@Override
public AddingColumn<E> setWidth(Integer width)
{
columnBuilder.setWidth(width);
return this;
}
@Override
public AddingColumn<E> setColumnGenerator(ColumnGenerator columnGenerator)
{
columnBuilder.setColumnGenerator(columnGenerator);
return this;
}
public <T extends Object> Builder<E> addColumn(final String heading,
final SingularAttribute<E, T> headingPropertyId, final boolean defaultVisibleState,
final boolean lockedState, int width)
{
return addColumn(heading, headingPropertyId.getName(), defaultVisibleState, lockedState, width);
}
public <T extends Object> Builder<E> addColumn(final String heading, final String headingPropertyId,
final boolean defaultVisibleState, final boolean lockedState)
{
cols.add(new HeadingToPropertyId(heading, headingPropertyId, null, defaultVisibleState, lockedState, null));
return this;
}
public <T extends Object> Builder<E> addColumn(final String heading,
final SingularAttribute<E, T> headingPropertyId, final boolean defaultVisibleState,
final boolean lockedState)
{
return addColumn(heading, headingPropertyId.getName(), defaultVisibleState, lockedState);
}
/* Add generated column methods */
/**
* Add a new generated table column
*
* @param heading
* the column heading that will be displayed
* @param headingPropertyId
* the heading property id
* @param columnGenerator
* the column generator
* @param defaultVisibleState
* whether the column is visible by default
* @param lockedState
* whether the visibility of a column can be modified
* @param width
* the width of the column
* @return the builder
*/
public Builder<E> addGeneratedColumn(final String heading, final String headingPropertyId,
final ColumnGenerator columnGenerator, final boolean defaultVisibleState, final boolean lockedState,
final int width)
{
cols.add(new HeadingToPropertyId(heading, headingPropertyId, columnGenerator, defaultVisibleState,
lockedState, width));
return this;
}
public <T extends Object> Builder<E> addGeneratedColumn(final String heading,
final SingularAttribute<E, T> headingPropertyId, final ColumnGenerator columnGenerator,
final boolean defaultVisibleState, final boolean lockedState, int width)
{
return addGeneratedColumn(heading, headingPropertyId.getName(), columnGenerator, defaultVisibleState,
lockedState, width);
}
public Builder<E> addGeneratedColumn(final String heading, final String headingPropertyId,
final ColumnGenerator columnGenerator, final boolean defaultVisibleState, final boolean lockedState)
{
cols.add(new HeadingToPropertyId(heading, headingPropertyId, columnGenerator, defaultVisibleState,
lockedState, null));
return this;
}
public <T extends Object> Builder<E> addGeneratedColumn(final String heading,
final SingularAttribute<E, T> headingPropertyId, final ColumnGenerator columnGenerator,
final boolean defaultVisibleState, final boolean lockedState)
{
return addGeneratedColumn(heading, headingPropertyId.getName(), columnGenerator, defaultVisibleState,
lockedState);
}
public Builder<E> addGeneratedColumn(final String heading, final ColumnGenerator columnGenerator,
final boolean defaultVisibleState, final boolean lockedState, int width)
{
return addGeneratedColumn(heading, heading + "-generated", columnGenerator, defaultVisibleState,
lockedState, width);
}
public Builder<E> addGeneratedColumn(final String heading, final ColumnGenerator columnGenerator,
final boolean defaultVisibleState, final boolean lockedState)
{
return addGeneratedColumn(heading, heading + "-generated", columnGenerator, defaultVisibleState,
lockedState);
}
/* Add column convenience methods */
public <T extends Object> Builder<E> addColumn(final String heading, final String headingPropertyId,
final int width)
{
return addColumn(heading, headingPropertyId, true, false, width);
}
public <T extends Object> Builder<E> addColumn(final String heading,
final SingularAttribute<E, T> headingPropertyId, final int width)
{
return addColumn(heading, headingPropertyId, true, false, width);
}
public Builder<E> addColumn(final String heading, final String headingPropertyId)
{
return addColumn(heading, headingPropertyId, true, false);
}
public <T extends Object> Builder<E> addColumn(final String heading,
final SingularAttribute<E, T> headingPropertyId)
{
return addColumn(heading, headingPropertyId, true, false);
}
public <T extends Object> Builder<E> addColumn(String heading, Path pathToHeadingPropertyId)
{
return addColumn(heading, pathToHeadingPropertyId.getName());
}
/* Add hidden column convenience methods */
public Builder<E> addHiddenColumn(final String heading, final String headingPropertyId, final int width)
{
return addColumn(heading, headingPropertyId, false, false, width);
}
public <T extends Object> Builder<E> addHiddenColumn(final String heading,
final SingularAttribute<E, T> headingPropertyId, final int width)
{
return addColumn(heading, headingPropertyId, false, false, width);
}
public Builder<E> addHiddenColumn(final String heading, final String headingPropertyId)
{
return addColumn(heading, headingPropertyId, false, false);
}
public <T extends Object> Builder<E> addHiddenColumn(final String heading,
final SingularAttribute<E, T> headingPropertyId)
{
return addColumn(heading, headingPropertyId, false, false);
}
/* Add generated column convenience methods */
public Builder<E> addGeneratedColumn(final String heading, final String headingPropertyId,
final ColumnGenerator columnGenerator, final int width)
{
return addGeneratedColumn(heading, headingPropertyId, columnGenerator, true, false, width);
}
public <T extends Object> Builder<E> addGeneratedColumn(final String heading,
final SingularAttribute<E, T> headingPropertyId, final ColumnGenerator columnGenerator, final int width)
{
return addGeneratedColumn(heading, headingPropertyId, columnGenerator, true, false, width);
}
public Builder<E> addGeneratedColumn(final String heading, final String headingPropertyId,
final ColumnGenerator columnGenerator)
{
return addGeneratedColumn(heading, headingPropertyId, columnGenerator, true, false);
}
public <T extends Object> Builder<E> addGeneratedColumn(final String heading,
final SingularAttribute<E, T> headingPropertyId, final ColumnGenerator columnGenerator)
{
return addGeneratedColumn(heading, headingPropertyId, columnGenerator, true, false);
}
public Builder<E> addGeneratedColumn(final String heading, final ColumnGenerator columnGenerator,
final int width)
{
return addGeneratedColumn(heading, heading + "-generated", columnGenerator, width);
}
public Builder<E> addGeneratedColumn(final String heading, final ColumnGenerator columnGenerator)
{
return addGeneratedColumn(heading, heading + "-generated", columnGenerator);
}
/* Add hidden generated column convenience methods */
public Builder<E> addHiddenGeneratedColumn(final String heading, final String headingPropertyId,
final ColumnGenerator columnGenerator, final int width)
{
return addGeneratedColumn(heading, headingPropertyId, columnGenerator, false, false, width);
}
public <T extends Object> Builder<E> addHiddenGeneratedColumn(final String heading,
final SingularAttribute<E, T> headingPropertyId, final ColumnGenerator columnGenerator, final int width)
{
return addGeneratedColumn(heading, headingPropertyId, columnGenerator, false, false, width);
}
public Builder<E> addHiddenGeneratedColumn(final String heading, final String headingPropertyId,
final ColumnGenerator columnGenerator)
{
return addGeneratedColumn(heading, headingPropertyId, columnGenerator, false, false);
}
public <T extends Object> Builder<E> addHiddenGeneratedColumn(final String heading,
final SingularAttribute<E, T> headingPropertyId, final ColumnGenerator columnGenerator)
{
return addGeneratedColumn(heading, headingPropertyId, columnGenerator, false, false);
}
public Builder<E> addHiddenGeneratedColumn(final String heading, final ColumnGenerator columnGenerator,
final int width)
{
return addGeneratedColumn(heading, columnGenerator, false, false, width);
}
public Builder<E> addHiddenGeneratedColumn(final String heading, final ColumnGenerator columnGenerator)
{
return addGeneratedColumn(heading, columnGenerator, false, false);
}
/**
* Add a date column and format it.
*
* @param heading
* - the headling label for this column
* @param column
* - the Date column that is to be displayed in the column
* @param format
* - the format for the Date. format is passed to a
* SimpleDateFormat
*/
public Builder<E> addColumn(String headingLabel, SingularAttribute<E, Date> column, String dateFormat,
int width)
{
return addGeneratedColumn(headingLabel, column.getName(),
new DateColumnGenerator<E>(column.getName(), dateFormat), true, false, width);
}
/**
* Add a date column and format it.
*
* @param heading
* - the headling label for this column
* @param column
* - the Date column that is to be displayed in the column
* @param format
* - the format for the Date. format is passed to a
* SimpleDateFormat
*/
public Builder<E> addColumn(String headingLabel, String headingPropertyId, String dateFormat, int width)
{
// We make the alias the same as the underlying property so that we
// can sort this column.
// Generated columns are not normally sortable however by mapping
// our generated column to the underlying date column our generated
// column becomes sortable.
return addGeneratedColumn(headingLabel, headingPropertyId,
new DateColumnGenerator<E>(headingPropertyId, dateFormat), true, false, width);
}
public void setAutoExpandColumns()
{
autoExpandColumns = true;
}
public void setEraseSavedConfig()
{
eraseSavedConfig = true;
}
public Builder<E> addGeneratedBooleanIconColumn(String heading, final SingularAttribute<E, Boolean> attribute,
final FontAwesome trueIcon, final FontAwesome falseIcon)
{
ColumnGenerator generator = new ColumnGenerator()
{
private static final long serialVersionUID = -7730752061513328598L;
@Override
public Object generateCell(Table source, Object itemId, Object columnId)
{
Boolean checked = (Boolean) source.getItem(itemId).getItemProperty(attribute.getName()).getValue();
final Label label = new Label();
label.setContentMode(ContentMode.HTML);
if (checked)
{
if (trueIcon != null)
{
label.setValue(trueIcon.getHtml());
}
}
else
{
if (falseIcon != null)
{
label.setValue(falseIcon.getHtml());
}
}
return label;
}
};
addGeneratedColumn(heading, attribute, generator);
return this;
}
}
/**
* Date Column generator used to format Date columns.
*
* @author bsutton
*
* @param <E>
*/
static class DateColumnGenerator<E> implements ColumnGenerator
{
private static final long serialVersionUID = 1;
private Logger logger = LogManager.getLogger();
final private SimpleDateFormat sdf;
final private SimpleDateFormat sdfParse = new SimpleDateFormat("yyyy-MM-dd");
final private String headingPropertyId;
DateColumnGenerator(String headingPropertyId, String format)
{
this.headingPropertyId = headingPropertyId;
this.sdf = new SimpleDateFormat(format);
}
@Override
public Object generateCell(Table source, Object itemId, Object columnId)
{
Item item = source.getItem(itemId);
Object objDate = item.getItemProperty(headingPropertyId).getValue();
String formattedDate = "";
if (objDate instanceof Date)
{
formattedDate = sdf.format((Date) objDate);
}
else if (objDate != null)
{
String strDate = objDate.toString();
try
{
formattedDate = sdf.format(sdfParse.parse(strDate));
}
catch (ParseException e)
{
// just so we have a value.
formattedDate = "Invalid";
logger.error(
"Looks like our assumptions about the format of dates is wrong. Please update the parse format to match:"
+ strDate + " " + sdf.toPattern());
}
}
Label label = new Label(formattedDate);
return label;
}
}
public List<HeadingToPropertyId> getColumns()
{
return cols;
}
public void applyToTable(Table table)
{
final StackTraceElement[] trace = new Exception().getStackTrace();
for (StackTraceElement call : trace)
{
if (!call.getClassName().contains("au.com.vaadinutils"))
{
if (call.getClassName().contains("."))
{
applyToTable(table, call.getClassName().substring(call.getClassName().lastIndexOf(".")));
}
else
{
applyToTable(table, call.getClassName());
}
return;
}
}
throw new RuntimeException("Unable to determine calling class name, "
+ " use applyToTable(Table table, String uniqueTableId) " + " instead of applyToTable(Table table)");
}
/**
*
* @param table
* @param uniqueTableId
* - an id for this layout/table combination, it is used to
* identify stored column widths in a key value map
*/
public void applyToTable(final Table table, final String uniqueTableId)
{
try
{
final List<String> colsToShow = new LinkedList<String>();
for (HeadingToPropertyId column : getColumns())
{
colsToShow.add(column.getPropertyId());
table.setColumnHeader(column.getPropertyId(), column.getHeader());
if (column.isGenerated())
{
table.addGeneratedColumn(column.getPropertyId(), column.getColumnGenerator());
}
else
{
Preconditions.checkArgument(table.getContainerPropertyIds().contains(column.getPropertyId()),
column.getPropertyId() + " is not a valid property id, valid property ids are "
+ table.getContainerPropertyIds().toString());
}
if (column.getWidth() != null)
{
table.setColumnWidth(column.getPropertyId(), column.getWidth());
}
else
{
if (autoExpandColumns)
{
table.setColumnExpandRatio(column.getPropertyId(), (float) (1.0 / getColumns().size()));
}
}
if (!column.isVisibleByDefault())
{
table.setColumnCollapsingAllowed(true);
table.setColumnCollapsed(column.getPropertyId(), true);
}
if (column.isLocked())
{
table.setColumnCollapsible(column.getPropertyId(), false);
}
}
table.setVisibleColumns(colsToShow.toArray());
if (eraseSavedConfig)
{
eraseSavedConfig(uniqueTableId);
}
configureSaveColumnWidths(table, uniqueTableId);
configureSaveColumnOrder(table, uniqueTableId);
configureSaveColumnVisible(table, uniqueTableId);
}
catch (Exception e)
{
logger.error(e, e);
}
}
void eraseSavedConfig(final String uniqueTableId)
{
UserSettingsStorageFactory.getUserSettingsStorage().erase(uniqueTableId);
}
private void configureSaveColumnWidths(final Table table, final String uniqueTableId)
{
final String keyStub = uniqueTableId + "-width";
for (HeadingToPropertyId id : getColumns())
{
final String setWidth = UserSettingsStorageFactory.getUserSettingsStorage()
.get(keyStub + "-" + id.getPropertyId());
if (setWidth != null && setWidth.length() > 0)
{
try
{
table.setColumnWidth(id.getPropertyId(), Integer.parseInt(setWidth));
}
catch (Exception e)
{
logger.warn(e);
}
}
}
table.addColumnResizeListener(new ColumnResizeListener()
{
private static final long serialVersionUID = 4034036880290943146L;
@Override
public void columnResize(ColumnResizeEvent event)
{
final String propertyId = (String) event.getPropertyId();
final int width = event.getCurrentWidth();
UserSettingsStorageFactory.getUserSettingsStorage().store(keyStub + "-" + propertyId, "" + width);
}
});
}
private void configureSaveColumnOrder(final Table table, final String uniqueTableId)
{
final String keyStub = uniqueTableId + "-order";
final Object[] availableColumns = table.getVisibleColumns();
final String columns = UserSettingsStorageFactory.getUserSettingsStorage().get(keyStub);
if (availableColumns.length > 0 && columns != null && !columns.isEmpty())
{
final Object[] parsedColumns = columns.replaceAll("\\[|\\]", "").split(", ?");
if (parsedColumns.length > 0)
{
table.setVisibleColumns(calculateColumnOrder(availableColumns, parsedColumns));
}
}
table.addColumnReorderListener(new ColumnReorderListener()
{
private static final long serialVersionUID = -2810298692555333890L;
@Override
public void columnReorder(ColumnReorderEvent event)
{
final Object[] columns = ((Table) event.getSource()).getVisibleColumns();
UserSettingsStorageFactory.getUserSettingsStorage().store(keyStub, "" + Arrays.toString(columns));
}
});
}
/**
* If a column order has already been saved for a user, but the columns for
* a table have been modified, then we need to remove any columns that no
* longer exist and add any new columns to the list of visible columns.
*
* @param availableColumns
* the columns that are available in the table
* @param parsedColumns
* the column order that has been restored from preferences
* @return the calculated order of columns with old removed and new added
*/
private Object[] calculateColumnOrder(final Object[] availableColumns, final Object[] parsedColumns)
{
final List<Object> availableList = new ArrayList<>(Arrays.asList(availableColumns));
final List<Object> parsedList = new ArrayList<>(Arrays.asList(parsedColumns));
// Remove old columns
parsedList.retainAll(availableList);
// Add new columns in the same index position as they were added to the
// table in
final List<Object> newList = new ArrayList<>(availableList);
newList.removeAll(parsedList);
for (Object column : newList)
{
parsedList.add(availableList.indexOf(column), column);
}
return parsedList.toArray();
}
private void configureSaveColumnVisible(final Table table, final String uniqueTableId)
{
final String keyStub = uniqueTableId + "-visible";
for (HeadingToPropertyId id : getColumns())
{
final String setVisible = UserSettingsStorageFactory.getUserSettingsStorage()
.get(keyStub + "-" + id.getPropertyId());
if (setVisible != null && !setVisible.isEmpty())
{
table.setColumnCollapsed(id.getPropertyId(), !Boolean.parseBoolean(setVisible));
}
}
table.addColumnCollapseListener(new ColumnCollapseListener()
{
private static final long serialVersionUID = 8903793320816698902L;
@Override
public void columnCollapseStateChange(ColumnCollapseEvent event)
{
final String propertyId = (String) event.getPropertyId();
final boolean isVisible = !table.isColumnCollapsed(propertyId);
UserSettingsStorageFactory.getUserSettingsStorage().store(keyStub + "-" + propertyId, "" + isVisible);
}
});
}
@Override
public String toString()
{
return Arrays.toString(cols.toArray());
}
}