diff --git a/opentasks/build.gradle b/opentasks/build.gradle index 9b5884782..73aa5cfa0 100644 --- a/opentasks/build.gradle +++ b/opentasks/build.gradle @@ -46,6 +46,7 @@ android { dependencies { implementation project(':opentasks-provider') + implementation project(':opentaskspal') implementation 'com.android.support:appcompat-v7:' + SUPPORT_LIBRARY_VERSION implementation 'com.android.support:design:' + SUPPORT_LIBRARY_VERSION implementation('org.dmfs:android-xml-magic:0.1.1') { diff --git a/opentasks/src/main/java/org/dmfs/tasks/model/ContentSet.java b/opentasks/src/main/java/org/dmfs/tasks/model/ContentSet.java index 1e32f0f21..3784b960c 100644 --- a/opentasks/src/main/java/org/dmfs/tasks/model/ContentSet.java +++ b/opentasks/src/main/java/org/dmfs/tasks/model/ContentSet.java @@ -24,6 +24,7 @@ import android.os.Parcelable; import android.util.Log; +import org.dmfs.opentaskspal.jems.procedure.Procedure; import org.dmfs.tasks.utils.AsyncContentLoader; import org.dmfs.tasks.utils.ContentValueMapper; import org.dmfs.tasks.utils.OnContentLoadedListener; @@ -523,6 +524,28 @@ public void finishBulkUpdate() } + /** + * Execute a bulk update for this {@link ContentSet}. + *

+ * Shortcut for using {@link #startBulkUpdate()} and {@link #finishBulkUpdate()}. + * + * @param body + * do the changes to {@link ContentSet} here + */ + public void bulkUpdate(Procedure body) + { + startBulkUpdate(); + try + { + body.process(this); + } + finally + { + finishBulkUpdate(); + } + } + + /** * Remove the value with the given key from the ContentSet. This is actually replacing the value by null. * diff --git a/opentasks/src/main/java/org/dmfs/tasks/model/adapters/CombinedDateTimeFieldAdapter.java b/opentasks/src/main/java/org/dmfs/tasks/model/adapters/CombinedDateTimeFieldAdapter.java new file mode 100644 index 000000000..6a5c31dc6 --- /dev/null +++ b/opentasks/src/main/java/org/dmfs/tasks/model/adapters/CombinedDateTimeFieldAdapter.java @@ -0,0 +1,152 @@ +/* + * Copyright 2017 dmfs GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.dmfs.tasks.model.adapters; + +import android.content.ContentValues; +import android.database.Cursor; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import org.dmfs.opentaskspal.datetime.CombinedDateTime; +import org.dmfs.opentaskspal.datetime.general.OptionalTimeZone; +import org.dmfs.opentaskspal.datetimefields.DateTimeFields; +import org.dmfs.opentaskspal.datetimefields.adapters.DateTimeDateTimeFields; +import org.dmfs.opentaskspal.readdata.cursor.CursorCombinedDateTime; +import org.dmfs.opentaskspal.utils.binarybooleans.BinaryIntBoolean; +import org.dmfs.rfc5545.DateTime; +import org.dmfs.tasks.model.ContentSet; +import org.dmfs.tasks.model.OnContentChangeListener; +import org.dmfs.tasks.model.datetime.ContentSetCombinedDateTime; +import org.dmfs.tasks.model.datetime.ContentSetDateTimeFields; + + +/** + * Knows how to load and store combined date-time values as {@link DateTime} from/to {@link Cursor}, {@link ContentSet}, {@link ContentValues}. + *

+ * Combined date-time values are stored as three values: timestamp, time zone, all-day flag + * + * @author Gabor Keszthelyi + */ +public final class CombinedDateTimeFieldAdapter extends FieldAdapter +{ + private final String mTimestampField; + private final String mTimeZoneField; + private final String mIsAllDayField; + + + public CombinedDateTimeFieldAdapter(@NonNull String timestampField, + @NonNull String timeZoneField, + @NonNull String isAllDayField) + { + mTimestampField = timestampField; + mTimeZoneField = timeZoneField; + mIsAllDayField = isAllDayField; + } + + + @Override + public DateTime get(@NonNull ContentSet values) + { + return new ContentSetCombinedDateTime(values, mTimestampField, mTimeZoneField, mIsAllDayField).value(null); + } + + + @Override + public DateTime get(@NonNull Cursor cursor) + { + return new CursorCombinedDateTime(cursor, mTimestampField, mTimeZoneField, mIsAllDayField).value(null); + } + + + /** + * Provides a default value for 'now' that respects the time zone and all-day values if they've been already selected + * (i.e. they are present in the provided {@link ContentSet}. + *

+ *

  • If all-day flag is true, an all-day {@link DateTime} for today is returned.
  • + *
  • If time zone is provided, a {@link DateTime} for the current time instance with that time zone is returned
  • + *
  • If time zone is not provided, a floating {@link DateTime} for the current time instance is returned
  • + *

    + * Note: it is up to the presentation layer to apply local time zone when needed + */ + @Override + public DateTime getDefault(@NonNull ContentSet values) + { + DateTimeFields fields = new ContentSetDateTimeFields(values, mTimestampField, mTimeZoneField, mIsAllDayField); + return new CombinedDateTime(System.currentTimeMillis(), + new OptionalTimeZone(fields.timeZoneId()), + new BinaryIntBoolean(fields.isAllDay())).value(); + } + + + @Override + public void set(@NonNull ContentSet values, @Nullable DateTime dateTime) + { + values.bulkUpdate(contentSet -> + { + if (dateTime != null) + { + DateTimeFields dateTimeFields = new DateTimeDateTimeFields(dateTime); + values.put(mTimestampField, dateTimeFields.timestamp()); + values.put(mTimeZoneField, dateTimeFields.timeZoneId()); + values.put(mIsAllDayField, dateTimeFields.isAllDay()); + } + else + { + // write timestamp only, other fields may still use all-day and timezone + values.put(mTimestampField, (Long) null); + } + }); + } + + + @Override + public void set(@NonNull ContentValues values, @Nullable DateTime dateTime) + { + // TODO Remove semantic code duplication with the above + // Update type with isNewValue(), isClear(), isNoChange(), T newValue() might help here + if (dateTime != null) + { + DateTimeFields dateTimeFields = new DateTimeDateTimeFields(dateTime); + values.put(mTimestampField, dateTimeFields.timestamp()); + values.put(mTimeZoneField, dateTimeFields.timeZoneId()); + values.put(mIsAllDayField, dateTimeFields.isAllDay()); + } + else + { + // write timestamp only, other fields may still use all-day and timezone + values.put(mTimestampField, (Long) null); + } + } + + + @Override + public void registerListener(ContentSet values, OnContentChangeListener listener, boolean initalNotification) + { + values.addOnChangeListener(listener, mTimestampField, initalNotification); + values.addOnChangeListener(listener, mTimeZoneField, initalNotification); + values.addOnChangeListener(listener, mIsAllDayField, initalNotification); + } + + + @Override + public void unregisterListener(ContentSet values, OnContentChangeListener listener) + { + values.removeOnChangeListener(listener, mTimestampField); + values.removeOnChangeListener(listener, mTimeZoneField); + values.removeOnChangeListener(listener, mIsAllDayField); + } +} diff --git a/opentasks/src/main/java/org/dmfs/tasks/model/adapters/TimestampDateTimeFieldAdapter.java b/opentasks/src/main/java/org/dmfs/tasks/model/adapters/TimestampDateTimeFieldAdapter.java new file mode 100644 index 000000000..7b4e54854 --- /dev/null +++ b/opentasks/src/main/java/org/dmfs/tasks/model/adapters/TimestampDateTimeFieldAdapter.java @@ -0,0 +1,95 @@ +/* + * Copyright 2017 dmfs GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.dmfs.tasks.model.adapters; + +import android.content.ContentValues; +import android.database.Cursor; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import org.dmfs.opentaskspal.datetime.general.OptionalDateTimeTimestamp; +import org.dmfs.opentaskspal.datetime.general.OptionalTimestampDateTime; +import org.dmfs.opentaskspal.readdata.cursor.LongCursorColumnValue; +import org.dmfs.rfc5545.DateTime; +import org.dmfs.tasks.model.ContentSet; +import org.dmfs.tasks.model.OnContentChangeListener; + + +/** + * Knows how to load and store a {@link DateTime} value that doesn't use timezone and the all-day flag, just the timestamp. + * + * @author Gabor Keszthelyi + */ +public final class TimestampDateTimeFieldAdapter extends FieldAdapter +{ + private final String mTimestampField; + + + public TimestampDateTimeFieldAdapter(@NonNull String timestampField) + { + mTimestampField = timestampField; + } + + + @Override + public DateTime get(@NonNull ContentSet values) + { + return new OptionalTimestampDateTime(values.getAsLong(mTimestampField)).value(null); + } + + + @Override + public DateTime get(@NonNull Cursor cursor) + { + return new OptionalTimestampDateTime(new LongCursorColumnValue(cursor, mTimestampField)).value(null); + } + + + @Override + public DateTime getDefault(@NonNull ContentSet values) + { + return DateTime.now(); + } + + + @Override + public void set(@NonNull ContentSet values, @Nullable DateTime value) + { + values.bulkUpdate(contentSet -> contentSet.put(mTimestampField, new OptionalDateTimeTimestamp(value).value(null))); + } + + + @Override + public void set(@NonNull ContentValues values, @Nullable DateTime value) + { + values.put(mTimestampField, new OptionalDateTimeTimestamp(value).value(null)); + } + + + @Override + public void registerListener(@NonNull ContentSet values, @NonNull OnContentChangeListener listener, boolean initialNotification) + { + values.addOnChangeListener(listener, mTimestampField, initialNotification); + } + + + @Override + public void unregisterListener(@NonNull ContentSet values, @NonNull OnContentChangeListener listener) + { + values.removeOnChangeListener(listener, mTimestampField); + } +} diff --git a/opentasks/src/main/java/org/dmfs/tasks/model/datetime/ContentSetCombinedDateTime.java b/opentasks/src/main/java/org/dmfs/tasks/model/datetime/ContentSetCombinedDateTime.java new file mode 100644 index 000000000..8ddb853de --- /dev/null +++ b/opentasks/src/main/java/org/dmfs/tasks/model/datetime/ContentSetCombinedDateTime.java @@ -0,0 +1,42 @@ +/* + * Copyright 2018 dmfs GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.dmfs.tasks.model.datetime; + +import android.support.annotation.NonNull; + +import org.dmfs.opentaskspal.datetime.OptionalCombinedDateTime; +import org.dmfs.optional.decorators.DelegatingOptional; +import org.dmfs.rfc5545.DateTime; +import org.dmfs.tasks.model.ContentSet; + + +/** + * Combined date-time from a {@link ContentSet}. + * + * @author Gabor Keszthelyi + */ +public final class ContentSetCombinedDateTime extends DelegatingOptional +{ + public ContentSetCombinedDateTime(@NonNull ContentSet contentSet, + @NonNull String timestampKey, + @NonNull String timeZoneKey, + @NonNull String isAllDayKey) + { + super(new OptionalCombinedDateTime( + new ContentSetDateTimeFields(contentSet, timestampKey, timeZoneKey, isAllDayKey))); + } +} diff --git a/opentasks/src/main/java/org/dmfs/tasks/model/datetime/ContentSetDateTimeFields.java b/opentasks/src/main/java/org/dmfs/tasks/model/datetime/ContentSetDateTimeFields.java new file mode 100644 index 000000000..23d8c0e9c --- /dev/null +++ b/opentasks/src/main/java/org/dmfs/tasks/model/datetime/ContentSetDateTimeFields.java @@ -0,0 +1,89 @@ +/* + * Copyright 2018 dmfs GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.dmfs.tasks.model.datetime; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import org.dmfs.opentaskspal.datetimefields.DateTimeFields; +import org.dmfs.opentaskspal.datetimefields.decorators.DelegatingDateTimeFields; +import org.dmfs.opentaskspal.datetimefields.decorators.Evaluated; +import org.dmfs.tasks.model.ContentSet; + + +/** + * {@link DateTimeFields} from a {@link ContentSet}. + * + * @author Gabor Keszthelyi + */ +public final class ContentSetDateTimeFields extends DelegatingDateTimeFields +{ + + public ContentSetDateTimeFields(@NonNull ContentSet contentSet, + @NonNull String timestampKey, + @NonNull String timeZoneKey, + @NonNull String isAllDayKey) + { + // Evaluated eagerly because ContentSet is mutable, values in it may change + super(new Evaluated(new LazyContentSetDateTimeFields(contentSet, timestampKey, timeZoneKey, isAllDayKey))); + } + + + private static final class LazyContentSetDateTimeFields implements DateTimeFields + { + private final ContentSet mContentSet; + private final String mTimestampKey; + private final String mTimeZoneKey; + private final String mIsAllDayKey; + + + public LazyContentSetDateTimeFields(@NonNull ContentSet contentSet, + @NonNull String timestampKey, + @NonNull String timeZoneKey, + @NonNull String isAllDayKey) + { + mContentSet = contentSet; + mTimestampKey = timestampKey; + mTimeZoneKey = timeZoneKey; + mIsAllDayKey = isAllDayKey; + } + + + @Nullable + @Override + public Long timestamp() + { + return mContentSet.getAsLong(mTimestampKey); + } + + + @Nullable + @Override + public String timeZoneId() + { + return mContentSet.getAsString(mTimeZoneKey); + } + + + @Nullable + @Override + public Integer isAllDay() + { + return mContentSet.getAsInteger(mIsAllDayKey); + } + } +} diff --git a/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetime/CombinedDateTime.java b/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetime/CombinedDateTime.java new file mode 100644 index 000000000..941dd2e43 --- /dev/null +++ b/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetime/CombinedDateTime.java @@ -0,0 +1,58 @@ +/* + * Copyright 2018 dmfs GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.dmfs.opentaskspal.datetime; + +import android.support.annotation.NonNull; + +import org.dmfs.jems.single.Single; +import org.dmfs.jems.single.decorators.DelegatingSingle; +import org.dmfs.jems.single.elementary.ValueSingle; +import org.dmfs.optional.Optional; +import org.dmfs.rfc5545.DateTime; +import org.dmfs.tasks.contract.TaskContract; + +import java.util.TimeZone; + + +/** + * {@link Single} for a {@link DateTime} value composed from the three related fields' values (timestamp, timezone, all-day) + * interpreted according to {@link TaskContract}. + * + * @author Gabor Keszthelyi + */ +public final class CombinedDateTime extends DelegatingSingle +{ + public CombinedDateTime(@NonNull Single timestamp, + @NonNull Optional timeZone, + @NonNull Single isAllDay) + { + super(() -> + isAllDay.value() ? + new DateTime(timestamp.value()).toAllDay() + : + // Null time zone means floating time + new DateTime(timeZone.value(null), timestamp.value())); + } + + + public CombinedDateTime(@NonNull Long timestamp, + @NonNull Optional timeZone, + @NonNull Single isAllDay) + { + this(new ValueSingle<>(timestamp), timeZone, isAllDay); + } +} diff --git a/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetime/OptionalCombinedDateTime.java b/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetime/OptionalCombinedDateTime.java new file mode 100644 index 000000000..b4fd86aca --- /dev/null +++ b/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetime/OptionalCombinedDateTime.java @@ -0,0 +1,62 @@ +/* + * Copyright 2018 dmfs GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.dmfs.opentaskspal.datetime; + +import android.support.annotation.NonNull; + +import org.dmfs.jems.optional.decorators.Mapped; +import org.dmfs.jems.single.Single; +import org.dmfs.opentaskspal.datetime.general.OptionalTimeZone; +import org.dmfs.opentaskspal.datetimefields.DateTimeFields; +import org.dmfs.opentaskspal.utils.binarybooleans.BinaryIntBoolean; +import org.dmfs.optional.NullSafe; +import org.dmfs.optional.Optional; +import org.dmfs.optional.decorators.DelegatingOptional; +import org.dmfs.rfc5545.DateTime; +import org.dmfs.tasks.contract.TaskContract; + +import java.util.TimeZone; + + +/** + * {@link Optional} {@link DateTime} value composed from the three related fields' values (timestamp, timezone, all-day) + * interpreted according to {@link TaskContract}. + * + * @author Gabor Keszthelyi + */ +public final class OptionalCombinedDateTime extends DelegatingOptional +{ + + public OptionalCombinedDateTime(Optional timestamp, + Optional timeZone, + Single isAllDay) + { + super(new Mapped<>( + ts -> new CombinedDateTime(ts, timeZone, isAllDay).value(), + timestamp) + ); + } + + + public OptionalCombinedDateTime(@NonNull DateTimeFields fields) + { + this(new NullSafe<>(fields.timestamp()), + new OptionalTimeZone(fields.timeZoneId()), + new BinaryIntBoolean(fields.isAllDay())); + } + +} diff --git a/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetime/general/OptionalDateTimeTimestamp.java b/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetime/general/OptionalDateTimeTimestamp.java new file mode 100644 index 000000000..cc0fc6be2 --- /dev/null +++ b/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetime/general/OptionalDateTimeTimestamp.java @@ -0,0 +1,46 @@ +/* + * Copyright 2018 dmfs GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.dmfs.opentaskspal.datetime.general; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import org.dmfs.jems.optional.decorators.Mapped; +import org.dmfs.optional.NullSafe; +import org.dmfs.optional.Optional; +import org.dmfs.optional.decorators.DelegatingOptional; +import org.dmfs.rfc5545.DateTime; + + +/** + * {@link Optional} for a {@link Long} timestamp of a provided optional {@link DateTime}. + * + * @author Gabor Keszthelyi + */ +public final class OptionalDateTimeTimestamp extends DelegatingOptional +{ + public OptionalDateTimeTimestamp(@NonNull Optional dateTime) + { + super(new Mapped<>(DateTime::getTimestamp, dateTime)); + } + + + public OptionalDateTimeTimestamp(@Nullable DateTime dateTime) + { + this(new NullSafe<>(dateTime)); + } +} diff --git a/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetime/general/OptionalTimeZone.java b/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetime/general/OptionalTimeZone.java new file mode 100644 index 000000000..bd42c3a14 --- /dev/null +++ b/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetime/general/OptionalTimeZone.java @@ -0,0 +1,47 @@ +/* + * Copyright 2018 dmfs GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.dmfs.opentaskspal.datetime.general; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import org.dmfs.jems.optional.decorators.Mapped; +import org.dmfs.optional.NullSafe; +import org.dmfs.optional.Optional; +import org.dmfs.optional.decorators.DelegatingOptional; + +import java.util.TimeZone; + + +/** + * {@link Optional} {@link TimeZone} for an optional time zone id. + * + * @author Gabor Keszthelyi + */ +public final class OptionalTimeZone extends DelegatingOptional +{ + public OptionalTimeZone(@NonNull Optional timeZoneId) + { + super(new Mapped<>(TimeZone::getTimeZone, timeZoneId)); + } + + + public OptionalTimeZone(@Nullable String timeZoneId) + { + this(new NullSafe<>(timeZoneId)); + } +} diff --git a/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetime/general/OptionalTimestampDateTime.java b/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetime/general/OptionalTimestampDateTime.java new file mode 100644 index 000000000..bfaedcdea --- /dev/null +++ b/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetime/general/OptionalTimestampDateTime.java @@ -0,0 +1,49 @@ + +/* + * Copyright 2018 dmfs GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.dmfs.opentaskspal.datetime.general; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import org.dmfs.jems.optional.decorators.Mapped; +import org.dmfs.optional.NullSafe; +import org.dmfs.optional.Optional; +import org.dmfs.optional.decorators.DelegatingOptional; +import org.dmfs.rfc5545.DateTime; + + +/** + * {@link Optional} for {@link DateTime} created from an optional timestamp. + *

    + * UTC is used to interpret pure timestamp date-times. Formatter for UI can convert to local time zone before displaying. + * + * @author Gabor Keszthelyi + */ +public final class OptionalTimestampDateTime extends DelegatingOptional +{ + public OptionalTimestampDateTime(@NonNull Optional timestamp) + { + super(new Mapped<>(ts -> new TimestampDateTime(ts).value(), timestamp)); + } + + + public OptionalTimestampDateTime(@Nullable Long timestamp) + { + this(new NullSafe<>(timestamp)); + } +} \ No newline at end of file diff --git a/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetime/general/TimeZones.java b/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetime/general/TimeZones.java new file mode 100644 index 000000000..1594af47e --- /dev/null +++ b/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetime/general/TimeZones.java @@ -0,0 +1,38 @@ +/* + * Copyright 2018 dmfs GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.dmfs.opentaskspal.datetime.general; + +import java.util.TimeZone; + + +/** + * Time zone constants. + * + * @author Gabor Keszthelyi + */ +public final class TimeZones +{ + public static final TimeZone UTC = TimeZone.getTimeZone("UTC"); + + public static final TimeZone LOCAL = TimeZone.getDefault(); + + + private TimeZones() + { + + } +} diff --git a/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetime/general/TimestampDateTime.java b/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetime/general/TimestampDateTime.java new file mode 100644 index 000000000..89d4ded57 --- /dev/null +++ b/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetime/general/TimestampDateTime.java @@ -0,0 +1,46 @@ +/* + * Copyright 2018 dmfs GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.dmfs.opentaskspal.datetime.general; + +import android.support.annotation.NonNull; + +import org.dmfs.jems.single.Single; +import org.dmfs.jems.single.decorators.DelegatingSingle; +import org.dmfs.jems.single.elementary.ValueSingle; +import org.dmfs.rfc5545.DateTime; + + +/** + * {@link Single} for {@link DateTime} created from a timestamp. + *

    + * UTC is used to interpret pure timestamp date-times. Formatter for UI can convert to local time zone before displaying. + * + * @author Gabor Keszthelyi + */ +public final class TimestampDateTime extends DelegatingSingle +{ + public TimestampDateTime(@NonNull Single timestamp) + { + super(() -> new DateTime(timestamp.value())); + } + + + public TimestampDateTime(@NonNull Long timestamp) + { + this(new ValueSingle<>(timestamp)); + } +} diff --git a/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetimefields/DateTimeFields.java b/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetimefields/DateTimeFields.java new file mode 100644 index 000000000..a51e0c389 --- /dev/null +++ b/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetimefields/DateTimeFields.java @@ -0,0 +1,54 @@ +/* + * Copyright 2018 dmfs GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.dmfs.opentaskspal.datetimefields; + +import android.content.ContentValues; +import android.database.Cursor; +import android.support.annotation.Nullable; + + +/** + * The three values that together represent a combined date-time in the provider. (Start or due date-time in practice.) + * Corresponds one-to-one to the actual values in the database, + * and thus to the values in {@link Cursor}, {@link ContentValues}, and ContentSet. + * + * @author Gabor Keszthelyi + */ +public interface DateTimeFields +{ + + /** + * The timestamp. ({@code null} if it's empty) + */ + @Nullable + Long timestamp(); + + /** + * Time zone id. ({@code null} if it's empty) + */ + @Nullable + String timeZoneId(); + + /** + * All-day flag of the date-time. 1 for true, 0 for false. + *

    + * ({@code null} if it's empty, which is always interpreted as 0-false) + */ + @Nullable + Integer isAllDay(); + +} diff --git a/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetimefields/adapters/CursorDateTimeFields.java b/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetimefields/adapters/CursorDateTimeFields.java new file mode 100644 index 000000000..d3015ba89 --- /dev/null +++ b/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetimefields/adapters/CursorDateTimeFields.java @@ -0,0 +1,91 @@ +/* + * Copyright 2018 dmfs GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.dmfs.opentaskspal.datetimefields.adapters; + +import android.database.Cursor; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import org.dmfs.opentaskspal.datetimefields.DateTimeFields; +import org.dmfs.opentaskspal.datetimefields.decorators.DelegatingDateTimeFields; +import org.dmfs.opentaskspal.datetimefields.decorators.Evaluated; +import org.dmfs.opentaskspal.readdata.cursor.IntCursorColumnValue; +import org.dmfs.opentaskspal.readdata.cursor.LongCursorColumnValue; +import org.dmfs.opentaskspal.readdata.cursor.StringCursorColumnValue; + + +/** + * {@link DateTimeFields} from a {@link Cursor}. + * + * @author Gabor Keszthelyi + */ +public final class CursorDateTimeFields extends DelegatingDateTimeFields +{ + public CursorDateTimeFields(@NonNull Cursor cursor, + @NonNull String timestampColumn, + @NonNull String timeZoneColumn, + @NonNull String isAllDayColumn) + { + // Evaluated eagerly to not capture Cursor + super(new Evaluated(new LazyCursorDateTimeFields(cursor, timestampColumn, timeZoneColumn, isAllDayColumn))); + } + + + private static final class LazyCursorDateTimeFields implements DateTimeFields + { + private final Cursor mCursor; + private final String mTimestampColumn; + private final String mTimeZoneColumn; + private final String mIsAllDayColumn; + + + private LazyCursorDateTimeFields(@NonNull Cursor cursor, + @NonNull String timestampColumn, + @NonNull String timeZoneColumn, + @NonNull String isAllDayColumn) + { + mCursor = cursor; + mTimestampColumn = timestampColumn; + mTimeZoneColumn = timeZoneColumn; + mIsAllDayColumn = isAllDayColumn; + } + + + @Nullable + @Override + public Long timestamp() + { + return new LongCursorColumnValue(mCursor, mTimestampColumn).value(null); + } + + + @Nullable + @Override + public String timeZoneId() + { + return new StringCursorColumnValue(mCursor, mTimeZoneColumn).value(null); + } + + + @Nullable + @Override + public Integer isAllDay() + { + return new IntCursorColumnValue(mCursor, mIsAllDayColumn).value(null); + } + } +} diff --git a/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetimefields/adapters/DateTimeDateTimeFields.java b/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetimefields/adapters/DateTimeDateTimeFields.java new file mode 100644 index 000000000..c1dc872b4 --- /dev/null +++ b/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetimefields/adapters/DateTimeDateTimeFields.java @@ -0,0 +1,65 @@ +/* + * Copyright 2018 dmfs GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.dmfs.opentaskspal.datetimefields.adapters; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import org.dmfs.opentaskspal.datetimefields.DateTimeFields; +import org.dmfs.rfc5545.DateTime; + +import java.util.TimeZone; + + +/** + * {@link DateTimeFields} corresponding to the given {@link DateTime}. + * + * @author Gabor Keszthelyi + */ +public final class DateTimeDateTimeFields implements DateTimeFields +{ + private final DateTime mDateTime; + + + public DateTimeDateTimeFields(@NonNull DateTime dateTime) + { + mDateTime = dateTime; + } + + + @Override + public Long timestamp() + { + return mDateTime.getTimestamp(); + } + + + @Nullable + @Override + public String timeZoneId() + { + TimeZone timeZone = mDateTime.getTimeZone(); + return timeZone == null ? null : timeZone.getID(); + } + + + @Override + public Integer isAllDay() + { + return mDateTime.isAllDay() ? 1 : 0; + } +} diff --git a/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetimefields/adapters/RowDataSnapshotDateTimeFields.java b/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetimefields/adapters/RowDataSnapshotDateTimeFields.java new file mode 100644 index 000000000..425859202 --- /dev/null +++ b/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetimefields/adapters/RowDataSnapshotDateTimeFields.java @@ -0,0 +1,73 @@ +/* + * Copyright 2018 dmfs GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.dmfs.opentaskspal.datetimefields.adapters; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import org.dmfs.android.contentpal.RowDataSnapshot; +import org.dmfs.opentaskspal.datetimefields.DateTimeFields; + + +/** + * {@link DateTimeFields} from a {@link RowDataSnapshot}. + * + * @author Gabor Keszthelyi + */ +public final class RowDataSnapshotDateTimeFields implements DateTimeFields +{ + private final RowDataSnapshot mRowDataSnapshot; + private final String mTimestampKey; + private final String mTimeZoneKey; + private final String mIsAllDayKey; + + + public RowDataSnapshotDateTimeFields(@NonNull RowDataSnapshot rowDataSnapshot, + @NonNull String timestampKey, + @NonNull String timeZoneKey, + @NonNull String isAllDayKey) + { + mRowDataSnapshot = rowDataSnapshot; + mTimestampKey = timestampKey; + mTimeZoneKey = timeZoneKey; + mIsAllDayKey = isAllDayKey; + } + + + @Nullable + @Override + public Long timestamp() + { + return mRowDataSnapshot.data(mTimestampKey, Long::valueOf).value(null); + } + + + @Nullable + @Override + public String timeZoneId() + { + return mRowDataSnapshot.data(mTimeZoneKey, s -> s).value(null); + } + + + @Nullable + @Override + public Integer isAllDay() + { + return mRowDataSnapshot.data(mIsAllDayKey, Integer::valueOf).value(null); + } +} diff --git a/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetimefields/decorators/DelegatingDateTimeFields.java b/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetimefields/decorators/DelegatingDateTimeFields.java new file mode 100644 index 000000000..87765ebd6 --- /dev/null +++ b/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetimefields/decorators/DelegatingDateTimeFields.java @@ -0,0 +1,62 @@ +/* + * Copyright 2018 dmfs GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.dmfs.opentaskspal.datetimefields.decorators; + +import android.support.annotation.Nullable; + +import org.dmfs.opentaskspal.datetimefields.DateTimeFields; + + +/** + * Abstract {@link DateTimeFields} that delegates to the given {@link DateTimeFields}. + * + * @author Gabor Keszthelyi + */ +public abstract class DelegatingDateTimeFields implements DateTimeFields +{ + private final DateTimeFields mDateTimeFields; + + + protected DelegatingDateTimeFields(DateTimeFields delegate) + { + mDateTimeFields = delegate; + } + + + @Nullable + @Override + public final Long timestamp() + { + return mDateTimeFields.timestamp(); + } + + + @Nullable + @Override + public final String timeZoneId() + { + return mDateTimeFields.timeZoneId(); + } + + + @Nullable + @Override + public final Integer isAllDay() + { + return mDateTimeFields.isAllDay(); + } +} diff --git a/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetimefields/decorators/Evaluated.java b/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetimefields/decorators/Evaluated.java new file mode 100644 index 000000000..00d2278c7 --- /dev/null +++ b/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetimefields/decorators/Evaluated.java @@ -0,0 +1,66 @@ +/* + * Copyright 2018 dmfs GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.dmfs.opentaskspal.datetimefields.decorators; + +import android.support.annotation.Nullable; + +import org.dmfs.opentaskspal.datetimefields.DateTimeFields; + + +/** + * Eager evaluation decorator for {@link DateTimeFields}. + * + * @author Gabor Keszthelyi + */ +public final class Evaluated implements DateTimeFields +{ + private final Long mTimestamp; + private final String mTimeZoneId; + private final Integer mIsAllDay; + + + public Evaluated(DateTimeFields delegate) + { + mTimestamp = delegate.timestamp(); + mTimeZoneId = delegate.timeZoneId(); + mIsAllDay = delegate.isAllDay(); + } + + + @Nullable + @Override + public Long timestamp() + { + return mTimestamp; + } + + + @Nullable + @Override + public String timeZoneId() + { + return mTimeZoneId; + } + + + @Nullable + @Override + public Integer isAllDay() + { + return mIsAllDay; + } +} diff --git a/opentaskspal/src/main/java/org/dmfs/opentaskspal/jems/optional/Conditional.java b/opentaskspal/src/main/java/org/dmfs/opentaskspal/jems/optional/Conditional.java new file mode 100644 index 000000000..607a7a034 --- /dev/null +++ b/opentaskspal/src/main/java/org/dmfs/opentaskspal/jems/optional/Conditional.java @@ -0,0 +1,89 @@ +/* + * Copyright 2018 dmfs GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.dmfs.opentaskspal.jems.optional; + +import org.dmfs.jems.predicate.Predicate; +import org.dmfs.jems.single.Single; +import org.dmfs.jems.single.elementary.ValueSingle; +import org.dmfs.optional.Optional; +import org.dmfs.optional.Present; + +import java.util.NoSuchElementException; + +import static org.dmfs.optional.Absent.absent; + + +/** + * {@link Optional} that is present with the value of the provided target if it satisfies the given {@link Predicate}, + * otherwise it is absent. + * + * @author Gabor Keszthelyi + * @deprecated use from jems when available + */ +@Deprecated +public final class Conditional implements Optional +{ + private final Predicate mPredicate; + private final Single mTargetSingle; + + private Optional mCachedDelegate; + + + public Conditional(Predicate predicate, Single targetSingle) + { + mPredicate = predicate; + mTargetSingle = targetSingle; + } + + + public Conditional(Predicate predicate, T targetValue) + { + this(predicate, new ValueSingle<>(targetValue)); + } + + + @Override + public boolean isPresent() + { + return cachedDelegate().isPresent(); + } + + + @Override + public T value(T defaultValue) + { + return cachedDelegate().value(defaultValue); + } + + + @Override + public T value() throws NoSuchElementException + { + return cachedDelegate().value(); + } + + + private Optional cachedDelegate() + { + if (mCachedDelegate == null) + { + T targetValue = mTargetSingle.value(); + mCachedDelegate = mPredicate.satisfiedBy(targetValue) ? new Present<>(targetValue) : absent(); + } + return mCachedDelegate; + } +} diff --git a/opentaskspal/src/main/java/org/dmfs/opentaskspal/jems/optional/Evaluated.java b/opentaskspal/src/main/java/org/dmfs/opentaskspal/jems/optional/Evaluated.java new file mode 100644 index 000000000..f2638282d --- /dev/null +++ b/opentaskspal/src/main/java/org/dmfs/opentaskspal/jems/optional/Evaluated.java @@ -0,0 +1,67 @@ +/* + * Copyright 2018 dmfs GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.dmfs.opentaskspal.jems.optional; + +import org.dmfs.optional.Optional; + +import java.util.NoSuchElementException; + + +/** + * Eagerly evaluation {@link Optional} decorator. + * + * @author Gabor Keszthelyi + * @deprecated use it from jems when available + */ +@Deprecated +public final class Evaluated implements Optional +{ + private final boolean mIsPresent; + private final T mValue; + + + public Evaluated(Optional delegate) + { + mIsPresent = delegate.isPresent(); + mValue = mIsPresent ? delegate.value() : null; + } + + + @Override + public boolean isPresent() + { + return mIsPresent; + } + + + @Override + public T value(T defaultValue) + { + return mIsPresent ? mValue : defaultValue; + } + + + @Override + public T value() throws NoSuchElementException + { + if (!mIsPresent) + { + throw new NoSuchElementException("Value absent"); + } + return mValue; + } +} diff --git a/opentaskspal/src/main/java/org/dmfs/opentaskspal/jems/procedure/Procedure.java b/opentaskspal/src/main/java/org/dmfs/opentaskspal/jems/procedure/Procedure.java new file mode 100644 index 000000000..df8178b29 --- /dev/null +++ b/opentaskspal/src/main/java/org/dmfs/opentaskspal/jems/procedure/Procedure.java @@ -0,0 +1,28 @@ +/* + * Copyright 2018 dmfs GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.dmfs.opentaskspal.jems.procedure; + +/** + * @author Gabor Keszthelyi + * @deprecated use it from jems when available + */ +@Deprecated +public interface Procedure +{ + void process(T t); + +} diff --git a/opentaskspal/src/main/java/org/dmfs/opentaskspal/readdata/EffectiveDueDate.java b/opentaskspal/src/main/java/org/dmfs/opentaskspal/readdata/EffectiveDueDate.java index e5cf713c3..d41c46dc4 100644 --- a/opentaskspal/src/main/java/org/dmfs/opentaskspal/readdata/EffectiveDueDate.java +++ b/opentaskspal/src/main/java/org/dmfs/opentaskspal/readdata/EffectiveDueDate.java @@ -41,7 +41,7 @@ public final class EffectiveDueDate extends DelegatingOptional { public static final Projection PROJECTION = new Composite<>( new MultiProjection<>(Tasks.DUE, Tasks.DTSTART), - TaskDateTime.PROJECTION, + TaskComposedDateTime.PROJECTION, TaskDuration.PROJECTION); @@ -49,9 +49,9 @@ public EffectiveDueDate(@NonNull RowDataSnapshot rowDataSnapshot) { super(new FirstPresent<>( new Seq<>( - new TaskDateTime(Tasks.DUE, rowDataSnapshot), + new TaskComposedDateTime(Tasks.DUE, rowDataSnapshot), new Zipped<>( - new TaskDateTime(Tasks.DTSTART, rowDataSnapshot), + new TaskComposedDateTime(Tasks.DTSTART, rowDataSnapshot), new TaskDuration(rowDataSnapshot), DateTime::addDuration)))); } diff --git a/opentaskspal/src/main/java/org/dmfs/opentaskspal/readdata/RowDataSnapshotComposedDateTime.java b/opentaskspal/src/main/java/org/dmfs/opentaskspal/readdata/RowDataSnapshotComposedDateTime.java new file mode 100644 index 000000000..075a8919c --- /dev/null +++ b/opentaskspal/src/main/java/org/dmfs/opentaskspal/readdata/RowDataSnapshotComposedDateTime.java @@ -0,0 +1,44 @@ +/* + * Copyright 2017 dmfs GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.dmfs.opentaskspal.readdata; + +import android.support.annotation.NonNull; + +import org.dmfs.android.contentpal.RowDataSnapshot; +import org.dmfs.opentaskspal.datetime.OptionalCombinedDateTime; +import org.dmfs.opentaskspal.datetimefields.adapters.RowDataSnapshotDateTimeFields; +import org.dmfs.optional.Optional; +import org.dmfs.optional.decorators.DelegatingOptional; +import org.dmfs.rfc5545.DateTime; + + +/** + * An {@link Optional} combined date-time from a {@link RowDataSnapshot}. + * + * @author Gabor Keszthelyi + */ +public final class RowDataSnapshotComposedDateTime extends DelegatingOptional +{ + public RowDataSnapshotComposedDateTime(@NonNull RowDataSnapshot rowDataSnapshot, + @NonNull String timestampColumn, + @NonNull String timeZoneColumn, + @NonNull String isAllDayColumn) + { + super(new OptionalCombinedDateTime( + new RowDataSnapshotDateTimeFields(rowDataSnapshot, timestampColumn, timeZoneColumn, isAllDayColumn))); + } +} diff --git a/opentaskspal/src/main/java/org/dmfs/opentaskspal/readdata/EffectiveTimezone.java b/opentaskspal/src/main/java/org/dmfs/opentaskspal/readdata/TaskComposedDateTime.java similarity index 52% rename from opentaskspal/src/main/java/org/dmfs/opentaskspal/readdata/EffectiveTimezone.java rename to opentaskspal/src/main/java/org/dmfs/opentaskspal/readdata/TaskComposedDateTime.java index 502a50a1f..fdbe6735d 100644 --- a/opentaskspal/src/main/java/org/dmfs/opentaskspal/readdata/EffectiveTimezone.java +++ b/opentaskspal/src/main/java/org/dmfs/opentaskspal/readdata/TaskComposedDateTime.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 dmfs GmbH + * Copyright 2018 dmfs GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,29 +20,24 @@ import org.dmfs.android.contentpal.Projection; import org.dmfs.android.contentpal.RowDataSnapshot; -import org.dmfs.android.contentpal.projections.SingleColProjection; -import org.dmfs.jems.single.Single; -import org.dmfs.jems.single.decorators.DelegatingSingle; -import org.dmfs.jems.single.elementary.ValueSingle; +import org.dmfs.android.contentpal.projections.MultiProjection; +import org.dmfs.optional.decorators.DelegatingOptional; +import org.dmfs.rfc5545.DateTime; import org.dmfs.tasks.contract.TaskContract.Tasks; -import java.util.TimeZone; - /** - * The {@link Single} effective {@link TimeZone} of a task. If the task has no TimeZone, i.e. is floating, this will return the local {@link TimeZone}. + * Composed date-time of a task corresponding to the given timestamp column in the provided {@link RowDataSnapshot}. * - * @author Marten Gajda * @author Gabor Keszthelyi */ -public final class EffectiveTimezone extends DelegatingSingle +public final class TaskComposedDateTime extends DelegatingOptional { - public static final Projection PROJECTION = new SingleColProjection<>(Tasks.TZ); + public static final Projection PROJECTION = new MultiProjection<>(Tasks.TZ, Tasks.IS_ALLDAY); - public EffectiveTimezone(@NonNull RowDataSnapshot rowData) + public TaskComposedDateTime(@NonNull String timestampColumn, @NonNull RowDataSnapshot rowDataSnapshot) { - super(new ValueSingle<>(rowData.data(Tasks.TZ, TimeZone::getTimeZone).value())); + super(new RowDataSnapshotComposedDateTime(rowDataSnapshot, timestampColumn, Tasks.TZ, Tasks.IS_ALLDAY)); } - } diff --git a/opentaskspal/src/main/java/org/dmfs/opentaskspal/readdata/TaskDateTime.java b/opentaskspal/src/main/java/org/dmfs/opentaskspal/readdata/TaskDateTime.java deleted file mode 100644 index 2b5879b97..000000000 --- a/opentaskspal/src/main/java/org/dmfs/opentaskspal/readdata/TaskDateTime.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2017 dmfs GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.dmfs.opentaskspal.readdata; - -import android.support.annotation.NonNull; - -import org.dmfs.android.contentpal.Projection; -import org.dmfs.android.contentpal.RowDataSnapshot; -import org.dmfs.android.contentpal.projections.Composite; -import org.dmfs.android.contentpal.projections.SingleColProjection; -import org.dmfs.optional.Optional; -import org.dmfs.optional.decorators.DelegatingOptional; -import org.dmfs.optional.decorators.Mapped; -import org.dmfs.rfc5545.DateTime; -import org.dmfs.tasks.contract.TaskContract.Tasks; - - -/** - * An {@link Optional} of a specific {@link DateTime} value of a task. - * - * @author Marten Gajda - * @author Gabor Keszthelyi - */ -public final class TaskDateTime extends DelegatingOptional -{ - public static final Projection PROJECTION = new Composite<>( - new SingleColProjection<>(Tasks.IS_ALLDAY), - EffectiveTimezone.PROJECTION); - - - public TaskDateTime(@NonNull String columnName, @NonNull final RowDataSnapshot rowData) - { - super(new Mapped<>( - - (Long timeStamp) -> rowData.data(Tasks.IS_ALLDAY, "1"::equals).value(false) ? - new DateTime(timeStamp).toAllDay() : - new DateTime(timeStamp).shiftTimeZone(new EffectiveTimezone(rowData).value()), - - rowData.data(columnName, Long::valueOf))); - } -} diff --git a/opentaskspal/src/main/java/org/dmfs/opentaskspal/readdata/cursor/CursorCombinedDateTime.java b/opentaskspal/src/main/java/org/dmfs/opentaskspal/readdata/cursor/CursorCombinedDateTime.java new file mode 100644 index 000000000..0dfc3fa12 --- /dev/null +++ b/opentaskspal/src/main/java/org/dmfs/opentaskspal/readdata/cursor/CursorCombinedDateTime.java @@ -0,0 +1,43 @@ +/* + * Copyright 2018 dmfs GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.dmfs.opentaskspal.readdata.cursor; + +import android.database.Cursor; +import android.support.annotation.NonNull; + +import org.dmfs.opentaskspal.datetime.OptionalCombinedDateTime; +import org.dmfs.opentaskspal.datetimefields.adapters.CursorDateTimeFields; +import org.dmfs.optional.decorators.DelegatingOptional; +import org.dmfs.rfc5545.DateTime; + + +/** + * Combined date-time from a {@link Cursor}. + * + * @author Gabor Keszthelyi + */ +public final class CursorCombinedDateTime extends DelegatingOptional +{ + public CursorCombinedDateTime(@NonNull Cursor cursor, + @NonNull String timestampColumn, + @NonNull String timeZoneColumn, + @NonNull String isAllDayColumn) + { + super(new OptionalCombinedDateTime( + new CursorDateTimeFields(cursor, timestampColumn, timeZoneColumn, isAllDayColumn))); + } +} diff --git a/opentaskspal/src/main/java/org/dmfs/opentaskspal/readdata/cursor/IntCursorColumnValue.java b/opentaskspal/src/main/java/org/dmfs/opentaskspal/readdata/cursor/IntCursorColumnValue.java new file mode 100644 index 000000000..babd85b8a --- /dev/null +++ b/opentaskspal/src/main/java/org/dmfs/opentaskspal/readdata/cursor/IntCursorColumnValue.java @@ -0,0 +1,36 @@ +/* + * Copyright 2018 dmfs GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.dmfs.opentaskspal.readdata.cursor; + +import android.database.Cursor; + +import org.dmfs.optional.Optional; +import org.dmfs.optional.decorators.DelegatingOptional; + + +/** + * {@link Optional} for a {@link Integer} value in a {@link Cursor}. + * + * @author Gabor Keszthelyi + */ +public final class IntCursorColumnValue extends DelegatingOptional +{ + public IntCursorColumnValue(Cursor cursor, String columnName) + { + super(new OptionalCursorColumnValue<>(cursor, columnName, Cursor::getInt)); + } +} diff --git a/opentaskspal/src/main/java/org/dmfs/opentaskspal/readdata/cursor/LongCursorColumnValue.java b/opentaskspal/src/main/java/org/dmfs/opentaskspal/readdata/cursor/LongCursorColumnValue.java new file mode 100644 index 000000000..728b4e793 --- /dev/null +++ b/opentaskspal/src/main/java/org/dmfs/opentaskspal/readdata/cursor/LongCursorColumnValue.java @@ -0,0 +1,36 @@ +/* + * Copyright 2018 dmfs GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.dmfs.opentaskspal.readdata.cursor; + +import android.database.Cursor; + +import org.dmfs.optional.Optional; +import org.dmfs.optional.decorators.DelegatingOptional; + + +/** + * {@link Optional} for a {@link Long} value in a {@link Cursor}. + * + * @author Gabor Keszthelyi + */ +public final class LongCursorColumnValue extends DelegatingOptional +{ + public LongCursorColumnValue(Cursor cursor, String columnName) + { + super(new OptionalCursorColumnValue<>(cursor, columnName, Cursor::getLong)); + } +} diff --git a/opentaskspal/src/main/java/org/dmfs/opentaskspal/readdata/cursor/OptionalCursorColumnValue.java b/opentaskspal/src/main/java/org/dmfs/opentaskspal/readdata/cursor/OptionalCursorColumnValue.java new file mode 100644 index 000000000..9adbdad51 --- /dev/null +++ b/opentaskspal/src/main/java/org/dmfs/opentaskspal/readdata/cursor/OptionalCursorColumnValue.java @@ -0,0 +1,53 @@ +/* + * Copyright 2018 dmfs GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.dmfs.opentaskspal.readdata.cursor; + +import android.database.Cursor; +import android.support.annotation.NonNull; + +import org.dmfs.jems.function.BiFunction; +import org.dmfs.jems.optional.decorators.Mapped; +import org.dmfs.opentaskspal.jems.optional.Conditional; +import org.dmfs.opentaskspal.jems.optional.Evaluated; +import org.dmfs.optional.Optional; +import org.dmfs.optional.decorators.DelegatingOptional; + + +/** + * {@link Optional} for a value from a {@link Cursor}. + *

    + * Handles the cases where return values for primitive types cannot be assessed whether they represent absent value + * (like 0 for {@link Cursor#getInt(int)}) by checking {@link Cursor#isNull(int)} first. + * + * @author Gabor Keszthelyi + */ +public final class OptionalCursorColumnValue extends DelegatingOptional +{ + public OptionalCursorColumnValue(@NonNull Cursor cursor, + @NonNull String columnName, + @NonNull BiFunction getFunction) + { + // Eager evaluation to avoid capturing Cursor + super(new Evaluated<>( + new Mapped<>(columnIndex -> getFunction.value(cursor, columnIndex), + new Conditional(cursor::isNull, + cursor.getColumnIndexOrThrow(columnName)) + )) + ); + } +} + diff --git a/opentaskspal/src/main/java/org/dmfs/opentaskspal/readdata/cursor/StringCursorColumnValue.java b/opentaskspal/src/main/java/org/dmfs/opentaskspal/readdata/cursor/StringCursorColumnValue.java new file mode 100644 index 000000000..84059afc2 --- /dev/null +++ b/opentaskspal/src/main/java/org/dmfs/opentaskspal/readdata/cursor/StringCursorColumnValue.java @@ -0,0 +1,36 @@ +/* + * Copyright 2018 dmfs GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.dmfs.opentaskspal.readdata.cursor; + +import android.database.Cursor; + +import org.dmfs.optional.Optional; +import org.dmfs.optional.decorators.DelegatingOptional; + + +/** + * * {@link Optional} for a {@link String} value in a {@link Cursor}. + * + * @author Gabor Keszthelyi + */ +public final class StringCursorColumnValue extends DelegatingOptional +{ + public StringCursorColumnValue(Cursor cursor, String columnName) + { + super(new OptionalCursorColumnValue<>(cursor, columnName, Cursor::getString)); + } +} diff --git a/opentaskspal/src/main/java/org/dmfs/opentaskspal/utils/binarybooleans/BinaryIntBoolean.java b/opentaskspal/src/main/java/org/dmfs/opentaskspal/utils/binarybooleans/BinaryIntBoolean.java new file mode 100644 index 000000000..d9f508137 --- /dev/null +++ b/opentaskspal/src/main/java/org/dmfs/opentaskspal/utils/binarybooleans/BinaryIntBoolean.java @@ -0,0 +1,50 @@ +/* + * Copyright 2018 dmfs GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.dmfs.opentaskspal.utils.binarybooleans; + +import android.support.annotation.Nullable; + +import org.dmfs.jems.single.Single; + + +/** + * Adapter from binary {@link Integer} values 0 and 1 to {@link Boolean}. + * {@code null} is considered as {@code false}. + *

    + * (Also any other invalid value is considered as {@code false} as well.) + * + * @author Gabor Keszthelyi + */ +public final class BinaryIntBoolean implements Single +{ + private static final Integer ONE = 1; + + private final Integer mIntValue; + + + public BinaryIntBoolean(@Nullable Integer intValue) + { + mIntValue = intValue; + } + + + @Override + public Boolean value() + { + return ONE.equals(mIntValue); + } +} diff --git a/opentaskspal/src/test/java/org/dmfs/opentaskspal/readdata/EffectiveDueDateTest.java b/opentaskspal/src/test/java/org/dmfs/opentaskspal/readdata/EffectiveDueDateTest.java index 24eabd921..d9960493d 100644 --- a/opentaskspal/src/test/java/org/dmfs/opentaskspal/readdata/EffectiveDueDateTest.java +++ b/opentaskspal/src/test/java/org/dmfs/opentaskspal/readdata/EffectiveDueDateTest.java @@ -17,18 +17,17 @@ package org.dmfs.opentaskspal.readdata; import org.dmfs.android.contentpal.RowDataSnapshot; -import org.dmfs.jems.hamcrest.matchers.AbsentMatcher; import org.dmfs.optional.Present; import org.dmfs.rfc5545.DateTime; import org.dmfs.rfc5545.Duration; import org.dmfs.tasks.contract.TaskContract.Tasks; import org.junit.Test; -import java.util.TimeZone; import java.util.concurrent.TimeUnit; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; +import static org.dmfs.jems.hamcrest.matchers.AbsentMatcher.isAbsent; import static org.dmfs.jems.mockito.doubles.TestDoubles.failingMock; import static org.dmfs.optional.Absent.absent; import static org.hamcrest.MatcherAssert.assertThat; @@ -54,8 +53,8 @@ public void test_whenDueIsPresent_shouldUseThat() doReturn(new Present<>(timestamp)).when(mockData).data(eq(Tasks.DUE), any()); doReturn(absent()).when(mockData).data(eq(Tasks.DTSTART), any()); doReturn(absent()).when(mockData).data(eq(Tasks.DURATION), any()); - doReturn(new Present<>(false)).when(mockData).data(eq(Tasks.IS_ALLDAY), any()); - doReturn(new Present<>(TimeZone.getTimeZone("UTC"))).when(mockData).data(eq(Tasks.TZ), any()); + doReturn(new Present<>(0)).when(mockData).data(eq(Tasks.IS_ALLDAY), any()); + doReturn(new Present<>("UTC")).when(mockData).data(eq(Tasks.TZ), any()); DateTime actual = new EffectiveDueDate(mockData).value(); assertEquals(timestamp, actual.getTimestamp()); @@ -69,10 +68,10 @@ public void test_whenDueIsAbsent_startIsAbsent_durationIsAbsent_shouldBeAbsent() doReturn(absent()).when(mockData).data(eq(Tasks.DUE), any()); doReturn(absent()).when(mockData).data(eq(Tasks.DTSTART), any()); doReturn(absent()).when(mockData).data(eq(Tasks.DURATION), any()); - doReturn(new Present<>(false)).when(mockData).data(eq(Tasks.IS_ALLDAY), any()); - doReturn(new Present<>(TimeZone.getTimeZone("UTC"))).when(mockData).data(eq(Tasks.TZ), any()); + doReturn(new Present<>(0)).when(mockData).data(eq(Tasks.IS_ALLDAY), any()); + doReturn(new Present<>("UTC")).when(mockData).data(eq(Tasks.TZ), any()); - assertThat(new EffectiveDueDate(mockData), AbsentMatcher.isAbsent()); + assertThat(new EffectiveDueDate(mockData), isAbsent()); } @@ -81,12 +80,12 @@ public void test_whenDueIsAbsent_startIsPresent_durationIsAbsent_shouldBeAbsent( { RowDataSnapshot mockData = failingMock(RowDataSnapshot.class); doReturn(absent()).when(mockData).data(eq(Tasks.DUE), any()); - doReturn(new Present<>(234234)).when(mockData).data(eq(Tasks.DTSTART), any()); + doReturn(new Present<>(234234L)).when(mockData).data(eq(Tasks.DTSTART), any()); doReturn(absent()).when(mockData).data(eq(Tasks.DURATION), any()); - doReturn(new Present<>(false)).when(mockData).data(eq(Tasks.IS_ALLDAY), any()); - doReturn(new Present<>(TimeZone.getTimeZone("UTC"))).when(mockData).data(eq(Tasks.TZ), any()); + doReturn(new Present<>(0)).when(mockData).data(eq(Tasks.IS_ALLDAY), any()); + doReturn(new Present<>("UTC")).when(mockData).data(eq(Tasks.TZ), any()); - assertThat(new EffectiveDueDate(mockData), AbsentMatcher.isAbsent()); + assertThat(new EffectiveDueDate(mockData), isAbsent()); } @@ -97,10 +96,10 @@ public void test_whenDueIsAbsent_startIsAbsent_durationIsPresent_shouldBeAbsent( doReturn(absent()).when(mockData).data(eq(Tasks.DUE), any()); doReturn(absent()).when(mockData).data(eq(Tasks.DTSTART), any()); doReturn(new Present<>(Duration.parse("P7W"))).when(mockData).data(eq(Tasks.DURATION), any()); - doReturn(new Present<>(false)).when(mockData).data(eq(Tasks.IS_ALLDAY), any()); - doReturn(new Present<>(TimeZone.getTimeZone("UTC"))).when(mockData).data(eq(Tasks.TZ), any()); + doReturn(new Present<>(0)).when(mockData).data(eq(Tasks.IS_ALLDAY), any()); + doReturn(new Present<>("UTC")).when(mockData).data(eq(Tasks.TZ), any()); - assertThat(new EffectiveDueDate(mockData), AbsentMatcher.isAbsent()); + assertThat(new EffectiveDueDate(mockData), isAbsent()); } @@ -113,8 +112,8 @@ public void test_whenDueIsAbsent_startIsPresent_durationIsPresent_shouldUseStart doReturn(absent()).when(mockData).data(eq(Tasks.DUE), any()); doReturn(new Present<>(timestamp)).when(mockData).data(eq(Tasks.DTSTART), any()); doReturn(new Present<>(Duration.parse("PT2H"))).when(mockData).data(eq(Tasks.DURATION), any()); - doReturn(new Present<>(false)).when(mockData).data(eq(Tasks.IS_ALLDAY), any()); - doReturn(new Present<>(TimeZone.getTimeZone("Europe/Berlin"))).when(mockData).data(eq(Tasks.TZ), any()); + doReturn(new Present<>(0)).when(mockData).data(eq(Tasks.IS_ALLDAY), any()); + doReturn(new Present<>("Europe/Berlin")).when(mockData).data(eq(Tasks.TZ), any()); DateTime actual = new EffectiveDueDate(mockData).value(); assertEquals(timestamp + TimeUnit.HOURS.toMillis(2), actual.getTimestamp()); diff --git a/opentaskspal/src/test/java/org/dmfs/opentaskspal/readdata/TaskDateTimeTest.java b/opentaskspal/src/test/java/org/dmfs/opentaskspal/readdata/RowSnapshotCombinedDateTimeTest.java similarity index 68% rename from opentaskspal/src/test/java/org/dmfs/opentaskspal/readdata/TaskDateTimeTest.java rename to opentaskspal/src/test/java/org/dmfs/opentaskspal/readdata/RowSnapshotCombinedDateTimeTest.java index 699de6a3d..431cb5fbf 100644 --- a/opentaskspal/src/test/java/org/dmfs/opentaskspal/readdata/TaskDateTimeTest.java +++ b/opentaskspal/src/test/java/org/dmfs/opentaskspal/readdata/RowSnapshotCombinedDateTimeTest.java @@ -17,17 +17,15 @@ package org.dmfs.opentaskspal.readdata; import org.dmfs.android.contentpal.RowDataSnapshot; -import org.dmfs.jems.hamcrest.matchers.AbsentMatcher; import org.dmfs.optional.Present; import org.dmfs.rfc5545.DateTime; import org.dmfs.tasks.contract.TaskContract.Tasks; import org.junit.Test; -import java.util.TimeZone; - import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; +import static org.dmfs.jems.hamcrest.matchers.AbsentMatcher.isAbsent; import static org.dmfs.jems.mockito.doubles.TestDoubles.failingMock; import static org.dmfs.optional.Absent.absent; import static org.hamcrest.MatcherAssert.assertThat; @@ -37,11 +35,11 @@ /** - * Unit test for {@link TaskDateTime}. + * Unit test for {@link RowDataSnapshotComposedDateTime}. * * @author Gabor Keszthelyi */ -public final class TaskDateTimeTest +public final class RowSnapshotCombinedDateTimeTest { @Test @@ -49,8 +47,10 @@ public void test_whenColumnValueIsAbsent_shouldBeAbsent() { RowDataSnapshot mockData = failingMock(RowDataSnapshot.class); doReturn(absent()).when(mockData).data(eq(Tasks.DTSTART), any()); + doReturn(absent()).when(mockData).data(eq(Tasks.TZ), any()); + doReturn(absent()).when(mockData).data(eq(Tasks.IS_ALLDAY), any()); - assertThat(new TaskDateTime(Tasks.DTSTART, mockData), AbsentMatcher.isAbsent()); + assertThat(new RowDataSnapshotComposedDateTime(mockData, Tasks.DTSTART, Tasks.TZ, Tasks.IS_ALLDAY), isAbsent()); } @@ -61,9 +61,10 @@ public void test_whenIsAllDayIsPresentAndTrue_shouldReturnAllDayDateTime() RowDataSnapshot mockData = failingMock(RowDataSnapshot.class); doReturn(new Present<>(timeStamp)).when(mockData).data(eq(Tasks.DTSTART), any()); - doReturn(new Present<>(true)).when(mockData).data(eq(Tasks.IS_ALLDAY), any()); + doReturn(new Present<>(1)).when(mockData).data(eq(Tasks.IS_ALLDAY), any()); + doReturn(new Present<>("UTC")).when(mockData).data(eq(Tasks.TZ), any()); - DateTime actual = new TaskDateTime(Tasks.DTSTART, mockData).value(); + DateTime actual = new RowDataSnapshotComposedDateTime(mockData, Tasks.DTSTART, Tasks.TZ, Tasks.IS_ALLDAY).value(); assertTrue(actual.isAllDay()); assertEquals(new DateTime(timeStamp).toAllDay(), actual); } @@ -76,10 +77,10 @@ public void test_whenIsAllDayIsPresentAndFalse_shouldReturnNotAllDayDateTime() RowDataSnapshot mockData = failingMock(RowDataSnapshot.class); doReturn(new Present<>(timeStamp)).when(mockData).data(eq(Tasks.DTSTART), any()); - doReturn(new Present<>(false)).when(mockData).data(eq(Tasks.IS_ALLDAY), any()); - doReturn(new Present<>(TimeZone.getTimeZone("UTC"))).when(mockData).data(eq(Tasks.TZ), any()); + doReturn(new Present<>(0)).when(mockData).data(eq(Tasks.IS_ALLDAY), any()); + doReturn(new Present<>("UTC")).when(mockData).data(eq(Tasks.TZ), any()); - DateTime actual = new TaskDateTime(Tasks.DTSTART, mockData).value(); + DateTime actual = new RowDataSnapshotComposedDateTime(mockData, Tasks.DTSTART, Tasks.TZ, Tasks.IS_ALLDAY).value(); assertFalse(actual.isAllDay()); assertEquals(timeStamp, actual.getTimestamp()); } @@ -92,10 +93,10 @@ public void test_whenIsAllDayIsFalse_shouldReturnDateTimeWithTimeZoneShifted() RowDataSnapshot mockData = failingMock(RowDataSnapshot.class); doReturn(new Present<>(timeStamp)).when(mockData).data(eq(Tasks.DTSTART), any()); - doReturn(new Present<>(false)).when(mockData).data(eq(Tasks.IS_ALLDAY), any()); - doReturn(new Present<>(TimeZone.getTimeZone("Europe/Berlin"))).when(mockData).data(eq(Tasks.TZ), any()); + doReturn(new Present<>(0)).when(mockData).data(eq(Tasks.IS_ALLDAY), any()); + doReturn(new Present<>("Europe/Berlin")).when(mockData).data(eq(Tasks.TZ), any()); - DateTime actual = new TaskDateTime(Tasks.DTSTART, mockData).value(); + DateTime actual = new RowDataSnapshotComposedDateTime(mockData, Tasks.DTSTART, Tasks.TZ, Tasks.IS_ALLDAY).value(); assertFalse(actual.isAllDay()); assertEquals(timeStamp, actual.getTimestamp()); assertEquals("Europe/Berlin", actual.getTimeZone().getID()); diff --git a/opentaskspal/src/test/java/org/dmfs/opentaskspal/utils/binarybooleans/BinaryIntBooleanTest.java b/opentaskspal/src/test/java/org/dmfs/opentaskspal/utils/binarybooleans/BinaryIntBooleanTest.java new file mode 100644 index 000000000..e821da5c9 --- /dev/null +++ b/opentaskspal/src/test/java/org/dmfs/opentaskspal/utils/binarybooleans/BinaryIntBooleanTest.java @@ -0,0 +1,45 @@ +/* + * Copyright 2018 dmfs GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.dmfs.opentaskspal.utils.binarybooleans; + +import org.junit.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + + +/** + * Unit test for {@link BinaryIntBoolean}. + * + * @author Gabor Keszthelyi + */ +public final class BinaryIntBooleanTest +{ + + @Test + public void test() + { + assertThat(new BinaryIntBoolean(1).value(), is(true)); + assertThat(new BinaryIntBoolean(0).value(), is(false)); + assertThat(new BinaryIntBoolean(null).value(), is(false)); + + // Invalid values, considered as false to avoid validation code: + assertThat(new BinaryIntBoolean(-1).value(), is(false)); + assertThat(new BinaryIntBoolean(2).value(), is(false)); + } + +} \ No newline at end of file