// Copyright 2015 The Project Buendia Authors // // 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 distrib- // uted 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 // specific language governing permissions and limitations under the License. package org.projectbuendia.client.models; import android.content.ContentValues; import android.database.Cursor; import android.support.annotation.NonNull; import org.joda.time.DateTime; import org.joda.time.Interval; import org.json.JSONException; import org.json.JSONObject; import org.projectbuendia.client.json.JsonOrder; import org.projectbuendia.client.providers.Contracts; import org.projectbuendia.client.utils.Utils; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; /** An order in the app model. */ @Immutable public final class Order extends Base<String> implements Comparable<Order> { public static final char NON_BREAKING_SPACE = '\u00a0'; public final @Nullable String uuid; public final String patientUuid; public final String instructions; public final DateTime start; public final @Nullable DateTime stop; public static Order fromJson(JsonOrder order) { return new Order(order.uuid, order.patient_uuid, order.instructions, order.start_millis, order.stop_millis); } // TODO/robustness: Store medication, dosage, and frequency as separate fields instead of // mashing them into one free-text instructions field. This will also enable // internationalization. // [Any amount of non-whitespace][space][any text][space][more than one digit]x[space][any text] // OR [Any amount of non-whitespace][an optional space][any text] // Note that the second branch will match everything - even the empty string - and shoehorn // the order instructions into the medication and dosage fields. public static final Pattern INSTRUCTIONS_PATTERN = Pattern.compile( "([^ ]*) (.*) ([0-9]+)x .*|([^ ]*) ?(.*)"); public static String getInstructions(String medication, String dosage, String frequency) { medication = Utils.valueOrDefault(medication, ""); dosage = Utils.valueOrDefault(dosage, ""); frequency = Utils.valueOrDefault(frequency, ""); if (!frequency.isEmpty()) { frequency += "x daily"; } return (medication.replace(' ', NON_BREAKING_SPACE) + " " + dosage + " " + frequency) .trim(); } public static String getMedication(String instructions) { Matcher matcher = INSTRUCTIONS_PATTERN.matcher(Utils.valueOrDefault(instructions, "")); if (matcher.matches()) { // These groups are the "([^ ]*)" in each branch of the regex above. String group = Utils.valueOrDefault(matcher.group(1), matcher.group(4)); return group.replace(NON_BREAKING_SPACE, ' '); } return null; } public static String getDosage(String instructions) { Matcher matcher = INSTRUCTIONS_PATTERN.matcher(Utils.valueOrDefault(instructions, "")); if (matcher.matches()) { // These groups are the `(.*)` in each branch of the regex. return Utils.valueOrDefault(matcher.group(2), matcher.group(5)); } return null; } public static String getFrequency(String instructions) { Matcher matcher = INSTRUCTIONS_PATTERN.matcher(Utils.valueOrDefault(instructions, "")); if (matcher.matches()) { return matcher.group(3); } return null; } public Order(@Nullable String uuid, String patientUuid, String instructions, DateTime start, @Nullable DateTime stop) { this.uuid = uuid; this.patientUuid = patientUuid; this.instructions = instructions; this.start = start; this.stop = stop; } public Order(@Nullable String uuid, String patientUuid, String instructions, Long startMillis, @Nullable Long stopMillis) { this.uuid = uuid; this.patientUuid = patientUuid; this.instructions = instructions; this.start = new DateTime(startMillis); this.stop = stopMillis == null ? null : new DateTime(stopMillis); } public String getMedication() { return getMedication(instructions); } public String getDosage() { return getDosage(instructions); } public String getFrequency() { return getFrequency(instructions); } public Interval getInterval() { return Utils.toInterval(start, stop); } @Override public int compareTo(@NonNull Order other) { int result = start.compareTo(other.start); result = result != 0 ? result : instructions.compareTo(other.instructions); return result; } public JSONObject toJson() throws JSONException { JSONObject json = new JSONObject(); json.put("patient_uuid", patientUuid); json.put("instructions", instructions); json.put("start_millis", start.getMillis()); // Use `JSONObject.NULL` instead of `null` so that the value is actually set. json.put("stop_millis", stop == null ? JSONObject.NULL : stop.getMillis()); return json; } public ContentValues toContentValues() { ContentValues cv = new ContentValues(); cv.put(Contracts.Orders.UUID, uuid); cv.put(Contracts.Orders.PATIENT_UUID, patientUuid); cv.put(Contracts.Orders.INSTRUCTIONS, instructions); cv.put(Contracts.Orders.START_MILLIS, start.getMillis()); cv.put(Contracts.Orders.STOP_MILLIS, stop == null ? null : stop.getMillis()); return cv; } /** An {@link CursorLoader} that reads a Cursor and creates an {@link Order}. */ @Immutable public static class Loader implements CursorLoader<Order> { @Override public Order fromCursor(Cursor cursor) { return new Order( cursor.getString(cursor.getColumnIndex(Contracts.Orders.UUID)), cursor.getString(cursor.getColumnIndex(Contracts.Orders.PATIENT_UUID)), cursor.getString(cursor.getColumnIndex(Contracts.Orders.INSTRUCTIONS)), cursor.getLong(cursor.getColumnIndex(Contracts.Orders.START_MILLIS)), cursor.getLong(cursor.getColumnIndex(Contracts.Orders.STOP_MILLIS)) ); } } }