001    package net.sf.cpsolver.coursett.model;
002    
003    import java.text.SimpleDateFormat;
004    import java.util.BitSet;
005    import java.util.Enumeration;
006    import java.util.Locale;
007    
008    import net.sf.cpsolver.coursett.Constants;
009    import net.sf.cpsolver.ifs.util.ToolBox;
010    
011    /**
012     * Time part of placement.
013     *
014     * @version
015     * CourseTT 1.1 (University Course Timetabling)<br>
016     * Copyright (C) 2006 Tomáš Müller<br>
017     * <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
018     * Lazenska 391, 76314 Zlin, Czech Republic<br>
019     * <br>
020     * This library is free software; you can redistribute it and/or
021     * modify it under the terms of the GNU Lesser General Public
022     * License as published by the Free Software Foundation; either
023     * version 2.1 of the License, or (at your option) any later version.
024     * <br><br>
025     * This library is distributed in the hope that it will be useful,
026     * but WITHOUT ANY WARRANTY; without even the implied warranty of
027     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
028     * Lesser General Public License for more details.
029     * <br><br>
030     * You should have received a copy of the GNU Lesser General Public
031     * License along with this library; if not, write to the Free Software
032     * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
033     */
034    
035    public class TimeLocation {
036            private static SimpleDateFormat sDateFormatShort = new SimpleDateFormat("MM/dd", Locale.US);
037        private int iStartSlot;
038        
039        private int iPreference;
040        private double iNormalizedPreference;
041        
042        private Long iTimePatternId = null;
043        private int iHashCode;
044    
045        private int iDayCode;
046        private int iLength;
047        private int iNrMeetings;
048        private int iBreakTime;
049        
050        private BitSet iWeekCode;
051        private Long iDatePatternId = null;
052        private String iDatePatternName = null;
053        
054        /** Constructor
055         * @param dayCode days (combination of 1 for Monday, 2 for Tuesday, ...)
056         * @param startTime start slot
057         * @param length number of slots
058         * @param pref time preference
059         */
060        public TimeLocation(int dayCode, int startTime, int length, int pref, double normPref, Long datePatternId, String datePatternName, BitSet weekCode, int breakTime) {
061            iPreference = pref;
062            iNormalizedPreference = normPref;
063            iStartSlot = startTime;
064            iDayCode = dayCode;
065            iLength = length;
066            iBreakTime = breakTime;
067            iNrMeetings = 0;
068            for (int i=0;i<Constants.DAY_CODES.length;i++) {
069                if ((iDayCode & Constants.DAY_CODES[i])==0) continue;
070                iNrMeetings++;
071            }
072            iHashCode = combine(combine(iDayCode, iStartSlot),iLength);
073            iDatePatternName = datePatternName;
074            iWeekCode = weekCode;
075            iDatePatternId = datePatternId;
076            if (iDatePatternName==null) iDatePatternName = "not set";
077            if (iWeekCode==null) {
078                    iWeekCode = new BitSet(366);
079                    for (int i=0;i<=365;i++)
080                            iWeekCode.set(i);
081            }
082        }
083        
084        /** Number of meetings */
085        public int getNrMeetings() {
086            return iNrMeetings;
087        }
088        
089        public int getBreakTime() {
090            return iBreakTime;
091        }
092    
093        private static int combine(int a, int b) {
094            int ret = 0;
095            for (int i=0;i<15;i++) ret = ret | ((a & (1<<i))<<i) | ((b & (1<<i))<<(i+1));
096            return ret;
097        }
098        
099        /** Days (combination of 1 for Monday, 2 for Tuesday, ...) */
100        public int getDayCode() { return iDayCode; }
101        /** Days for printing purposes */
102        public String getDayHeader() { 
103            StringBuffer sb = new StringBuffer();
104            for (int i=0;i<Constants.DAY_CODES.length;i++)
105                if ((iDayCode & Constants.DAY_CODES[i])!=0)
106                    sb.append(Constants.DAY_NAMES_SHORT[i]);
107            return sb.toString(); 
108        }
109        /** Start time for printing purposes */
110        public String getStartTimeHeader() { 
111            int min = iStartSlot * Constants.SLOT_LENGTH_MIN + Constants.FIRST_SLOT_TIME_MIN;
112            int h = min/60;
113            int m = min%60;
114            return (h>12?h-12:h)+":"+(m<10?"0":"")+m+(h>=12?"p":"a");
115        }
116        /** End time for printing purposes */
117        public String getEndTimeHeader() { 
118            int min = (iStartSlot + iLength) * Constants.SLOT_LENGTH_MIN + Constants.FIRST_SLOT_TIME_MIN - getBreakTime();
119            int m = min % 60;
120            int h = min / 60;
121            return (h>12?h-12:h)+":"+(m<10?"0":"")+m+(h>=12?"p":"a");
122        }
123        /** End time for printing purposes */
124        public String getEndTimeHeaderNoAdj() { 
125            int min = (iStartSlot + iLength) * Constants.SLOT_LENGTH_MIN + Constants.FIRST_SLOT_TIME_MIN;
126            int m = min % 60;
127            int h = min / 60;
128            return (h>12?h-12:h)+":"+(m<10?"0":"")+m+(h>=12?"p":"a");
129        }
130        /** Start slot */
131        public int getStartSlot() { return iStartSlot; }
132        /** Used slots in a day (combination of 1..first, 2..second,...) */
133        
134        /** true if days overlap */
135        public boolean shareDays(TimeLocation anotherLocation) {
136            return ((iDayCode & anotherLocation.iDayCode)!=0);
137        }
138        /** number of overlapping days */
139        public int nrSharedDays(TimeLocation anotherLocation) {
140            int ret=0;
141            for (int i=0;i<Constants.NR_DAYS;i++) {
142                    if ((iDayCode & Constants.DAY_CODES[i])==0) continue;
143                    if ((anotherLocation.iDayCode & Constants.DAY_CODES[i])==0) continue;
144                    ret++;
145            }
146            return ret;
147        }
148        /** true if hours overlap */
149        public boolean shareHours(TimeLocation anotherLocation) {
150            return (iStartSlot+iLength > anotherLocation.iStartSlot) && (anotherLocation.iStartSlot+anotherLocation.iLength > iStartSlot);
151        }
152        /** number of overlapping days */
153        public int nrSharedHours(TimeLocation anotherLocation) {
154            int end = Math.min(iStartSlot+iLength, anotherLocation.iStartSlot+anotherLocation.iLength);
155            int start = Math.max(iStartSlot, anotherLocation.iStartSlot);
156            return (end<start?0:end-start);
157        }
158        /** true if weeks overlap */
159        public boolean shareWeeks(TimeLocation anotherLocation) {
160            return iWeekCode.intersects(anotherLocation.iWeekCode);
161        }
162        /** true if weeks overlap */
163        public boolean shareWeeks(BitSet weekCode) {
164            return iWeekCode.intersects(weekCode);
165        }
166        public boolean hasDay(int day) {
167            return iWeekCode.get(day);
168        }
169        /** true if overlap */
170        public boolean hasIntersection(TimeLocation anotherLocation) {
171            return shareDays(anotherLocation) && shareHours(anotherLocation) && shareWeeks(anotherLocation);
172        }
173    
174        /** Used slots */
175        public IntEnumeration getSlots() { return new SlotsEnum(); }
176        /** Used start slots (for each meeting) */
177        public IntEnumeration getStartSlots() { return new StartSlotsEnum(); }
178        /** Days */
179        public IntEnumeration getDays() { return new DaysEnum(); }
180        public int[] getDaysArray() {
181            int[] days = new int[getNrMeetings()];
182            int i = 0;
183            for (IntEnumeration e=getDays();e.hasMoreElements();)
184                    days[i++] = e.nextInt();
185            return days;
186        }
187        
188        /** Text representation */
189        public String getName() { return getDayHeader()+" "+getStartTimeHeader(); }
190        public String getLongName() { return getDayHeader()+" "+getStartTimeHeader()+" - "+getEndTimeHeader()+" "+getDatePatternName(); }
191        public String getLongNameNoAdj() { return getDayHeader()+" "+getStartTimeHeader()+" - "+getEndTimeHeaderNoAdj()+" "+getDatePatternName(); }
192        /** Preference */
193        public int getPreference() { return iPreference; }
194        public void setPreference(int preference) { iPreference = preference; }
195        /** Length */
196        public int getLength() { return iLength; }
197        /** Length */
198        public int getNrSlotsPerMeeting() { return iLength; }
199        /** Normalized preference */
200        public double getNormalizedPreference() { return iNormalizedPreference;}
201        public void setNormalizedPreference(double normalizedPreference) { iNormalizedPreference = normalizedPreference; }
202        /** Time pattern model (can be null) */
203        public Long getTimePatternId() { return iTimePatternId; }
204        public Long getDatePatternId() { return iDatePatternId; }
205        public void setTimePatternId(Long timePatternId) { iTimePatternId = timePatternId; }
206        public BitSet getWeekCode() { return iWeekCode; }
207        public String getDatePatternName() { return iDatePatternName; }
208        public void setDatePattern(Long datePatternId, String datePatternName, BitSet weekCode) {
209            iDatePatternId = datePatternId;
210            iDatePatternName = datePatternName;
211            iWeekCode = weekCode;
212        }
213        
214        public String toString() { return getName()+" ("+iNormalizedPreference+")"; }
215        public int hashCode() {
216            return iHashCode;
217        }
218        
219        /** Integer enumeration */
220        public interface IntEnumeration extends Enumeration {
221            public int nextInt();
222        }
223        
224        private class StartSlotsEnum implements IntEnumeration {
225            int day = -1;
226            boolean hasNext = false;
227            private StartSlotsEnum() {
228                    hasNext = nextDay();
229            }
230            boolean nextDay() {
231                    do {
232                            day++;
233                            if (day>=Constants.DAY_CODES.length) return false;
234                    } while ((Constants.DAY_CODES[day]&iDayCode)==0);
235                    return true;
236            }
237            public boolean hasMoreElements() {
238                    return hasNext;
239            }
240            public Object nextElement() {
241                    return new Integer(nextInt());
242            }
243            public int nextInt() {
244                    int slot = (day*Constants.SLOTS_PER_DAY)+iStartSlot;
245                    hasNext = nextDay();
246                    return slot;
247            }
248        }
249        private class DaysEnum extends StartSlotsEnum {
250            private DaysEnum() {
251                    super();
252            }
253            public int nextInt() {
254                    int ret = day;
255                    hasNext = nextDay();
256                    return ret;
257            }
258        }
259        private class SlotsEnum extends StartSlotsEnum {
260            int pos = 0;
261            private SlotsEnum() {
262                    super();
263            }
264            private boolean nextSlot() {
265                    if (pos+1<iLength) {
266                            pos++; return true;
267                    }
268                    if (nextDay()) {
269                            pos = 0; return true;
270                    }
271                    return false;
272            }
273            public int nextInt() {
274                    int slot = (day*Constants.SLOTS_PER_DAY)+iStartSlot+pos;
275                    hasNext = nextSlot();
276                    return slot;
277            }
278        }
279    
280        public boolean equals(Object o) {
281            if (o==null || !(o instanceof TimeLocation)) return false;
282            TimeLocation t = (TimeLocation)o;
283            if (getStartSlot()!=t.getStartSlot()) return false;
284            if (getLength()!=t.getLength()) return false;
285            if (getDayCode()!=t.getDayCode()) return false;
286            return ToolBox.equals(getTimePatternId(),t.getTimePatternId()) && ToolBox.equals(getDatePatternId(),t.getDatePatternId());
287        }
288        
289        public int getNrWeeks() {
290            return getNrWeeks(0, iWeekCode.size()-1);
291        }
292        
293        public int getNrWeeks(int startDay, int endDay) {
294            /*
295            BitSet x = new BitSet(1+(endDay-startDay)/Constants.NR_DAYS);
296            for (int i=iWeekCode.nextSetBit(startDay); i<=endDay && i>=0; i=iWeekCode.nextSetBit(i+1))
297                    x.set((i-startDay)/Constants.NR_DAYS);
298            return x.cardinality();
299            */
300            int card = iWeekCode.get(startDay, endDay).cardinality();
301            if (card==0) return 0;
302            if (card<=7) return 1;
303            return (5+card)/6;
304        }
305    }