001: /*
002: * Copyright 2002-2007 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: * http://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:
017: package 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: */
026: public 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
036: .indexOf(PropertyAccessor.PROPERTY_KEY_PREFIX_CHAR);
037: return (separatorIndex != -1 ? propertyPath.substring(0,
038: separatorIndex) : propertyPath);
039: }
040:
041: /**
042: * Check whether the given property path indicates an indexed or nested property.
043: * @param propertyPath the property path to check
044: * @return whether the path indicates an indexed or nested property
045: */
046: public static boolean isNestedOrIndexedProperty(String propertyPath) {
047: if (propertyPath == null) {
048: return false;
049: }
050: for (int i = 0; i < propertyPath.length(); i++) {
051: char ch = propertyPath.charAt(i);
052: if (ch == PropertyAccessor.NESTED_PROPERTY_SEPARATOR_CHAR
053: || ch == PropertyAccessor.PROPERTY_KEY_PREFIX_CHAR) {
054: return true;
055: }
056: }
057: return false;
058: }
059:
060: /**
061: * Determine the first nested property separator in the
062: * given property path, ignoring dots in keys (like "map[my.key]").
063: * @param propertyPath the property path to check
064: * @return the index of the nested property separator, or -1 if none
065: */
066: public static int getFirstNestedPropertySeparatorIndex(
067: String propertyPath) {
068: return getNestedPropertySeparatorIndex(propertyPath, false);
069: }
070:
071: /**
072: * Determine the first nested property separator in the
073: * given property path, ignoring dots in keys (like "map[my.key]").
074: * @param propertyPath the property path to check
075: * @return the index of the nested property separator, or -1 if none
076: */
077: public static int getLastNestedPropertySeparatorIndex(
078: String propertyPath) {
079: return getNestedPropertySeparatorIndex(propertyPath, true);
080: }
081:
082: /**
083: * Determine the first (or last) nested property separator in the
084: * given property path, ignoring dots in keys (like "map[my.key]").
085: * @param propertyPath the property path to check
086: * @param last whether to return the last separator rather than the first
087: * @return the index of the nested property separator, or -1 if none
088: */
089: private static int getNestedPropertySeparatorIndex(
090: String propertyPath, boolean last) {
091: boolean inKey = false;
092: int length = propertyPath.length();
093: int i = (last ? length - 1 : 0);
094: while (last ? i >= 0 : i < length) {
095: switch (propertyPath.charAt(i)) {
096: case PropertyAccessor.PROPERTY_KEY_PREFIX_CHAR:
097: case PropertyAccessor.PROPERTY_KEY_SUFFIX_CHAR:
098: inKey = !inKey;
099: break;
100: case PropertyAccessor.NESTED_PROPERTY_SEPARATOR_CHAR:
101: if (!inKey) {
102: return i;
103: }
104: }
105: if (last) {
106: i--;
107: } else {
108: i++;
109: }
110: }
111: return -1;
112: }
113:
114: /**
115: * Determine whether the given registered path matches the given property path,
116: * either indicating the property itself or an indexed element of the property.
117: * @param propertyPath the property path (typically without index)
118: * @param registeredPath the registered path (potentially with index)
119: * @return whether the paths match
120: */
121: public static boolean matchesProperty(String registeredPath,
122: String propertyPath) {
123: if (!registeredPath.startsWith(propertyPath)) {
124: return false;
125: }
126: if (registeredPath.length() == propertyPath.length()) {
127: return true;
128: }
129: if (registeredPath.charAt(propertyPath.length()) != PropertyAccessor.PROPERTY_KEY_PREFIX_CHAR) {
130: return false;
131: }
132: return (registeredPath.indexOf(
133: PropertyAccessor.PROPERTY_KEY_SUFFIX_CHAR, propertyPath
134: .length() + 1) == registeredPath.length() - 1);
135: }
136:
137: /**
138: * Determine the canonical name for the given property path.
139: * Removes surrounding quotes from map keys:<br>
140: * <code>map['key']</code> -> <code>map[key]</code><br>
141: * <code>map["key"]</code> -> <code>map[key]</code>
142: * @param propertyName the bean property path
143: * @return the canonical representation of the property path
144: */
145: public static String canonicalPropertyName(String propertyName) {
146: if (propertyName == null) {
147: return "";
148: }
149:
150: // The following code does not use JDK 1.4's StringBuffer.indexOf(String)
151: // method to retain JDK 1.3 compatibility. The slight loss in performance
152: // is not really relevant, as this code will typically just run on startup.
153:
154: StringBuffer buf = new StringBuffer(propertyName);
155: int searchIndex = 0;
156: while (searchIndex != -1) {
157: int keyStart = buf.toString().indexOf(
158: PropertyAccessor.PROPERTY_KEY_PREFIX, searchIndex);
159: searchIndex = -1;
160: if (keyStart != -1) {
161: int keyEnd = buf.toString().indexOf(
162: PropertyAccessor.PROPERTY_KEY_SUFFIX,
163: keyStart
164: + PropertyAccessor.PROPERTY_KEY_PREFIX
165: .length());
166: if (keyEnd != -1) {
167: String key = buf.substring(keyStart
168: + PropertyAccessor.PROPERTY_KEY_PREFIX
169: .length(), keyEnd);
170: if ((key.startsWith("'") && key.endsWith("'"))
171: || (key.startsWith("\"") && key
172: .endsWith("\""))) {
173: buf.delete(keyStart + 1, keyStart + 2);
174: buf.delete(keyEnd - 2, keyEnd - 1);
175: keyEnd = keyEnd - 2;
176: }
177: searchIndex = keyEnd
178: + PropertyAccessor.PROPERTY_KEY_SUFFIX
179: .length();
180: }
181: }
182: }
183: return buf.toString();
184: }
185:
186: /**
187: * Determine the canonical names for the given property paths.
188: * @param propertyNames the bean property paths (as array)
189: * @return the canonical representation of the property paths
190: * (as array of the same size)
191: * @see #canonicalPropertyName(String)
192: */
193: public static String[] canonicalPropertyNames(String[] propertyNames) {
194: if (propertyNames == null) {
195: return null;
196: }
197: String[] result = new String[propertyNames.length];
198: for (int i = 0; i < propertyNames.length; i++) {
199: result[i] = canonicalPropertyName(propertyNames[i]);
200: }
201: return result;
202: }
203:
204: }
|