Source code: com/eireneh/bible/control/map/Map.java
1
2 package com.eireneh.bible.control.map;
3
4 import java.io.*;
5
6 import com.eireneh.util.*;
7 import com.eireneh.bible.passage.*;
8
9 /**
10 * A map is an array of Nodes (verses with position).
11 *
12 * <table border='1' cellPadding='3' cellSpacing='0' width="100%">
13 * <tr><td bgColor='white'class='TableRowColor'><font size='-7'>
14 * Distribution Licence:<br />
15 * Project B is free software; you can redistribute it
16 * and/or modify it under the terms of the GNU General Public License,
17 * version 2 as published by the Free Software Foundation.<br />
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21 * General Public License for more details.<br />
22 * The License is available on the internet
23 * <a href='http://www.gnu.org/copyleft/gpl.html'>here</a>, by writing to
24 * <i>Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
25 * MA 02111-1307, USA</i>, Or locally at the Licence link below.<br />
26 * The copyright to this program is held by it's authors.
27 * </font></td></tr></table>
28 * @see <a href='http://www.eireneh.com/servlets/Web'>Project B Home</a>
29 * @see docs.Licence
30 * @author Joe Walker
31 * @version D0.I0.T0
32 */
33 public class Map implements Serializable
34 {
35 /**
36 * Basic constructor
37 */
38 public Map(int dimensions)
39 {
40 this.dimensions = dimensions;
41 this.nodes = new Position[Books.versesInBible()];
42
43 try
44 {
45 // Create the array of Nodes
46 for (int i=1; i<=Books.versesInBible(); i++)
47 {
48 Verse verse = new Verse(i);
49 nodes[i-1] = new Position(new float[dimensions]);
50 }
51 }
52 catch (NoSuchVerseException ex)
53 {
54 throw new LogicError(ex);
55 }
56 }
57
58 /**
59 * Get the number of dimensions in the nodes in this map
60 * @return The number of dimensions
61 */
62 public int getDimensions()
63 {
64 return dimensions;
65 }
66
67 /**
68 * Get the position (as a float array) of a node by the ordinal number
69 * of the verse that it contains
70 * @param ord The verse ordinal number
71 * @return The requested node position
72 */
73 public float[] getPosition(int ord)
74 {
75 return (float[]) nodes[ord-1].pos.clone();
76 }
77
78 /**
79 * Get the position (as a float array) of a node by the ordinal number
80 * of the verse that it contains
81 * @param ord The verse ordinal number
82 * @param idx The index into the position array for the given verse
83 * @return The requested node position
84 */
85 public float getPositionDimension(int ord, int idx)
86 {
87 return nodes[ord-1].pos[idx];
88 }
89
90 /**
91 * Get the position of a node by the ordinal number of the verse that
92 * it contains
93 * @param ord The verse ordinal number
94 * @return The requested node position
95 */
96 public void setPosition(int ord, float[] pos)
97 {
98 nodes[ord-1].pos = pos;
99
100 fireMapChanged(ord);
101 }
102
103 /**
104 * Get the position of a node by the ordinal number of the verse that
105 * it contains
106 * @param ord The verse ordinal number
107 * @param idx The index into the position array for the given verse
108 * @param f The new position
109 * @return The requested node position
110 */
111 public void setPositionDimension(int ord, int idx, float f)
112 {
113 nodes[ord-1].pos[idx] = f;
114
115 fireMapChanged(ord);
116 }
117
118 /**
119 * Fix the layout to a fairly random one
120 */
121 public void setLayoutRandom()
122 {
123 int vie = Books.versesInBible();
124 for (int i=1; i<=vie; i++)
125 {
126 nodes[i-1] = new Position(new float[] { (float) Math.random(), (float) Math.random() });
127 }
128 }
129
130 /**
131 * Fix the layout to a simple book/chapter line default
132 */
133 public void setLayoutSimple()
134 {
135 if (dimensions != 2 && dimensions != 3)
136 throw new IllegalArgumentException("Can't set simple layout for maps with "+dimensions+" dimensions.");
137
138 try
139 {
140 int bie = Books.booksInBible();
141 int ord = 1;
142
143 for (int b=1; b<=bie; b++)
144 {
145 int vib = Books.versesInBook(b);
146 int cib = Books.chaptersInBook(b);
147 int vord = 1;
148 for (int c=1; c<=cib; c++)
149 {
150 int vic = Books.versesInChapter(b, c);
151 for (int v=1; v<=vic; v++)
152 {
153 float[] fla;
154 if (dimensions == 2)
155 {
156 float x = ((float) (vord - 1)) / (vib - 1);
157 float y = ((float) (b - 1)) / (bie - 1);
158 fla = new float[] { x, y };
159 }
160 else
161 {
162 float x = 1 - ((float) (vic - v)) / vic;
163 float y = 1 - ((float) (cib - c)) / cib;
164 float z = 1 - ((float) (bie - b)) / bie;
165 fla = new float[] { x, y, z };
166 }
167
168 nodes[ord-1] = new Position(fla);
169 ord++;
170 vord++;
171 }
172 }
173 }
174
175 fireMapRewritten();
176 }
177 catch (NoSuchVerseException ex)
178 {
179 throw new LogicError(ex);
180 }
181 }
182
183 /**
184 * Apply the rules to the map.
185 * @param map The set of nodes to move around
186 * @param rules The rules to apply
187 */
188 public void applyRules(Rule[] rules)
189 {
190 // For each verse
191 for (int i=1; i<=Books.versesInBible(); i++)
192 {
193 Position[][] dar = new Position[rules.length][];
194 for (int j=0; j<rules.length; j++)
195 {
196 dar[j] = rules[j].getDesiredPosition(this, i);
197 }
198
199 Position[] total = cat(dar);
200 Position ave = average(total);
201
202 nodes[i-1] = ave;
203 }
204
205 fireMapRewritten();
206 }
207
208 /**
209 * Add a map listener to the list of things wanting
210 * to know whenever we make some changes to the map
211 */
212 public void addMapListener(MapListener li)
213 {
214 listeners.add(MapListener.class, li);
215 }
216
217 /**
218 * Remove a progress listener from the list of things wanting
219 * to know whenever we make some progress
220 */
221 public void removeMapListener(MapListener li)
222 {
223 listeners.remove(MapListener.class, li);
224 }
225
226 /**
227 * Before we save/load something to/from disk we want to ensure that
228 * we don't loose the list of things that have registered to recieve
229 * map change events.
230 */
231 public EventListenerList getEventListenerList()
232 {
233 return listeners;
234 }
235
236 /**
237 * Before we save/load something to/from disk we want to ensure that
238 * we don't loose the list of things that have registered to recieve
239 * map change events.
240 */
241 public void setEventListenerList(EventListenerList listeners)
242 {
243 this.listeners = listeners;
244 }
245
246 /**
247 * What is the average position of all the nodes in this map
248 * @return The center of gravity
249 */
250 public Position getCenterOfGravity()
251 {
252 // to cheat ...
253 // return new Position(new float[] { 0.5F, 0.5F });
254
255 if (cog == null || replies > MAX_REPLIES)
256 {
257 cog = average(nodes);
258 replies = 0;
259 }
260
261 replies++;
262 return cog;
263 }
264
265 /**
266 * Called to fire a MapEvent to all the Listeners, when a single node
267 * has changed position.
268 * @param percent The percentage of the way through that we are now
269 */
270 protected void fireMapChanged(int ord)
271 {
272 // Guaranteed to return a non-null array
273 Object[] contents = listeners.getListenerList();
274
275 // Process the listeners last to first, notifying
276 // those that are interested in this event
277 MapEvent ev = null;
278 for (int i=contents.length-2; i>=0; i-=2)
279 {
280 if (contents[i] == MapListener.class)
281 {
282 if (ev == null)
283 ev = new MapEvent(this, ord);
284
285 ((MapListener) contents[i+1]).mapChanged(ev);
286 }
287 }
288 }
289
290 /**
291 * Called to fire a MapEvent to all the Listeners, when a single node
292 * has changed position.
293 * @param percent The percentage of the way through that we are now
294 */
295 protected void fireMapRewritten()
296 {
297 // Guaranteed to return a non-null array
298 Object[] contents = listeners.getListenerList();
299
300 // Process the listeners last to first, notifying
301 // those that are interested in this event
302 MapEvent ev = null;
303 for (int i=contents.length-2; i>=0; i-=2)
304 {
305 if (contents[i] == MapListener.class)
306 {
307 if (ev == null)
308 ev = new MapEvent(this);
309
310 ((MapListener) contents[i+1]).mapRewritten(ev);
311 }
312 }
313 }
314
315 /**
316 * Take an array of Position arrays can cat them all together to make
317 * a single array containing all of them.
318 * @param The array of Position arrays
319 * @return The single big array
320 */
321 public static Position[] cat(Position[][] dar)
322 {
323 int size = 0;
324 for (int i=0; i<dar.length; i++)
325 {
326 size += dar[i].length;
327 }
328
329 Position[] total = new Position[size];
330
331 int offset = 0;
332 for (int i=0; i<dar.length; i++)
333 {
334 System.arraycopy(dar[i], 0, total, offset, dar[i].length);
335 offset += dar[i].length;
336 }
337
338 return total;
339 }
340
341 /**
342 * Find the avaerage position of an array of Positions
343 */
344 public static Position average(Position[] array)
345 {
346 int dimensions = array[0].pos.length;
347 double[] tot = new double[dimensions];
348
349 for (int i=0; i<array.length; i++)
350 {
351 for (int j=0; j<dimensions; j++)
352 {
353 tot[j] += array[i].pos[j];
354 }
355 }
356
357 float[] retcode = new float[dimensions];
358
359 for (int j=0; j<dimensions; j++)
360 {
361 retcode[j] = (float) (tot[j] / array.length);
362 }
363
364 return new Position(retcode);
365 }
366
367 /**
368 * Initialize the transient fields
369 * @param in The stream to read our state from
370 */
371 private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException
372 {
373 in.defaultReadObject();
374 listeners = new EventListenerList();
375 }
376
377 /** What is the maximum calculations between re-calcing the CoG */
378 private static final int MAX_REPLIES = 1000;
379
380 /** The current center of gravity */
381 private Position cog = null;
382
383 /** How long until we next calculate the center of gravity */
384 private int replies = 0;
385
386 /** The array of verse nodes */
387 private Position[] nodes;
388
389 /** The number of dimensions in the display */
390 private int dimensions;
391
392 /** The number of links that we track for a node */
393 public static final int LINKS_PER_NODE = 20;
394
395 /** The list of listeners */
396 protected transient EventListenerList listeners = new EventListenerList();
397
398 /** Serialization ID - a serialization of nodes and dimensions */
399 static final long serialVersionUID = -193572391252539071L;
400 }