001/*
002 *  Licensed to the Apache Software Foundation (ASF) under one
003 *  or more contributor license agreements.  See the NOTICE file
004 *  distributed with this work for additional information
005 *  regarding copyright ownership.  The ASF licenses this file
006 *  to you under the Apache License, Version 2.0 (the
007 *  "License"); you may not use this file except in compliance
008 *  with the License.  You may obtain a copy of the License at
009 *
010 *        http://www.apache.org/licenses/LICENSE-2.0
011 *
012 *  Unless required by applicable law or agreed to in writing,
013 *  software distributed under the License is distributed on an
014 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 *  KIND, either express or implied.  See the License for the
016 *  specific language governing permissions and limitations
017 *  under the License.
018 */
019package org.apache.isis.viewer.restfulobjects.applib.util;
020
021import java.util.Collections;
022import java.util.List;
023import java.util.Map;
024import java.util.regex.Matcher;
025import java.util.regex.Pattern;
026import com.google.common.base.Objects;
027import com.google.common.base.Splitter;
028import com.google.common.collect.Lists;
029import com.google.common.collect.Maps;
030import org.apache.isis.viewer.restfulobjects.applib.JsonRepresentation;
031
032public class PathNode {
033    private static final Pattern NODE = Pattern.compile("^([^\\[]*)(\\[(.+)\\])?$");
034    private static final Pattern WHITESPACE = Pattern.compile("\\s+");
035    private static final Pattern LIST_CRITERIA_SYNTAX = Pattern.compile("^([^=]+)=(.+)$");
036
037    public static final PathNode NULL = new PathNode("", Collections.<String, String> emptyMap());
038
039    public static List<String> split(String path) {
040        List<String> parts = Lists.newArrayList();
041        String curr = null;
042        for (String part : Splitter.on(".").split(path)) {
043            if(curr != null) {
044                if(part.contains("]")) {
045                    curr = curr + "." + part;
046                    parts.add(curr);
047                    curr = null;
048                } else {
049                    curr = curr + "." + part;
050                }
051                continue;
052            }
053            if(!part.contains("[")) {
054                parts.add(part);
055                continue;
056            } 
057            if(part.contains("]")) {
058                parts.add(part);
059            } else {
060                curr = part;
061            }
062        }
063        return parts;
064    }
065
066    public static PathNode parse(final String path) {
067        final Matcher nodeMatcher = NODE.matcher(path);
068        if (!nodeMatcher.matches()) {
069            return null;
070        }
071        final int groupCount = nodeMatcher.groupCount();
072        if (groupCount < 1) {
073            return null;
074        }
075        final String key = nodeMatcher.group(1);
076        final Map<String, String> criteria = Maps.newHashMap();
077        final String criteriaStr = nodeMatcher.group(3);
078        if (criteriaStr != null) {
079            for (final String criterium : Splitter.on(WHITESPACE).split(criteriaStr)) {
080                final Matcher keyValueMatcher = LIST_CRITERIA_SYNTAX.matcher(criterium);
081                if (keyValueMatcher.matches()) {
082                    criteria.put(keyValueMatcher.group(1), keyValueMatcher.group(2));
083                } else {
084                    // take content as a map criteria
085                    criteria.put(criterium, null);
086                }
087            }
088        }
089
090        return new PathNode(key, criteria);
091    }
092
093    private final String key;
094    private final Map<String, String> criteria;
095
096    private PathNode(final String key, final Map<String, String> criteria) {
097        this.key = key;
098        this.criteria = Collections.unmodifiableMap(criteria);
099    }
100
101    public String getKey() {
102        return key;
103    }
104
105    public Map<String, String> getCriteria() {
106        return criteria;
107    }
108
109    public boolean hasCriteria() {
110        return !getCriteria().isEmpty();
111    }
112
113    public boolean matches(final JsonRepresentation repr) {
114        if (!repr.isMap()) {
115            return false;
116        }
117        for (final Map.Entry<String, String> criterium : getCriteria().entrySet()) {
118            String requiredValue = criterium.getValue();
119            if(requiredValue != null) {
120                // list syntax
121                String actualValue = repr.getString(criterium.getKey());
122                if(actualValue == null) {
123                    return false;
124                }
125
126                // determine if fuzzy match (ie without additional parameters)
127                // eg [rel=urn:org.restfulobjects:rel/details;action="list"] matches [rel=urn:org.restfulobjects:rel/details]
128                final int actualValueSemiIndex = actualValue.indexOf(";");
129                final int requiredValueSemiIndex = requiredValue.indexOf(";");
130                if(actualValueSemiIndex != -1 && requiredValueSemiIndex == -1 ) {
131                    actualValue = actualValue.substring(0, actualValueSemiIndex);
132                }
133                if(actualValueSemiIndex == -1 && requiredValueSemiIndex != -1) {
134                    requiredValue = requiredValue.substring(0, requiredValueSemiIndex);
135                }
136                if (!Objects.equal(requiredValue, actualValue)) {
137                    return false;
138                }
139            } else {
140                // map syntax
141                return repr.getRepresentation(criterium.getKey()) != null;
142            }
143        }
144        return true;
145    }
146
147
148    @Override
149    public int hashCode() {
150        final int prime = 31;
151        int result = 1;
152        result = prime * result + ((key == null) ? 0 : key.hashCode());
153        return result;
154    }
155
156    @Override
157    public boolean equals(Object obj) {
158        if (this == obj)
159            return true;
160        if (obj == null)
161            return false;
162        if (getClass() != obj.getClass())
163            return false;
164        PathNode other = (PathNode) obj;
165        if (key == null) {
166            if (other.key != null)
167                return false;
168        } else if (!key.equals(other.key))
169            return false;
170        return true;
171    }
172
173
174    @Override
175    public String toString() {
176        return key + (criteria.isEmpty() ? "" : criteria);
177    }
178
179}