    001package net.sf.cpsolver.coursett.model;
    002
    003import java.util.BitSet;
    004import java.util.Enumeration;
    005
    006import net.sf.cpsolver.coursett.Constants;
    007import net.sf.cpsolver.ifs.util.ToolBox;
    008
    009/**
    010 * Time part of placement.
    011 *
    012 * @version CourseTT 1.2 (University Course Timetabling)<br>
    013 *          Copyright (C) 2006 - 2010 Tomáš Müller<br>
    014 *          <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
    015 *          <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
    016 * <br>
    017 *          This library is free software; you can redistribute it and/or modify
    018 *          it under the terms of the GNU Lesser General Public License as
    019 *          published by the Free Software Foundation; either version 3 of the
    020 *          License, or (at your option) any later version. <br>
    021 * <br>
    022 *          This library is distributed in the hope that it will be useful, but
    023 *          WITHOUT ANY WARRANTY; without even the implied warranty of
    024 *          MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    025 *          Lesser General Public License for more details. <br>
    026 * <br>
    027 *          You should have received a copy of the GNU Lesser General Public
    028 *          License along with this library; if not see
    029 *          <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>.
    030 */
    031
    032public class TimeLocation {
    033    private int iStartSlot;
    034
    035    private int iPreference;
    036    private double iNormalizedPreference;
    037
    038    private Long iTimePatternId = null;
    039    private int iHashCode;
    040
    041    private int iDayCode;
    042    private int iLength;
    043    private int iNrMeetings;
    044    private int iBreakTime;
    045
    046    private BitSet iWeekCode;
    047    private Long iDatePatternId = null;
    048    private String iDatePatternName = null;
    049    private int iDatePreference;
    050
    051    /**
    052     * Constructor
    053     *
    054     * @param dayCode
    055     *            days (combination of 1 for Monday, 2 for Tuesday, ...)
    056     * @param startTime
    057     *            start slot
    058     * @param length
    059     *            number of slots
    060     * @param pref
    061     *            time preference
    062     */
    063    public TimeLocation(int dayCode, int startTime, int length, int pref, double normPref, int datePatternPreference,
    064            Long datePatternId, String datePatternName, BitSet weekCode, int breakTime) {
    065        iPreference = pref;
    066        iNormalizedPreference = normPref;
    067        iStartSlot = startTime;
    068        iDayCode = dayCode;
    069        iLength = length;
    070        iBreakTime = breakTime;
    071        iNrMeetings = 0;
    072        for (int i = 0; i < Constants.DAY_CODES.length; i++) {
    073            if ((iDayCode & Constants.DAY_CODES[i]) == 0)
    074                continue;
    075            iNrMeetings++;
    076        }
    077        iHashCode = combine(combine(iDayCode, iStartSlot), iLength);
    078        iDatePatternName = datePatternName;
    079        iWeekCode = weekCode;
    080        iDatePatternId = datePatternId;
    081        if (iDatePatternName == null)
    082            iDatePatternName = "not set";
    083        iDatePreference = datePatternPreference;
    084        if (iWeekCode == null) {
    085            iWeekCode = new BitSet(366);
    086            for (int i = 0; i <= 365; i++)
    087                iWeekCode.set(i);
    088        }
    089    }
    090
    091    public TimeLocation(int dayCode, int startTime, int length, int pref, double normPref, Long datePatternId,
    092            String datePatternName, BitSet weekCode, int breakTime) {
    093        this(dayCode, startTime, length, pref, normPref, 0, datePatternId, datePatternName, weekCode, breakTime);
    094    }
    095
    096    /** Number of meetings */
    097    public int getNrMeetings() {
    098        return iNrMeetings;
    099    }
    100
    101    public int getBreakTime() {
    102        return iBreakTime;
    103    }
    104
    105    public void setBreakTime(int breakTime) {
    106        iBreakTime = breakTime;
    107    }
    108
    109    private static int combine(int a, int b) {
    110        int ret = 0;
    111        for (int i = 0; i < 15; i++)
    112            ret = ret | ((a & (1 << i)) << i) | ((b & (1 << i)) << (i + 1));
    113        return ret;
    114    }
    115
    116    /** Days (combination of 1 for Monday, 2 for Tuesday, ...) */
    117    public int getDayCode() {
    118        return iDayCode;
    119    }
    120
    121    /** Days for printing purposes */
    122    public String getDayHeader() {
    123        StringBuffer sb = new StringBuffer();
    124        for (int i = 0; i < Constants.DAY_CODES.length; i++)
    125            if ((iDayCode & Constants.DAY_CODES[i]) != 0)
    126                sb.append(Constants.DAY_NAMES_SHORT[i]);
    127        return sb.toString();
    128    }
    129
    130    /** Start time for printing purposes */
    131    public String getStartTimeHeader() {
    132        int min = iStartSlot * Constants.SLOT_LENGTH_MIN + Constants.FIRST_SLOT_TIME_MIN;
    133        int h = min / 60;
    134        int m = min % 60;
    135        return (h > 12 ? h - 12 : h) + ":" + (m < 10 ? "0" : "") + m + (h >= 12 ? "p" : "a");
    136    }
    137
    138    /** End time for printing purposes */
    139    public String getEndTimeHeader() {
    140        int min = (iStartSlot + iLength) * Constants.SLOT_LENGTH_MIN + Constants.FIRST_SLOT_TIME_MIN - getBreakTime();
    141        int m = min % 60;
    142        int h = min / 60;
    143        return (h > 12 ? h - 12 : h) + ":" + (m < 10 ? "0" : "") + m + (h >= 12 ? "p" : "a");
    144    }
    145
    146    /** End time for printing purposes */
    147    public String getEndTimeHeaderNoAdj() {
    148        int min = (iStartSlot + iLength) * Constants.SLOT_LENGTH_MIN + Constants.FIRST_SLOT_TIME_MIN;
    149        int m = min % 60;
    150        int h = min / 60;
    151        return (h > 12 ? h - 12 : h) + ":" + (m < 10 ? "0" : "") + m + (h >= 12 ? "p" : "a");
    152    }
    153
    154    /** Start slot */
    155    public int getStartSlot() {
    156        return iStartSlot;
    157    }
    158
    159    /** Used slots in a day (combination of 1..first, 2..second,...) */
    160
    161    /** true if days overlap */
    162    public boolean shareDays(TimeLocation anotherLocation) {
    163        return ((iDayCode & anotherLocation.iDayCode) != 0);
    164    }
    165
    166    /** number of overlapping days */
    167    public int nrSharedDays(TimeLocation anotherLocation) {
    168        int ret = 0;
    169        for (int i = 0; i < Constants.NR_DAYS; i++) {
    170            if ((iDayCode & Constants.DAY_CODES[i]) == 0)
    171                continue;
    172            if ((anotherLocation.iDayCode & Constants.DAY_CODES[i]) == 0)
    173                continue;
    174            ret++;
    175        }
    176        return ret;
    177    }
    178
    179    /** true if hours overlap */
    180    public boolean shareHours(TimeLocation anotherLocation) {
    181        return (iStartSlot + iLength > anotherLocation.iStartSlot)
    182                && (anotherLocation.iStartSlot + anotherLocation.iLength > iStartSlot);
    183    }
    184
    185    /** number of overlapping time slots (ignoring days) */
    186    public int nrSharedHours(TimeLocation anotherLocation) {
    187        int end = Math.min(iStartSlot + iLength, anotherLocation.iStartSlot + anotherLocation.iLength);
    188        int start = Math.max(iStartSlot, anotherLocation.iStartSlot);
    189        return (end < start ? 0 : end - start);
    190    }
    191
    192    /** true if weeks overlap */
    193    public boolean shareWeeks(TimeLocation anotherLocation) {
    194        return iWeekCode.intersects(anotherLocation.iWeekCode);
    195    }
    196
    197    /** true if weeks overlap */
    198    public boolean shareWeeks(BitSet weekCode) {
    199        return iWeekCode.intersects(weekCode);
    200    }
    201
    202    public boolean hasDay(int day) {
    203        return iWeekCode.get(day);
    204    }
    205
    206    /** true if overlap */
    207    public boolean hasIntersection(TimeLocation anotherLocation) {
    208        return shareDays(anotherLocation) && shareHours(anotherLocation) && shareWeeks(anotherLocation);
    209    }
    210
    211    /** Used slots */
    212    public IntEnumeration getSlots() {
    213        return new SlotsEnum();
    214    }
    215
    216    /** Used start slots (for each meeting) */
    217    public IntEnumeration getStartSlots() {
    218        return new StartSlotsEnum();
    219    }
    220
    221    /** Days */
    222    public IntEnumeration getDays() {
    223        return new DaysEnum();
    224    }
    225
    226    private int[] iDaysCache = null;
    227    public int[] getDaysArray() {
    228        if (iDaysCache == null) {
    229            iDaysCache = new int[getNrMeetings()];
    230            int i = 0;
    231            for (Enumeration<Integer> e = getDays(); e.hasMoreElements();)
    232                iDaysCache[i++] = e.nextElement();
    233        }
    234        return iDaysCache;
    235    }
    236
    237    /** Text representation */
    238    public String getName() {
    239        return getDayHeader() + " " + getStartTimeHeader();
    240    }
    241
    242    public String getLongName() {
    243        return getDayHeader() + " " + getStartTimeHeader() + " - " + getEndTimeHeader() + " " + getDatePatternName();
    244    }
    245
    246    public String getLongNameNoAdj() {
    247        return getDayHeader() + " " + getStartTimeHeader() + " - " + getEndTimeHeaderNoAdj() + " "
    248                + getDatePatternName();
    249    }
    250
    251    /** Preference */
    252    public int getPreference() {
    253        return iPreference;
    254    }
    255
    256    public void setPreference(int preference) {
    257        iPreference = preference;
    258    }
    259
    260    /** Length */
    261    public int getLength() {
    262        return iLength;
    263    }
    264
    265    /** Length */
    266    public int getNrSlotsPerMeeting() {
    267        return iLength;
    268    }
    269
    270    /** Normalized preference */
    271    public double getNormalizedPreference() {
    272        return iNormalizedPreference;
    273    }
    274
    275    public void setNormalizedPreference(double normalizedPreference) {
    276        iNormalizedPreference = normalizedPreference;
    277    }
    278
    279    /** Time pattern model (can be null) */
    280    public Long getTimePatternId() {
    281        return iTimePatternId;
    282    }
    283
    284    public Long getDatePatternId() {
    285        return iDatePatternId;
    286    }
    287
    288    public void setTimePatternId(Long timePatternId) {
    289        iTimePatternId = timePatternId;
    290    }
    291
    292    public BitSet getWeekCode() {
    293        return iWeekCode;
    294    }
    295
    296    public String getDatePatternName() {
    297        return iDatePatternName;
    298    }
    299
    300    public void setDatePattern(Long datePatternId, String datePatternName, BitSet weekCode) {
    301        iDatePatternId = datePatternId;
    302        iDatePatternName = datePatternName;
    303        iWeekCode = weekCode;
    304    }
    305
    306    public int getDatePatternPreference() {
    307        return iDatePreference;
    308    }
    309
    310    @Override
    311    public String toString() {
    312        return getName() + " (" + iNormalizedPreference + ")";
    313    }
    314
    315    @Override
    316    public int hashCode() {
    317        return iHashCode;
    318    }
    319
    320    private class StartSlotsEnum implements IntEnumeration {
    321        int day = -1;
    322        boolean hasNext = false;
    323
    324        private StartSlotsEnum() {
    325            hasNext = nextDay();
    326        }
    327
    328        boolean nextDay() {
    329            do {
    330                day++;
    331                if (day >= Constants.DAY_CODES.length)
    332                    return false;
    333            } while ((Constants.DAY_CODES[day] & iDayCode) == 0);
    334            return true;
    335        }
    336
    337        @Override
    338        public boolean hasMoreElements() {
    339            return hasNext;
    340        }
    341
    342        @Override
    343        public Integer nextElement() {
    344            int slot = (day * Constants.SLOTS_PER_DAY) + iStartSlot;
    345            hasNext = nextDay();
    346            return slot;
    347        }
    348
    349        @Deprecated
    350        @Override
    351        public Integer nextInt() {
    352            return nextElement();
    353        }
    354    }
    355
    356    private class DaysEnum extends StartSlotsEnum {
    357        private DaysEnum() {
    358            super();
    359        }
    360
    361        @Override
    362        public Integer nextElement() {
    363            int ret = day;
    364            hasNext = nextDay();
    365            return ret;
    366        }
    367    }
    368
    369    private class SlotsEnum extends StartSlotsEnum {
    370        int pos = 0;
    371
    372        private SlotsEnum() {
    373            super();
    374        }
    375
    376        private boolean nextSlot() {
    377            if (pos + 1 < iLength) {
    378                pos++;
    379                return true;
    380            }
    381            if (nextDay()) {
    382                pos = 0;
    383                return true;
    384            }
    385            return false;
    386        }
    387
    388        @Override
    389        public Integer nextElement() {
    390            int slot = (day * Constants.SLOTS_PER_DAY) + iStartSlot + pos;
    391            hasNext = nextSlot();
    392            return slot;
    393        }
    394    }
    395
    396    @Override
    397    public boolean equals(Object o) {
    398        if (o == null || !(o instanceof TimeLocation))
    399            return false;
    400        TimeLocation t = (TimeLocation) o;
    401        if (getStartSlot() != t.getStartSlot())
    402            return false;
    403        if (getLength() != t.getLength())
    404            return false;
    405        if (getDayCode() != t.getDayCode())
    406            return false;
    407        return ToolBox.equals(getTimePatternId(), t.getTimePatternId())
    408                && ToolBox.equals(getDatePatternId(), t.getDatePatternId());
    409    }
    410
    411    public int getNrWeeks() {
    412        return getNrWeeks(0, iWeekCode.size() - 1);
    413    }
    414
    415    public int getNrWeeks(int startDay, int endDay) {
    416        /*
    417         * BitSet x = new BitSet(1+(endDay-startDay)/Constants.NR_DAYS); for
    418         * (int i=iWeekCode.nextSetBit(startDay); i<=endDay && i>=0;
    419         * i=iWeekCode.nextSetBit(i+1)) x.set((i-startDay)/Constants.NR_DAYS);
    420         * return x.cardinality();
    421         */
    422        int card = iWeekCode.get(startDay, endDay).cardinality();
    423        if (card == 0)
    424            return 0;
    425        if (card <= 7)
    426            return 1;
    427        return (5 + card) / 6;
    428    }
    429
    430    public interface IntEnumeration extends Enumeration<Integer> {
    431        @Deprecated
    432        public Integer nextInt();
    433    }
    434
    435    private Integer iFirstMeeting = null;
    436    public int getFirstMeeting(int dayOfWeekOffset) {
    437        if (iFirstMeeting == null) {
    438            int idx = -1;
    439            while ((idx = getWeekCode().nextSetBit(1 + idx)) >= 0) {
    440                int dow = (idx + dayOfWeekOffset) % 7;
    441                if ((getDayCode() & Constants.DAY_CODES[dow]) != 0) break;
    442            }
    443            iFirstMeeting = idx;
    444        }
    445        return iFirstMeeting;
    446    }
    447}