// 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.openmrs.projectbuendia; import org.openmrs.Order; import org.openmrs.Person; import org.openmrs.Provider; import org.openmrs.User; import org.openmrs.api.context.Context; import org.openmrs.projectbuendia.webservices.rest.InvalidObjectDataException; import javax.annotation.Nullable; import java.text.DateFormat; import java.text.Normalizer; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Comparator; import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.TimeZone; import java.util.regex.Matcher; import java.util.regex.Pattern; public class Utils { /** ISO 8601 format for a complete date and time in UTC. */ public static final DateFormat FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); /** A SimpleDateFormat that formats as "yyyy-MM-dd" in UTC. */ public static final DateFormat YYYYMMDD_UTC_FORMAT = new SimpleDateFormat("yyyy-MM-dd"); /** A SimpleDateFormat that formats a date and time to be auto-parsed in a spreadsheet. */ public static final DateFormat SPREADSHEET_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); public static final TimeZone UTC = TimeZone.getTimeZone("Etc/UTC"); static { FORMAT.setTimeZone(UTC); YYYYMMDD_UTC_FORMAT.setTimeZone(UTC); SPREADSHEET_FORMAT.setTimeZone(UTC); } /** * Compares two objects that may each be null, Integer, or String. null sorts * before everything; all Integers sort before all Strings; Integers sort * according to numeric value; Strings sort according to string value. */ public static Comparator<Object> nullIntStrComparator = new Comparator<Object>() { @Override public int compare(Object a, Object b) { if (a instanceof Integer && b instanceof Integer) { return (Integer) a - (Integer) b; } if (a instanceof String && b instanceof String) { return ((String) a).compareTo((String) b); } return (a == null ? 0 : a instanceof Integer ? 1 : 2) - (b == null ? 0 : b instanceof Integer ? 1 : 2); } }; /** * Compares two lists, each of whose elements is a null, Integer, or String, * lexicographically by element, just like Python does. */ public static Comparator<List<Object>> nullIntStrListComparator = new Comparator<List<Object>>() { @Override public int compare(List<Object> a, List<Object> b) { for (int i = 0; i < Math.min(a.size(), b.size()); i++) { int result = nullIntStrComparator.compare(a.get(i), b.get(i)); if (result != 0) return result; } return a.size() - b.size(); } }; // Note: Use of \L here assumes a string that is already NFC-normalized. private static final Pattern NUMBER_OR_WORD_PATTERN = Pattern.compile("([0-9]+)|\\p{L}+"); /** * Compares two strings in a way that sorts alphabetic parts in alphabetic * order and numeric parts in numeric order, while guaranteeing that: * - compare(s, t) == 0 if and only if s.equals(t). * - compare(s, s + t) < 0 for any strings s and t. * - compare(s + x, s + y) == Integer.compare(x, y) for all integers x, y * and strings s that do not end in a digit. * - compare(s + t, s + u) == compare(s, t) for all strings s and strings * t, u that consist entirely of Unicode letters. * For example, the strings ["b1", "a11a", "a11", "a2", "a2b", "a2a", "a1"] * have the sort order ["a1", "a2", "a2a", "a2b", "a11", "a11a", "b1"]. */ public static Comparator<String> alphanumericComparator = new Comparator<String>() { @Override public int compare(String a, String b) { String aNormalized = Normalizer.normalize(a == null ? "" : a, Normalizer.Form.NFC); String bNormalized = Normalizer.normalize(b == null ? "" : b, Normalizer.Form.NFC); List<Object> aParts = getParts(aNormalized); List<Object> bParts = getParts(bNormalized); // Add a separator to ensure that the tiebreakers added below are never // compared against the actual numeric or alphabetic parts. aParts.add(null); bParts.add(null); // Break ties between strings that yield the same parts (e.g. "a04b" // and "a4b") using the normalized original string as a tiebreaker. aParts.add(aNormalized); bParts.add(bNormalized); // Break ties between strings that become the same after normalization // using the non-normalized string as a further tiebreaker. aParts.add(a); bParts.add(b); return nullIntStrListComparator.compare(aParts, bParts); } /** * Breaks a string into a list of Integers (from sequences of ASCII digits) * and Strings (from sequences of letters). Other characters are ignored. */ private List<Object> getParts(String str) { Matcher matcher = NUMBER_OR_WORD_PATTERN.matcher(str); List<Object> parts = new ArrayList<>(); while (matcher.find()) { String part = matcher.group(); String intPart = matcher.group(1); parts.add(intPart != null ? Integer.valueOf(intPart) : part); } return parts; } }; /** * Adjusts an encounter datetime to ensure that OpenMRS will accept it. * The OpenMRS core is not designed for a client-server setup -- it will * summarily reject a submitted encounter if the encounter_datetime is in * the future, even if the client's clock is off by only one millisecond. * @param datetime The date and time of an encounter. * @return */ public static Date fixEncounterDateTime(Date datetime) { Date now = new Date(); if (datetime.after(now)) { datetime = now; } return datetime; } /** Formats a {@link Date} as an ISO 8601 string in the UTC timezone. */ public static String toIso8601(Date dateTime) { return FORMAT.format(dateTime); } /** Parses an ISO 8601-formatted date into a {@link Date}. */ public static Date fromIso8601(String iso8601) throws ParseException { return FORMAT.parse(iso8601); } /** Parses a yyyy-MM-dd date, yielding a Date object at UTC midnight on the given date. */ public static Date parseLocalDate(String text, String fieldName) { try { return YYYYMMDD_UTC_FORMAT.parse(text); } catch (ParseException e) { throw new InvalidObjectDataException(String.format( "The %s field should be in yyyy-MM-dd format", fieldName)); } } /** * Converts a JSON-parsed number (sometimes Integer, sometimes Long) to a nullable Long. */ public static Long asLong(Object obj) { if (obj == null) { return null; } if (obj instanceof Integer) { return Long.valueOf((Integer) obj); } if (obj instanceof Long) { return (Long) obj; } throw new ClassCastException("Expected value of type Long or Integer"); } /** * Iterates backwards through revision orders until it finds the root order. */ public static Order getRootOrder(Order order) { while (order.getPreviousOrder() != null) { order = order.getPreviousOrder(); } return order; } public static @Nullable User getUserFromProvider(@Nullable Provider provider) { if (provider == null) { return null; } Person person = provider.getPerson(); if (person == null) { throw new IllegalStateException( "Should not be possible to get null person from provider."); } List<User> users = Context.getUserService().getUsersByPerson(person, false); if (users.size() < 1) { // This is a server error. throw new IllegalStateException("There is no user for the associated provider"); } return users.get(0); } public static @Nullable User getUserFromProviderUuid(@Nullable String providerUuid) { if (providerUuid == null) { return null; } Provider provider = Context.getProviderService().getProviderByUuid(providerUuid); return getUserFromProvider(provider); } public static @Nullable Provider getProviderFromUser(@Nullable User user) { if (user == null) { return null; } Person person = user.getPerson(); Iterator<Provider> providers = Context.getProviderService().getProvidersByPerson(person).iterator(); if (providers.hasNext()) { return providers.next(); } else { return null; } } }