Generic Methods for Rendering TableColumns in JavaFX - java

So my application uses a number of TableViews within different FXMLViewControllers to present a number of different JPA Entities. The example below is for JobSupplierParts.
/**
* renderDoubleColumn takes a TableColumn setting its value and type before setting up edit event handling.
* #param column the tableColumn to be set up.
* #param field the name of the field to be mapped to.
* #param methodName the set method name of the field.
*/
protected void renderDoubleColumn(TableColumn<JobSupplierPart, Double> column, String field, String methodName) {
String className = "BiasDB.JobSupplierPart";
column.setCellValueFactory(new PropertyValueFactory<>(field));
column.setCellFactory(TextFieldTableCell.<JobSupplierPart, Double>forTableColumn(new DoubleStringConverter()));
column.setOnEditCommit(
new EventHandler<TableColumn.CellEditEvent<JobSupplierPart, Double>>() {
#Override
public void handle(TableColumn.CellEditEvent<JobSupplierPart, Double> t) {
JobSupplierPart supplierPart = t.getTableView().getItems().get(t.getTablePosition().getRow());
try {
Class<?> c = Class.forName(className);
Method method = c.getDeclaredMethod(methodName, Double.class);
method.invoke(supplierPart, t.getNewValue());
supplierPart.setTotal(updateItem(supplierPart));
} catch (ClassNotFoundException|NoSuchMethodException|IllegalAccessException|InvocationTargetException ex) {
logger.error("renderDoubleColumn",ex);
} //End try to get method from String.
try {
jobSupplierPartController.edit(supplierPart);
} catch (Exception ex) {
logger.error("renderDoubleColumn",ex);
}
t.getTableView().refresh();
}
} //END Event Handler
); //END SetOnEditCommit.
}
//END renderDoubleColumn
I can call this with:
renderDoubleColumn(discountColumn, "discount", "setDiscount");
BUT - I have to create new methods for each JPA Entity. Is it possible to replace the references to JobSupplierPart such that it becomes a generic method much like I have achieved with the methods? I have tried and alternatives such as T or K but they all returned errrors. The controller can just be passed as a parameter. Or is this a really bad practice/poor performance thing to do?

So I don't know if the Java aficionados will agree with this solution but in response to an answer posted and then deleted shortly after I was able to make the code cleaner. I also moved the set/edit section into a method so now I have:
/**
* renderBigDecimalColumn takes a TableColumn setting its value and type before setting up edit event handling.
* #param column the tableColumn to be set up.
* #param field the name of the field to be mapped to.
*/
private void renderBigDecimalColumn(TableColumn<AccountAsset, BigDecimal> column, String field) {
//Set an observable value for the column
column.setCellValueFactory(new PropertyValueFactory<>(field));
//Set how we want the cell to be rendered
// This line varies for the different cell types e.g. Strings, Bools etc.
column.setCellFactory(TextFieldTableCell.<AccountAsset, BigDecimal>forTableColumn(new BigDecimalStringConverter()));
//Set how we want the cell to be edited including the row update.
column.setOnEditCommit(t -> {
handleEditCommit(t, field);
}); //END SetOnEditCommit.
} //END renderBigDecimalColumn
And my handleEditCommit method looks like:
/** handleEditCommit deals with updating and saving the new data from the table view.
*
* #param t
* #param field
*/
private void handleEditCommit(javafx.scene.control.TableColumn.CellEditEvent<AccountAsset,?> t, String field) {
AccountAsset rowData = t.getTableView().getItems().get(t.getTablePosition().getRow());
//Set the new value.
try {
BeanUtils.setProperty(rowData, field, t.getNewValue());
} catch (IllegalAccessException | InvocationTargetException ex) {
logger.error("handleEditCommit / Setter", ex);
}
//Save the new rowData back to the database.
try {
tableDataController.edit(rowData);
} catch (Exception ex) {
logger.error("handleEditCommit / Edit", ex);
}
}

Related

Cannot find classes in a package using Class.forName()

Using Java I am implementing a Page Factory object for selenium testing that takes the name of a page object and instantiates it through reflection for use by Cucumber step definitions. The problem I am having is that the code below cannot find the declared class. Both the object PageFactory which contains this code and the page object LoginPage reside in a package called pages.
/**
* This method take a string containing a Page Object class name (case-sensitive) and returns an instance of the Page Object.
* This allows us to operate on pages without knowing they exist when we write step definitions.
* #param choice String
* #return Page Object cast as a Page
*/
public static Page getPage(String choice) {
Page entity = null;
try {
entity = (Page) Class.forName(choice).newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return entity;
}
I receive a stack trace with java.lang.ClassNotFoundException: LoginPage as the beginning of the error. If I change the entity creation to the following code, then it works.
private static String packageName = "pages";
entity = (Page) Class.forName(packageName + "." + choice).newInstance();
The problem is that I want to organize my pages. When I create pages.mywebsite and place LoginPage within that, PageFactory won't know where to find the file.
Leaving aside the problem that I could have two namespaces pages.mywebsite and pages.myotherwebsite that both have a LoginPage object, how can I find the files I want without declaring the exact package, and just say "Look in this package and the ones below for the class"?
You could get the classpath using System.getProperty("java.class.path"), split it around File.pathSeparator, and scan the results using FileVisitor.
This is how I solved my problem. I moved the reflection into a findPageInPackage method, and the called it recursively to search through directories.
Full code here: https://github.com/dougnoel/sentinel/blob/master/src/main/java/com/dougnoel/sentinel/pages/PageFactory.java
/**
* Returns a page object if it exists in the package searched.
* #param pageName String the name of the page object class to instantiate
* #param packageName String the name of the package to search
* #return Page the page object if it exists, otherwise null
*/
private static Page findPageInPackage(String pageName, String packageName) {
Page page = null;
try {
page = (Page) Class.forName(packageName + "." + pageName).newInstance();
} catch (InstantiationException e) {
log.trace("{}.{} Page Object creation failed.", packageName, pageName);
log.trace("java.lang.InstantiationException: {}", e.getMessage());
} catch (IllegalAccessException e) {
log.trace("{}.{} Page Object creation failed.", packageName, pageName);
log.trace("java.lang.IllegalAccessException: {}", e.getMessage());
} catch (ClassNotFoundException e) {
log.trace("{}.{} Page Object creation failed.", packageName, pageName);
log.trace("java.lang.ClassNotFoundException: {}", e.getMessage());
}
return page;
}
/**
* Returns the Page Object for the page name. This allows us to operate on pages
* without knowing they exist when we write step definitions.
* <p>
* Searches any optional page object packages set with the pageObjectPackages system property, and
* then searches the defaultPackageName value.
* <p>
* <b>Example:</b>
* <p>
* <code>System.setProperty("pageObjectPackages", "pages.SharedComponent,pages.UserAdminTool");</code>
*
* #param pageName String the name of the page in
* <a href="https://en.wikipedia.org/wiki/Camel_case">Pascal
* case</a>
* #return Page the specific page object cast as a generic page object
* #throws PageNotFoundException if page could not be built or retrieved.
* #throws ConfigurationNotFoundException if the value is not found in the configuration file
*/
public static Page buildOrRetrievePage(String pageName) throws PageNotFoundException, ConfigurationNotFoundException {
Page page = pages.get(pageName);
final String errorMessage = "The page you want to test could not be built. At least one Page object package is required to run a test. Please add a pageObjectPackages property to your conf/sentinel.yml configuration file and try again.";
if (page != null) {
return page;
} else {
if (pageObjectPackagesList == null) {
pageObjectPackagesList = ConfigurationManager.getPageObjectPackageList();
if(pageObjectPackagesList == null) {
throw new PageNotFoundException(errorMessage);
}
}
for (String pageObjectPackage : pageObjectPackagesList) {
log.trace("pageObjectPackage: " + pageObjectPackage);
page = findPageInPackage(pageName, pageObjectPackage);
if (page != null) {
break; // If we have a page object, stop searching.
}
}
}
if(page == null) {
throw new PageNotFoundException(errorMessage);
}
pages.put(pageName, page);
return page;
}

Eclipse custom perspective changed approach Kepler to Mars

I am updating an existing working RCP 3 app from
Kepler to Mars. It was written by another guy so having to learn a lot about RCP as I go.
What worked in Kepler was this:
public class ShowSelectViewDialogHandler extends DialogHandler {
/**
* The name of the parameter providing the view identifier.
*/
private static final String VIEW_ID_COMMAND_PARAMETER_NAME = "org.eclipse.ui.views.showView.viewId"; //$NON-NLS-1$
private static final String MAKE_FAST_COMMAND_PARAMETER_NAME = "org.eclipse.ui.views.showView.makeFast"; //$NON-NLS-1$
private final IHandler handler;
/**
* Creates a new ShowViewHandler that will open the view in its default location.
*/
public ShowSelectViewDialogHandler (final IHandler handler) {
this.handler = handler;
}
#Override
public final Object execute(final ExecutionEvent event) throws ExecutionException {
Object result = null;
IWorkbenchWindow window = EDMUIApplication.instance().getWorkbenchAdvisor().getWorkbenchWindowAdvisor().getWindow();
Map<String, String> parameters = event.getParameters();
String viewId = parameters.get(ShowSelectViewDialogHandler.VIEW_ID_COMMAND_PARAMETER_NAME);
String makeFast = parameters.get(ShowSelectViewDialogHandler.MAKE_FAST_COMMAND_PARAMETER_NAME);
if (viewId == null) {
ShowViewDialog dialog = new ShowViewDialog(window, new EDMUIViewRegistry(EDMUIConstants.CATEGORY_IDS));
if (dialog.open() == Window.OK) {
for (IViewDescriptor viewDescriptor : dialog.getSelection()) {
result = this.openView(window, viewDescriptor.getId(), makeFast);
}
}
} else {
result = this.openView(window, viewId, makeFast);
}
return result;
}
/**
* Opens the view with the given ID.
*
* #param window - workbench window of the view.
* #param viewId - id of the view to open.
* #param makeFast - command parameter.
* #return result of the handler execution.
* #throws ExecutionException - if default handler execution fails.
*/
private Object openView(final IWorkbenchWindow window, final String viewId, final String makeFast) throws ExecutionException {
Object result = null;
try {
Parameterization[] parameterization = this.createParameterization(viewId, makeFast, IWorkbenchCommandConstants.VIEWS_SHOW_VIEW);
result = this.executeDefaultHandler(this.handler, window, parameterization, IWorkbenchCommandConstants.VIEWS_SHOW_VIEW);
} catch (NotDefinedException ex) {
throw new ExecutionException(ex.getMessage(), ex);
}
return result;
}
/**
* Creates parameterization for the command.
*
* #param viewId - view id parameter value.
* #param makeFast - make fast parameter value.
* #param commandId - id of the command.
* #return created parameterization.
* #throws NotDefinedException - if there is no such parameter.
*/
private Parameterization[] createParameterization(final String viewId, final String makeFast, final String commandId) throws NotDefinedException {
ICommandService commandService = (ICommandService) PlatformUI.getWorkbench().getService(ICommandService.class);
Command command = commandService.getCommand(IWorkbenchCommandConstants.VIEWS_SHOW_VIEW);
IParameter viewIdParameter = command.getParameter(ShowSelectViewDialogHandler.VIEW_ID_COMMAND_PARAMETER_NAME);
IParameter makeFastParameter = command.getParameter(ShowSelectViewDialogHandler.MAKE_FAST_COMMAND_PARAMETER_NAME);
return new Parameterization[] { new Parameterization(viewIdParameter, viewId), new Parameterization(makeFastParameter, makeFast) };
}
But now ShowViewDialog signature has changed. Also the original author made the statement that his approach was based on ShowViewHandler and there must be a better more standard way of achieving the same affect, i.e. controlling the display of our reduced set of views.
Any ideas on how to achieve this? There might be a tutorial somewhere, I found the Vogella one, but it is fairly general.
ShowViewDialog is an internal dialog so it should not have been used in the first place. As you have found internal dialogs can be changed without warning.
It looks like your code is using your own implementation of IViewRegistry. To stick to using only official APIs you would have to write your own version of the show view dialog. This is a fairly simple dialog using FilteredTree and the IViewRegistry getCategories and getViews methods.
There isn't a more standard way of doing this.

JAVA creating an event list from a text file

I have a controller class, it basically holds an event list.
ArrayList <Event> eventList = new ArrayList<>();
The controller has an addEvent(Event e) method.
I've extended the controller class to be a specific kind of controller, and extended event to provide specific kinds of events for my new controller as inner classes.
public class NewController extends Controller{
//.. controller code/methods
class SpecificEvent extends Event{
//..
}
}
I've hard coded my controller to work as I intended, but I wanted to be able to make a configuration file that would populate my event list.
I made a .txt file with a list of events:
Event=SpecificEvent, Arg1=<Integer>val, .. ArgN=<?>val, Event=SpecificEventN, ArgN=<?>val
I filtered out the event class name and arguments into a list:
fileContents.stream()
.forEach(s -> {
Scanner sc = new Scanner(s)
.useDelimiter("=|,");
while (sc.hasNext()){
Scanner sc2 = new Scanner(sc.next()).useDelimiter("[//w]");
args.add(sc.next());
}
});
My problem is that events have different constructor argument types and lengths; I don't know how to build them from my file. I'm new to this kind of work and I figure this is run of the mill implementation.
Do I use the Reflect package? Please help. I was thinking off an Event Factory?
Thanks for the community help. This factory will do the job when provided a string array argument, {String classname, args.... }
/**
* ClassFactory method, build an event.
*
* #param arguments Required arguments to build an Event.
* #return Built event.
* #throws java.lang.ClassNotFoundException
*/
public Event buildEvent(String [] arguments) throws ClassNotFoundException {
Class<?>[] argumentTypes = {};
Object[] parameters = {};
try {
//define the class type from the first argument
Class<?> eventClass
= Class.forName(packageName + arguments[0]);
if (arguments.length() > 1) {
argumentTypes = new Class<?>[]{Long.TYPE};
parameters = new Object[]{Long.parseLong(arguments[1])};
Constructor<?> constructor
= eventClass.getDeclaredConstructor(argumentTypes);
Object instance = constructor.newInstance(parameters);
return ((Event) instance);
//default
} else {
Constructor<?> constructor
= eventClass.getDeclaredConstructor();
Object instance = constructor.newInstance();
return ((Event) instance);
}
} catch (ClassNotFoundException cnfe) {
System.out.println("Class not available in this package.");
} catch (NoSuchMethodException |
SecurityException |
InstantiationException |
IllegalAccessException |
IllegalArgumentException |
InvocationTargetException e) {
log.log(Level.SEVERE, "Class Builder: {0}", e.getMessage());
}
return null;
}

ExtendedDataTable in RichFaces 4: DataModel handling

I have another question, somewhat related to the one I posted in January. I have a list, which is rich:extendedDataTable component, and it gets updated on the fly, as the user types his search criteria in a separate text box (i.e. the user types in the first 4 characters and as he keeps typing, the results list changes). And in the end it works fine, when I use RichFaces 3, but as I upgraded to RichFaces 4, I've got all sorts of compilation problems. The following classes are no longer accessible and there no suitable replacement for these, it seems:
org.richfaces.model.DataProvider
org.richfaces.model.ExtendedTableDataModel
org.richfaces.model.selection.Selection
org.richfaces.model.selection.SimpleSelection
Here is what it was before:
This is the input text that should trigger the search logic:
<h:inputText id="firmname" value="#{ExtendedTableBean.searchValue}">
<a4j:support ajaxSingle="true" eventsQueue="firmListUpdate"
reRender="resultsTable"
actionListener="#{ExtendedTableBean.searchForResults}" event="onkeyup" />
</h:inputText>
Action listener is what should update the list. Here is the extendedDataTable, right below the inputText:
<rich:extendedDataTable tableState="#{ExtendedTableBean.tableState}" var="item"
id="resultsTable" value="#{ExtendedTableBean.dataModel}">
... <%-- I'm listing columns here --%>
</rich:extendedDataTable>
And here's the back-end code, where I use my data model handling:
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package com.beans;
import java.io.FileInputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.ConcurrentModificationException;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.faces.context.FacesContext;
import javax.faces.event.ActionEvent;
import org.richfaces.model.DataProvider;
import org.richfaces.model.ExtendedTableDataModel;
public class ExtendedTableBean {
private String sortMode="single";
private ExtendedTableDataModel<ResultObject> dataModel;
//ResultObject is a simple pojo and getResultsPerValue is a method that
//read the data from the properties file, assigns it to this pojo, and
//adds a pojo to the list
private Object tableState;
private List<ResultObject> results = new CopyOnWriteArrayList<ResultObject>();
private List<ResultObject> selectedResults =
new CopyOnWriteArrayList<ResultObject>();
private String searchValue;
/**
* This is the action listener that the user triggers, by typing the search value
*/
public void searchForResults(ActionEvent e) {
synchronized(results) {
results.clear();
}
//I don't think it's necessary to clear results list all the time, but here
//I also make sure that we start searching if the value is at least 4
//characters long
if (this.searchValue.length() > 3) {
results.clear();
updateTableList();
} else {
results.clear();
}
dataModel = null; // to force the dataModel to be updated.
}
public List<ResultObject> getResultsPerValue(String searchValue) {
List<ResultObject> resultsList = new CopyOnWriteArrayList<ResultObject>();
//Logic for reading data from the properties file, populating ResultObject
//and adding it to the list
return resultsList;
}
/**
* This method updates a firm list, based on a search value
*/
public void updateTableList() {
try {
List<ResultObject> searchedResults = getResultsPerValue(searchValue);
//Once the results have been retrieved from the properties, empty
//current firm list and replace it with what was found.
synchronized(firms) {
firms.clear();
firms.addAll(searchedFirms);
}
} catch(Throwable xcpt) {
//Exception handling
}
}
/**
* This is a recursive method, that's used to constantly keep updating the
* table list.
*/
public synchronized ExtendedTableDataModel<ResultObject> getDataModel() {
try {
if (dataModel == null) {
dataModel = new ExtendedTableDataModel<ResultObject>(
new DataProvider<ResultObject>() {
public ResultObject getItemByKey(Object key) {
try {
for(ResultObject c : results) {
if (key.equals(getKey(c))){
return c;
}
}
} catch (Exception ex) {
//Exception handling
}
return null;
}
public List<ResultObject> getItemsByRange(
int firstRow, int endRow) {
return Collections.unmodifiableList(results.subList(firstRow, endRow));
}
public Object getKey(ResultObject item) {
return item.getResultName();
}
public int getRowCount() {
return results.size();
}
});
}
} catch (Exception ex) {
//Exception handling
}
return dataModel;
}
//Getters and setters
}
Now that the classes ExtendedTableDataModel and DataProvider are no longer available, what should I be using instead? RichFaces forum claims there's really nothing and developers are pretty much on their own there (meaning they have to do their own implementation). Does anyone have any other idea or suggestion?
Thanks again for all your help and again, sorry for a lengthy question.
You could convert your data model to extend the abstract org.ajax4jsf.model.ExtendedDataModel instead which actually is a more robust and performant datamodel for use with <rich:extendedDataTable/>. A rough translation of your existing model to the new one below (I've decided to use your existing ExtendedDataModel<ResultObject> as the underlying data source instead of the results list to demonstrate the translation):
public class MyDataModel<ResultObject> extends ExtendedDataModel<ResultObject>{
String currentKey; //current row in the model
Map<String, ResultObject> cachedResults = new HashMap<String, ResultObject>(); // a local cache of search/pagination results
List<String> cachedRowKeys; // a local cache of key values for cached items
int rowCount;
ExtendedTableDataModel<ResultObject> dataModel; // the underlying data source. can be anything
public void setRowKey(Object item){
this.currentKey = (ResultObject)item.getResultName();
}
public void walk(FacesContext context, DataVisitor visitor, Range range, Object argument) throws IOException {
int firstRow = ((SequenceRange)range).getFirstRow();
int numberOfRows = ((SequenceRange)range).getRows();
cachedRowkeys = new ArrayList<String>();
for (ResultObject result : dataModel.getItemsByRange(firstRow,numberOfRows)) {
cachedRowKeys.add(result.getResultName());
cachedResults.put(result.getResultName(), result); //populate cache. This is strongly advised as you'll see later.
visitor.process(context, result.getResultName(), argument);
}
}
}
public Object getRowData() {
if (currentKey==null) {
return null;
} else {
ResultObject selectedRowObject = cachedResults.get(currentKey); // return result from internal cache without making the trip to the database or other underlying datasource
if (selectedRowObject==null) { //if the desired row is not within the range of the cache
selectedRowObject = dataModel.getItemByKey(currentKey);
cachedResults.put(currentKey, selectedRowObject);
return selectedRowObject;
} else {
return selectedRowObject;
}
}
public int getRowCount(){
if(rowCount == 0){
rowCount = dataModel.getRowCount(); //cache row count
return rowCount;
}
return rowCount
}
Those are the 3 most important methods in that class. There are a bunch of other methods, basically carry over from legacy versions that you don't need to worry yourself about. If you're saving JSF state to client, you might be interested in the org.ajax4jsf.model.SerializableDataModel for serialization purposes. See an example for that here. It's an old blog but the logic is still applicable.
Unrelated to this, your current implementation of getRowData will perform poorly in production grade app. Having to iterate thru every element to return a result? Try a better search algorithm.

Validators for combobox in Vaadin

In text fields I can check how many characters entered by the user, for example:
Field field;
field.addValidator(
new StringLengthValidator(
"WARNING MESSAGE HERE", 6, 20, false));
If the number is not within the range, then warning message is throw. For numeric fields I can check the type:
field.addValidator(new Validator() {
public boolean isValid(Object value) {
try {
float f = Float.parseFloat((String) value);
return true;
} catch (Exception e) {
field.getWindow().showNotification("WARNING MESSAGE HERE");
field.setValue(0);
return false;
}
}
public void validate(Object value)
throws InvalidValueException {
}
});
For the combobox I specify the following:
final ComboBox combobox = new ComboBox("...");
if("someProperty".equals(propertyId)) {
field = combobox;
}
field.setRequired(true);
field.setRequiredError("WARNING MESSAGE HERE");
If I leave it blank, then no warning displayed and form sent to the server. What validator needed for ComboBox?
I would be very grateful for the information. Thanks to all.
What you are looking for is the immediate callback to the server after the user changes anything.
// Fire value changes immediately when the field loses focus
combobox.setImmediate(true);
For that users don't have to wait to get a validation until they commit or do anything else what needs to interact with the server.
I suppose you should explicitly call validate() method:
/**
* Checks the validity of the Validatable by validating the field with all
* attached validators except when the field is empty. An empty field is
* invalid if it is required and valid otherwise.
*
* The "required" validation is a built-in validation feature. If the field
* is required, but empty, validation will throw an EmptyValueException with
* the error message set with setRequiredError().
*
* #see com.vaadin.data.Validatable#validate()
*/
public void validate() throws Validator.InvalidValueException {
if (isEmpty()) {
if (isRequired()) {
throw new Validator.EmptyValueException(requiredError);
} else {
return;
}
}
// If there is no validator, there can not be any errors
if (validators == null) {
return;
}
...
By default Form performs validation:
/**
* Checks the validity of the validatable.
*
* #see com.vaadin.data.Validatable#validate()
*/
#Override
public void validate() throws InvalidValueException {
super.validate();
for (final Iterator<Object> i = propertyIds.iterator(); i.hasNext();) {
(fields.get(i.next())).validate();
}
}

Categories

Resources