/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.isis.viewer.restfulobjects.applib.util;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.google.common.base.Objects;
import com.google.common.base.Splitter;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.apache.isis.viewer.restfulobjects.applib.JsonRepresentation;
public class PathNode {
private static final Pattern NODE = Pattern.compile("^([^\\[]*)(\\[(.+)\\])?$");
private static final Pattern WHITESPACE = Pattern.compile("\\s+");
private static final Pattern LIST_CRITERIA_SYNTAX = Pattern.compile("^([^=]+)=(.+)$");
public static final PathNode NULL = new PathNode("", Collections.<String, String> emptyMap());
public static List<String> split(String path) {
List<String> parts = Lists.newArrayList();
String curr = null;
for (String part : Splitter.on(".").split(path)) {
if(curr != null) {
if(part.contains("]")) {
curr = curr + "." + part;
parts.add(curr);
curr = null;
} else {
curr = curr + "." + part;
}
continue;
}
if(!part.contains("[")) {
parts.add(part);
continue;
}
if(part.contains("]")) {
parts.add(part);
} else {
curr = part;
}
}
return parts;
}
public static PathNode parse(final String path) {
final Matcher nodeMatcher = NODE.matcher(path);
if (!nodeMatcher.matches()) {
return null;
}
final int groupCount = nodeMatcher.groupCount();
if (groupCount < 1) {
return null;
}
final String key = nodeMatcher.group(1);
final Map<String, String> criteria = Maps.newHashMap();
final String criteriaStr = nodeMatcher.group(3);
if (criteriaStr != null) {
for (final String criterium : Splitter.on(WHITESPACE).split(criteriaStr)) {
final Matcher keyValueMatcher = LIST_CRITERIA_SYNTAX.matcher(criterium);
if (keyValueMatcher.matches()) {
criteria.put(keyValueMatcher.group(1), keyValueMatcher.group(2));
} else {
// take content as a map criteria
criteria.put(criterium, null);
}
}
}
return new PathNode(key, criteria);
}
private final String key;
private final Map<String, String> criteria;
private PathNode(final String key, final Map<String, String> criteria) {
this.key = key;
this.criteria = Collections.unmodifiableMap(criteria);
}
public String getKey() {
return key;
}
public Map<String, String> getCriteria() {
return criteria;
}
public boolean hasCriteria() {
return !getCriteria().isEmpty();
}
public boolean matches(final JsonRepresentation repr) {
if (!repr.isMap()) {
return false;
}
for (final Map.Entry<String, String> criterium : getCriteria().entrySet()) {
String requiredValue = criterium.getValue();
if(requiredValue != null) {
// list syntax
String actualValue = repr.getString(criterium.getKey());
if(actualValue == null) {
return false;
}
// determine if fuzzy match (ie without additional parameters)
// eg [rel=urn:org.restfulobjects:rel/details;action="list"] matches [rel=urn:org.restfulobjects:rel/details]
final int actualValueSemiIndex = actualValue.indexOf(";");
final int requiredValueSemiIndex = requiredValue.indexOf(";");
if(actualValueSemiIndex != -1 && requiredValueSemiIndex == -1 ) {
actualValue = actualValue.substring(0, actualValueSemiIndex);
}
if(actualValueSemiIndex == -1 && requiredValueSemiIndex != -1) {
requiredValue = requiredValue.substring(0, requiredValueSemiIndex);
}
if (!Objects.equal(requiredValue, actualValue)) {
return false;
}
} else {
// map syntax
return repr.getRepresentation(criterium.getKey()) != null;
}
}
return true;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((key == null) ? 0 : key.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
PathNode other = (PathNode) obj;
if (key == null) {
if (other.key != null)
return false;
} else if (!key.equals(other.key))
return false;
return true;
}
@Override
public String toString() {
return key + (criteria.isEmpty() ? "" : criteria);
}
}