X Going further
108. Other Topics
This book can’t cover everything but hopefully it’s shown you a solid body of Groovy skills that will get you started. In this chapter I’ll very briefly touch on a few additional items that you may be interested in and give you some links to help you research further.
Type Checking
Groovy doesn’t check data types at compile time. Thinking of Groovy as a dynamic language helps you see why this may be the case - variables could be changing types as they move through the system and my code can use approaches such as duck typing to focus on behaviours rather than types. However, you may want to be specific about types and catch incorrect type allocations at compile time.
Consider the following code:
class Person {
String id
Integer getId() {id}
}
def pete = new Person(id: 12.3)
println pete.id
Groovy will compile this code with groovyc but when you try to run the code you’ll get a Groovy runtime exception (org.codehaus.groovy.runtime.typehandling.GroovyCastException) when we try to return 12.3 from getId - Groovy handles converting 12.3 to the id field as it’s a String but fails when trying to convert that String into an Integer. Naturally I could have written the Person class to be a lot more dynamic but you can also see that I’ve mucked around my types by declaring id as a String but return an Integer from its getter.
The @groovy.transform.TypeChecked annotation can be applied to the class to make sure types are checked at compilation time:
@groovy.transform.TypeChecked
class Person {
String id
Integer getId() {id}
}
def pete = new Person(id: 12.3)
println pete.id
Instead of a runtime exception I now get a compile-time error for trying to return an Integer from getId(). If I only want to have
type checking performed on a specific method, I can just annotate that method:
class Person {
String id
@groovy.transform.TypeChecked
Integer getId() {id}
}
def pete = new Person(id: 12.3)
println pete.id
For more information please refer to the Static type checking and Type checking extensions sections in the Groovy documentation.
Static Compilation
The @groovy.transform.CompileStatic annotation combines the functionality of @groovy.transform.TypeChecked with direct method invocation. Essentially, this removes the need for the Groovy runtime to be involved when using statically compiled classes and methods.
For more information please refer to the Static compilation section in the Groovy documentation. 10 things your static language can’t do, Compiling groovy code statically, and the Java Performance Tuning Guide are also good reads.
Metaprogramming
In the Shapes demo I touched very briefly on metaprogramming when I used the propertyMissing method to provide properties at runtime. That only glanced the surface of what’s possible and, by digging deeper you’ll discover how to:
- Use the
invokeMethodandmethodMissingmethods ofgroovy.lang.GroovyObjectto let your class handle and provide methods on-the-fly - Intercept method calls with
groovy.lang.GroovyInterceptable - Access another class’s
MetaClassto add methods
That last item lets you extend the functionality of existing classes - here’s a silly example:
Number.metaClass.addSeven << {
delegate + 7
}
Number n = 10
assert 27 == 20.addSeven()
For more information please refer to the Metaprogramming section in the Groovy documentation.
Generics
Generics allow classes, interfaces or methods to adapt to an instance-specified data type. You most often see generics used with collections such as Lists and Maps. The following snippet uses the diamond notation (<>) to indicate that the nums list should contain subtypes of Number:
List<Number> nums = [1, 2, 3, 4, 5]
However, Groovy isn’t overly respectful of generics and the following also works:
List<Number> nums = [1, 2, 3, 4, 'rabbit']
… so we can turn on type checking to catch my mistake:
@groovy.transform.TypeChecked
class NumberLister {
List<Number> nums = [1, 2, 3, 4, 'rabbit']
}
The Java Tutorial features a section on Generics and there’s a Generics in Java article in Wikipedia.
Inner Classes
Inner classes are classes that are declared within another class. Often used to improve encapsulation, you can
sometimes cause healthy debate when you ask “should I use an inner classes or a closure?”. In the example below I’ve decided that the Address inner class would be a useful way to handle the address field:
import groovy.transform.ToString
@ToString
class Person {
String name
Address address
@ToString
class Address {
String street
String suburb
String country
String prepareMailingSticker() {
"$name\n$street\n$suburb\n$country\n"
}
}
Person(name, street, suburb, country) {
this.name = name
this.address = new Address(street: street, suburb: suburb, country: coun\
try)
}
String getMailingSticker() {address.prepareMailingSticker()}
}
Person phil = new Person('Phil', '12 Smith St', 'Kimba', 'Australia')
println phil
print phil.mailingSticker
The Groovy documentation covers inner classes.
Single abstract methods
A number of classes related to responding to events implement an interface with a Single Abstract Method (SAM). Such interfaces have one method signature defined and this is usually focused on handling an event raised by an invoking class. A common example is a class such as a Button that handles user events such as a mouse click - the Button doesn’t necessarily know what you need it to do and it concerns itself more with presentation in the user interface.
Traditionally, Java developers would use what’s called an anonymous class. These are just written to handle the event but, as a class, aren’t useful as a more generic member of the codebase. This book hasn’t delved into them but Groovy supports anonymous classes and the example below will give you an indication of what one looks like:
interface Command {
public void handle(String eventTitle)
}
class Invoker {
private final commands = [ ]
void addReceiver(Command command) {
this.commands << command
}
private void event(String title) {
commands.each { cmd ->
cmd.handle(title)
}
}
void onClickEvent() {
event('Clicked')
}
void onDoubleClickEvent() {
event('Double Clicked')
}
}
def window = new Invoker()
window.addReceiver(new Command() {
public void handle(String eventTitle) {
println "I just received a '$eventTitle' event"
}
})
window.onClickEvent()
window.onDoubleClickEvent()
In the example above you’ll see that the window.addReceiver method is passed an interesting piece of syntax in new Command() {...}. An anonymous class is declared with the new keyword being invoked on an existing interface or class that the anonymous class will extend and then the body of the class is provided. For SAM interfaces this is usually what you can see in the example - a single-method anonymous class. As soon as you get an even moderately functional user interface you’ll start to see anonymous classes everywhere.
Luckily, Groovy allows you to use a closure instead of an anonymous class for SAM interfaces and this helps unclutter the code:
interface Command {
public void handle(String eventTitle)
}
class Invoker {
private final commands = [ ]
void addReceiver(Command command) {
this.commands << command
}
private void event(String title) {
commands.each { cmd ->
cmd.handle(title)
}
}
void onClickEvent() {
event('Clicked')
}
void onDoubleClickEvent() {
event('Double Clicked')
}
}
def window = new Invoker()
window.addReceiver { println "I just received a '$it' event" }
window.onClickEvent()
window.onDoubleClickEvent()
In the code above, Groovy transparently coerces the closure to the correct interface type. Prior to Groovy 2.2 you needed to cast the closure to the interface through use of the as keyword.
window.addReceiver { println "I just received a '$it' event" } as Command
The Groovy documentation has a section on SAMs and Wikipedia describes the Command Pattern on which this model of interaction is based.
Observable Maps
The ObservableMap, ObservableList and ObservableSet classes, located in the groovy.util package, can alert implementations of the java.beans.PropertyChangeListener interface when a member of the collection has changed. As PropertyChangeListener is a SAM interface, we can use closures:
PropertyChangeListenerdef myInfo = [ name: 'Jane',
pet : 'Mittens' ] as ObservableMap
myInfo.addPropertyChangeListener { evt ->
println "$evt.propertyName was changed: from $evt.oldValue to $evt.newValue"
}
myInfo.pet = 'Fido'
This models the Observer pattern.
Threads
The Thread class is used to create new execution threads in an application, allowing program tasks to work concurrently. Starting a thread requires passing the start method an implementation of java.lang.Runnable which, you guessed it, is a SAM interface:
Runnableprintln 'Start'
new Thread().start {
println 'This is a new thread'
}
println 'End'
The example above is somewhat Java-centric and Groovy’s addition of a static start(Closure closure) method to the Thread class avoids the need to call new Thread():
Thread.start {
println 'This is a new thread'
}
109. The great beyond
That covers most (not all) of the Groovy syntax. My goal was to introduce you to the “core” syntax of Groovy so that you can start programming with a good working knowledge in the language. From this point I hope you’ll find this book and the Groovy documentation to be essential companions in your Groovy programming efforts.
There are further Groovy features you may like to use in your projects:
- Develop a domain specific language
- Try your hand at metaprogramming
- Utilise various Groovy modules:
As I mentioned at the very beginning of this book, Groovy in Action (2nd Edition) is also a great reference for those wanting to go further.
Build large applications
Gradle is a build automation tool that should be your go-to when building non-trivial programs. In fact, I would suggest that checking out Gradle is a great next-step after reading this book.
For those coming from the Java world, Gradle is an excellent replacement for Apache Maven and Apache Ant.
Use the Groovy ecosystem
There are several high-quality projects that use Groovy, it’s worth checking them out:
-
Grails - a full-stack web application framework for the Java Virtual Machine
- That means it’s a great tool-set for building web applications
- Griffon - a desktop application toolkit
- Spock - a testing framework
- CodeNarc - a code analysis tool for Groovy
Whilst it’s not written in Groovy, the Spring Boot project is worth a look as you can use Groovy to quickly write some nifty applications using the Spring framework.
Appendix: The Shapes demo code listing
This chapter consists purely of the code listing for the Shapes demo. Please check out the Shapes demo mini site as it provides the source code and a range of other reports and information.
package org.groovy_tutorial.shapes
TwoDimensionalShape
package org.groovy_tutorial.shapes
/**
* An interface for basic two-dimensional objects
*
* @see <a href="https://en.wikipedia.org/wiki/List_of_two-dimensional_geometric\
_shapes">
* Wikipedia: List of two-dimensional geometric shapes</a>
*
* @author Duncan Dickinson
*/
interface TwoDimensionalShape {
/**
* The length of the path surrounding a 2D shape
* @see <a href="https://en.wikipedia.org/wiki/Perimeter">Wikipedia: Perimet\
er</a>
* @return the perimeter of the shape
*/
BigDecimal getPerimeter()
/**
* The extent of a 2D shape in a plane
* @see <a href="https://en.wikipedia.org/wiki/Area">Wikipedia: Area</a>
* @return the area of the shape
*/
BigDecimal getArea()
/**
* A handy display string
* @return a text representation of the shape
*/
String getDisplayInfo()
/**
* @return the name of the shape
*/
String getShapeName()
}
ShapeUtil
package org.groovy_tutorial.shapes
/**
* A general utility class
*
* @author Duncan Dickinson
*/
class ShapeUtil {
/**
* The shapes library supports sides of length > 0. This method helps check \
this.
* @param sides a series of parameters, each reflecting a side's length
* @return true if all sides are valid, false otherwise
*/
static boolean checkSides(Number... sides) {
for (side in sides) {
if (side <= 0) {
return false
}
}
true
}
/**
* Helper method - throws an exception if checkSides returns false
* @param sides a series of parameters, each reflecting a side's length
* @throws IllegalArgumentException if one of the sides <= 0
*/
static void checkSidesException(Number... sides) throws IllegalArgumentExcep\
tion {
if (!checkSides(sides)) {
throw new IllegalArgumentException('The side must be a positive numb\
er.')
}
}
}
Sides
package org.groovy_tutorial.shapes
/**
* A basic trait describing the outer edges (sides) of a 2D shape.
*
* This trait uses the missingProperty method to allow implementations
* to define sides using lower case characters (e.g. a, b, c). These properties \
must:
* <ul>
* <li>Be a single, lower-case character (matching Sides.SIDE_NAME_PATTERN)<\
/li>
* <li>Be only assigned a positive numeric value</li>
* </ul>
*
* Once getPerimeter is called, the sides map is locked down and can't be modifi\
ed.
*
* @author Duncan Dickinson
*/
trait Sides {
/** Defines the acceptable naming strategy for sides */
static final SIDE_NAME_PATTERN = /[a-z]/
/** Used to hold the named list of sides */
private final Map sideMap = [ : ]
/** The perimeter, as determined by the sum of the sides */
private BigDecimal perimeter = null
/**
* Calculates the perimeter of the shape (once).
* After calling this method, the sides are locked down and you can't add or\
edit them
* via propertyMissing
* @return the sum of the sideMap (the perimeter)
*/
BigDecimal getPerimeter() {
perimeter = perimeter ?:sideMap.values().sum().toBigDecimal()
}
/**
* @return a CLONE of the sideMap
*/
Map getSideMap() {
sideMap.clone() as Map
}
/**
* Gets the value for a named side
* @param name the name of the side (e.g. a, b, c)
* @return the value of the side
* @throws MissingPropertyException if name not found
*/
def propertyMissing(String name) throws MissingPropertyException {
if (name.matches(SIDE_NAME_PATTERN)) {
return sideMap.get(name)
}
throw new MissingPropertyException("Property $name not found")
}
/**
* Sets the length (value) for a named side
* @param name the name of the side
* @param value the length of the side
* @return the value back to the caller
* @throws ReadOnlyPropertyException if the perimeter has been calculated
* @throws IllegalArgumentException if the value <= 0
* @throws MissingPropertyException if name doesn't match SIDE_NAME_PATTERN
*/
def propertyMissing(String name, value)
throws ReadOnlyPropertyException, IllegalArgumentException, MissingP\
ropertyException {
if (name.matches(SIDE_NAME_PATTERN)) {
if (perimeter) {
throw new ReadOnlyPropertyException(name, Sides)
}
if (value in Number) {
ShapeUtil.checkSidesException(value)
sideMap.put(name, value as Number)
return sideMap.get(name)
}
throw new IllegalArgumentException("The value [$value] is not a posi\
tive number.")
}
throw new MissingPropertyException("Property $name not found")
}
}
Circle
package org.groovy_tutorial.shapes
import static java.lang.Math.PI
import groovy.transform.EqualsAndHashCode
import groovy.transform.ToString
/**
* Describes a circle
* @author Duncan Dickinson
*/
@EqualsAndHashCode(includes = 'radius')
@ToString(includeNames = true, includeFields = true, includePackage = true)
final class Circle implements TwoDimensionalShape {
private static final String SHAPE_NAME = 'Circle'
/** The radius of the circle */
final BigDecimal radius
/** The circle's perimeter (circumference) */
final BigDecimal perimeter
/** The circle's area */
final BigDecimal area
/**
*
* @param radius the radius of the circle (must be a positive number)
* @throws IllegalArgumentException if radius <= 0
*/
Circle(BigDecimal radius) throws IllegalArgumentException {
ShapeUtil.checkSidesException(radius)
this.radius = radius
this.perimeter = calculatePerimeter(radius)
this.area = calculateArea(radius)
}
/**
* Helper function - defers to calculatePerimeter
* @see #calculatePerimeter(Number)
* @param radius
* @return the circumference (perimeter)
* @throws IllegalArgumentException if radius <= 0
*/
static BigDecimal calculateCircumference(Number radius) throws IllegalArgume\
ntException {
calculatePerimeter(radius)
}
/**
* Calculates the perimeter of a circle using the formula: p = 2*Pi*r
* @param radius
* @return the perimeter
* @throws IllegalArgumentException if radius <= 0
*/
static BigDecimal calculatePerimeter(Number radius) throws IllegalArgumentEx\
ception {
ShapeUtil.checkSidesException(radius)
(2 * PI * radius) as BigDecimal
}
/**
* Calculates the area of a circle using the formula: a = Pi*r^2
* @param radius
* @return the area
* @throws IllegalArgumentException if radius <= 0
*/
static BigDecimal calculateArea(Number radius) throws IllegalArgumentExcepti\
on {
ShapeUtil.checkSidesException(radius)
(PI * radius**2) as BigDecimal
}
/**
* Calculates the circle's diameter using the formula: d = 2r
* @param radius
* @return the diameter
* @throws IllegalArgumentException if radius <= 0
*/
static BigDecimal calculateDiameter(Number radius) throws IllegalArgumentExc\
eption {
ShapeUtil.checkSidesException(radius)
(radius * 2) as BigDecimal
}
@Override
String getDisplayInfo() {
"$SHAPE_NAME: radius = $radius; diameter = $diameter; \
circumference = ${circumference}; area = ${area}"
}
/**
* Just a convenience - equivalent to getPerimeter
* @return the circumference
*/
BigDecimal getCircumference() {
perimeter
}
/**
* A pseudo getter
* @return the diameter
*/
Number getDiameter() {
calculateDiameter(this.radius)
}
@Override
String getShapeName() {
SHAPE_NAME
}
}
Rectangle
package org.groovy_tutorial.shapes
import groovy.transform.EqualsAndHashCode
import groovy.transform.ToString
/**
* Describes a rectangle
*
* @author Duncan Dickinson
*/
@EqualsAndHashCode(includes = 'length,width')
@ToString(includeNames = true, includeFields = true, includePackage = true)
class Rectangle implements TwoDimensionalShape, Sides {
private static final String SHAPE_NAME = 'Rectangle'
/** The area of the rectangle */
final BigDecimal area
/**
*
* @param length
* @param width
* @throws IllegalArgumentException if one of the sides <= 0
*/
Rectangle(Number length, Number width) throws IllegalArgumentException {
a = length
b = width
c = length
d = width
//Calling this causes the Sides trait to calculate the perimeter
//and lock off its sideMap
this.perimeter
this.area = length * width
}
@Override
String getDisplayInfo() {
"$SHAPE_NAME: length = $a; width = $b; perimeter = $perimeter; area = $a\
rea"
}
@Override
String getShapeName() {
SHAPE_NAME
}
}
Square
package org.groovy_tutorial.shapes
import groovy.transform.EqualsAndHashCode
import groovy.transform.ToString
/**
* Describes a square
*
* @author Duncan Dickinson
*/
@EqualsAndHashCode(callSuper = true)
@ToString(includeNames = true, includeFields = true, includePackage = true, incl\
udeSuper = true)
final class Square extends Rectangle {
private static final String SHAPE_NAME = 'Square'
/**
* @param length
* @throws IllegalArgumentException if one of the sides <= 0
*/
Square(Number length) throws IllegalArgumentException {
super(length, length)
}
@Override
String getDisplayInfo() {
"$SHAPE_NAME: length = ${a}; perimeter = $perimeter; area = $area"
}
}
Triangle
package org.groovy_tutorial.shapes
import groovy.transform.EqualsAndHashCode
import groovy.transform.ToString
import groovy.util.logging.Log
/**
* Describes a generic triangle.
*
* For more specific types, use a class from {@link org.groovy_tutorial.shapes.t\
riangle}
*
* @author Duncan Dickinson
*/
@Log
@EqualsAndHashCode(includes = 'sideMap')
@ToString(includeNames = true, includeFields = true, includePackage = true)
class Triangle implements TwoDimensionalShape, Sides {
static final String SHAPE_NAME = 'Triangle'
final BigDecimal area
/**
* Configures the sides (a, b, c) of the triangle and calls the perimeter pr\
operty
* of the Sides trait in order to make the sides (mostly) immutable and lock\
in
* the perimeter calculation
*
* The protected calculateArea method is called to determine the area of the
* triangle. The result is assigned to the <code>area</code> field.
*
* @param a One of the triangle's three sides
* @param b One of the triangle's three sides
* @param c One of the triangle's three sides
* @throws IllegalArgumentException if one of the sides <= 0
*/
Triangle(Number a, Number b, Number c) throws IllegalArgumentException {
this.a = a
this.b = b
this.c = c
//Calling this causes the Sides trait to calculate the perimeter
//and lock off its sideMap
this.perimeter
this.area = calculateArea()
}
/**
* Determines the area of this triangle.
* In this implementation we call the static calculateArea(a, b, c) method
*
* Subclasses can override this method if they feel they're able to provide a
* leaner calculation.
*
* @return the area
*/
protected BigDecimal calculateArea() {
calculateArea(a, b, c)
}
/**
* Uses Heron's formula to determine the area of the Triangle
*
* @see <a href="https://en.wikipedia.org/wiki/Heron%27s_formula">Wikipedia \
- Heron's Formula</a>
* @throws IllegalArgumentException if a, b or c are <= 0
*/
static final BigDecimal calculateArea(Number a, Number b, Number c) throws I\
llegalArgumentException {
log.info "Triangle.calculateArea was called with a=$a, b=$b, c=$c"
ShapeUtil.checkSidesException(a, b, c)
Number s = (a + b + c) / 2
Math.sqrt(s * (s - a) * (s - b) * (s - c))
}
@Override
String getDisplayInfo() {
"$SHAPE_NAME: Side A = $a; Side B = $b; \
Side C = $c; perimeter = $perimeter; area = $area"
}
@Override
String getShapeName() {
SHAPE_NAME
}
}
package org.groovy_tutorial.shapes.triangle
TriangleSubtype
package org.groovy_tutorial.shapes.triangle
import groovy.transform.SelfType
import org.groovy_tutorial.shapes.Triangle
/**
* A basic example of a targeted trait
*
* @author Duncan Dickinson
*/
@SelfType(Triangle)
trait TriangleSubtype {
/**
* Expects that implementations provide a value for the TRIANGLE_TYPE String
* @return a String representing the type of triangle
*/
String getTriangleType() {
TRIANGLE_TYPE
}
}
TriangleRightAngled
package org.groovy_tutorial.shapes.triangle
import groovy.transform.EqualsAndHashCode
import groovy.transform.ToString
import groovy.util.logging.Log
import org.groovy_tutorial.shapes.ShapeUtil
import org.groovy_tutorial.shapes.Triangle
/**
* A triangle made famous by Pythagoras
*
* @author Duncan Dickinson
*/
@Log
@EqualsAndHashCode(callSuper = true)
@ToString(includeNames = true, includeFields = true, includePackage = true, incl\
udeSuper = true)
final class TriangleRightAngled extends Triangle implements TriangleSubtype {
static final String TRIANGLE_TYPE = 'Right-angled'
/**
* Tell us the "other sides" and the hypotenuse will be determined for you!
* @param a one of the "other sides"
* @param b one of the "other sides"
* @throws IllegalArgumentException if one of the sides <= 0
*/
TriangleRightAngled(Number a, Number b) throws IllegalArgumentException {
super(a, b, calculateHypotenuse(a, b))
}
/**
* Determines the area of this triangle.
* In this implementation we call the static calculateArea(a, b) method
* @return the area
*/
@Override
protected BigDecimal calculateArea() {
calculateArea(a, b)
}
/**
* Determine a right-angled triangle's hypotenuse using Pythagoras' theorem
* @param a
* @param b
* @return the hypotenuse
* @throws IllegalArgumentException if a or b <= 0
*/
static Number calculateHypotenuse(Number a, Number b) throws IllegalArgument\
Exception {
ShapeUtil.checkSidesException(a, b)
Math.sqrt(a**2 + b**2)
}
/**
* Uses 0.5 * a * b
* @param a
* @param b
* @return the area
* @throws IllegalArgumentException if a or b <= 0
*/
static Number calculateArea(Number a, Number b) throws IllegalArgumentExcept\
ion {
log.info "TriangleRightAngled.calculateArea was called with a=$a, b=$b"
ShapeUtil.checkSidesException(a, b)
0.5 * a * b
}
@Override
String getDisplayInfo() {
"$TRIANGLE_TYPE ${super.displayInfo}"
}
}
TriangleIsosceles
package org.groovy_tutorial.shapes.triangle
import groovy.transform.EqualsAndHashCode
import groovy.transform.ToString
import groovy.util.logging.Log
import org.groovy_tutorial.shapes.ShapeUtil
import org.groovy_tutorial.shapes.Triangle
/**
* A triangle with two sides of equal length
*
* @see <a href="https://en.wikipedia.org/wiki/Isosceles_triangle">Wikipedia - I\
sosceles triangle</a>
*
* @author Duncan Dickinson
*/
@Log
@EqualsAndHashCode(callSuper = true)
@ToString(includeNames = true, includeFields = true, includePackage = true, incl\
udeSuper = true)
class TriangleIsosceles extends Triangle implements TriangleSubtype {
protected static String TRIANGLE_TYPE = 'Isosceles'
/**
* Create a triangle that has one base side and two equal sides (legs)
* @param a the base
* @param b the leg(s)
* @throws IllegalArgumentException if one of the sides <= 0
*/
TriangleIsosceles(Number base, Number leg) throws IllegalArgumentException {
super(base, leg, leg)
}
/**
* Determines the area of this triangle.
* In this implementation we call the static calculateArea(a, b) method
* @return
*/
@Override
protected BigDecimal calculateArea() {
calculateArea(a, b)
}
/**
* Calculates the area of an isosceles triangle using a simplified version o\
f Heron's formula
* @param base the base
* @param leg the leg(s)
* @return
* @see <a href="https://en.wikipedia.org/wiki/Isosceles_triangle#Area">Wiki\
pedia article</a>
* @throws IllegalArgumentException if base or leg <= 0
*/
static final BigDecimal calculateArea(Number base, Number leg) throws Illega\
lArgumentException {
log.info "TriangleIsosceles.calculateArea was called with base=$base, b=\
$leg"
ShapeUtil.checkSidesException(base, leg)
def height = Math.sqrt(leg**2 - (base**2 / 4))
base * (height / 2)
}
@Override
String getDisplayInfo() {
"$TRIANGLE_TYPE ${super.displayInfo}"
}
}
TriangleEquilateral
package org.groovy_tutorial.shapes.triangle
import groovy.transform.EqualsAndHashCode
import groovy.transform.ToString
import groovy.util.logging.Log
import org.groovy_tutorial.shapes.ShapeUtil
/**
* A triangle with three equal sides
*
* @see <a href="https://en.wikipedia.org/wiki/Equilateral_triangle">Wikipedia -\
Equilateral triangle</a>
*
* @author Duncan Dickinson
*/
@Log
@EqualsAndHashCode(callSuper = true)
@ToString(includeNames = true, includeFields = true, includePackage = true, incl\
udeSuper = true)
final class TriangleEquilateral extends TriangleIsosceles {
static final String TRIANGLE_TYPE = 'Equilateral'
/**
*
* @param a
* @throws IllegalArgumentException if a <= 0
*/
TriangleEquilateral(Number a) throws IllegalArgumentException {
super(a, a)
}
/**
* Determines the area of this triangle.
* In this implementation we call the static calculateArea(a) method
* @return the area
*/
@Override
protected BigDecimal calculateArea() {
calculateArea(a)
}
/**
* Calculates the area of an equilateral triangle
* @param a the edge (side) length
* @return the area
* @see <a href="https://en.wikipedia.org/wiki/Equilateral_triangle#Derivati\
on_of_area_formula">Wikipedia article</a>
* @throws IllegalArgumentException if a <= 0
*/
static BigDecimal calculateArea(Number a) throws IllegalArgumentException {
log.info "TriangleEquilateral.calculateArea was called with a=$a"
ShapeUtil.checkSidesException(a)
(Math.sqrt(3) / 4) * a**2
}
@Override
String getDisplayInfo() {
"$TRIANGLE_TYPE ${super.displayInfo}"
}
}
package org.groovy_tutorial.shapes.triangle
This package provides the basic command-line script (Main.groovy) for demonstrating the Shapes demo.
Main
package org.groovy_tutorial.shapes.app
import org.groovy_tutorial.shapes.Circle
import org.groovy_tutorial.shapes.Rectangle
import org.groovy_tutorial.shapes.Square
import org.groovy_tutorial.shapes.Triangle
import org.groovy_tutorial.shapes.triangle.TriangleEquilateral
import org.groovy_tutorial.shapes.triangle.TriangleIsosceles
import org.groovy_tutorial.shapes.triangle.TriangleRightAngled
def shapes = [
new Rectangle(10, 2),
new Square(4),
new Circle(8),
new Triangle(5, 8, 10),
new TriangleRightAngled(3, 4),
new TriangleIsosceles(2, 8),
new TriangleEquilateral(6) ]
println """
${'=' * 80}
Welcome to the Shapes demo
${'=' * 80}
"""
shapes.each {
println "${it.displayInfo}\n"
//println " - toString(): ${it.toString()}"
}
Colophon
I wish I had a 19th century engraving on the cover so that I could tell you about a really cool animal. If I did I would use the Pied butcherbird, perhaps accompanied with a picture from one of John Gould’s books. I would then tell you that this small-ish bird has a beautiful song and a really friendly composure. My resident (wild) butcherbirds like to sing on my deck when it’s raining, follow me when I mow and, occasionally, bathe under the sprinkler on hot days.