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}