Groovy to Java conversion - Pyosch/powertac-server GitHub Wiki

up

This is a place to develop and share a process for converting code from Groovy/Grails to Spring/Java.

Basic process

  • semicolons terminate each statement.
  • def replaced by type name.
  • classes and non-private methods are public.
  • virtually all instance variables are private.
  • BigDecimal types are double.
  • Integer and Double are int and double respectively.
  • Groovy allows strings to be delimited by double-quotes, single-quotes, or slashes. Java allows only double-quotes.
  • Groovy allows variable substitution in strings with the $var notation. Java does not.
  • Groovy does run-time type discrimination for method dispatch; Java does not. Consider using the Visitor pattern (See AccountingService.processTransaction() for an example).
  • Most domain types have a number of Hibernate-related fields, such as constraints, hasMany, mapping, etc. These can be simply removed.
  • ID values are long, and are filled in with IdGenerator.createId(). All domain types should have ID values to simplify state logging.
  • There are many places that need template specifications (such as List<Rate> rather than just List or []), and occasional places where casts must be added. Collection types need to be initialized in constructors in many cases.
  • Explicit constructors need to be written for most types, and Groovy constructor calls that use attribute: value notation need to be changed to match the constructors. For some types (most transaction types, for example), the constructor can take all attributes as arguments. Other types (Rate is a good example), need a simple constructor combined with a set of "builder-style" or "fluent" setters that return this rather than void. See common/test/../RateTests.java for a good example of how these work. Note that a method called setX() that does not return void is not recognized as a setter by most code-generation tools, including OR mapping tools. Therefore, we name these fluent setters withX(). See Issue #349 for details.
  • Groovy auto-generates getters and setters. These need to be explicit in Java. Eclipse will generate them for you if you ask. Some tests access private fields that should not have public setters, or even public getters. Consider using ReflectionTestUtils.setField(thing, "field", value) to set thing.field to value in these cases, rather than exposing features that should be private.
  • Direct field access code in Groovy needs to be replaced with calls to getters and setters.
  • Groovy closures need to be replaced with valid Java. In many cases this can be done with a simple for loop; in other cases, it will take some restructuring.
  • Groovy allows method calls without the parentheses around the arguments. This needs to be fixed. There are lots of examples in log.info calls.
  • We are using log4j explicitly instead of the Groovy logger; each class that uses a logger needs a declaration at the top like
    static private Logger log = Logger.getLogger(ClassName.class.getName());
  • @XStreamConverter types are ClassName.class, not just ClassName, and they are now in the common.xml package.

Documentation

Each class needs a copyright statementat the top, and a class header comment just above the class declaration as a minimum. We will be generating and publishing Javadoc -- please make sure it's readable and helpful.

Data queries

In Grails, most data queries are done by calls to (automatically generated) static methods on domain classes. Although it may have been possible to make something like this work in Java, we have elected to use explicit repositories, most of which are in common/domain/../repo. These repositories can be autowired. In some cases the method names are the same as what's in the Grails version, but in many cases that was not practical. Of course, there are a number of domain classes whose instances are not being stored at all, and for which there were indeed queries (mostly in test code) in the Grails version. Examples are message types like transactions and market positions.

State recording

The Grails implementation used "auditable" domain types to generate change records in the database. This worked to some extent, but it had the major disadvantage that the data was lost if the server crashed. The new scheme depends on generating log messages when the state of the server is changed. These messages are generated automatically, based on source code annotations. A class annotated with @Domain will have its constructor calls logged, and a method annotated with @StateChange will be logged when it returns.

Testing

  • We are using JUnit4, which uses an annotation scheme rather than the inheritance-based scheme of JUnit 3. See common/test/../BalancingTransactionTests for a fairly straightforward example.
  • XML serialization testing is moved to individual domain type tests, as we can see in BalancingTransactionTests.
  • The logger must be set up in the @BeforeClass method for most tests, otherwise log4j complains bitterly about not being initialized properly.
  • Some tests depend on Spring dependency injection, either for the test script itself, or because the unit under test has @Autowired dependencies. Good examples of this situation are common/test/../TariffTests and accounting/test/../AccountingServiceTests. To handle these cases three elements are needed:
    • You need to get Spring involved in running the test. You do this by annotating the test class with @RunWith(SpringJUnit4ClassRunner.class) as seen in accounting/test/../AccountingServiceTests.
    • You need to configure Spring, to tell it how to find the autowire resources, and to specify mock implementations of the ones that you want to mock, or that not in the current dependency tree. For example, the BrokerProxyService and CompetitionControlService are in powertac-server, which is at the top of the dependency tree. If you are testing a unit that depends on these services, you will need to mock them. You do that using mockito, and by recording the mocks in your test configuration file. Note that the mocks must appear in the file before any "normal" bean definitions, or they don't get created correctly.
    • You must let the Spring test-runner know where to find the config file wiht another annotation on the test class, for example @ContextConfiguration(locations = {"file :test/test-config.xml"}).
  • If you are using mockito mocks, there are two main things you can do with them.
    • One is to verify that a method got called with the correct arguments, and if you like you can capture the arguments with an ArgumentCaptor and do other stuff with them.
    • The other is to tell the mock what to do when a mocked method gets called. Again, there are examples of both uses in AccountingServiceTests.
⚠️ **GitHub.com Fallback** ⚠️