cademy.cdf.turtlelogo.Terrarium.add(Turtle,UnderTheShell)   A
last analyzed

Complexity

Conditions 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
c 0
b 0
f 0
cc 1
rs 10
1
package org.gannacademy.cdf.turtlelogo;
2
3
import javax.swing.*;
4
import java.awt.*;
5
import java.util.ArrayList;
6
import java.util.List;
7
8
/**
9
 * <p>A {@link Turtle} lives (and draws) inside a <code>Terrarium</code>.</p>
10
 *
11
 * <p><img src="doc-files/trail.png" alt="Turtle leaving a trail"></p>
12
 *
13
 * <p>A terrarium can be resized using the {@link #setSize(int, int)} method and repositioned on the screen using the
14
 * {@link #setPosition(int, int)}. The terrarium can also have a custom background color (set via the
15
 * {@link #setBackground(Color)} method &mdash; it defaults to white.</p>
16
 *
17
 * <p><img src="doc-files/setBackground.png" alt="setBackground() example"></p>
18
 *
19
 * <p>Initially, there is only a single terrarium, into which all new turtles are added. However, it is possible to
20
 * instantiate additional terraria, and to direct Turtles to them using the {@link Turtle#setTerrarium(Terrarium)}
21
 * method. From a technical standpoint, the initial terrarium is a quasi-singleton, and will continue to be treated as
22
 * a singleton by any new turtles as they are instantiated. The singleton terrarium instance can be accessed statically
23
 * via the {@link #getInstance()} method. When additional terraria have been instantiated, they may also be accessed
24
 * statically via their index (in instantiation order) using the {@link #getInstance(int)} method.</p>
25
 *
26
 * @author <a href="https://github.com/gann-cdf/turtlelogo/issues">Seth Battis</a>
27
 */
28
public class Terrarium extends JPanel {
29
  /**
30
   * The parts of the terrarium that are "under the surface" are not meant to be used by students. This mechanism
31
   * (inspired by <a href="https://stackoverflow.com/a/18634125">this awesome Stack Overflow answer</a>) recreates a
32
   * version of the C++ <code>friend</code> concept: a public method that is only available to <i>some</i> other
33
   * objects, rather than <i>all</i> other objects.
34
   *
35
   * @author <a href="https://github.com/gann-cdf/turtlelogo/issues">Seth Battis</a>
36
   */
37
  public static final class UnderTheSurface {
38
    private UnderTheSurface() {
39
    }
40
  }
41
42
  protected static final UnderTheSurface UNDER_THE_SURFACE = new UnderTheSurface();
43
44
  /**
45
   * 600 pixels
46
   */
47
  public static final int DEFAULT_WIDTH = 600;
48
49
  /**
50
   * 400 pixels
51
   */
52
  public static final int DEFAULT_HEIGHT = 400;
53
54
  /**
55
   * {@link Color#WHITE}
56
   */
57
  public static final Color DEFAULT_BACKGROUND = Color.WHITE;
58
59
  private static List<Terrarium> terraria;
60
  private JFrame frame;
61
  private List<Turtle> turtles;
0 ignored issues
show
Bug Best Practice introduced by
Fields like turtles in a serializable class should either be transient or serializable.
Loading history...
62
  private List<Track> tracks;
0 ignored issues
show
Bug Best Practice introduced by
Fields like tracks in a serializable class should either be transient or serializable.
Loading history...
63
64
  /**
65
   * Construct a new terrarium of default dimensions, centered on the screen in its own window
66
   */
67
  public Terrarium() {
68
    super();
69
    setPreferredSize(new Dimension(DEFAULT_WIDTH, DEFAULT_HEIGHT));
70
    setBackground(DEFAULT_BACKGROUND);
71
    turtles = new ArrayList<>();
72
    tracks = new ArrayList<>();
73
    getFrame();
74
    addInstance(this);
75
  }
76
77
  private static void addInstance(Terrarium terrarium) {
78
    if (terraria == null) {
79
      terraria = new ArrayList<>();
80
    }
81
    terraria.add(terrarium);
82
  }
83
84
  /**
85
   * Get the default terrarium instance (instantiating it, if necessary)
86
   *
87
   * @return The default terrarium
88
   */
89
  public static Terrarium getInstance() {
90
    return getInstance(0);
91
  }
92
93
  /**
94
   * Get a particular Terrarium instance
95
   *
96
   * @param index [0..<i>n</i>) if there are <i>n</i> terraria, sequenced by instantiation
97
   *              order
98
   * @return The terrarium at this index
99
   */
100
  public static Terrarium getInstance(int index) {
101
    if (terraria == null) {
102
      terraria = new ArrayList<>();
103
      new Terrarium();
104
    }
105
    return terraria.get(index);
106
  }
107
108
  private JFrame getFrame() {
109
    if (frame == null) {
110
      frame = new JFrame("Turtle Logo");
111
      frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
112
      frame.add(this);
113
      frame.pack();
114
      frame.setLocationRelativeTo(null);
115
      frame.setVisible(true);
116
    }
117
    return frame;
118
  }
119
120
  /**
121
   * <p>Adds a new turtle track to the terrarium</p>
122
   *
123
   * <p>May only be called by {@link Turtle} and its subclasses, enforced via {@link Turtle.UnderTheShell}.</p>
124
   *
125
   * @param track to be added
126
   * @param key   to authenticate "Turtleness"
127
   */
128
  public synchronized void add(Track track, Turtle.UnderTheShell key) {
129
    key.hashCode();
130
    tracks.add(track);
131
    repaint();
132
  }
133
134
  /**
135
   * Clear all turtle tracks from the terrarium
136
   */
137
  public synchronized void clear() {
138
    tracks.clear();
139
    repaint();
140
  }
141
142
  /**
143
   * <p>Adds a new turtle to the terrarium</p>
144
   *
145
   * <p>May only be called by {@link Turtle} and its subclasses, enforced via {@link Turtle.UnderTheShell}.</p>
146
   *
147
   * @param turtle to be added
148
   * @param key    to authenticate "Turtleness"
149
   */
150
  public synchronized void add(Turtle turtle, Turtle.UnderTheShell key) {
151
    key.hashCode();
152
    turtles.add(turtle);
153
    repaint();
154
  }
155
156
  /**
157
   * <p>Remove a turtle from the terrarium</p>
158
   *
159
   * <p>May only be called by {@link Turtle} and its subclasses, enforced via {@link Turtle.UnderTheShell}.</p>
160
   *
161
   * @param turtle to be removed
162
   * @param key    to authenticate "Turtleness"
163
   */
164
  public synchronized void remove(Turtle turtle, Turtle.UnderTheShell key) {
165
    key.hashCode();
166
    turtles.remove(turtle);
167
    repaint();
168
  }
169
170
  /**
171
   * Adjust the dimensions of the terrarium view
172
   *
173
   * @param width  in pixels
174
   * @param height in pixels
175
   */
176
  public void setSize(int width, int height) {
177
    setPreferredSize(new Dimension(width, height));
178
    getFrame().pack();
179
    getFrame().repaint();
180
  }
181
182
  /**
183
   * Adjust the location of the terrarium window. Note that the screen origin is in the top, left corner of the display
184
   * and that, while the X-axis increases from left to right, the Y-axis <i>increases</i> from top to bottom. The
185
   * coordinates given are for the origin of the window (the top, left corner)
186
   *
187
   * @param x coordinate
188
   * @param y coordinate
189
   */
190
  public void setPosition(int x, int y) {
191
    getFrame().setLocation(x, y);
192
    getFrame().repaint();
193
  }
194
195
  /**
196
   * <p>Repaint the contents of the terrarium (tracks and turtles) as-needed</p>
197
   *
198
   * <p>This method is called automatically by the enclosing {@link JFrame} to repaint the terrarium as-needed. It is
199
   * not meant to be called at will. If the terrarium needs to be updated, a {@link #repaint()} request will schedule
200
   * the update.</p>
201
   *
202
   * @param context for drawing commands
203
   */
204
  @Override
205
  public synchronized void paintComponent(Graphics context) {
206
    super.paintComponent(context);
207
    Graphics2D context2D = (Graphics2D) context;
208
    draw(context2D);
209
  }
210
211
  /**
212
   * For synchronous drawing requests (e.g. saving images)
213
   *
214
   * @param context for drawing commands
215
   */
216
  protected void draw(Graphics2D context) {
217
    context.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
218
    for (Track track : tracks) {
219
      track.draw(context, UNDER_THE_SURFACE);
220
    }
221
    for (Turtle turtle : turtles) {
222
      turtle.draw(context, UNDER_THE_SURFACE);
223
    }
224
  }
225
}
226