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;
026
027import org.apache.isis.viewer.restfulobjects.applib.JsonRepresentation;
028
029import com.google.common.base.Objects;
030import com.google.common.base.Splitter;
031import com.google.common.collect.Lists;
032import com.google.common.collect.Maps;
033
034public class PathNode {
035    private static final Pattern NODE = Pattern.compile("^([^\\[]*)(\\[(.+)\\])?$");
036    private static final Pattern WHITESPACE = Pattern.compile("\\s+");
037    private static final Pattern LIST_CRITERIA_SYNTAX = Pattern.compile("^([^=]+)=(.+)$");
038
039    public static final PathNode NULL = new PathNode("", Collections.<String, String> emptyMap());
040
041    public static List<String> split(String path) {
042        List<String> parts = Lists.newArrayList();
043        String curr = null;
044        for (String part : Splitter.on(".").split(path)) {
045            if(curr != null) {
046                if(part.contains("]")) {
047                    curr = curr + "." + part;
048                    parts.add(curr);
049                    curr = null;
050                } else {
051                    curr = curr + "." + part;
052                }
053                continue;
054            }
055            if(!part.contains("[")) {
056                parts.add(part);
057                continue;
058            } 
059            if(part.contains("]")) {
060                parts.add(part);
061            } else {
062                curr = part;
063            }
064        }
065        return parts;
066    }
067
068    public static PathNode parse(final String path) {
069        final Matcher nodeMatcher = NODE.matcher(path);
070        if (!nodeMatcher.matches()) {
071            return null;
072        }
073        final int groupCount = nodeMatcher.groupCount();
074        if (groupCount < 1) {
075            return null;
076        }
077        final String key = nodeMatcher.group(1);
078        final Map<String, String> criteria = Maps.newHashMap();
079        final String criteriaStr = nodeMatcher.group(3);
080        if (criteriaStr != null) {
081            for (final String criterium : Splitter.on(WHITESPACE).split(criteriaStr)) {
082                final Matcher keyValueMatcher = LIST_CRITERIA_SYNTAX.matcher(criterium);
083                if (keyValueMatcher.matches()) {
084                    criteria.put(keyValueMatcher.group(1), keyValueMatcher.group(2));
085                } else {
086                    // take content as a map criteria
087                    criteria.put(criterium, null);
088                }
089            }
090        }
091
092        return new PathNode(key, criteria);
093    }
094
095    private final String key;
096    private final Map<String, String> criteria;
097
098    private PathNode(final String key, final Map<String, String> criteria) {
099        this.key = key;
100        this.criteria = Collections.unmodifiableMap(criteria);
101    }
102
103    public String getKey() {
104        return key;
105    }
106
107    public Map<String, String> getCriteria() {
108        return criteria;
109    }
110
111    public boolean hasCriteria() {
112        return !getCriteria().isEmpty();
113    }
114
115    public boolean matches(final JsonRepresentation repr) {
116        if (!repr.isMap()) {
117            return false;
118        }
119        for (final Map.Entry<String, String> criterium : getCriteria().entrySet()) {
120            final String requiredValue = criterium.getValue();
121            if(requiredValue != null) {
122                // list syntax
123                final String actualValue = repr.getString(criterium.getKey());
124                if (!Objects.equal(requiredValue, actualValue)) {
125                    return false;
126                }
127            } else {
128                // map syntax
129                return repr.getRepresentation(criterium.getKey()) != null;
130            }
131        }
132        return true;
133    }
134
135
136    @Override
137    public int hashCode() {
138        final int prime = 31;
139        int result = 1;
140        result = prime * result + ((key == null) ? 0 : key.hashCode());
141        return result;
142    }
143
144    @Override
145    public boolean equals(Object obj) {
146        if (this == obj)
147            return true;
148        if (obj == null)
149            return false;
150        if (getClass() != obj.getClass())
151            return false;
152        PathNode other = (PathNode) obj;
153        if (key == null) {
154            if (other.key != null)
155                return false;
156        } else if (!key.equals(other.key))
157            return false;
158        return true;
159    }
160
161
162    @Override
163    public String toString() {
164        return key + (criteria.isEmpty() ? "" : criteria);
165    }
166
167}