1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
|
package unsw.blackout;
import unsw.utils.Angle;
import java.util.Optional;
import java.util.Vector;
public abstract class EntityBase {
private String id;
private Angle position;
private double height;
private Vector<File> received_files = new Vector<File>();
private Vector<File> sent_files = new Vector<File>();
/**
* BaseEntity is a class that contains an ID and a type which represents any object in our network.
*/
EntityBase(String id, Angle position, double height) {
this.id = id;
this.position = position;
this.height = height;
}
// Getters.
public String getId() {
return this.id;
}
public Angle getAngle() {
return this.position;
}
public double getHeight() {
return this.height;
}
public Vector<File> getReceivedFiles() {
return this.received_files;
}
public Vector<File> getSentFiles() {
return this.sent_files;
}
public void setAngle(Angle angle) {
this.position = angle;
}
/**
* Returns the velocity of the entity in km/s.
*/
public abstract double getVelocity();
/**
* Returns the range of the entity in km.
*/
public abstract double getRange();
/**
* Returns a boolean flagging if the entity may communicate with the other entity.
* Warning: it is possible that one entity may allow, while the other does not.
*/
protected abstract boolean isSupportedEntity(EntityBase entity);
/**
* Returns a boolean flagging if the entity is physically in range and unobstructed
* to another entity.
*/
protected abstract boolean isPhysicallyReachable(EntityBase entity);
/**
* Returns a boolean flagging if the entity may retransmit messages to other entities.
*/
protected boolean canRelay() {
return false;
}
/**
* Returns a boolean flagging if the entity should NOT delete files when out of range.
*/
protected boolean canTransient() {
return false;
}
/**
* Returns a boolean flagging if the entity may shrink files.
*/
protected boolean canShrink() {
return false;
}
/**
* Returns an optional integer limit on the number of files it may hold.
* Returning no value means no limit.
*/
protected abstract Optional<Integer> getFileStoreLimit();
/**
* Returns an optional integer limit on the number of bytes it may hold across all files.
* Returning no value means no limit.
*/
protected abstract Optional<Integer> getByteStoreLimit();
/**
* Returns an optional integer limit on the number of bytes downloadable per minute.
* Returning no value means no limit.
*/
protected abstract Optional<Integer> getByteDownloadSpeed();
/**
* Returns an optional integer limit on the number of bytes uploadable per minute.
* Returning no value means no limit.
*/
protected abstract Optional<Integer> getByteUploadSpeed();
/**
* Helper function to calculate an offset to the position class in radians.
*/
protected double getMoveOffsetRadians() {
return (this.getVelocity() / this.getHeight());
}
/**
* Moves the entity to the expected position after 1 minute.
*/
public void move() {
double offset = this.getMoveOffsetRadians();
double rposition = this.position.toRadians() + offset;
this.position = Angle.fromRadians(rposition % Math.toRadians(360.0));
}
/**
* Helper function, returns the number of files that have not been fully transmitted in the vector.
*/
static private int getNumUncompletedFiles(Vector<File> files) {
int count = 0;
for (File file : files) {
if (file.hasFullyTransmitted()) {
continue;
}
++count;
}
return count;
}
/**
* Gets the number of bytes in received files.
*/
protected int getNumBytesInFiles() {
int count = 0;
for (File file : this.received_files) {
count += file.getContents().length();
}
return count;
}
/**
* Returns the speed a file should be downloaded per tick for fairness during multiple transfers.
* Empty optional means infinite value.
*/
private Optional<Integer> getDownloadBandwidth() {
Optional<Integer> download_speed = this.getByteDownloadSpeed();
if (download_speed.isEmpty()) {
return Optional.empty();
}
return Optional.of(download_speed.get() / EntityBase.getNumUncompletedFiles(this.getReceivedFiles()));
}
/**
* Returns the speed a file should be uploaded per tick for fairness during multiple transfers.
* Empty optional means infinite value.
*/
private Optional<Integer> getUploadBandwidth() {
Optional<Integer> upload_speed = this.getByteUploadSpeed();
if (upload_speed.isEmpty()) {
return Optional.empty();
}
return Optional.of(upload_speed.get() / EntityBase.getNumUncompletedFiles(this.getSentFiles()));
}
/**
* Heper function, returns the min of two optional integers, or a concrete value representing infinity.
*/
static private int getOptionalMin(Optional<Integer> a, Optional<Integer> b) {
if (a.isPresent() && b.isPresent()) {
return Math.min(a.get(), b.get());
} else if (a.isPresent()) {
return a.get();
} else if (b.isPresent()) {
return b.get();
}
return Integer.MAX_VALUE;
}
public void removeSentFiles(Vector<File> files) {
for (File file : files) {
getSentFiles().remove(file);
}
}
/**
* Transfers currently sending files by the expected values after 1 minute.
* Returns false if the file should be deleted.
*/
public boolean maybeTransferFile(File file, EntityBase target) {
// Delete and early out files if we are out of range - but keep if transient!
if (!this.isPhysicallyReachable(target)) {
if (!target.canTransient()) {
target.getReceivedFiles().remove(file);
return false;
}
return true;
}
Optional<Integer> upload_bandwidth = this.getUploadBandwidth();
Optional<Integer> download_bandwidth = target.getDownloadBandwidth();
// The amount we can send is the min of our upload bandwidth and their download bandwidth.
int bandwidth = EntityBase.getOptionalMin(upload_bandwidth, download_bandwidth);
file.addBytes(bandwidth);
return true;
}
/**
* Recursive DPS helper function to determine whether an entity may communicate with another.
*/
private boolean canCommunicateSearch(EntityBase target, Vector<EntityBase> entities, Vector<EntityBase> visited) {
visited.add(this);
for (EntityBase entity : entities) {
if (entity.equals(this)) {
continue;
}
if (visited.contains(entity)) {
continue;
}
if (!this.isSupportedEntity(entity) || !entity.isSupportedEntity(this)) {
continue;
}
if (!this.isPhysicallyReachable(entity)) {
continue;
}
if (target.equals(entity)) {
return true;
}
if (entity.canRelay()) {
if (entity.canCommunicateSearch(target, entities, visited)) {
return true;
}
}
}
return false;
}
/**
* Determines whether an entity may communicate with another entity, including via hops from other entities.
*/
public boolean canCommunicate(EntityBase target, Vector<EntityBase> entities) {
Vector<EntityBase> visited = new Vector<EntityBase>();
return canCommunicateSearch(target, entities, visited);
}
/**
* Helper to extract files via their filenames, returns an empty optional if it does not exist.
*/
static private Optional<File> getFileFromFilename(String filename, Vector<File> files) {
for (File file : files) {
if (file.getFilename().equals(filename)) {
return Optional.of(file);
}
}
return Optional.empty();
}
/**
* Sends a file to the entity, while respecting the constraints imposed by both entities.
*/
public void sendFileTo(String filename, EntityBase target) throws FileTransferException {
Optional<File> origin_file = EntityBase.getFileFromFilename(filename, this.getReceivedFiles());
if (origin_file.isEmpty()) { // throw if the file does not exist
throw new FileTransferException.VirtualFileNotFoundException(filename);
}
if (!origin_file.get().hasFullyTransmitted()) { // throw if the file has not fully downloaded
throw new FileTransferException.VirtualFileNotFoundException(filename);
}
// throw if the file already exist or is currently downloading, nicely handles both here
if (EntityBase.getFileFromFilename(filename, target.getReceivedFiles()).isPresent()) {
throw new FileTransferException.VirtualFileAlreadyExistsException(filename);
}
Optional<Integer> upload_speed = this.getByteUploadSpeed(); // throw if we don't have enough bandwidth
if (upload_speed.isPresent() && EntityBase.getNumUncompletedFiles(this.sent_files) >= upload_speed.get()) {
throw new FileTransferException.VirtualFileNoBandwidthException(this.getId());
}
Optional<Integer> download_speed = target.getByteDownloadSpeed(); // throw if they don't have enough bandwidth
if (download_speed.isPresent() && EntityBase.getNumUncompletedFiles(target.getReceivedFiles()) >= download_speed.get()) {
throw new FileTransferException.VirtualFileNoBandwidthException(target.getId());
}
// Throw if they can't store any more files.
Optional<Integer> download_file_limit = target.getFileStoreLimit();
if (download_file_limit.isPresent() && target.getReceivedFiles().size() >= download_file_limit.get()) {
throw new FileTransferException.VirtualFileNoStorageSpaceException("Max Files Reached");
}
// Throw if they don't have enough space.
Optional<Integer> download_byte_limit = target.getByteStoreLimit();
int filesize = origin_file.get().getContents().length();
if (download_byte_limit.isPresent() && target.getNumBytesInFiles() + filesize >= download_byte_limit.get()) {
throw new FileTransferException.VirtualFileNoStorageSpaceException("Max Storage Reached");
}
// Finally we upload the file.
String contents = origin_file.get().getContents();
File upload_file = new File(filename, contents, 0, target.getId());
this.getSentFiles().add(upload_file);
target.getReceivedFiles().add(upload_file);
}
// We know that our ID is unique, so that's the only thing we need to compare here.
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
EntityBase other = (EntityBase) obj;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
return true;
}
}
|