001 /** 002 * planningtool - A Planning Tool with Critiquing Support. 003 * 004 * Copyright (C) 2006 olale 005 006 * This program is free software; you can redistribute it and/or 007 * modify it under the terms of the GNU General Public License 008 * as published by the Free Software Foundation; either version 2 009 * of the License, or (at your option) any later version. 010 011 * This program is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 014 * GNU General Public License for more details. 015 016 * You should have received a copy of the GNU General Public License 017 * along with this program; if not, write to the Free Software 018 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 019 020 * Contact information: 021 * E-mail: olale@ida.liu.se 022 * olale@lysator.liu.se 023 */ 024 package se.liu.ida.critiquer.activities; 025 026 import java.io.Serializable; 027 import java.util.ArrayList; 028 import java.util.Calendar; 029 import java.util.Date; 030 import java.util.HashSet; 031 032 import org.jfree.data.gantt.Task; 033 import org.jfree.data.gantt.TaskSeries; 034 035 import se.liu.ida.critiquer.activities.parameters.ActivityParameter; 036 import se.liu.ida.critiquer.activities.parameters.CommanderParameter; 037 import se.liu.ida.critiquer.activities.parameters.DescriptionParameter; 038 import se.liu.ida.critiquer.activities.parameters.NameParameter; 039 import se.liu.ida.critiquer.activities.parameters.NumberSelectionParameter; 040 import se.liu.ida.critiquer.activities.parameters.Parameter; 041 import se.liu.ida.critiquer.activities.parameters.TimeParameter; 042 import se.liu.ida.critiquer.constraints.ConstraintFactory; 043 import se.liu.ida.critiquer.constraints.TimeParameterOrdering; 044 import se.liu.ida.critiquer.constraints.VisualConstraints; 045 import se.liu.ida.critiquer.gui.NameChangeListener; 046 import se.liu.ida.critiquer.mics.ReferenceHolder; 047 import se.liu.ida.critiquer.resources.Agent; 048 049 public abstract class Activity implements Serializable { 050 051 private interface Updater { 052 053 public void update(ActivityUpdateListener l); 054 } 055 056 private static final long serialVersionUID = 1L; 057 058 private ArrayList<ActivityParameter> params; 059 060 private Activity parent = null; 061 062 private ArrayList<Activity> children = new ArrayList<Activity>(); 063 064 private transient static ArrayList<ActivityUpdateListener> updateListeners = null; 065 066 public String type = "Default mission"; 067 068 private ArrayList<ActivityUpdateListener> myUpdateListeners; 069 070 private boolean missionTimeChanged = false; 071 072 private HashSet<TimeParameterOrdering> temporalConstraints = new HashSet<TimeParameterOrdering>(); 073 074 private static ArrayList<ActivityConsistencyCheck> staticConsistencyCheckers = new ArrayList<ActivityConsistencyCheck>(); 075 076 private TimeParameterOrdering afterParentStart; 077 078 private TimeParameterOrdering beforeParentEnd; 079 080 private static int pendingUpdates = 0; 081 082 private void addPendingUpdate() { 083 pendingUpdates++; 084 } 085 086 private void removePendingUpdate() { 087 pendingUpdates--; 088 } 089 090 public int getPendingUpdates() { 091 return pendingUpdates; 092 } 093 /** 094 * Each subclass of Activity should have a static counter of how many 095 * activities have been created, so that we may create unique new names for 096 * newly created activities. 097 * 098 * @return the number of objects of this class that have been created. 099 */ 100 public abstract int getNumActivities(); 101 102 public Activity getRootActivity() { 103 if (parent == null) { 104 return this; 105 } else { 106 return parent.getRootActivity(); 107 } 108 } 109 110 /** 111 * Creates a dummy placeholder activity, not to be used in tasks 112 */ 113 public Activity() { 114 System.err.println("Dummy activity created"); 115 } 116 117 public Activity(Activity parent) { 118 init(parent); 119 } 120 121 public Activity(String type, Activity parent) { 122 this.type = type; 123 init(parent); 124 } 125 126 private void init(Activity parent) { 127 ReferenceHolder.allActivities.add(this); 128 addDefaultParams(); 129 initParentRelationship(parent); 130 signalActivityCreated(); 131 } 132 133 /** 134 * 135 * Called when an activity is added to an overarching activity. 136 * 137 * @param parent 138 */ 139 public void initParentRelationship(Activity parent) { 140 this.parent = parent; 141 if (parent != null) { 142 afterParentStart = ConstraintFactory.connectStartToStart(parent, this); 143 VisualConstraints.addConstraint(afterParentStart); 144 beforeParentEnd = ConstraintFactory.connectEndToEnd(parent, this); 145 VisualConstraints.addConstraint(beforeParentEnd); 146 parent.addChild(this); 147 } 148 signalActivityUpdated(); 149 } 150 151 /** 152 * This method indicates whether or not an activity can be assigned an agent 153 * or if it should inherit agents from parent activities. It is determined 154 * by whether or not there is a commander assigned to the activity. 155 * 156 */ 157 public boolean isAgentAssignable() { 158 CommanderParameter param = (CommanderParameter) ActivityUtils.getParamByClassAndName(this, 159 CommanderParameter.class, 160 "commander"); 161 return (param != null && param.isEnabled()); 162 } 163 164 /** 165 * Decouple this agent from its parent 166 * 167 */ 168 public void endParentRelationship() { 169 170 if (parent != null) { 171 parent.removeChild(this); 172 this.parent = null; 173 } 174 afterParentStart.disableConstraint(); 175 VisualConstraints.removeConstraint(afterParentStart); 176 beforeParentEnd.disableConstraint(); 177 VisualConstraints.removeConstraint(beforeParentEnd); 178 signalActivityUpdated(); 179 } 180 181 private Activity getLastChild() { 182 if (!children.isEmpty()) { 183 return children.get(children.size() - 1); 184 } 185 return null; 186 } 187 188 public static void addActivityUpdateListener(ActivityUpdateListener ul) { 189 if (!Activity.getStaticUpdateListeners().contains(ul)) { 190 Activity.getStaticUpdateListeners().add(ul); 191 } else { 192 System.out.println("Note: Trying to att update listener " + ul + " twice"); 193 } 194 195 } 196 197 /** 198 * @return Returns the params. 199 */ 200 public ArrayList<ActivityParameter> getParams() { 201 if (params == null) { 202 params = new ArrayList<ActivityParameter>(); 203 } 204 return params; 205 } 206 207 public <T> void addParam(ActivityParameter<T> p) { 208 getParams().add(p); 209 signalParamAdded(p); 210 } 211 212 public static <T> boolean isConsistent(Activity a, Parameter<T> p, T newValue) { 213 boolean consistent = true; 214 for (ActivityConsistencyCheck check : staticConsistencyCheckers) { 215 if (!check.paramValueCheck(a, p, newValue)) { 216 consistent = false; 217 break; 218 } 219 } 220 return consistent; 221 } 222 223 private synchronized void notifyAllActivityListeners(Updater u) { 224 /** 225 * We are processing the updater, so reduce the "pending" count 226 */ 227 removePendingUpdate(); 228 for (ActivityUpdateListener listener : Activity.getStaticUpdateListeners()) { 229 u.update(listener); 230 } 231 /* 232 * First notify all generic listeners, then those specific to this 233 * instance 234 */ 235 236 for (ActivityUpdateListener listener : getMyUpdateListeners()) { 237 u.update(listener); 238 } 239 } 240 241 public synchronized void signalActivityTimeChanged() { 242 setMissionTimeChanged(true); 243 /** 244 * create a new activity parameter nobody should be interested in per se 245 */ 246 ActivityParameter<String> dummyParam = new ActivityParameter<String>("dummy", this) { 247 248 /** 249 * 250 */ 251 private static final long serialVersionUID = 1L; 252 253 @Override 254 public String toString() { 255 return null; 256 } 257 258 }; 259 signalParamChanged(dummyParam); 260 setMissionTimeChanged(false); 261 } 262 263 private void signalActivityCreated() { 264 addPendingUpdate(); 265 notifyAllActivityListeners(new Updater() { 266 267 public void update(ActivityUpdateListener l) { 268 l.activityCreated(Activity.this); 269 } 270 271 }); 272 273 } 274 275 /** 276 * 277 * When the activity is updated in some way, but not through parameter 278 * values, this method is called 279 * 280 */ 281 282 public void signalActivityUpdated() { 283 addPendingUpdate(); 284 notifyAllActivityListeners(new Updater() { 285 286 public void update(ActivityUpdateListener l) { 287 l.activityUpdated(Activity.this); 288 } 289 290 }); 291 292 } 293 294 public void signalActivityRemoved() { 295 addPendingUpdate(); 296 notifyAllActivityListeners(new Updater() { 297 298 public void update(ActivityUpdateListener l) { 299 l.activityRemoved(Activity.this); 300 } 301 302 }); 303 304 } 305 306 private <T> void signalParamAdded(final Parameter<T> p) { 307 addPendingUpdate(); 308 notifyAllActivityListeners(new Updater() { 309 310 public void update(ActivityUpdateListener l) { 311 l.paramAdded(Activity.this, p); 312 } 313 314 }); 315 316 } 317 318 public <T> void signalParamChanged(final Parameter<T> p) { 319 addPendingUpdate(); 320 notifyAllActivityListeners(new Updater() { 321 322 public void update(ActivityUpdateListener l) { 323 l.paramChanged(Activity.this, p); 324 } 325 326 }); 327 328 } 329 330 public void addPrivateUpdateListener(ActivityUpdateListener l) { 331 getMyUpdateListeners().add(l); 332 } 333 334 public void removePrivateUpdateListener(ActivityUpdateListener l) { 335 getMyUpdateListeners().remove(l); 336 } 337 338 public String toString() { 339 return ActivityUtils.getNameParameter(this).toString(); 340 } 341 342 public boolean hasParent() { 343 return getParent() != null; 344 } 345 346 /** 347 * @return Returns the parent. 348 */ 349 public Activity getParent() { 350 return parent; 351 } 352 353 /** 354 * @param parent 355 * The parent to set. 356 */ 357 public void setParent(Activity parent) { 358 if (this.parent != null) { 359 endParentRelationship(); 360 } 361 initParentRelationship(parent); 362 } 363 364 /** 365 * Add name, description, start and end time for all activities 366 */ 367 public void addDefaultParams() { 368 addParam(new NameParameter(this)); 369 addParam(new DescriptionParameter(this)); 370 ActivityUtils.addCommanderParameter(this); 371 372 // Add time parameters 373 TimeParameter start = new TimeParameter("start time", TimeParameter.Type.START, this); 374 addParam(start); 375 TimeParameter end = new TimeParameter("end time", TimeParameter.Type.END, this); 376 Calendar endTime = (Calendar) start.getValue().clone(); 377 endTime.add(Calendar.HOUR_OF_DAY, 1); 378 end.setValue(endTime); 379 addParam(end); 380 ActivityUtils.addMaxTimeParameter(this); 381 } 382 383 public void addChild(Activity child) { 384 getChildren().add(child); 385 signalActivityUpdated(); 386 } 387 388 public void removeChild(Activity currentActivity) { 389 getChildren().remove(currentActivity); 390 signalActivityUpdated(); 391 } 392 393 /** 394 * @return Returns the children. 395 */ 396 public ArrayList<Activity> getChildren() { 397 if (children == null) { 398 children = new ArrayList<Activity>(); 399 } 400 return children; 401 } 402 403 public void addAll(ArrayList<Activity> c) { 404 getChildren().addAll(c); 405 signalActivityUpdated(); 406 } 407 408 public void removeChildren() { 409 getChildren().clear(); 410 signalActivityUpdated(); 411 } 412 413 /** 414 * Each agent has it's own task series. A task series is used in the time 415 * view to list all activities over time 416 * 417 * If there is no specific agent assigned to this task but the task still 418 * has a duration, assign a dummy agent to a task that spans that duration 419 */ 420 public ArrayList<TaskSeries> createTaskSeries() { 421 ArrayList<TaskSeries> collection = new ArrayList<TaskSeries>(); 422 HashSet<Agent> agents = ActivityUtils.getAgentsInActivity(this); 423 Date start = getStartTime(); 424 Date end = getEndTime(); 425 if (agents.isEmpty() && end.after(start)) { 426 TaskSeries series = new TaskSeries(Agent.dummyAgent.toString()); 427 series.add(new Task(this.toString(), start, end)); 428 collection.add(series); 429 } else if (!agents.isEmpty()) { 430 for (Agent agent : agents) { 431 TaskSeries series = new TaskSeries(agent.toString()); 432 series.add(new Task(this.toString(), start, end)); 433 collection.add(series); 434 } 435 } 436 return collection; 437 } 438 439 private Date getStartTime() { 440 TimeParameter startParam = ActivityUtils.getStartTimeParameter(this); 441 if (startParam != null) { 442 return startParam.getValue().getTime(); 443 } 444 return null; 445 } 446 447 private Date getEndTime() { 448 TimeParameter endParam = ActivityUtils.getEndTimeParameter(this); 449 if (endParam != null) { 450 return endParam.getValue().getTime(); 451 } 452 return null; 453 454 } 455 456 public static void addNameChangeListener(final NameChangeListener listener) { 457 AbstractParamChangedListener nameChangeListener = new AbstractParamChangedListener() { 458 459 /** 460 * 461 */ 462 private static final long serialVersionUID = 1L; 463 464 public <T> void paramChanged(Activity activity, Parameter<T> p) { 465 if (p instanceof NameParameter) { 466 listener.nameChanged(); 467 } 468 469 } 470 471 }; 472 Activity.getStaticUpdateListeners().add(nameChangeListener); 473 } 474 475 /* 476 * (non-Javadoc) 477 * 478 * @see java.lang.Object#equals(java.lang.Object) 479 */ 480 @Override 481 public boolean equals(Object o) { 482 return o != null && o instanceof Activity && toString().equals(o.toString()); 483 } 484 485 /** 486 * @return Returns the updateListeners. 487 */ 488 public static ArrayList<ActivityUpdateListener> getStaticUpdateListeners() { 489 if (Activity.updateListeners == null) { 490 Activity.updateListeners = new ArrayList<ActivityUpdateListener>(); 491 } 492 return Activity.updateListeners; 493 } 494 495 /** 496 * @return Returns the updateListeners. 497 */ 498 ArrayList<ActivityUpdateListener> getMyUpdateListeners() { 499 if (myUpdateListeners == null) { 500 myUpdateListeners = new ArrayList<ActivityUpdateListener>(); 501 } 502 return myUpdateListeners; 503 } 504 505 public int indexOfChild(Activity activity) { 506 return getChildren().indexOf(activity); 507 } 508 509 /** 510 * Calculates the time it takes to complete a mission by setting the "end 511 * time" parameter (if there is one) to some time after "start time", 512 * depending on which agents are selected for this mission. 513 */ 514 public abstract long calculateTimeToComplete() throws IncompleteActivityException; 515 516 public long getMaxTime() { 517 NumberSelectionParameter maxTimeParam = ActivityUtils.getParamByClassAndName(this, 518 NumberSelectionParameter.class, 519 "max time"); 520 int returnValue = 0; 521 if (maxTimeParam != null && maxTimeParam.hasValue()) { 522 returnValue = maxTimeParam.getValue(); 523 } 524 return returnValue; 525 } 526 527 /** 528 * @return Returns the missionTimeChanged. 529 */ 530 public boolean isMissionTimeChanged() { 531 return missionTimeChanged; 532 } 533 534 /** 535 * @param missionTimeChanged 536 * The missionTimeChanged to set. 537 */ 538 public void setMissionTimeChanged(boolean missionTimeChanged) { 539 this.missionTimeChanged = missionTimeChanged; 540 } 541 542 /** 543 * <p> 544 * This function is called when an activity is deserialized. 545 * </p> 546 */ 547 public void postLoad() { 548 signalActivityCreated(); 549 } 550 551 /** 552 * @return Returns the temporalConstraints. 553 */ 554 public HashSet<TimeParameterOrdering> getTemporalConstraints() { 555 return temporalConstraints; 556 } 557 558 public void preSave() { 559 560 } 561 562 /** 563 * <p> 564 * Remove the temporal ordering between the current activity and a2 565 * </p> 566 * <p> 567 * TODO: This could be an instance method so that additional ordering 568 * information could be added by subclasses, if location parameters need to 569 * be syncronized for instance 570 * </p> 571 * 572 * @param laterActivity 573 * the activity that was ordered after <code>this</code> 574 * 575 * @see se.liu.ida.critiquer.activities.Activity#order(se.liu.ida.critiquer.activities.Activity) 576 * for more information. 577 * 578 */ 579 public void removeOrder(Activity laterActivity) { 580 TimeParameterOrdering toBeRemoved = null; 581 for (TimeParameterOrdering ordering : ReferenceHolder.temporalConstraints) { 582 if (ordering.getT1().getActivity().equals(this) && ordering.getT2().getActivity().equals(laterActivity)) { 583 toBeRemoved = ordering; 584 break; 585 } 586 } 587 588 if (toBeRemoved != null) { 589 toBeRemoved.disableConstraint(); 590 ReferenceHolder.temporalConstraints.remove(toBeRemoved); 591 VisualConstraints.removeConstraint(toBeRemoved); 592 } 593 signalActivityUpdated(); 594 laterActivity.signalActivityUpdated(); 595 } 596 597 /** 598 * <p> 599 * Introduce an ordering between two activities by ordering the end time 600 * parameter of the current activity before the start time parameter of a2. 601 * </p> 602 * TODO: Possibly add other constraints that relate to ordering two 603 * consecutive actions 604 * 605 * 606 * @param laterActivity 607 * The activity that should come after <code>this</code> 608 */ 609 public void order(Activity laterActivity) { 610 TimeParameterOrdering order = ConstraintFactory.connectEndToStart(this, laterActivity); 611 if (!ReferenceHolder.temporalConstraints.contains(order)) { 612 ReferenceHolder.temporalConstraints.add(order); 613 VisualConstraints.addConstraint(order); 614 } 615 signalActivityUpdated(); 616 laterActivity.signalActivityUpdated(); 617 } 618 619 /** 620 * Can this activity be a child of parent? 621 */ 622 public boolean canBeChildOf(Activity parent) { 623 boolean ok = true; 624 for (ActivityConsistencyCheck consistencyChecker : staticConsistencyCheckers) { 625 ok = consistencyChecker.childOfCheck(parent, this); 626 if (!ok) { 627 break; 628 } 629 } 630 return ok; 631 } 632 633 /** 634 * Can this activity come before activity in a mission? 635 */ 636 public boolean canComeBefore(Activity activity) { 637 boolean ok = true; 638 for (ActivityConsistencyCheck consistencyChecker : staticConsistencyCheckers) { 639 ok = consistencyChecker.orderingCheck(this, activity); 640 if (!ok) { 641 break; 642 } 643 } 644 return ok; 645 } 646 647 /** 648 * @return Returns the staticConsistencyCheckers. 649 */ 650 public static ArrayList<ActivityConsistencyCheck> getStaticConsistencyCheckers() { 651 return staticConsistencyCheckers; 652 } 653 654 /** 655 * @param checker 656 * The ActivityConsistencyChecker to add. 657 */ 658 public static void addStaticConsistencyChecker(ActivityConsistencyCheck checker) { 659 Activity.staticConsistencyCheckers.add(checker); 660 } 661 662 }