5
\$\begingroup\$

Description

Generate Property Classes for JavaFX Screenshot

Java 1.8 is used.

This code can also be downloaded from PCloud:Zip File , Github


File Summary

  • FXMLController.java: Controller for Scene.fxml
  • JavaDataType.java: Java Data Type Enum
  • MainApp.java: Main Application Sub Class
  • PropertyClassBuilder.java: Code Generator
  • PropertyInfo.java: Property Info property class
  • pom.xml: Maven (Generated)
  • Scene.fxml: Main FXML file (Generated from SceneBuilder)

Dependencies

  • javafx

Code

FXMLController.java: (111 lines, 3198 bytes)

/**
 * Controller for Scene.fxml
 *
 * @author Bhathiya
 */
public class FXMLController {

    //------
    //Table of Properties
    @FXML
    private TableView<PropertyInfo> tvProperties;

    @FXML
    private TableColumn<PropertyInfo, String> tcName;

    @FXML
    private TableColumn<PropertyInfo, String> tcType;
    //------

    @FXML
    private ComboBox<String> cmbPropertyType;

    @FXML
    private TextArea txtCode;

    @FXML
    private TextField txtProperty;

    @FXML
    private TextField txtClassName;


    //property info observable list : this is bind to table view
    private ObservableList<PropertyInfo> propertyInfoList;

    //property type observable list : this is bind to combobox of data types
    private ObservableList<String> propertyTypeList;

    @FXML
    void addOnAction(ActionEvent event) {
        String name = txtProperty.getText().trim();
        if (name.isEmpty()) {
            return;
        }
        String type = cmbPropertyType.getValue();
        propertyInfoList.add(new PropertyInfo(name, type));
        //clear
        txtProperty.clear();
        cmbPropertyType.getSelectionModel().selectFirst();
    }

    @FXML
    void generateOnAction(ActionEvent event) {
        String className = txtClassName.getText().trim();
        if (className.isEmpty()) {
            return;
        }
        PropertyClassBuilder generator = new PropertyClassBuilder(className);
        txtCode.setText(generator.generateCode(propertyInfoList));

    }

    @FXML
    void btnClearOnAction(ActionEvent event) {
        propertyInfoList.clear();
        txtClassName.clear();
        txtCode.clear();
        txtProperty.clear();
        cmbPropertyType.getSelectionModel().selectFirst();
    }

    @FXML
    void btnRemoveOnAction(ActionEvent event) {
        PropertyInfo propertyInfo = tvProperties.getSelectionModel().
                getSelectedItem();
        if (propertyInfo != null) {
            propertyInfoList.remove(propertyInfo);
        }
    }

    @FXML
    void initialize() {
        //Create Observable List for types
        propertyTypeList = cmbPropertyType.getItems();
        propertyTypeList.clear();
        propertyTypeList.addAll("String", "Integer", "Boolean", "Double",
                "Float", "Long");
        cmbPropertyType.getSelectionModel().selectFirst();
        //Create Observable List for propertyInfoList
        propertyInfoList = tvProperties.getItems();
        propertyInfoList.clear();
        //Bind Columns
        tcName.setCellValueFactory(
                new PropertyValueFactory<>("name"));

        tcType.setCellValueFactory(
                new PropertyValueFactory<>("type"));
    }
}

JavaDataType.java: (58 lines, 1207 bytes)

/**
 * Java Data Types : contains basic wrapper class names
 * @author Bhathiya
 */
public enum JavaDataType {

    STRING("String"),
    INTEGER("Integer"),
    BOOLEAN("Boolean"),
    DOUBLE("Double"),
    FLOAT("Float"),
    LONG("Long");

    /**
     * String value
     */
    private final String value;

    private JavaDataType(String value) {
        this.value = value;
    }

    /**
     * 
     * @return string value
     */
    @Override
    public String toString() {
        return value;
    }

    /**
     * convert String to JavaDataType
     * @param type String type
     * @return JavaDataType or null if not found
     */
    public static JavaDataType fromString(String type) {
        switch (type) {
            case "String":
                return STRING;
            case "Integer":
                return INTEGER;
            case "Boolean":
                return BOOLEAN;
            case "Double":
                return DOUBLE;
            case "Float":
                return FLOAT;
            case "Long":
                return LONG;
        }
        return null;
    }


}

MainApp.java: (32 lines, 766 bytes)

/**
 * Main Application
 * @author Bhathiya
 */
public class MainApp extends Application {

    /**
     * Start Application
     * @param stage main stage
     * @throws Exception
     */
    @Override
    public void start(Stage stage) throws Exception {
        Parent root = FXMLLoader.load(getClass().getResource("/fxml/Scene.fxml"));

        Scene scene = new Scene(root);

        stage.setTitle("Java FX Property Class Builder");
        stage.setResizable(false);
        stage.setScene(scene);
        stage.show();
    }

}

PropertyClassBuilder.java: (115 lines, 3922 bytes)

/**
 *
 * @author Bhathiya
 */
public class PropertyClassBuilder {

    /**
     * class name for code generator to use
     */
    private final String className;

    /**
     * constructor for code generator
     *
     * @param className class name
     */
    public PropertyClassBuilder(String className) {
        this.className = className;
    }

    /**
     * constructor for code generator, Class name used is : PropertyClassName
     */
    public PropertyClassBuilder() {
        this("PropertyClassName");
    }

    /**
     * generate code
     *
     * @param propertyInfoList
     * @return
     */
    public String generateCode(List<PropertyInfo> propertyInfoList) {
        String code = "import javafx.beans.property.*;\n" + "\n" + "/**\n"
                + " * Class Information\n" + " * @author Your Name\n" + " */\n"
                + "public class %s {\n" + "\n" + "%s\n" + "\n"
                + "    public %s() {\n" + "%s\n" + "    }\n" + "\n" + "%s\n"
                + "\n" + "}";
        StringBuilder fields = new StringBuilder();
        StringBuilder initCodes = new StringBuilder();
        StringBuilder methods = new StringBuilder();

        propertyInfoList.forEach((PropertyInfo propertyInfo) -> {
            JavaDataType type = JavaDataType.fromString(propertyInfo.getType());
            String name = propertyInfo.getName();
            fields.append(toPrivateField(type, name));
            initCodes.append(toInitCode(type, name));
            methods.append(toGetterMethod(type, name));
            methods.append("\n\n");
            methods.append(toSetterMethod(type, name));
            methods.append("\n\n");
            methods.append(toPropertyMethod(type, name));
            methods.append("\n\n");
        });

        return String.format(code, className, fields.toString(), className,
                initCodes.toString(), methods.toString());
    }

    //create private field code
    private static String toPrivateField(JavaDataType type, String name) {
        return String.format("    private final %s %s;\n",
                toPropertyType(type), name);
    }

    //create code for "creating new property object"
    private static String toInitCode(JavaDataType type, String name) {
        return String.format("        %s = new %s();\n", name,
                toPropertyType(type));
    }

    //to upper camel case
    private static String toUpperCamelCase(String name) {
        return name.substring(0, 1).toUpperCase() + name.substring(1);
    }

    //create getter method
    private static String toGetterMethod(JavaDataType type, String name) {
        String code = "    public %s %s%s() {\n"
                + "        return %s.get();\n" + "    }";
        String getPrepend = "get";
        if (type == JavaDataType.BOOLEAN) {
            getPrepend = "is";
        }
        return String.format(code, type, getPrepend, toUpperCamelCase(name),
                name);
    }

    //create setter method
    private static String toSetterMethod(JavaDataType type, String name) {
        String code = "    public void set%1$s(%2$s %3$s) {\n"
                + "        this.%3$s.set(%3$s);\n" + "    }";

        return String.format(code, toUpperCamelCase(name),
                type, name);
    }

    //create property method
    private static String toPropertyMethod(JavaDataType type, String name) {
        String code = "    public %1$s %2$sProperty() {\n"
                + "        return %2$s;\n" + "    }";

        return String.format(code, toPropertyType(type), name);
    }

    //get property type based on Wrapper Class name
    private static String toPropertyType(JavaDataType type) {
        return String.format("Simple%sProperty", type);
    }
}

PropertyInfo.java: (64 lines, 1323 bytes)

/**
 * Property info property class
 * @author Bhathiya
 */
public class PropertyInfo {
    /**
     * field name for a property
     */
    private final SimpleStringProperty name;

    /**
     * data type for a property
     */
    private final SimpleStringProperty type;

    /**
     * PropertyInfo default constructor
     * initialize final fields
     */
    public PropertyInfo() {
        name = new SimpleStringProperty();
        type = new SimpleStringProperty();
    }

    /**
     * PropertyInfo constructor
     * @param name field name for a property
     * @param type data type for a property
     */
    public PropertyInfo(String name, String type) {
        this();
        this.name.set(name);
        this.type.set(type);
    }

    public SimpleStringProperty nameProperty() {
        return name;
    }

    public SimpleStringProperty typeProperty() {
        return type;
    }

    public String getName() {
        return name.get();
    }

    public void setName(String name) {
        this.name.set(name);
    }

    public String getType() {
        return type.get();
    }

    public void setType(String type) {
        this.type.set(type);
    }

}

pom.xml: (110 lines, 5039 bytes)

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>info.simpll</groupId>
    <artifactId>FXPropertyClassBuilder</artifactId>
    <version>0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>FXPropertyClassBuilder</name>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <mainClass>info.simpll.fxprop.MainApp</mainClass>
    </properties>

    <organization>
        <!-- Used as the 'Vendor' for JNLP generation -->
        <name>Your Organisation</name>
    </organization>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <version>2.6</version>
                <executions>
                    <execution>
                        <id>unpack-dependencies</id>
                        <phase>package</phase>
                        <goals>
                            <goal>unpack-dependencies</goal>
                        </goals>
                        <configuration>
                            <excludeScope>system</excludeScope>
                            <excludeGroupIds>junit,org.mockito,org.hamcrest</excludeGroupIds>
                            <outputDirectory>${project.build.directory}/classes</outputDirectory>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>1.2.1</version>
                <executions>
                    <execution>
                        <id>unpack-dependencies</id>

                        <phase>package</phase>
                        <goals>
                            <goal>exec</goal>
                        </goals>
                        <configuration>
                            <executable>${java.home}/../bin/javafxpackager</executable>
                            <arguments>
                                <argument>-createjar</argument>
                                <argument>-nocss2bin</argument>
                                <argument>-appclass</argument>
                                <argument>${mainClass}</argument>
                                <argument>-srcdir</argument>
                                <argument>${project.build.directory}/classes</argument>
                                <argument>-outdir</argument>
                                <argument>${project.build.directory}</argument>
                                <argument>-outfile</argument>
                                <argument>${project.build.finalName}.jar</argument>
                            </arguments>
                        </configuration>
                    </execution>
                    <execution>
                        <id>default-cli</id>
                        <goals>
                            <goal>exec</goal>                            
                        </goals>
                        <configuration>
                            <executable>${java.home}/bin/java</executable>
                            <commandlineArgs>${runfx.args}</commandlineArgs>
                        </configuration>
                    </execution>
                </executions>  
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <compilerArgument>-Xlint:unchecked</compilerArgument>
                    <compilerArguments>                        
                        <bootclasspath>${sun.boot.class.path}${path.separator}${java.home}/lib/jfxrt.jar</bootclasspath>
                    </compilerArguments>
                    <showDeprecation>true</showDeprecation>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.16</version>
                <configuration>
                    <additionalClasspathElements>
                        <additionalClasspathElement>${java.home}/lib/jfxrt.jar</additionalClasspathElement>
                    </additionalClasspathElements>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

Scene.fxml: (49 lines, 3051 bytes)

<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<AnchorPane id="AnchorPane" prefHeight="530.0" prefWidth="677.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="info.simpll.fxprop.FXMLController">
    <children>
      <TitledPane animated="false" collapsible="false" layoutX="25.0" layoutY="26.0" prefHeight="171.0" prefWidth="297.0" text="Property">
        <content>
          <AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="155.0" prefWidth="295.0">
               <children>
                  <Label layoutX="14.0" layoutY="14.0" text="Property Name" />
                  <TextField fx:id="txtProperty" layoutX="110.0" layoutY="10.0" promptText="Property Name" />
                  <Label layoutX="14.0" layoutY="57.0" text="Property Type" />
                  <ComboBox fx:id="cmbPropertyType" layoutX="110.0" layoutY="53.0" prefWidth="150.0" promptText="Property Type" />
                  <Button layoutX="110.0" layoutY="101.0" mnemonicParsing="false" onAction="#addOnAction" text="Add" />
               </children>
            </AnchorPane>
        </content>
      </TitledPane>
      <TitledPane animated="false" collapsible="false" layoutX="334.0" layoutY="27.0" prefHeight="171.0" prefWidth="332.0" text="Field Information">
         <content>
            <AnchorPane>
               <children>
                  <AnchorPane prefHeight="179.0" prefWidth="306.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
                     <children>
                        <TableView fx:id="tvProperties" prefHeight="126.0" prefWidth="252.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="55.0" AnchorPane.topAnchor="0.0">
                          <columns>
                            <TableColumn fx:id="tcName" prefWidth="116.0" text="Name" />
                            <TableColumn fx:id="tcType" prefWidth="114.0" text="Type" />
                          </columns>
                        </TableView>
                        <Button layoutX="272.0" layoutY="2.0" mnemonicParsing="false" onAction="#btnRemoveOnAction" text="-" />
                     </children>
                  </AnchorPane>
               </children>
            </AnchorPane>
         </content>
      </TitledPane>
      <TextArea fx:id="txtCode" layoutX="25.0" layoutY="236.0" prefHeight="280.0" prefWidth="641.0" />
      <Button layoutX="226.0" layoutY="205.0" mnemonicParsing="false" onAction="#generateOnAction" text="Generate Code" />
      <Button layoutX="620.0" layoutY="205.0" mnemonicParsing="false" onAction="#btnClearOnAction" text="Clear" />
      <Label layoutX="25.0" layoutY="209.0" text="Class Name" />
      <TextField fx:id="txtClassName" layoutX="94.0" layoutY="205.0" prefHeight="25.0" prefWidth="124.0" promptText="Class Name" />
    </children>
</AnchorPane>

Questions

Does this follow Java/Java8/JavaFX Conventions ?

\$\endgroup\$
2
  • \$\begingroup\$ btw what's all that unpack-dependencies stuff in the pom, and is it really necessary? \$\endgroup\$
    – janos
    Sep 27, 2014 at 22:46
  • \$\begingroup\$ @janos as I said pom.xml is generated. Netbeans create it when a new Maven JavaFX project is created. \$\endgroup\$
    – JaDogg
    Sep 28, 2014 at 3:50

1 Answer 1

2
\$\begingroup\$

Duplicated string literals in FXMLController.initialize

You're duplicating the names used by the enums:

propertyTypeList.addAll("String", "Integer", "Boolean", "Double",
        "Float", "Long");

This is not good. As usual, if you make a change in one place, you have to remember to make the same change at the other too. It's fragile.

It would be better to iterate over the values of the enum, for example:

for (JavaDataType javaDataType : JavaDataType.values()) {
    propertyTypeList.add(javaDataType.toString());
}

Or if you fancy Java 8:

propertyTypeList.addAll(Arrays.asList(JavaDataType.values()).stream()
        .map(JavaDataType::toString)
        .collect(Collectors.toList()));

Duplicated string literals in JavaDataType.fromString

Similar to the earlier point, it's a pity to duplicate the string values of the enums in the case statements. Better build a static map from them and use it, without duplicating anything.

private static final Map<String, JavaDataType> STRING_TO_ENUM = new HashMap<>();

static {
    for (JavaDataType javaDataType : values()) {
        STRING_TO_ENUM.put(javaDataType.toString(), javaDataType);
    }
}

public static JavaDataType fromString(String type) {
    return STRING_TO_ENUM.get(type);
}

Hardcoded Java class names

Still about JavaDataType... Instead of hardcoding the class names of String, Double, ..., how about using the real class types instead?

STRING(String.class),
INTEGER(Integer.class),
BOOLEAN(Boolean.class),
DOUBLE(Double.class),
FLOAT(Float.class),
LONG(Long.class);

private final String value;

JavaDataType(Class<?> klass) {
    this.value = klass.getSimpleName();
}

Minor things

This javadoc comment is really pointless, especially on a private field:

/**
 * String value
 */
private final String value;

This is really hard to read:

String code = "import javafx.beans.property.*;\n" + "\n" + "/**\n"
            + " * Class Information\n" + " * @author Your Name\n" + " */\n"
            + "public class %s {\n" + "\n" + "%s\n" + "\n"
            + "    public %s() {\n" + "%s\n" + "    }\n" + "\n" + "%s\n"
            + "\n" + "}";

For one thing, why write a string segmented like this:

"import javafx.beans.property.*;\n" + "\n" + "/**\n"

why not as:

"import javafx.beans.property.*;\n\n/**\n"

And the whole expression would become a lot easier to read if you break a line after each actual \n in the string, like this:

String code = "import javafx.beans.property.*;\n\n"
        + "/**\n"
        + " * Class Information\n"
        + " * @author Your Name\n"
        + " */\n"
        + "public class %s {\n\n"
        + "%s\n\n"
        + "    public %s() {\n"
        + "%s\n"
        + "    }\n\n"
        + "%s\n\n"
        + "}";

You don't need to call .toString() on parameters when formatting a string:

return String.format(code, className, fields.toString(), className,
            initCodes.toString(), methods.toString());

That is:

return String.format(code, className, fields, className, initCodes, methods);

You could chain these together, which will make it slightly shorter:

methods.append(toGetterMethod(type, name));
methods.append("\n\n");
methods.append(toSetterMethod(type, name));
methods.append("\n\n");
methods.append(toPropertyMethod(type, name));
methods.append("\n\n");

like this:

methods.append(toGetterMethod(type, name))
.append("\n\n")
.append(toSetterMethod(type, name))
.append("\n\n")
.append(toPropertyMethod(type, name))
.append("\n\n");
\$\endgroup\$
0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.