Java tutorial
// 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 com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import org.joda.time.Instant; import org.joda.time.LocalDate; import org.joda.time.ReadableInstant; import org.json.JSONException; import org.json.JSONObject; import org.projectbuendia.client.utils.Utils; import java.util.Map; import java.util.Objects; import java.util.Set; import javax.annotation.Nonnull; import javax.annotation.Nullable; /** The value of an observation, represented as a union of all the possible data types. */ public final class ObsValue implements Comparable<ObsValue> { /** The observed value as a concept UUID, if the observation is for a coded concept. */ public final @Nullable String uuid; /** The observed numeric value, if the observation is for a numeric concept. */ public final @Nullable Double number; /** The observed string value, if the observation is for a text concept. */ public final @Nullable String text; /** The observed value as a LocalDate, if the observation is for a calendar date. */ public final @Nullable LocalDate date; /** The observed value as an Instant, if the observation is for an instant in time. */ public final @Nullable Instant instant; /** * The localized name of the observed value concept (specified in the 'uuid' field). * This is initially null, not used for comparison, and filled in only as needed for display. */ public @Nullable String name; public static final ObsValue MIN_VALUE = ObsValue.newCoded(false); public static final ObsValue MAX_VALUE = ObsValue.newTime(Long.MAX_VALUE); public static final ObsValue MIN_DATE = ObsValue.newDate(Utils.MIN_DATE); public static final ObsValue MAX_DATE = ObsValue.newDate(Utils.MAX_DATE); public static final ObsValue MIN_TIME = ObsValue.newTime(Utils.MIN_TIME); public static final ObsValue MAX_TIME = ObsValue.newTime(Utils.MAX_TIME); public static final ObsValue ZERO = ObsValue.newNumber(0); public static final ObsValue FALSE = ObsValue.newCoded(false); public static final ObsValue TRUE = ObsValue.newCoded(true); private static final Map<String, Integer> CODED_VALUE_ORDERING = new ImmutableMap.Builder<String, Integer>() .put(ConceptUuids.NO_UUID, -100).put(ConceptUuids.NONE_UUID, -1).put(ConceptUuids.NORMAL_UUID, -1) .put(ConceptUuids.SOLID_FOOD_UUID, -1).put(ConceptUuids.MILD_UUID, 1).put(ConceptUuids.MODERATE_UUID, 2) .put(ConceptUuids.SEVERE_UUID, 3).put(ConceptUuids.YES_UUID, 100).build(); /** Coded values that are considered false by asBoolean(). */ private static final Set<String> FALSE_CONCEPT_UUIDS = ImmutableSet.of(ConceptUuids.NO_UUID, ConceptUuids.NONE_UUID, ConceptUuids.NORMAL_UUID, ConceptUuids.UNKNOWN_UUID); // All constructors must honour the invariant that exactly one field is non-null. public static ObsValue newCoded(boolean bool) { return newCoded(bool ? ConceptUuids.YES_UUID : ConceptUuids.NO_UUID); } public static ObsValue newCoded(@Nonnull String uuid) { return new ObsValue(uuid, null, null, null, null); } public static ObsValue newCoded(@Nonnull String uuid, String name) { ObsValue ov = ObsValue.newCoded(uuid); ov.name = name; return ov; } public static ObsValue newNumber(double number) { return new ObsValue(null, number, null, null, null); } public static ObsValue newText(@Nonnull String text) { return new ObsValue(null, null, text, null, null); } public static ObsValue newDate(@Nonnull LocalDate date) { return new ObsValue(null, null, null, date, null); } public static ObsValue newTime(@Nonnull ReadableInstant instant) { return new ObsValue(null, null, null, null, instant); } public static ObsValue newTime(long millis) { return new ObsValue(null, null, null, null, new Instant(millis)); } public boolean asBoolean() { if (uuid != null) { return !FALSE_CONCEPT_UUIDS.contains(uuid); } else if (number != null) { return number != 0; } else if (text != null) { return !text.isEmpty(); } else if (date != null) { return true; } else if (instant != null) { return true; } return false; } @Override public String toString() { if (uuid != null) { return "ObsValue(uuid=" + uuid + ", name=" + name + ")"; } else if (number != null) { return "ObsValue(number=" + number + ")"; } else if (text != null) { return "ObsValue(text=" + text + ")"; } else if (date != null) { return "ObsValue(date=" + date + ")"; } else if (instant != null) { return "ObsValue(instant=" + instant + ")"; } else { throw new IllegalStateException(); // this should never happen } } public JSONObject toJson() throws JSONException { JSONObject jo = new JSONObject(); if (uuid != null) { jo.put("uuid", uuid); } else if (number != null) { jo.put("number", number); } else if (text != null) { jo.put("text", text); } else if (date != null) { jo.put("date", date.toString()); // LocalDate gives a string in yyyy-mm-dd format } else if (instant != null) { jo.put("instant", instant.getMillis()); } return jo; } @Override public boolean equals(Object other) { if (!(other instanceof ObsValue)) return false; ObsValue o = (ObsValue) other; return Objects.equals(uuid, o.uuid) // compare only the final fields && Objects.equals(number, o.number) && Objects.equals(text, o.text) && Objects.equals(date, o.date) && Objects.equals(instant, o.instant); } @Override public int hashCode() { return Objects.hashCode(number) // hash only the final fields + Objects.hashCode(text) + Objects.hashCode(uuid) + Objects.hashCode(date) + Objects.hashCode(instant); } /** * Compares ObsValue instances according to a total ordering such that: * - All non-null values are greater than null. * - The lowest value is the "false" Boolean value (encoded as the coded concept for "No"). * - Next are all coded values, ordered from least severe to most severe (if they can * be interpreted as having a severity); or from first to last (if they can * be interpreted as having a typical temporal sequence). * - Next is the "true" Boolean value (encoded as the coded concept for "Yes"). * - Next are all numeric values, ordered from least to greatest. * - Next are all text values, ordered lexicographically from A to Z. * - Next are all date values, ordered from least to greatest. * - Next are all instant values, ordered from least to greatest. * @param other The other Value to compare to. * @return */ @Override public int compareTo(@Nullable ObsValue other) { if (other == null) return 1; int result = 0; result = Integer.compare(getTypeOrdering(), other.getTypeOrdering()); if (result != 0) return result; if (uuid != null) { result = Integer.compare(getUuidOrdering(), other.getUuidOrdering()); if (result != 0) return result; result = uuid.compareTo(other.uuid); } else if (number != null) { result = Double.compare(number, other.number); } else if (text != null) { result = text.compareTo(other.text); } else if (date != null) { result = date.compareTo(other.date); } else if (instant != null) { result = instant.compareTo(other.instant); } return result; } /** This constructor is private so that we can ensure exactly one field is non-null. */ private ObsValue(@Nullable String uuid, @Nullable Double number, @Nullable String text, @Nullable LocalDate date, @Nullable ReadableInstant instant) { this.uuid = uuid; this.number = number; this.text = text; this.date = date; this.instant = new Instant(instant); } /** Gets a number specifying the ordering of ObsValues of different types. */ private int getTypeOrdering() { return uuid != null ? 1 : number != null ? 2 : text != null ? 3 : date != null ? 4 : instant != null ? 5 : 0; // this 0 case should never happen } /** * Gets a number specifying the ordering of coded values. These are arranged from least to * most severe, or earliest to latest in typical temporal sequence, so that the maximum value * in a list of values for a particular concept is the most severe value or latest value. */ private int getUuidOrdering() { Integer cvo = CODED_VALUE_ORDERING.get(uuid); return cvo == null ? 0 : cvo; } }