Intro
I am working on an open source project Treez where I organize so called "Atoms" in a tree view. Those Atoms sometimes have a lot of attributes and those attributes are modified either through user actions in a tree view or through an API in an Eclipse code editor.
The attributes of my Atoms themselves are represented by reusable "AttributeAtoms". Those hold the actual attribute value and provide additional functionality like validation (other possible terms for "Atom" might be "widget", "bean", "property" or "tree node").
Question(s)
In the past I provided a getter/setter pair for each of my Atom attributes. This is a lot of extra work and it blows up the size of my Atom classes (see code examples below). Now I am looking for an alternative solution that
- makes it less work to create new Atoms (and less work to maintain them).
- avoids the "redundant" getter/setter boiler plate code.
I'm going to describe a few options below. Which of the options would you use? Have you suggestions on how to improve those options? Do you know even further options?
Getter/Setter code example
private AttributeAtom<String> myAttribute = new FilePathAttributeAtom("myAttribtue");
public String getMyAttribute() {
return myAttribute.getValue();
}
public void setMyAttribute(String value) {
this.myAtrribute.setValue(value);
}
Related articles
- does java have something similar to C# properties?
- (no) Properties in Java?
- http://blog.netopyr.com/2011/05/19/creating-javafx-properties/
- http://www.eclipse.org/forums/index.php/t/781816/
- Why use getters and setters?
- What is the advantage of having a private attribute with getters and setters?
Considered options
A. Auto generated getters/setters with IDE
Eclipse provides the possibility to auto generate getters/setters.
- Does not work for my AttributeAtoms since the gettter/setter code looks slightly different.
- Does not avoid the extra "redundant" code.
If I decide to keep the getters/setters, I could try to create something similar for my AttributeAtoms. Also see this post about (not working) automatic getter/setter creation for JavaFx Properties: http://www.eclipse.org/forums/index.php/t/781816/
B. Annotations for generating getters/setters (Project Lombok)
Lombok provides the possibility to use Annotations for the automatic generation of getters and setters.
- Does neither work for my AttributeAtoms
- I tried to use Lombok with Eclipse. The code completion in the editor worked but I got "method not found" warnings. I might need to invest some more time to get Lombok working for classical attributes.
- Also see Is it safe to use Project Lombok?
If I decide to use Annotations to define getters/setters, it might be possible to extend Lombok to work for my AttributeAtoms.
A request to extend Lombok for JavaFx properties already exists here: https://groups.google.com/forum/#!searchin/project-lombok/getter$20and$20setter$20for$20properties/project-lombok/Ik6phxDXHVU/zzDkC2MpmvgJ
Here is an intro on how to extend Lambok with custom transformations: http://notatube.blogspot.de/2010/12/project-lombok-creating-custom.html
C. One generalized getter/setter for all attributes
I could use a single getter/setter pair for all Atom attributes like
Object get(String attributeName)
void set(String attriuteName, Object value)
- Type safety could be improved by passing additional type arguments.
- However, the code completion for my Atom would only suggest the single getter/setter and a user would not see which attributes are available. (Maybe this could be addressed by using Enums instead of Strings to identify the attributes. But those Enums need to be created somehow. Also see next option.)
D. Custom Eclipse editor and code processing
Maybe I could write an extra Eclipse plugin for my open source project that "allows to access private attributes" by suggesting corresponding fake methods for the code completion. Before compiling the user source code, fake calls like
myAtom.setMyAttribue(newValue);
would be translated to code for the really existing generalized getter (option C):
myAtom.set("myAttribute", newValue);
E. Public attributes
If I make my Atom attributes public, I do not need getters/setter code in each Atom. Instead, the reusable AttributeAtoms would provide get/set methods. The usage would for example look like this
myAtom.myAttribute.get();
myAtom.myAttribute.set(newValue);
instead of
myAtom.getMyAttribute();
myAtom.setMyAttribute(newValue);
Some disadvantages:
- Users need to get used to this "unconventional approach". Java users might expect
setMyAttribute(newValue)
and C# users might expectmyAtom.myAttribute = newValue
. It is possible to exchange the whole AttributeAtom, which I do not want to allow:
myAtom.myAttribute = completelyDifferentAttribute
Any strategies to improve this?
Is there a way to allow the access to the methods of an attribute while not allowing to exchange the attribute itself? I would need a new access modifier like
private *publicMethodAccess* AttributeAtom<String> myAttribute;
Atom code example
Here is an example Atom class. If you scroll to the bottom you will find many lines of code that are consumed by the getters/setters. This is ugly, isn't it?
package org.treez.results.atom.probe;
import java.util.ArrayList;
import java.util.List;
import org.apache.log4j.Logger;
import org.eclipse.swt.graphics.Image;
import org.treez.core.atom.attribute.AttributeRoot;
import org.treez.core.atom.attribute.ModelPath;
import org.treez.core.atom.attribute.ModelPathSelectionType;
import org.treez.core.atom.attribute.Section;
import org.treez.core.atom.attribute.base.AttributeAtom;
import org.treez.core.atom.variablerange.VariableRange;
import org.treez.core.data.column.ColumnType;
import org.treez.data.column.Columns;
import org.treez.data.output.OutputAtom;
import org.treez.data.table.Table;
import org.treez.results.Activator;
/**
* Collects data from a sweep and puts it in a single (probe-) table. That table can easier be used to produce plots
* than the distributed sweep results.
*/
public class SweepProbe extends AbstractProbe {
/**
* Logger for this class
*/
@SuppressWarnings("unused")
private static Logger sysLog = Logger.getLogger(SweepProbe.class);
//#region ATTRIBUTES
private AttributeAtom<String> xLabel;
private ModelPath xRange;
private AttributeAtom<String> yLabel;
private AttributeAtom<String> firstFamilyLabel;
private ModelPath firstFamilyRange;
private AttributeAtom<String> secondFamilyLabel;
private ModelPath secondFamilyRange;
private AttributeAtom<String> probeName;
private ModelPath sweepOutputModel;
private ModelPath firstProbeTable;
private AttributeAtom<String> probeColumnIndex;
private AttributeAtom<String> probeRowIndex;
//#end region
//#region CONSTRUCTORS
/**
* Constructor
*
* @param name
*/
public SweepProbe(String name) {
super(name);
createPropertyModel();
}
//#end region
//#region METHODS
/**
* Creates the model for the property control
*/
private void createPropertyModel() {
//root
AttributeRoot root = new AttributeRoot("root");
//page
org.treez.core.atom.attribute.Page page = root.createPage("page");
//x section
Section xSection = page.createSection("xSection", "X");
xSection.createSectionAction("action", "Run probe", () -> execute(treeViewRefreshable));
xLabel = xSection.createTextField("xLabel", "Label for x-Axis", "x");
xRange = xSection.createModelPath("xRange", "Range for x-Axis", "", VariableRange.class, this);
xRange.setSelectionType(ModelPathSelectionType.FLAT);
xRange.setValue("root.studies.sweep.threshold");
//y section
Section ySection = page.createSection("ySection", "Y");
yLabel = ySection.createTextField("yLabel", "Label for y-Axis", "y");
//first family section
Section firstFamilySection = page.createSection("firstFamily", "First family");
firstFamilySection.setExpanded(false);
firstFamilyLabel = firstFamilySection.createTextField("firstFamilyLabel", "Label for first family", "family1");
firstFamilyRange = firstFamilySection.createModelPath("firstFamilyRange", "Range for first family", "",
VariableRange.class, this);
//second family section
Section secondFamilySection = page.createSection("secondFamily", "Second family");
secondFamilySection.setExpanded(false);
secondFamilyLabel = secondFamilySection.createTextField("secondFamilyLabel", "Label for second family",
"family2");
secondFamilyRange = secondFamilySection.createModelPath("secondFamilyRange", "Range for second family", "",
VariableRange.class, this);
//probe section
Section probeSection = page.createSection("probe", "Probe");
probeName = probeSection.createTextField("propeName", "Name", "MyProbe");
sweepOutputModel = probeSection.createModelPath("sweepOutput", "SweepOutput", "", OutputAtom.class, this);
firstProbeTable = probeSection.createModelPath("tablePath", sweepOutputModel, Table.class);
firstProbeTable.setLabel("First probe table");
probeColumnIndex = probeSection.createTextField("probeColumnIndex", "Column index", "0");
probeRowIndex = probeSection.createTextField("probeColumnIndex", "Row index", "0");
setModel(root);
}
/**
* Provides an image to represent this atom
*/
@Override
public Image provideBaseImage() {
Image baseImage = Activator.getImage("sweep.png");
return baseImage;
}
//#region CREATE TABLE COLUMNS
/**
* Creates the required columns for the given table
*
* @param table
*/
@Override
protected void createTableColumns(Table table) {
//TODO
}
//#end region
//#region COLLECT PROBE DATA
@Override
protected void collectProbeDataAndFillTable() {
// TODO Auto-generated method stub
}
//#end region
//#end region
//#region ACCESSORS
//#region X LABEL
/**
* @return
*/
public String getXLabel() {
return xLabel.getValue();
}
/**
* @param label
*/
public void setXLabel(String label) {
xLabel.setValue(label);
}
//#end region
//#region X RANGE
/**
* @return
*/
public String getXRange() {
return xRange.getValue();
}
/**
* @param range
*/
public void setXRange(String range) {
xRange.setValue(range);
}
//#end region
//#region Y LABEL
/**
* @return
*/
public String getYLabel() {
return yLabel.getValue();
}
/**
* @param label
*/
public void setYLabel(String label) {
yLabel.setValue(label);
}
//#end region
//#region FIRST FAMILY LABEL
/**
* @return
*/
public String getFirstFamilyLabel() {
return firstFamilyLabel.getValue();
}
/**
* @param label
*/
public void setFirstFamilyLabel(String label) {
firstFamilyLabel.setValue(label);
}
//#end region
//#region FIRST FAMILY RANGE
/**
* @return
*/
public String getFirstFamilyRange() {
return firstFamilyRange.getValue();
}
/**
* @param range
*/
public void setFirstFamilyRange(String range) {
firstFamilyRange.setValue(range);
}
//#end region
//#region SECOND FAMILY LABEL
/**
* @return
*/
public String getSecondFamilyLabel() {
return secondFamilyLabel.getValue();
}
/**
* @param label
*/
public void setSecondFamilyLabel(String label) {
secondFamilyLabel.setValue(label);
}
//#end region
//#region SECOND FAMILY RANGE
/**
* @return
*/
public String getSecondFamilyRange() {
return secondFamilyRange.getValue();
}
/**
* @param range
*/
public void setSecondFamilyRange(String range) {
secondFamilyRange.setValue(range);
}
//#end region
//#region PROBE
/**
* @return
*/
public String getProbeName() {
return probeName.getValue();
}
/**
* @param name
*/
public void setProbeName(String name) {
probeName.setValue(name);
}
//#end region
//#region SWEEP OUTPUT MODEL
/**
* @return
*/
public String getSweepOutputModelName() {
return sweepOutputModel.getValue();
}
/**
* @param sweepOutputModel
*/
public void setSweepOutputModelName(String sweepOutputModel) {
this.sweepOutputModel.setValue(sweepOutputModel);
}
//#end region
//#region PROBE TABLE
/**
* @return
*/
public String getFirstProbeTable() {
return firstProbeTable.getValue();
}
/**
* @param firstProbeTable
*/
public void setFirstProbeTable(String firstProbeTable) {
this.firstProbeTable.setValue(firstProbeTable);
}
//#end region
//#region COLUMN INDEX
/**
* @return
*/
public String getProbeColumnIndex() {
return probeColumnIndex.getValue();
}
/**
* @param index
*/
public void setProbeColumnIndex(String index) {
probeColumnIndex.setValue(index);
}
//#end region
//#region ROW INDEX
/**
* @return
*/
public String getProbeRowIndex() {
return probeRowIndex.getValue();
}
/**
* @param index
*/
public void setProbeRowIndex(String index) {
probeRowIndex.setValue(index);
}
//#end region
//#end region
}