import java.awt.Rectangle; | import java.awt.Rectangle; | ||||
aspect Answer4a { | aspect Answer4a { | ||||
private Rectangle wholeCanvas = | |||||
new Rectangle(FigureElement.MIN_VALUE, FigureElement.MIN_VALUE, | |||||
FigureElement.MAX_VALUE - FigureElement.MIN_VALUE, | |||||
FigureElement.MAX_VALUE - FigureElement.MIN_VALUE); | |||||
Rectangle around(): execution(Rectangle Group.getBounds()) { | Rectangle around(): execution(Rectangle Group.getBounds()) { | ||||
return wholeCanvas; | |||||
return FigureElement.MAX_BOUNDS; | |||||
} | } | ||||
} | } |
private Rectangle Group.cache = null; | private Rectangle Group.cache = null; | ||||
Rectangle around(Group g): | Rectangle around(Group g): | ||||
execution(Rectangle Group.getBounds()) && this(g) { | |||||
execution(Rectangle Group.getBounds()) && this(g) | |||||
{ | |||||
if (g.cache == null) { | if (g.cache == null) { | ||||
g.cache = proceed(g); | g.cache = proceed(g); | ||||
} | } |
private Rectangle Group.cache = null; | private Rectangle Group.cache = null; | ||||
Rectangle around(Group g): | Rectangle around(Group g): | ||||
execution(Rectangle Group.getBounds()) && this(g) { | |||||
execution(Rectangle Group.getBounds()) && this(g) | |||||
{ | |||||
if (g.cache == null) { | if (g.cache == null) { | ||||
g.cache = proceed(g); | g.cache = proceed(g); | ||||
} | } |
private Group Point.enclosingGroup = null; | private Group Point.enclosingGroup = null; | ||||
before(Point p, Group g): | before(Point p, Group g): | ||||
execution(void add(FigureElement)) && args(p) && target(g) { | |||||
execution(void add(FigureElement)) && args(p) && target(g) | |||||
{ | |||||
p.enclosingGroup = g; | p.enclosingGroup = g; | ||||
} | } | ||||
Rectangle around(Group g): | Rectangle around(Group g): | ||||
execution(Rectangle Group.getBounds()) && this(g) { | |||||
execution(Rectangle Group.getBounds()) && this(g) | |||||
{ | |||||
if (g.cache == null) { | if (g.cache == null) { | ||||
g.cache = proceed(g); | g.cache = proceed(g); | ||||
} | } |
private Group FigureElement.enclosingGroup = null; | private Group FigureElement.enclosingGroup = null; | ||||
before(FigureElement p, Group g): | before(FigureElement p, Group g): | ||||
execution(void add(FigureElement)) && args(p) && target(g) { | |||||
execution(void add(FigureElement)) && args(p) && target(g) | |||||
{ | |||||
p.enclosingGroup = g; | p.enclosingGroup = g; | ||||
} | } | ||||
Rectangle around(Group g): | Rectangle around(Group g): | ||||
execution(Rectangle Group.getBounds()) && this(g) { | |||||
execution(Rectangle Group.getBounds()) && this(g) | |||||
{ | |||||
if (g.cache == null) { | if (g.cache == null) { | ||||
g.cache = proceed(g); | g.cache = proceed(g); | ||||
} | } |
import java.awt.geom.*; | import java.awt.geom.*; | ||||
public interface FigureElement { | public interface FigureElement { | ||||
public static final int MIN_VALUE = 0; | |||||
public static final int MAX_VALUE = 500; | |||||
public static final Rectangle MAX_BOUNDS = | |||||
new Rectangle(0, 0, 500, 500); | |||||
public abstract void move(int dx, int dy); | public abstract void move(int dx, int dy); | ||||
<h3>a. Make a constant override</h3> | <h3>a. Make a constant override</h3> | ||||
<p> <strong>Problem:</strong> Pass <code>tests.Test4a</code>.</p> | |||||
<p> <strong>Tools:</strong> <code>around</code>, | |||||
<code>FigureElement.MAX_BOUNDS</code> | |||||
</p> | |||||
<p> <code>Group</code>'s <code>getBounds()</code> method could be | <p> <code>Group</code>'s <code>getBounds()</code> method could be | ||||
understood to be a conservative approximation of the bounding box of a | understood to be a conservative approximation of the bounding box of a | ||||
group. If that is true, then it would be a legal (and much faster) | group. If that is true, then it would be a legal (and much faster) | ||||
implementation of <code>getBounds()</code> to simply always return a | implementation of <code>getBounds()</code> to simply always return a | ||||
rectangle consisting of the entire canvas, that is | |||||
rectangle consisting of the entire canvas. The entire canvas is returned | |||||
by the static method <code>FigureElement.MAX_BOUNDS</code>. | |||||
</p> | </p> | ||||
<blockquote><PRE> | |||||
new Rectangle(FigureElement.MIN_VALUE, FigureElement.MIN_VALUE, | |||||
FigureElement.MAX_VALUE - FigureElement.MIN_VALUE, | |||||
FigureElement.MAX_VALUE - FigureElement.MIN_VALUE) | |||||
</PRE></blockquote> | |||||
<p> Write an aspect to implement this change. You can override | <p> Write an aspect to implement this change. You can override | ||||
<code>Group</code>'s <code>getBounds()</code> method entirely with | <code>Group</code>'s <code>getBounds()</code> method entirely with | ||||
around advice intercepting the method. | around advice intercepting the method. | ||||
</p> | </p> | ||||
<p> Your code should pass the JUnit test case | |||||
<code>tests.Test4a</code> with this change. | |||||
</p> | |||||
<h3>b. Make a constant cache</h3> | |||||
<p> <strong>Answer: </strong> | |||||
<p> <strong>Problem:</strong> Pass <code>tests.Test4b</code>. | |||||
</p> | </p> | ||||
<blockquote><PRE> | |||||
package answers; | |||||
import figures.*; | |||||
import java.awt.Rectangle; | |||||
aspect Answer4a { | |||||
private Rectangle wholeCanvas = | |||||
new Rectangle(FigureElement.MIN_VALUE, FigureElement.MIN_VALUE, | |||||
FigureElement.MAX_VALUE - FigureElement.MIN_VALUE, | |||||
FigureElement.MAX_VALUE - FigureElement.MIN_VALUE); | |||||
Rectangle around(): execution(Rectangle Group.getBounds()) { | |||||
return wholeCanvas; | |||||
} | |||||
} | |||||
</PRE></blockquote> | |||||
<h3>b. Make a constant cache</h3> | |||||
<p> <strong>Tools:</strong> <code>private Rectangle Group.mumble;</code> | |||||
</p> | |||||
<p> Instead of making the (very) conservative approximation of | <p> Instead of making the (very) conservative approximation of | ||||
<code>getBounds()</code> from part (a), write an aspect instead that | <code>getBounds()</code> from part (a), write an aspect instead that | ||||
<p> <em>Hint: You can use an inter-type declaration to keep some | <p> <em>Hint: You can use an inter-type declaration to keep some | ||||
state for every <code>Group</code> object.</em> </p> | state for every <code>Group</code> object.</em> </p> | ||||
<p> Your code should pass the JUnit test case | |||||
<code>tests.Test4b</code> with this change. | |||||
</p> | |||||
<h3>c. Invalidate, part 1</h3> | <h3>c. Invalidate, part 1</h3> | ||||
<p> <strong>Problem:</strong> Pass <code>tests.Test4c</code>. | |||||
</p> | |||||
<p> <strong>Tools:</strong> <code>before</code> | |||||
</p> | |||||
<p> While caching in this way does save computation, it will lead to | <p> While caching in this way does save computation, it will lead to | ||||
incorrect bounding boxes if a <code>Group</code> is ever moved. | incorrect bounding boxes if a <code>Group</code> is ever moved. | ||||
Change your aspect so that it invalidates the cache whenever the | Change your aspect so that it invalidates the cache whenever the | ||||
<code>move()</code> method of <code>Group</code> is called. | <code>move()</code> method of <code>Group</code> is called. | ||||
</p> | </p> | ||||
<p> Your code should pass the JUnit test case | |||||
<code>tests.Test4c</code> with this change. | |||||
</p> | |||||
<h3>d. Invalidate, part 2</h3> | <h3>d. Invalidate, part 2</h3> | ||||
<p> <strong>Problem:</strong> Pass <code>tests.Test4d</code>.</p> | |||||
<p> <strong>Tools:</strong> <code>your solution to 3c</code></p> | |||||
<p> Of course, part (c) didn't really solve the problem. What if a | <p> Of course, part (c) didn't really solve the problem. What if a | ||||
<code>Point</code> that is part of a <code>Group</code> moves? | <code>Point</code> that is part of a <code>Group</code> moves? | ||||
Whenever either of a Point's fields are set it should invalidate the | Whenever either of a Point's fields are set it should invalidate the | ||||
slightly different than the problem in 3c: Here you care about fields, | slightly different than the problem in 3c: Here you care about fields, | ||||
where there you cared about method calls. </p> | where there you cared about method calls. </p> | ||||
<p> Your code should pass the JUnit test case | |||||
<code>tests.Test4d</code> with this change. | |||||
</p> | |||||
<h3>e. Invalidate, part 3</h3> | <h3>e. Invalidate, part 3</h3> | ||||
<p> <strong>Problem:</strong> Pass <code>tests.Test4e</code>.</p> | |||||
<p> <strong>Tools:</strong> <em>You're on you're own</em></p> | |||||
<p> Did you really do part (d) correctly? Run the JUnit test | <p> Did you really do part (d) correctly? Run the JUnit test | ||||
<code>tests.Test4e</code> to see. If you pass, congratulations, now | <code>tests.Test4e</code> to see. If you pass, congratulations, now | ||||
go help other people. Otherwise, you have fallen prey to our cruel | go help other people. Otherwise, you have fallen prey to our cruel |
public class Test extends TestCase { | public class Test extends TestCase { | ||||
public Test(String name) { super(name); } | public Test(String name) { super(name); } | ||||
public Test() {} | |||||
public static void main(String[] args) { | public static void main(String[] args) { | ||||
junit.textui.TestRunner.run(Test.class); | junit.textui.TestRunner.run(Test.class); |
import junit.framework.*; | import junit.framework.*; | ||||
public class Test4a extends Test { | public class Test4a extends Test { | ||||
Rectangle wholeCanvas = | |||||
new Rectangle(FigureElement.MIN_VALUE, FigureElement.MIN_VALUE, | |||||
FigureElement.MAX_VALUE, FigureElement.MAX_VALUE); | |||||
public Test4a(String name) { super(name); } | |||||
public static void main(String[] args) { | public static void main(String[] args) { | ||||
junit.textui.TestRunner.run(Test4a.class); | junit.textui.TestRunner.run(Test4a.class); | ||||
} | } | ||||
public void setUp() { | |||||
super.setUp(); | |||||
} | |||||
public void testGroupBounds() { | public void testGroupBounds() { | ||||
assertEquals(g.getBounds(), wholeCanvas); | |||||
assertEquals(g.getBounds(), FigureElement.MAX_BOUNDS); | |||||
} | } | ||||
} | } |
import junit.framework.*; | import junit.framework.*; | ||||
public class Test4b extends Test { | public class Test4b extends Test { | ||||
public Test4b(String name) { super(name); } | |||||
public static void main(String[] args) { | public static void main(String[] args) { | ||||
junit.textui.TestRunner.run(Test4b.class); | junit.textui.TestRunner.run(Test4b.class); | ||||
} | } | ||||
public void setUp() { | |||||
super.setUp(); | |||||
} | |||||
public void testBasicEquality() { | public void testBasicEquality() { | ||||
assertTrue(g.getBounds() == g.getBounds()); | assertTrue(g.getBounds() == g.getBounds()); | ||||
} | } | ||||
public void testNotWholeCanvas() { | public void testNotWholeCanvas() { | ||||
assertTrue("bounds for this group should not be the whole canvas", | assertTrue("bounds for this group should not be the whole canvas", | ||||
g.getBounds().getWidth() < | |||||
(FigureElement.MAX_VALUE - FigureElement.MIN_VALUE)); | |||||
g.getBounds().getWidth() < FigureElement.MAX_BOUNDS.getWidth()); | |||||
assertTrue("bounds for this group should not be the whole canvas", | assertTrue("bounds for this group should not be the whole canvas", | ||||
g.getBounds().getHeight() < | |||||
(FigureElement.MAX_VALUE - FigureElement.MIN_VALUE)); | |||||
g.getBounds().getHeight() < FigureElement.MAX_BOUNDS.getHeight()); | |||||
} | } | ||||
} | } | ||||
import junit.framework.*; | import junit.framework.*; | ||||
public class Test4c extends Test { | public class Test4c extends Test { | ||||
public Test4c(String name) { super(name); } | |||||
public static void main(String[] args) { | public static void main(String[] args) { | ||||
junit.textui.TestRunner.run(Test4c.class); | junit.textui.TestRunner.run(Test4c.class); | ||||
} | } | ||||
public void setUp() { | |||||
super.setUp(); | |||||
} | |||||
public void testBasicEquality() { | public void testBasicEquality() { | ||||
assertTrue(g.getBounds() == g.getBounds()); | assertTrue(g.getBounds() == g.getBounds()); | ||||
} | } |
junit.textui.TestRunner.run(Test4d.class); | junit.textui.TestRunner.run(Test4d.class); | ||||
} | } | ||||
public void setUp() { | |||||
super.setUp(); | |||||
} | |||||
public void testBasicEquality() { | public void testBasicEquality() { | ||||
assertTrue(g.getBounds() == g.getBounds()); | assertTrue(g.getBounds() == g.getBounds()); | ||||
} | } |
junit.textui.TestRunner.run(Test4e.class); | junit.textui.TestRunner.run(Test4e.class); | ||||
} | } | ||||
public void setUp() { | |||||
super.setUp(); | |||||
} | |||||
public void testBasicEquality() { | public void testBasicEquality() { | ||||
assertTrue(g.getBounds() == g.getBounds()); | assertTrue(g.getBounds() == g.getBounds()); | ||||
} | } |