001/*-
002 * #%L
003 * HAPI FHIR - Core Library
004 * %%
005 * Copyright (C) 2014 - 2024 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.parser.path;
021
022import org.apache.commons.lang3.Validate;
023
024import java.util.ArrayList;
025import java.util.List;
026import java.util.StringTokenizer;
027import java.util.stream.Collectors;
028
029import static org.apache.commons.lang3.StringUtils.isNotBlank;
030
031public class EncodeContextPath {
032        private final List<EncodeContextPathElement> myPath;
033        private final ArrayList<EncodeContextPathElement> myResourcePath = new ArrayList<>(10);
034
035        public EncodeContextPath() {
036                this(new ArrayList<>(10));
037        }
038
039        public EncodeContextPath(String thePath) {
040                this();
041
042                StringTokenizer tok = new StringTokenizer(thePath, ".");
043                boolean first = true;
044                while (tok.hasMoreTokens()) {
045                        String next = tok.nextToken();
046                        if (first && next.equals("*")) {
047                                getPath().add(new EncodeContextPathElement("*", true));
048                        } else if (isNotBlank(next)) {
049                                getPath().add(new EncodeContextPathElement(next, Character.isUpperCase(next.charAt(0))));
050                        }
051                        first = false;
052                }
053        }
054
055        public EncodeContextPath(List<EncodeContextPathElement> thePath) {
056                myPath = thePath;
057        }
058
059        @Override
060        public String toString() {
061                return myPath.stream().map(t -> t.toString()).collect(Collectors.joining("."));
062        }
063
064        public List<EncodeContextPathElement> getPath() {
065                return myPath;
066        }
067
068        public EncodeContextPath getCurrentResourcePath() {
069                EncodeContextPath retVal = null;
070                for (int i = myPath.size() - 1; i >= 0; i--) {
071                        if (myPath.get(i).isResource()) {
072                                retVal = new EncodeContextPath(myPath.subList(i, myPath.size()));
073                                break;
074                        }
075                }
076                Validate.isTrue(retVal != null);
077                return retVal;
078        }
079
080        /**
081         * Add an element at the end of the path
082         */
083        public void pushPath(String thePathElement, boolean theResource) {
084                assert isNotBlank(thePathElement);
085                assert !thePathElement.contains(".");
086                assert theResource ^ Character.isLowerCase(thePathElement.charAt(0));
087
088                EncodeContextPathElement element = new EncodeContextPathElement(thePathElement, theResource);
089                getPath().add(element);
090                if (theResource) {
091                        myResourcePath.add(element);
092                }
093        }
094
095        /**
096         * Remove the element at the end of the path
097         */
098        public void popPath() {
099                EncodeContextPathElement removed = getPath().remove(getPath().size() - 1);
100                if (removed.isResource()) {
101                        myResourcePath.remove(myResourcePath.size() - 1);
102                }
103        }
104
105        public ArrayList<EncodeContextPathElement> getResourcePath() {
106                return myResourcePath;
107        }
108
109        public String getLeafElementName() {
110                return getPath().get(getPath().size() - 1).getName();
111        }
112
113        public String getLeafResourceName() {
114                return myResourcePath.get(myResourcePath.size() - 1).getName();
115        }
116
117        public String getLeafResourcePathFirstField() {
118                String retVal = null;
119                for (int i = getPath().size() - 1; i >= 0; i--) {
120                        if (getPath().get(i).isResource()) {
121                                break;
122                        } else {
123                                retVal = getPath().get(i).getName();
124                        }
125                }
126                return retVal;
127        }
128
129        /**
130         * Tests and returns whether this path starts with {@literal theCurrentResourcePath}
131         *
132         * @param theCurrentResourcePath The path to test
133         * @param theAllowSymmmetrical   If <code>true</code>, this method will return true if {@literal theCurrentResourcePath} starts with this path as well as testing whether this path starts with {@literal theCurrentResourcePath}
134         */
135        public boolean startsWith(EncodeContextPath theCurrentResourcePath, boolean theAllowSymmmetrical) {
136                for (int i = 0; i < getPath().size(); i++) {
137                        if (theCurrentResourcePath.getPath().size() == i) {
138                                return true;
139                        }
140                        EncodeContextPathElement expected = getPath().get(i);
141                        EncodeContextPathElement actual = theCurrentResourcePath.getPath().get(i);
142                        if (!expected.matches(actual)) {
143                                return false;
144                        }
145                }
146
147                if (theAllowSymmmetrical) {
148                        return true;
149                }
150
151                return getPath().size() == theCurrentResourcePath.getPath().size();
152        }
153
154        public boolean equalsPath(String thePath) {
155                EncodeContextPath parsedPath = new EncodeContextPath(thePath);
156                return getPath().equals(parsedPath.getPath());
157        }
158}