I have an eclipse plugin where you select text from the editor, press a shortcut(in my case it's alt+F1) and the Eclipse help search opens and automatically searches for that selected text.
Here is the code:
public class Button1 extends AbstractHandler {
public Button1() {}
public String getCurrentSelection()
{
IEditorPart part = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().getActiveEditor();
if (part instanceof ITextEditor)
{
final ITextEditor editor = (ITextEditor) part;
ISelection sel = editor.getSelectionProvider().getSelection();
if (sel instanceof TextSelection)
{
ITextSelection textSel = (ITextSelection) sel;
return textSel.getText();
}
}
return null;
}
public void searchInHelp(String str) {
IWorkbenchHelpSystem help = PlatformUI.getWorkbench().getHelpSystem();
help.search(str);
}
public Object execute(ExecutionEvent event) throws ExecutionException {
String str = getCurrentSelection();
searchInHelp(str);
return null;
}
}
Unfortunately when the search is performed it's performed for all the Eclipse Help while I only want to apply it to a certain scope like, let's say, Workbench User Guide, so that it only searches there.
As far as I've seen in IWorkbenchHelpSystem there is no handling of Help Search Scope, and, from what I've been told, everything that deals with the scope is in an internal package so it is not intended for use by other plugins.
I did found something like this:
String qualifier = ResourcesPlugin.getDescriptor().getUniqueIdentifier();
String key = ResourcesPlugin.PREF_AUTO_BUILDING;
boolean value = true;
IScopeContext context = new ProjectScope(MyProject);
IEclipsePreferences node = context.getNode(qualifier);
if (node != null)
node.putBoolean(key, value);
But I have no idea if this ProjectScope has anything to do with the Eclipse Help Scope or how to correctly integrate it with my code (i.e just add it in a method and call it before doing the search?).
So can anyone help me out on how to set the help scope when performing the search?
Related
Edit:
I first voted to close as a duplicate after finding this answer by James_D, which sets a TextFormatter on a TextField. But then firstly I found that (in a TableView context) the method TextFieldTableCell.forTableColumn() does not in fact draw a TextField when it starts editing, but instead a LabeledText, which does not subclass TextInputControl, and therefore does not have setTextFormatter().
Secondly, I wanted something which acted in a familiar sort of way. I may have produced the "canonical" solution in my answer: let others judge.
This is a TableColumn in a TableView (all Groovy):
TableColumn<Person, String> ageCol = new TableColumn("Age")
ageCol.cellValueFactory = { cdf -> cdf.value.ageProperty() }
int oldAgeValue
ageCol.onEditStart = new EventHandler(){
#Override
public void handle( Event event) {
oldAgeValue = event.oldValue
}
}
ageCol.cellFactory = TextFieldTableCell.forTableColumn(new IntegerStringConverter() {
#Override
public Integer fromString(String value) {
try {
return super.fromString(value)
}
catch ( NumberFormatException e) {
// inform user by some means...
println "string could not be parsed as integer..."
// ... and cancel the edit
return oldAgeValue
}
}
})
Excerpt from class Person:
public class Person {
private IntegerProperty age;
public void setAge(Integer value) { ageProperty().set(value) }
public Integer getAge() { return ageProperty().get() }
public IntegerProperty ageProperty() {
if (age == null) age = new SimpleIntegerProperty(this, "age")
return age
}
...
Without the start-edit Handler, when I enter a String which can't be parsed as an Integer NumberFormatException not surprisingly gets thrown. But I also find that the number in the cell then gets set to 0, which is likely not to be the desired outcome.
But the above strikes me as a pretty clunky solution.
I had a look at ageCol, and ageCol.cellFactory (as these are accessible from inside the catch block) but couldn't see anything better and obvious. I can also see that one can easily obtain the Callback (ageCol.cellFactory), but calling it would require the parameter cdf, i.e. the CellDataFeatures instance, which again you'd have to store somewhere.
I'm sure a validator mechanism of some kind was involved with Swing: i.e. before a value could be transferred from the editor component (via some delegate or something), it was possible to override some validating mechanism. But this IntegerStringConverter seems to function as a validator, although doesn't seem to provide any way to revert to the existing ("old") value if validation fails.
Is there a less clunky mechanism than the one I've shown above?
Edit
NB improved after kleopatra's valuable insights.
Edit2
Overhauled completely after realising that the best thing is to use the existing default editor and tweak it.
I thought I'd give an example with a LocalDate, slightly more fun than Integer. Given the following class:
class Person(){
...
private ObjectProperty<LocalDate> dueDate;
public void setDueDate(LocalDate value) {
dueDateProperty().set(value);
}
public LocalDate getDueDate() {
return (LocalDate) dueDateProperty().get();
}
public ObjectProperty dueDateProperty() {
if (dueDate == null) dueDate = new SimpleObjectProperty(this, "dueDate");
return dueDate;
}
Then you create a new editor cell class, which is exactly the same as TextFieldTreeTableCell (subclass of TreeTableCell), which is used by default to create an editor for a TreeTableView's table cell. However, you can't really subclass TextFieldTreeTableCell as, for example, its essential field textField is private.
So you copy the code in full from the source* (only about 30 lines), and you call it
class DueDateEditor extends TreeTableCell<Person, LocalDate> {
...
You then have to create a new StringConverter class, subclassing LocalDateStringConverter. The reason for subclassing is that if you don't do that it is impossible to catch the DateTimeParseException thrown by fromString() when an invalid date is received: if you use LocalDateStringConverter the JavaFX framework unfortunately catches it, without any frames in the stack trace involving your own code. So you do this:
class ValidatingLocalDateStringConverter extends LocalDateStringConverter {
boolean valid;
LocalDate fromString(String value) {
valid = true;
if (value.isBlank()) return null;
try {
return LocalDate.parse(value);
} catch (Exception e) {
valid = false;
}
return null;
}
}
Back in your DueDateEditor class you then rewrite the startEdit method as follows. NB, as with the TextFieldTreeTableCell class, textField is actually created lazily, when you first edit.
#Override
void startEdit() {
if (! isEditable()
|| ! getTreeTableView().isEditable()
|| ! getTableColumn().isEditable()) {
return;
}
super.startEdit();
if (isEditing()) {
if (textField == null) {
textField = CellUtils.createTextField(this, getConverter());
// this code added by me
ValidatingLocalDateStringConverter converter = getConverter();
Callable bindingFunc = new Callable(){
#Override
Object call() throws Exception {
// NB the return value from this is "captured" by the editor
converter.fromString( textField.getText() );
return converter.valid? '' : "-fx-background-color: red;";
}
}
def stringBinding = Bindings.createStringBinding( bindingFunc, textField.textProperty() );
textField.styleProperty().bind( stringBinding );
}
CellUtils.startEdit(this, getConverter(), null, null, textField);
}
}
NB don't bother trying to look up CellUtils: this is package-private, the package in question being javafx.scene.control.cell.
To set things up you do this:
Callback<TreeTableColumn, TreeTableCell> dueDateCellFactory =
new Callback<TreeTableColumn, TreeTableCell>() {
public TreeTableCell call(TreeTableColumn p) {
return new DueDateEditor( new ValidatingLocalDateStringConverter() );
}
}
dueDateColumn.setCellFactory(dueDateCellFactory);
... the result is a nice, reactive editor cell: when containing an invalid date (acceptable pattern yyyy-mm-dd; see other LocalDate.parse() variant for other formats) the background is red, otherwise normal. Entering with a valid date works seamlessly. You can also enter an empty String, which is returned as a null LocalDate.
With the above, pressing Enter with an invalid date sets the date to null. But overriding things to prevent this happening (i.e. forcing you to enter a valid date, or cancel the edit, e.g. by Escape) is trivial, using the ValidatingLocalDateStringConverter's valid field:
#Override
void commitEdit( LocalDate newDueDate ){
if( getConverter().valid )
super.commitEdit( newDueDate );
}
* I couldn't find this online. I extracted from the javafx source .jar file javafx-controls-11.0.2-sources.jar
I created an eclipse-rcp's project's plugin.xml with a new command with a parameter.
ArrayList<parameterization> parameters = new ArrayList<parameterization>();
IParameter iparam;
//get the command from plugin.xml
IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
ICommandService cmdService = (ICommandService)window.getService(ICommandService.class);
Command cmd = cmdService.getCommand("org.ipiel.demo.commands.click");
//get the parameter
iparam = cmd.getParameter("org.ipiel.demo.commands.click.paramenter1");
Parameterization params = new Parameterization(iparam, "commandValue");
parameters.add(params);
//build the parameterized command
ParameterizedCommand pc = new ParameterizedCommand(cmd, parameters.toArray(new Parameterization[parameters.size()]));
//execute the command
IHandlerService handlerService = (IHandlerService)window.getService(IHandlerService.class);
handlerService.executeCommand(pc, null);
I tried this example to pass parameters and it worked.
The issue in this example that I could pass only parameters of type String. ( because Parameterization )
I want to pass parameter of hash map and in general to pass any object.
I tried this code
IServiceLocator serviceLocator = PlatformUI.getWorkbench();
ICommandService commandService = (ICommandService) serviceLocator.getService(ICommandService.class);
ExecutionEvent executionEvent = new ExecutionEvent(cmd, paramArray, null, null);
cmd.executeWithChecks(executionEvent);
but it didn't work the parameters didn't move ( it was null)
Could you please help to to move object as parameter in command ?
Since it would get confusing to add another solution to my first answer, I'll provide another one for a second solution.
The choices I gave were " A) use the selected object of the "Execution Event" (examine that, it contains a lot of infos). B) you can use AbstractSourceProvider, so you can pass your object to the application context."
A) can be used in your Handler if your object is the selection of a Structured Object like a Tree:
MyObject p = (MyObject) ((IStructuredSelection) HandlerUtil.getCurrentSelection(event)).getFirstElement();
B) The usage of a Source provider is a bit more tricky. The main idea is, that you add your object to the application context. The important snippets for Eclipse 3.x from a project that I set up after I read this blog (note: it is in german and the example it provides doesn't work):
In your plugin.xml add:
<extension point="org.eclipse.ui.services">
<sourceProvider
provider="com.voo.example.sourceprovider.PersonSourceProvider">
<variable
name="com.voo.example.sourceprovider.currentPerson"
priorityLevel="activePartId">
</variable>
</sourceProvider>
Set up your own SourceProvider. Calling the "getCurrentState" you can get the variable (your Person object in this case) of that SourceProvider:
public class PersonSourceProvider extends AbstractSourceProvider{
/** This is the variable that is used as reference to the SourceProvider
*/
public static final String PERSON_ID = "com.voo.example.sourceprovider.currentPerson";
private Person currentPerson;
public PersonSourceProvider() {
}
#Override
public void dispose() {
currentPerson = null;
}
**/**
* Used to get the Status of the source from the framework
*/
#Override
public Map<String, Person> getCurrentState() {
Map<String, Person> personMap = new HashMap<String, Person>();
personMap.put(PERSON_ID, currentPerson);
return personMap;
}**
#Override
public String[] getProvidedSourceNames() {
return new String[]{PERSON_ID};
}
public void personChanged(Person p){
if (this.currentPerson != null && this.currentPerson.equals(p)){
return;
}
this.currentPerson = p;
fireSourceChanged(ISources.ACTIVE_PART_ID, PERSON_ID, this.currentPerson);
}
}
In your View you register to the SourceProvider and set the Object to the object you want to transfer to your Handler.
public void createPartControl(Composite parent) {
viewer = new TreeViewer(parent);
viewer.setLabelProvider(new ViewLabelProvider());
viewer.setContentProvider(new ViewContentProvider());
viewer.setInput(rootPerson);
getSite().setSelectionProvider(viewer);
viewer.addSelectionChangedListener(new ISelectionChangedListener() {
#Override
public void selectionChanged(SelectionChangedEvent event) {
Person p = null;
if (event.getSelection() instanceof TreeSelection) {
TreeSelection selection = (TreeSelection) event.getSelection();
if (selection.getFirstElement() instanceof Person) {
p = (Person) selection.getFirstElement();
}
}
if (p==null) {
return;
}
IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
ISourceProviderService service = (ISourceProviderService) window.getService(ISourceProviderService.class);
PersonSourceProvider sourceProvider = (PersonSourceProvider) service.getSourceProvider(PersonSourceProvider.PERSON_ID);
sourceProvider.personChanged(p);
}
});
}
And in your Handler you can just call the PersonSourceProvider#getCurrentState to get your Objects back.
Advantage of this method is, that you can use the Objectd anywhere you want. E.g. you can even set up a PropertyTester to enable/disable UI elements according to the currently selected Object.
The Parameterized Command does only accept Strings.
Here is an example for smaller objects:
Disclaimer: this is for Eclipse 3.x. I am not using Eclipse 4.x a lot, so you might have to adapt there in case you need it.
Create a Pluginproject (com.voo.example.commandparameter.advanced) with a View (com.voo.example.commandparameter.advanced.view) , a Command (com.voo.example.commandparameter.advanced.sysoCommand) with menu entry and Handler(com.voo.example.commandparameter.advanced.sysoCommand), and a universal Object (MyTestObject).
The Command needs a Parameter and a Parametertype in the plugin.xml, that gets passed to it:
<extension
point="org.eclipse.ui.commands">
<command
id="com.voo.example.commandparameter.advanced.sysoCommand"
name="SysoCommand">
<commandParameter
id="myObject"
name="object"
optional="true"
typeId="com.voo.example.commandparameter.advanced.testType">
</commandParameter>
</command>
<commandParameterType
id="com.voo.example.commandparameter.advanced.testType"
type="com.voo.example.commandparameter.advanced.MyTestObject">
</commandParameterType>
In the Object you set atrtibutes like name and street and define a convertToString method like that:
public String convertToString() {
return getName() +",,,"+ getStreet();
}
(you can override the toString method, too. I just used that method to set weired delimiters to the returned String)
And in a Class MyParamterConverter you can transfer it back:
public class MyParameterConverter extends AbstractParameterValueConverter {
public MyParameterConverter() {
}
#Override
public String convertToString(Object parameterValue)
throws ParameterValueConversionException {
return parameterValue.toString();
}
/**
* This will always create a new object. Just keep that in mind
* if you're trying to work with the objects.
*/
#Override
public Object convertToObject(String parameterValue)
throws ParameterValueConversionException {
//Split the String to get the attributes back
String delimiter =",,,";
String[] split = parameterValue.split(delimiter);
String name = split[0];
String street = split [1];
return new MyTestObject(name, street);
}
}
Now you can call the command with a buttonclick in your view, for example:
btnGo.addSelectionListener(new SelectionAdapter() {
#Override
public void widgetSelected(SelectionEvent event) {
MyTestObject testObject = new MyTestObject(textName.getText(),textStreet.getText());
ICommandService cS = (ICommandService)getSite().getService(ICommandService.class);
IHandlerService hS = (IHandlerService)getSite().getService(IHandlerService.class);
Command sysoComm = cS.getCommand("com.voo.example.commandparameter.advanced.sysoCommand");
HashMap<String, String> params = new HashMap<String, String>();
params.put("myObject", testObject.convertToString());
ParameterizedCommand pC = ParameterizedCommand.generateCommand(sysoComm, params);
try {
hS.executeCommand(pC, null);
} catch (Exception e) {
e.printStackTrace();
}
}
});
And the Handler can transform the passed parameters back :
public class MyObjectHandler extends AbstractHandler {
#Override
public Object execute(ExecutionEvent event) throws ExecutionException {
String param1 = event.getParameter("myObject");
MyParameterConverter converter = new MyParameterConverter();
Object convertToObject = null;
try {
convertToObject = converter.convertToObject(param1);
} catch (ParameterValueConversionException e) {
e.printStackTrace();
}
if (convertToObject instanceof MyTestObject) {
MyTestObject to = (MyTestObject) convertToObject;
System.out.println(to.toString());
}
return null;
}
}
This should work for most smaller sized objects that do not change while you pass them. If you need to pass bigger objects, you will have two choices: A) use the selected object of the "Execution Event" (examine that, it contains a lot of infos). B) you can use AbstractSourceProvider, so you can pass your object to the application context.
For a long time I have been focused on delivering an object via a command parameter. But in the end, the easiest workaround is to simply ignore the parameter stuff and put the desired object in a new child IExclipseContext and execute the command with that context. That way your handler gets your object injected.
Caller:
ECommandService commandService = // get commandService...
EHandlerService handlerService = // get handlerService...
IEclipseContext context = // get active or application context...
IEclipseContext childCtx = context.createChild();
childCtx.set(MyObject.class, instancOfMyObject);
ParameterizedCommand command = commandService.createCommand("my.command.id", null);
handlerService.executeHandler(command, childCtx);
In your handler:
#Execute
public void execute(#Optional MyObject myObject) {
if(myObject != null) {
// work with your object
}
}
Voila, no converters or callbacks (i.e. SelectionService) needed...
I am not really familiar with this as passing parameters to commands is quite rare. It looks like you have to use commandParameterType in the org.eclipse.ui.commands command definition to define code based on AbstractParameterValueConverter to convert between objects and the string for the parameter value.
How is possible to identify if a selected code is a method, a function, a variable...???
public class Modifiers implements IObjectActionDelegate{
private Shell shell;
public void run(IAction action) {
SelectedText selectedText;
IEditorPart editor = getActiveEditor();
if (editor instanceof AbstractTextEditor) {
selectedText = getSelectedText(editor);
//HOW TO IDENTIFY THE SELECTED CODE
}
}
public void selectionChanged(IAction action, ISelection selection) {
}
public void setActivePart(IAction action, IWorkbenchPart targetPart) {
shell = targetPart.getSite().getShell();
}
private IEditorPart getActiveEditor() {
return Activator.getDefault().getWorkbench().getActiveWorkbenchWindow()
.getActivePage().getActiveEditor();
}
private SelectedText getSelectedText(IEditorPart editor) {
SelectedText selectedText;
try {
final ISelection selection = editor.getEditorSite().getSelectionProvider().getSelection();
final ITextSelection textSelection = (ITextSelection) selection;
selectedText = new SelectedText(textSelection.getText(), textSelection.getOffset(), textSelection.getLength());
} catch (Exception e) {
selectedText = new SelectedText("", 0, 0);
}
return selectedText;
}
}
As you can see I have the selected code in selectedText. Now I want to know is how can I identify if the code in that variable is a method, a variable or whatever it contains.
When doing Refactors with eclipse it shows the code information you have selected. The idea is to do something like that.
Thanks for your help.
If i am correct,you need to simply move your mouse pointer on the code about which you want to get details, it will display a pop up window containing the details with specific symbol such as for static variable it will display 's' in that symbol or icon.
See carefully each symbol containing the different 2 letter and color has its own meaning.
i.e. green for public, red for private and gray for local etc.
I'm now looking into JTables and have a bunch of business objects that I retrieve from the DB with Hibernate + Spring Data JPA.
I love that Spring Data JPA handles all the cumbersome implementation of the DAL and was wondering if there's something similar for TableModel.
Basically, I would have something along the lines of:
public class GenericTableModel<T> extends AbstractTableModel
And the GenericTableModel would use reflection and/or annotations to look into T.
Does something like this exist? I hope I don't have to have a TableModel for each object I want to display on a JTable..
And the GenericTableModel would use reflection to look into T.
The Bean Table Model does this.
http://java.net/projects/beansbinding/
Will let you bind business objects to view components (including tables) as long as you have Bean-Style getters and setters.
I am in the process of writing a library for Swing Developers. Its a work in progress. But i have done what you are looking for. Take a look at the classes in this package:
http://code.google.com/p/swingobjects/source/browse/#git%2FSwingObjects%2Fsrc%2Forg%2Faesthete%2Fswingobjects%2Fview%2Ftable
For an example of how to use this, please take a look at this class- Look at line numbers - 70-85
http://code.google.com/p/swingobjects/source/browse/SwingObjects/src/test/CompTest.java
I havent got the documentation written yet. But if you dont follow anything, please comment back here.
Update - Code sample
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import org.aesthete.swingobjects.annotations.Column;
import org.aesthete.swingobjects.view.table.RowDataBean;
import org.aesthete.swingobjects.view.table.SwingObjTable;
import org.jdesktop.swingx.JXFrame;
public class TableDemo {
public static void main(String[] args) {
try {
//For this demo the Framework need not be initialised.. If you plan on using the entire framework, then
//its best you initialise it before working on anything...
// SwingObjectsInit.init("/swingobjects.properties", "/error.properties");
//Here's the data to show on the table
final List<Row> rows = new ArrayList<Row>();
rows.add(new Row("Data 1", "Data 2", "Yes", true));
rows.add(new Row("Data 3", "Data 4", "No", false));
//Create the swing table as below.. Provide the Row.class to say that the data in the rows
// will be from this class
final SwingObjTable<Row> table = new SwingObjTable<Row>(Row.class);
table.setData(rows);
table.setVisibleRowCount(4);
//Make any column into a combo box by calling the below method.
//A column can be automatically made into a checkbox, by defining your property in the Row class as a boolean
table.makeColumnsIntoComboBox(new String[] { "Yes", "No" }, 2);
//Initialise the frame and show it on the screen
final JXFrame frame = new JXFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setContentPane(new JScrollPane(table));
frame.pack();
frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
public static class Row extends RowDataBean{
#Column(index=0,name="Column 1",editable=true)
private String column1;
#Column(index=1,name="Column 2",editable=true)
private String column2;
#Column(index=2,name="Column 3",editable=true)
private String column3;
#Column(index=3,name="Column 4",editable=true)
private boolean column4;
public Row(String column1, String column2, String column3, boolean column4) {
super();
this.column1 = column1;
this.column2 = column2;
this.column3 = column3;
this.column4 = column4;
}
public String getColumn1() {
return column1;
}
public void setColumn1(String column1) {
this.column1 = column1;
}
public String getColumn2() {
return column2;
}
public void setColumn2(String column2) {
this.column2 = column2;
}
public String getColumn3() {
return column3;
}
public void setColumn3(String column3) {
this.column3 = column3;
}
public boolean getColumn4() {
return column4;
}
public void setColumn4(boolean column4) {
this.column4 = column4;
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((column1 == null) ? 0 : column1.hashCode());
result = prime * result + ((column2 == null) ? 0 : column2.hashCode());
result = prime * result + ((column3 == null) ? 0 : column3.hashCode());
result = prime * result + (column4 ? 1231 : 1237);
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Row other = (Row) obj;
if (column1 == null) {
if (other.column1 != null)
return false;
} else if (!column1.equals(other.column1))
return false;
if (column2 == null) {
if (other.column2 != null)
return false;
} else if (!column2.equals(other.column2))
return false;
if (column3 == null) {
if (other.column3 != null)
return false;
} else if (!column3.equals(other.column3))
return false;
if (column4 != other.column4)
return false;
return true;
}
}
}
Update: Answer to queries in Comments
1) The table can be inserted in any Component, right? (JPanel, JScrollPane, etc.),
Yes. The table extends JXTable (swingx) which extends JTable. So it can be placed inside any component. You would ideally place into a JScrollPane though.
2) We have control over the column names? (my app is localized in several langs)
Thanks for this. I didnt think about localisation when I started making the framework. You pointed me in the right direction. I was able to modify the framework quickly to achieve this. Its easy actually:
To make use of l10n - Before you start coding, make sure you initialize the Swing Objects Framework with the below line. If you dont provide a Locale, the default Locale will be applied.
SwingObjectsInit.init("swingobjects", "application",new Locale("fr", "FR"));
You need to have to have 2 sets of properties file.
1) swingobjects - This provides the defaults for the swingobjects framework. The file is already provided in my code base. Just copy paste the file onto a classpath location.
2) application - This is where you will need to put in your application's GUI texts/messages. You will have to make a new application properties file for every Locale you need.
Then finally, instead of saying
#Column(index=0,name="Column 1",editable=true)
private String column1;
You will have to use:
#Column(index=0,key="test.column1",editable=true)
private String column1;
Change name to key. This will make it read the Resource Bundle and search for a property test.column1 rather than your hard coded column Name.
3) Does SwingObjTable require hashCode and equals to be implemented?
The equals and hashcode is required if you use the Swing Objects Framework in its entirety. The Swing Objects Framework, allows you to set data for the GUI elements in a bean in your model class - Read as in MVC. The framework, then automatically updates the GUI if the bean's value has changed. In order to check if the bean's value has indeed changed, it needs to call the equals method. Hence you need to override it. You can make Eclipse generate this for you. Or if you dont use anything in the framework, then just call super.equals() from there.
Just check out / clone the git repo and you'll have be access the TableDemo example..
git clone https://writetosethu#code.google.com/p/swingobjects/
Is there a well-established approach for documenting Java "properties" file contents, including:
specifying the data type/contents expected for a given key
specifying whether a key is required for the application to function
providing a description of the key's meaning
Currently, I maintain (by hand) a .properties file that is the default, and I write a prose description of the data type and description of each key in a comment before. This does not lead to a programmatically accessible properties file.
I guess what I'm looking for is a "getopt" equivalent for properties files...
[EDIT: Related]
Java Configuration Frameworks
You could use some of the features in the Apache Commons Configuration package. It at least provides type access to your properties.
There are only conventions in the traditional java properties file. Some I've seen include providing, like you said, an example properties file. Another is to provide the default configuration with all the properties, but commented out.
If you really want to require something, maybe you're not looking for a properties file. You could use an XML configuration file and specify a schema with datatypes and requirements. You can use jaxb to compile the schema into java and read it i that way. With validation you can make sure the required properties are there.
The best you could hope for is when you execute your application, it reads, parses, and validates the properties in the file. If you absolutely had to stay properties based and didn't want to go xml, but needed this parsing. You could have a secondary properties file that listed each property that could be included, its type, and whether it was required. You'd then have to write a properties file validator that would take in a file to validate as well as a validation schema-like properties file. Something like
#list of required properties
required=prop1,prop2,prop3
#all properties and their types
prop1.type=Integer
prop2.type=String
I haven't looked through all of the Apache Configuration package, but they often have useful utilities like this. I wouldn't be surprised if you could find something in there that would simplify this.
Another option to check out is the project called OWNER. There, you define the interface that serves as the configuration object in your application, using types and annotations. Then, OWNER does the finding and parsing of the correct Properties file. Thus, you could write a javadoc for your interface and use that as the documentation.
I have never seen a standard way of doing it. What I would probably do is:
wrap or extend the java.util.Properties class
override (of extending) or provide a method (if wrapping) the store method (or storeToXML, etc) that writes out a comment for each line.
have the method that stores the properties have some sort of input file where you describe the properties of each one.
It doesn't get you anything over what you are doing by hand, except that you can manage the information in a different way that might be easier to deal with - for example you could have a program that spit out the comments to read in. It would potentially give you the programmatic access that you need, but it is a roll-your-own sort of thing.
Or it might just be too much work for too little to gain (which is why there isn't something obvious out there).
If you can specify the sort of comments you want to see I could take a stab at writing something if I get bored :-) (it is the sort of thing I like to do for fun, sick I know :-).
Ok... I got bored... here is something that is at least a start :-)
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
public class PropertiesVerifier
{
private final Map<String, PropertyInfo> optionalInfo;
private final Map<String, PropertyInfo> requiredInfo;
{
optionalInfo = new HashMap<String, PropertyInfo>();
requiredInfo = new HashMap<String, PropertyInfo>();
}
public PropertiesVerifier(final PropertyInfo[] infos)
{
for(final PropertyInfo info : infos)
{
final Map<String, PropertyInfo> infoMap;
if(info.isRequired())
{
infoMap = requiredInfo;
}
else
{
infoMap = optionalInfo;
}
infoMap.put(info.getName(), info);
}
}
public void verifyProperties(final Properties properties)
{
for(final Entry<Object, Object> property : properties.entrySet())
{
final String key;
final String value;
key = (String)property.getKey();
value = (String)property.getValue();
if(!(isValid(key, value)))
{
throw new IllegalArgumentException(value + " is not valid for: " + key);
}
}
}
public boolean isRequired(final String key)
{
return (requiredInfo.get(key) != null);
}
public boolean isOptional(final String key)
{
return (optionalInfo.get(key) != null);
}
public boolean isKnown(final String key)
{
return (isRequired(key) || isOptional(key));
}
public Class getType(final String key)
{
final PropertyInfo info;
info = getPropertyInfoFor(key);
return (info.getType());
}
public boolean isValid(final String key,
final String value)
{
final PropertyInfo info;
info = getPropertyInfoFor(key);
return (info.verify(value));
}
private PropertyInfo getPropertyInfoFor(final String key)
{
PropertyInfo info;
info = requiredInfo.get(key);
if(info == null)
{
info = optionalInfo.get(key);
if(info == null)
{
// should be a better exception maybe... depends on how you
// want to deal with it
throw new IllegalArgumentException(key + "
is not a valid property name");
}
}
return (info);
}
protected final static class PropertyInfo
{
private final String name;
private final boolean required;
private final Class clazz;
private final Verifier verifier;
protected PropertyInfo(final String nm,
final boolean mandatory,
final Class c)
{
this(nm, mandatory, c, getDefaultVerifier(c));
}
protected PropertyInfo(final String nm,
final boolean mandatory,
final Class c,
final Verifier v)
{
// check for null
name = nm;
required = mandatory;
clazz = c;
verifier = v;
}
#Override
public int hashCode()
{
return (getName().hashCode());
}
#Override
public boolean equals(final Object o)
{
final boolean retVal;
if(o instanceof PropertyInfo)
{
final PropertyInfo other;
other = (PropertyInfo)o;
retVal = getName().equals(other.getName());
}
else
{
retVal = false;
}
return (retVal);
}
public boolean verify(final String value)
{
return (verifier.verify(value));
}
public String getName()
{
return (name);
}
public boolean isRequired()
{
return (required);
}
public Class getType()
{
return (clazz);
}
}
private static Verifier getDefaultVerifier(final Class clazz)
{
final Verifier verifier;
if(clazz.equals(Boolean.class))
{
// shoudl use a singleton to save space...
verifier = new BooleanVerifier();
}
else
{
throw new IllegalArgumentException("Unknown property type: " +
clazz.getCanonicalName());
}
return (verifier);
}
public static interface Verifier
{
boolean verify(final String value);
}
public static class BooleanVerifier
implements Verifier
{
public boolean verify(final String value)
{
final boolean retVal;
if(value.equalsIgnoreCase("true") ||
value.equalsIgnoreCase("false"))
{
retVal = true;
}
else
{
retVal = false;
}
return (retVal);
}
}
}
And a simple test for it:
import java.util.Properties;
public class Main
{
public static void main(String[] args)
{
final Properties properties;
final PropertiesVerifier verifier;
properties = new Properties();
properties.put("property.one", "true");
properties.put("property.two", "false");
// properties.put("property.three", "5");
verifier = new PropertiesVerifier(
new PropertiesVerifier.PropertyInfo[]
{
new PropertiesVerifier.PropertyInfo("property.one",
true,
Boolean.class),
new PropertiesVerifier.PropertyInfo("property.two",
false,
Boolean.class),
// new PropertiesVerifier.PropertyInfo("property.three",
// true,
// Boolean.class),
});
System.out.println(verifier.isKnown("property.one"));
System.out.println(verifier.isKnown("property.two"));
System.out.println(verifier.isKnown("property.three"));
System.out.println(verifier.isRequired("property.one"));
System.out.println(verifier.isRequired("property.two"));
System.out.println(verifier.isRequired("property.three"));
System.out.println(verifier.isOptional("property.one"));
System.out.println(verifier.isOptional("property.two"));
System.out.println(verifier.isOptional("property.three"));
System.out.println(verifier.getType("property.one"));
System.out.println(verifier.getType("property.two"));
// System.out.println(verifier.getType("property.tthree"));
System.out.println(verifier.isValid("property.one", "true"));
System.out.println(verifier.isValid("property.two", "false"));
// System.out.println(verifier.isValid("property.tthree", "5"));
verifier.verifyProperties(properties);
}
}
One easy way is to distribute your project with a sample properties file, e.g. my project has in svn a "build.properties.example",with properties commented as necessary. The locally correct properties don't go into svn.
Since you mention "getopt", though, I'm wondering if you're really thinking of cmd line arguments? If there's a "main" that needs specific properties, I usually put it the relevant instructions in a "useage" message that prints out if the arguments are incorrect or "-h".