Groovy to Java conversion - Pyosch/powertac-server GitHub Wiki
This is a place to develop and share a process for converting code from Groovy/Grails to Spring/Java.
- semicolons terminate each statement.
-
def
replaced by type name. - classes and non-private methods are
public
. - virtually all instance variables are
private
. -
BigDecimal
types aredouble
. -
Integer
andDouble
areint
anddouble
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 justList
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 returnthis
rather than void. See common/test/../RateTests.java for a good example of how these work. Note that a method calledsetX()
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 setterswithX()
. 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 areClassName.class
, not justClassName
, and they are now in the common.xml package.
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.
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.
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.
- 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 arecommon/test/../TariffTests
andaccounting/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 inaccounting/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"})
.
- You need to get Spring involved in running the test. You do this by annotating the test class with
- 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
.
- One is to verify that a method got called with the correct arguments, and if you like you can capture the arguments with an