001/* 002 * Copyright 2002-2012 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.beans; 018 019/** 020 * Utility methods for classes that perform bean property access 021 * according to the {@link PropertyAccessor} interface. 022 * 023 * @author Juergen Hoeller 024 * @since 1.2.6 025 */ 026public abstract class PropertyAccessorUtils { 027 028 /** 029 * Return the actual property name for the given property path. 030 * @param propertyPath the property path to determine the property name 031 * for (can include property keys, for example for specifying a map entry) 032 * @return the actual property name, without any key elements 033 */ 034 public static String getPropertyName(String propertyPath) { 035 int separatorIndex = (propertyPath.endsWith(PropertyAccessor.PROPERTY_KEY_SUFFIX) ? 036 propertyPath.indexOf(PropertyAccessor.PROPERTY_KEY_PREFIX_CHAR) : -1); 037 return (separatorIndex != -1 ? propertyPath.substring(0, separatorIndex) : propertyPath); 038 } 039 040 /** 041 * Check whether the given property path indicates an indexed or nested property. 042 * @param propertyPath the property path to check 043 * @return whether the path indicates an indexed or nested property 044 */ 045 public static boolean isNestedOrIndexedProperty(String propertyPath) { 046 if (propertyPath == null) { 047 return false; 048 } 049 for (int i = 0; i < propertyPath.length(); i++) { 050 char ch = propertyPath.charAt(i); 051 if (ch == PropertyAccessor.NESTED_PROPERTY_SEPARATOR_CHAR || 052 ch == PropertyAccessor.PROPERTY_KEY_PREFIX_CHAR) { 053 return true; 054 } 055 } 056 return false; 057 } 058 059 /** 060 * Determine the first nested property separator in the 061 * given property path, ignoring dots in keys (like "map[my.key]"). 062 * @param propertyPath the property path to check 063 * @return the index of the nested property separator, or -1 if none 064 */ 065 public static int getFirstNestedPropertySeparatorIndex(String propertyPath) { 066 return getNestedPropertySeparatorIndex(propertyPath, false); 067 } 068 069 /** 070 * Determine the first nested property separator in the 071 * given property path, ignoring dots in keys (like "map[my.key]"). 072 * @param propertyPath the property path to check 073 * @return the index of the nested property separator, or -1 if none 074 */ 075 public static int getLastNestedPropertySeparatorIndex(String propertyPath) { 076 return getNestedPropertySeparatorIndex(propertyPath, true); 077 } 078 079 /** 080 * Determine the first (or last) nested property separator in the 081 * given property path, ignoring dots in keys (like "map[my.key]"). 082 * @param propertyPath the property path to check 083 * @param last whether to return the last separator rather than the first 084 * @return the index of the nested property separator, or -1 if none 085 */ 086 private static int getNestedPropertySeparatorIndex(String propertyPath, boolean last) { 087 boolean inKey = false; 088 int length = propertyPath.length(); 089 int i = (last ? length - 1 : 0); 090 while (last ? i >= 0 : i < length) { 091 switch (propertyPath.charAt(i)) { 092 case PropertyAccessor.PROPERTY_KEY_PREFIX_CHAR: 093 case PropertyAccessor.PROPERTY_KEY_SUFFIX_CHAR: 094 inKey = !inKey; 095 break; 096 case PropertyAccessor.NESTED_PROPERTY_SEPARATOR_CHAR: 097 if (!inKey) { 098 return i; 099 } 100 } 101 if (last) { 102 i--; 103 } 104 else { 105 i++; 106 } 107 } 108 return -1; 109 } 110 111 /** 112 * Determine whether the given registered path matches the given property path, 113 * either indicating the property itself or an indexed element of the property. 114 * @param propertyPath the property path (typically without index) 115 * @param registeredPath the registered path (potentially with index) 116 * @return whether the paths match 117 */ 118 public static boolean matchesProperty(String registeredPath, String propertyPath) { 119 if (!registeredPath.startsWith(propertyPath)) { 120 return false; 121 } 122 if (registeredPath.length() == propertyPath.length()) { 123 return true; 124 } 125 if (registeredPath.charAt(propertyPath.length()) != PropertyAccessor.PROPERTY_KEY_PREFIX_CHAR) { 126 return false; 127 } 128 return (registeredPath.indexOf(PropertyAccessor.PROPERTY_KEY_SUFFIX_CHAR, propertyPath.length() + 1) == 129 registeredPath.length() - 1); 130 } 131 132 /** 133 * Determine the canonical name for the given property path. 134 * Removes surrounding quotes from map keys:<br> 135 * {@code map['key']} -> {@code map[key]}<br> 136 * {@code map["key"]} -> {@code map[key]} 137 * @param propertyName the bean property path 138 * @return the canonical representation of the property path 139 */ 140 public static String canonicalPropertyName(String propertyName) { 141 if (propertyName == null) { 142 return ""; 143 } 144 145 StringBuilder sb = new StringBuilder(propertyName); 146 int searchIndex = 0; 147 while (searchIndex != -1) { 148 int keyStart = sb.indexOf(PropertyAccessor.PROPERTY_KEY_PREFIX, searchIndex); 149 searchIndex = -1; 150 if (keyStart != -1) { 151 int keyEnd = sb.indexOf( 152 PropertyAccessor.PROPERTY_KEY_SUFFIX, keyStart + PropertyAccessor.PROPERTY_KEY_PREFIX.length()); 153 if (keyEnd != -1) { 154 String key = sb.substring(keyStart + PropertyAccessor.PROPERTY_KEY_PREFIX.length(), keyEnd); 155 if ((key.startsWith("'") && key.endsWith("'")) || (key.startsWith("\"") && key.endsWith("\""))) { 156 sb.delete(keyStart + 1, keyStart + 2); 157 sb.delete(keyEnd - 2, keyEnd - 1); 158 keyEnd = keyEnd - 2; 159 } 160 searchIndex = keyEnd + PropertyAccessor.PROPERTY_KEY_SUFFIX.length(); 161 } 162 } 163 } 164 return sb.toString(); 165 } 166 167 /** 168 * Determine the canonical names for the given property paths. 169 * @param propertyNames the bean property paths (as array) 170 * @return the canonical representation of the property paths 171 * (as array of the same size) 172 * @see #canonicalPropertyName(String) 173 */ 174 public static String[] canonicalPropertyNames(String[] propertyNames) { 175 if (propertyNames == null) { 176 return null; 177 } 178 String[] result = new String[propertyNames.length]; 179 for (int i = 0; i < propertyNames.length; i++) { 180 result[i] = canonicalPropertyName(propertyNames[i]); 181 } 182 return result; 183 } 184 185}