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    }