001/*
002 * #%L
003 * HAPI FHIR - Core Library
004 * %%
005 * Copyright (C) 2014 - 2026 Smile CDR, Inc.
006 * %%
007 * Licensed under the Apache License, Version 2.0 (the "License");
008 * you may not use this file except in compliance with the License.
009 * You may obtain a copy of the License at
010 *
011 *      http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing, software
014 * distributed under the License is distributed on an "AS IS" BASIS,
015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 * #L%
019 */
020package ca.uhn.fhir.rest.api;
021
022import ca.uhn.fhir.i18n.Msg;
023
024import java.io.Serial;
025import java.io.Serializable;
026import java.util.Objects;
027
028/**
029 * Represents values for <a href="http://hl7.org/implement/standards/fhir/search.html#sort">sorting</a> resources
030 * returned by a server.
031 */
032public class SortSpec implements Serializable {
033
034        @Serial
035        private static final long serialVersionUID = 2866833099879713467L;
036
037        private SortSpec myChain;
038        private String myParamName;
039        private SortOrderEnum myOrder;
040
041        /**
042         * Constructor
043         */
044        public SortSpec() {
045                super();
046        }
047
048        /**
049         * Constructor
050         *
051         * @param theParamName The search name to sort on. See {@link #setParamName(String)} for more information.
052         */
053        public SortSpec(String theParamName) {
054                super();
055                myParamName = theParamName;
056        }
057
058        /**
059         * Constructor
060         *
061         * @param theParamName The search name to sort on. See {@link #setParamName(String)} for more information.
062         * @param theOrder     The order, or <code>null</code>. See {@link #setOrder(SortOrderEnum)} for more information.
063         */
064        public SortSpec(String theParamName, SortOrderEnum theOrder) {
065                super();
066                myParamName = theParamName;
067                myOrder = theOrder;
068        }
069
070        /**
071         * Constructor
072         *
073         * @param theParamName The search name to sort on. See {@link #setParamName(String)} for more information.
074         * @param theOrder     The order, or <code>null</code>. See {@link #setOrder(SortOrderEnum)} for more information.
075         * @param theChain     The next sorting spec, to be applied only when this spec makes two entries equal. See
076         *                     {@link #setChain(SortSpec)} for more information.
077         */
078        public SortSpec(String theParamName, SortOrderEnum theOrder, SortSpec theChain) {
079                super();
080                myParamName = theParamName;
081                myOrder = theOrder;
082                myChain = theChain;
083        }
084
085        /**
086         * Gets the chained sort specification, or <code>null</code> if none. If multiple sort parameters are chained
087         * (indicating a sub-sort), the second level sort is chained via this property.
088         */
089        public SortSpec getChain() {
090                return myChain;
091        }
092
093        /**
094         * Sets the chained sort specification, or <code>null</code> if none. If multiple sort parameters are chained
095         * (indicating a sub-sort), the second level sort is chained via this property.
096         */
097        public SortSpec setChain(SortSpec theChain) {
098                if (theChain == this) {
099                        throw new IllegalArgumentException(Msg.code(1966) + "Can not chain this to itself");
100                }
101                myChain = theChain;
102                return this;
103        }
104
105        /**
106         * Returns the actual name of the search param to sort by
107         */
108        public String getParamName() {
109                return myParamName;
110        }
111
112        /**
113         * Sets the actual name of the search param to sort by
114         */
115        public SortSpec setParamName(String theFieldName) {
116                myParamName = theFieldName;
117                return this;
118        }
119
120        /**
121         * Returns the sort order specified by this parameter, or <code>null</code> if none is explicitly provided (which
122         * means {@link SortOrderEnum#ASC} according to the <a
123         * href="http://hl7.org/implement/standards/fhir/search.html#sort">FHIR specification</a>)
124         */
125        public SortOrderEnum getOrder() {
126                return myOrder;
127        }
128
129        /**
130         * Sets the sort order specified by this parameter, or <code>null</code> if none should be explicitly defined (which
131         * means {@link SortOrderEnum#ASC} according to the <a
132         * href="http://hl7.org/implement/standards/fhir/search.html#sort">FHIR specification</a>)
133         */
134        public SortSpec setOrder(SortOrderEnum theOrder) {
135                myOrder = theOrder;
136                return this;
137        }
138
139        @Override
140        public String toString() {
141                StringBuilder b = new StringBuilder();
142                b.append(getParamName());
143                if (getOrder() != null) {
144                        b.append('(').append(getOrder()).append(')');
145                }
146                if (myChain != null) {
147                        b.append(',').append(myChain);
148                }
149                return b.toString();
150        }
151
152        @Override
153        public boolean equals(Object theO) {
154                if (!(theO instanceof SortSpec sortSpec)) {
155                        return false;
156                }
157                return Objects.equals(myChain, sortSpec.myChain)
158                                && Objects.equals(myParamName, sortSpec.myParamName)
159                                && myOrder == sortSpec.myOrder;
160        }
161
162        @Override
163        public int hashCode() {
164                return Objects.hash(myChain, myParamName, myOrder);
165        }
166
167        /**
168         * Convert strings like "-date" into a SortSpec object.
169         * Note: this does not account for DSTU2-style sort modifiers like "date:desc" or "date:asc"
170         * since those are on the parameter name, not the value.
171         *
172         * @param theParamValue a string like "-date" or "date"
173         * @return a parsed SortSpec object
174         */
175        public static SortSpec fromR3OrLaterParameterValue(String theParamValue) {
176                SortOrderEnum direction = SortOrderEnum.ASC;
177                if (theParamValue.startsWith("-")) {
178                        direction = SortOrderEnum.DESC;
179                        theParamValue = theParamValue.substring(1);
180                }
181                return new SortSpec(theParamValue, direction);
182        }
183}