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.constraints;
025    
026    import java.awt.Color;
027    import java.awt.Graphics2D;
028    import java.rmi.NotBoundException;
029    import java.text.DateFormat;
030    import java.util.ArrayList;
031    import java.util.Calendar;
032    import java.util.GregorianCalendar;
033    import java.util.List;
034    
035    import se.liu.ida.critiquer.activities.AbstractParamChangedListener;
036    import se.liu.ida.critiquer.activities.Activity;
037    import se.liu.ida.critiquer.activities.ActivityUtils;
038    import se.liu.ida.critiquer.activities.parameters.Parameter;
039    import se.liu.ida.critiquer.activities.parameters.TimeParameter;
040    import se.liu.ida.critiquer.gui.AbstractView;
041    import se.liu.ida.critiquer.gui.View;
042    
043    /**
044     * Check/ensure that t2 doesn't come before t1
045     * 
046     * This is a modifying constraint for now, but we let it implement the
047     * VisualConstraint interface just in case..
048     */
049    public class TimeParameterOrdering implements VisualConstraint {
050    
051            /**
052         * 
053         */
054            private static final long                               serialVersionUID        = 1L;
055    
056            private AbstractParamChangedListener     t1Listener;
057    
058            private AbstractParamChangedListener     t2Listener;
059    
060            private TimeParameter                                   t1;
061    
062            private TimeParameter                                   t2;
063    
064            private ConstraintPolicy                                 policy = ConstraintPolicy.MODIFY_VALUES;
065    
066            private boolean                                           slackAllowed          = true;
067    
068            /**
069         * Should the distance between the parameters be kept?
070         */
071            private boolean                                           endToStartConnector = false;
072    
073            /**
074         * Custom slack between activities can be 10 minutes
075         */
076            private int                                                       maxSlack                      = 10 * 60 * 1000;
077    
078            private Color                                                   color;
079    
080            private ArrayList<Class<? extends View>> applicableViews;
081    
082            /*
083         * (non-Javadoc)
084         * 
085         * @see java.lang.Object#toString()
086         */
087            @Override
088            public String toString() {
089                    return "[" + t1.getActivity()
090                               + ":"
091                               + t1.getName()
092                               + "]"
093                               + " < "
094                               + "["
095                               + t2.getActivity()
096                               + ":"
097                               + t2.getName()
098                               + "]";
099            }
100    
101            /*
102         * (non-Javadoc)
103         * 
104         * @see java.lang.Object#equals(java.lang.Object)
105         */
106            @Override
107            public boolean equals(Object obj) {
108                    boolean eq = false;
109                    if (obj instanceof TimeParameterOrdering) {
110                            TimeParameterOrdering tp = (TimeParameterOrdering) obj;
111                            eq = tp.toString().equals(toString());
112                    }
113                    return eq;
114            }
115    
116            private void modifyParameter(TimeParameter param, long timeInMillis) {
117                    Calendar oldTime = param.getValue();
118                    Calendar newTime = new GregorianCalendar();
119                    newTime.setTimeInMillis(timeInMillis);
120                    logModification(param, oldTime, newTime);
121                    param.setValue(newTime);
122            }
123    
124            /**
125         * Change the parameter which wasn't modified by the user, and possible
126         * parameters that are indirectly affected if this ParameterOrdering orders two consecutive activities to each other.
127         * 
128         * @param modifiedParameter
129         */
130            private void adjustWithRespectTo(TimeParameter modifiedParameter) {
131                    try {
132    
133                            long currentSlack = t2.getValue().getTimeInMillis() - t1.getValue().getTimeInMillis();
134                            boolean tooMuchSlack = (!isSlackAllowed()) && currentSlack > maxSlack;
135                            long time = 0;
136                            TimeParameter indirectlyModifiedParameter = null;
137                            boolean violation = isViolation();
138                            if (endToStartConnector && (violation || tooMuchSlack)) {
139                                    if (policy == ConstraintPolicy.MODIFY_VALUES) {
140                                            if (modifiedParameter == t1) {
141                                                    /**
142                                                     * Modified the end time of an activity, so update the end
143                                                     * of the next activity so that the time span of it is
144                                                     * preserved
145                                                     */
146                                                    indirectlyModifiedParameter = ActivityUtils.getEndTimeParameter(t2.getActivity());
147                                                    time = t1.getValue().getTimeInMillis() + indirectlyModifiedParameter.getValue()
148                                                                                                                                                                                            .getTimeInMillis()
149                                                               - t2.getValue().getTimeInMillis();
150                                            } else {
151                                                    /**
152                                                     * Modified the start time of an activity, so update the
153                                                     * start time of the previous activity so that the time span
154                                                     * is preserved.
155                                                     */
156                                                    indirectlyModifiedParameter = ActivityUtils.getStartTimeParameter(t1.getActivity());
157                                                    time = t2.getValue().getTimeInMillis() - (t1.getValue().getTimeInMillis() - indirectlyModifiedParameter.getValue()
158                                                                                                                                                                                                                                                               .getTimeInMillis());
159                                            }
160                                            modifyParameter(indirectlyModifiedParameter, time);
161                                            
162                                    }
163                            }
164                            if (violation || tooMuchSlack) {
165    
166                                    if (policy == ConstraintPolicy.MODIFY_VALUES) {
167                                            if (modifiedParameter == t1) {
168                                                    modifyParameter(t2, t1.getValue().getTimeInMillis());
169                                            } else {
170                                                    modifyParameter(t1, t2.getValue().getTimeInMillis());
171                                            }
172                                    }
173                            }
174    
175                    } catch (Exception e) {
176                            e.printStackTrace();
177                    }
178            }
179    
180            private void logModification(TimeParameter modifiedParameter, Calendar oldValue, Calendar newValue) {
181                    DateFormat shortFormat = DateFormat.getTimeInstance(DateFormat.SHORT);
182                    System.out.println(modifiedParameter.getFullName() + " changed from "
183                                                       + shortFormat.format(oldValue.getTime())
184                                                       + " to "
185                                                       + shortFormat.format(newValue.getTime()));
186            }
187    
188            private boolean isViolation() throws NotBoundException {
189                    return t1.getValue().after(t2.getValue());
190            }
191    
192            public void setPolicy(ConstraintPolicy policy) {
193                    this.policy = policy;
194                    // if (policy == ConstraintPolicy.MODIFY_VALUES) {
195                    // t1.setEditable(false);
196                    // t2.setEditable(false);
197                    // } else {
198                    // t1.setEditable(true);
199                    // t2.setEditable(true);
200                    // }
201            }
202    
203            /**
204         * Remove the activity update listeners when this constraint is removed
205         * 
206         */
207            public void disableConstraint() {
208                    t1.getActivity().removePrivateUpdateListener(t1Listener);
209                    t2.getActivity().removePrivateUpdateListener(t2Listener);
210            }
211    
212            public TimeParameterOrdering(TimeParameter t1Param, TimeParameter t2Param) {
213    
214                    this.t1 = t1Param;
215                    this.t2 = t2Param;
216                    endToStartConnector = t1.getActivity() != t2.getActivity() && ActivityUtils.getEndTimeParameter(t1.getActivity()) == t1
217                                                              && ActivityUtils.getStartTimeParameter(t2.getActivity()) == t2;
218                    /*
219             * When comparing the parameter that has changed, identity checks maybe
220             * not possible..
221             */
222                    t1Listener = new AbstractParamChangedListener() {
223    
224                            /**
225                 * 
226                 */
227                            private static final long serialVersionUID = 1L;
228    
229                            @Override
230                            public <T> void paramChanged(Activity activity, Parameter<T> p) {
231                                    if (t1 == p) {
232                                            adjustWithRespectTo(t1);
233                                    }
234                            }
235    
236                    };
237                    t1.getActivity().addPrivateUpdateListener(t1Listener);
238                    t2Listener = new AbstractParamChangedListener() {
239    
240                            /**
241                 * 
242                 */
243                            private static final long serialVersionUID = 1L;
244    
245                            @Override
246                            public <T> void paramChanged(Activity activity, Parameter<T> p) {
247                                    if (t2 == p) {
248                                            adjustWithRespectTo(t2);
249                                    }
250                            }
251    
252                    };
253                    t2.getActivity().addPrivateUpdateListener(t2Listener);
254                    try {
255                            if (isViolation()) {
256                                    /**
257                     * Adjust the later parameter by default
258                     */
259    
260                                    adjustWithRespectTo(t2);
261                            }
262                    } catch (NotBoundException e) {
263                            /**
264                 * Ignore this
265                 */
266                    }
267    
268            }
269    
270            public void viewUpdated(View v, Graphics2D g2) {
271                    if (policy == ConstraintPolicy.VISUALIZE) {
272                            System.out.println(this.getClass() + ".viewUpdated not implemented yet");
273    
274                    }
275            }
276    
277            public void enableEditFirst() {
278                    t1.setEditable(true);
279                    t2.setEditable(false);
280            }
281    
282            public Color getColor() {
283                    return color;
284            }
285    
286            public void setColor(Color c) {
287                    color = c;
288    
289            }
290    
291            /**
292         * @return Returns the allowSlack.
293         */
294            public boolean isSlackAllowed() {
295                    return slackAllowed;
296            }
297    
298            /**
299         * @param allowSlack
300         *            The allowSlack to set.
301         */
302            public void setSlackAllowed(boolean allowSlack) {
303                    this.slackAllowed = allowSlack;
304            }
305    
306            /**
307         * @return Returns the t1.
308         */
309            public TimeParameter getT1() {
310                    return t1;
311            }
312    
313            /**
314         * @return Returns the t2.
315         */
316            public TimeParameter getT2() {
317                    return t2;
318            }
319    
320            public List<Class<? extends View>> getApplicableViews() {
321                    if (applicableViews == null) {
322                            applicableViews = new ArrayList<Class<? extends View>>();
323                            applicableViews.add(AbstractView.class);
324                    }
325                    return applicableViews;
326            }
327    
328            public boolean isApplicableFor(View view) {
329                    return false;
330            }
331    
332            public void setActive(boolean active) {
333                    // TODO Auto-generated method stub
334    
335            }
336    
337            public boolean isActive() {
338                    // TODO Auto-generated method stub
339                    return false;
340            }
341    
342            /*
343         * (non-Javadoc)
344         * 
345         * @see se.liu.ida.critiquer.constraints.VisualConstraint#addStatusListener(se.liu.ida.critiquer.constraints.ConstraintStatusListener)
346         */
347            public void addStatusListener(ConstraintStatusListener l) {
348                    // TODO Auto-generated method stub
349    
350            }
351    
352            /*
353         * (non-Javadoc)
354         * 
355         * @see se.liu.ida.critiquer.constraints.VisualConstraint#removeStatusListener(se.liu.ida.critiquer.constraints.ConstraintStatusListener)
356         */
357            public void removeStatusListener(ConstraintStatusListener l) {
358                    // TODO Auto-generated method stub
359    
360            }
361    
362    }