001/*
002 * *** BEGIN LICENSE BLOCK *****
003 *  Version: MPL 1.1/GPL 2.0/LGPL 2.1
004 *
005 *  The contents of this file are subject to the Mozilla Public License Version
006 *  1.1 (the "License"); you may not use this file except in compliance with
007 *  the License. You may obtain a copy of the License at
008 *  http://www.mozilla.org/MPL/
009 *
010 *  Software distributed under the License is distributed on an "AS IS" basis,
011 *  WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
012 *  for the specific language governing rights and limitations under the
013 *  License.
014 *
015 *  The Original Code is part of dcm4che, an implementation of DICOM(TM) in
016 *  Java(TM), hosted at https://github.com/gunterze/dcm4che.
017 *
018 *  The Initial Developer of the Original Code is
019 *  Agfa Healthcare.
020 *  Portions created by the Initial Developer are Copyright (C) 2015
021 *  the Initial Developer. All Rights Reserved.
022 *
023 *  Contributor(s):
024 *  See @authors listed below
025 *
026 *  Alternatively, the contents of this file may be used under the terms of
027 *  either the GNU General Public License Version 2 or later (the "GPL"), or
028 *  the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
029 *  in which case the provisions of the GPL or the LGPL are applicable instead
030 *  of those above. If you wish to allow use of your version of this file only
031 *  under the terms of either the GPL or the LGPL, and not to allow others to
032 *  use your version of this file under the terms of the MPL, indicate your
033 *  decision by deleting the provisions above and replace them with the notice
034 *  and other provisions required by the GPL or the LGPL. If you do not delete
035 *  the provisions above, a recipient may use your version of this file under
036 *  the terms of any one of the MPL, the GPL or the LGPL.
037 *
038 *  ***** END LICENSE BLOCK *****
039 */
040
041package org.dcm4che3.conf.core.api;
042
043import java.io.Serializable;
044import java.util.ArrayList;
045import java.util.Collections;
046import java.util.Iterator;
047import java.util.List;
048import java.util.regex.Matcher;
049import java.util.regex.Pattern;
050
051/**
052 * <b>CAUTION:</b>
053 * List indexes in the path representation start with 0. When converted to XPath, 1 is added as in XPath indexes start with 1.
054 *
055 * @author Roman K
056 */
057public class Path implements Serializable {
058
059    private static final long serialVersionUID = 1069976968612802603L;
060    private static Pattern itemPattern = Pattern.compile("/(?<item>(\\\\/|[^/\\[\\]@\\*])*)");
061    private static Pattern simplePathPattern = Pattern.compile("(" + itemPattern + ")*");
062
063    public static final Path ROOT = new Path();
064
065    private final List<Object> pathItems;
066
067
068    private transient String simpleEscapedXPath;
069    private transient String simpleEscapedPath;
070
071    public Path() {
072        pathItems = Collections.unmodifiableList(new ArrayList<Object>());
073    }
074
075    public Path(Object... pathItems) {
076
077        ArrayList<Object> strings = new ArrayList<Object>(pathItems.length);
078        Collections.addAll(strings, pathItems);
079        this.pathItems = Collections.unmodifiableList(strings);
080
081        validate();
082    }
083
084    public Path(List<?> pathItems) {
085        this.pathItems = Collections.unmodifiableList(new ArrayList<Object>(pathItems));
086        validate();
087    }
088
089
090    public Path(Iterator<Object> stringIterator) {
091        ArrayList<Object> strings = new ArrayList<Object>();
092        while (stringIterator.hasNext())
093            strings.add(stringIterator.next());
094        this.pathItems = Collections.unmodifiableList(strings);
095        validate();
096    }
097
098    private void validate() {
099        for (Object pathItem : this.pathItems) {
100            if (!((pathItem instanceof String) || (pathItem instanceof Integer)))
101                throw new IllegalArgumentException("Item '" + pathItem + "' is not allowed in path");
102        }
103    }
104
105
106    @Override
107    public boolean equals(Object obj) {
108
109        if (obj == this)
110            return true;
111        if (!(obj instanceof Path))
112            return false;
113
114        return pathItems.equals(((Path) obj).pathItems);
115    }
116
117    public List<Object> getPathItems() {
118        return pathItems;
119    }
120
121    /**
122     * @param indexFrom inclusive
123     * @param indexTo   NOT inclusive
124     */
125    public Path subPath(int indexFrom, int indexTo) {
126
127        ArrayList<Object> newItems = new ArrayList<Object>();
128        while (indexFrom < indexTo) {
129            newItems.add(pathItems.get(indexFrom++));
130        }
131
132        return new Path(newItems);
133    }
134
135    public int size() {
136        return getPathItems().size();
137    }
138
139    public String toSimpleEscapedXPath() {
140        if (simpleEscapedXPath != null)
141            return simpleEscapedXPath;
142
143        String xpath = "";
144        for (Object item : pathItems) {
145
146            if (item instanceof String) {
147                xpath += "/" + ((String) item).replace("/", "\\/");
148            } else if (item instanceof Integer) {
149                // XPath indexes START WITH 1
150                xpath += "[" + ((Integer) item + 1) + "]";
151            } else
152                throw new RuntimeException("Unexpected error");
153
154        }
155        simpleEscapedXPath = xpath;
156        return xpath;
157    }
158
159    public String toSimpleEscapedPath() {
160
161        if (simpleEscapedPath != null)
162            return simpleEscapedPath;
163
164        String xpath = "";
165        for (Object item : pathItems) {
166            xpath += "/";
167
168            if (item instanceof Integer) {
169                xpath += "#";
170            }
171
172            xpath += item
173                    .toString()
174                    .replace("/", "\\/")
175                    .replace("#", "\\#");
176        }
177        simpleEscapedPath = xpath;
178
179        return xpath;
180    }
181
182
183    @Override
184    public String toString() {
185        return toSimpleEscapedXPath();
186    }
187
188    public static Path fromSimpleEscapedPath(String pathStr) {
189        Path path = fromSimpleEscapedPathOrNull(pathStr);
190
191        if (path == null)
192            throw new IllegalArgumentException("Simple path " + pathStr + " is invalid");
193        return path;
194    }
195
196    public static Path fromSimpleEscapedPathOrNull(String path) {
197
198        if (!simplePathPattern.matcher(path).matches())
199            return null;
200
201        Matcher matcher = itemPattern.matcher(path);
202
203        List<Object> list = new ArrayList<Object>();
204        while (matcher.find()) {
205            String item = matcher.group("item");
206
207            if (item.startsWith("#")) {
208                list.add(Integer.parseInt(item.substring(1)));
209            } else {
210                list.add(item
211                        .replace("\\/", "/")
212                        .replace("\\#", "#")
213                );
214            }
215
216        }
217
218        return new Path(list);
219    }
220}