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.core;
018:
019: import java.io.Externalizable;
020: import java.io.Serializable;
021: import java.lang.reflect.Proxy;
022: import java.util.Collection;
023: import java.util.HashSet;
024: import java.util.Iterator;
025: import java.util.Set;
026:
027: import org.springframework.util.Assert;
028: import org.springframework.util.ClassUtils;
029:
030: /**
031: * Provides methods to support various naming and other conventions used
032: * throughout the framework. Mainly for internal use within the framework.
033: *
034: * @author Rob Harrop
035: * @author Juergen Hoeller
036: * @since 2.0
037: */
038: public abstract class Conventions {
039:
040: /**
041: * Suffix added to names when using arrays.
042: */
043: private static final String PLURAL_SUFFIX = "List";
044:
045: /**
046: * Set of interfaces that are supposed to be ignored
047: * when searching for the 'primary' interface of a proxy.
048: */
049: private static final Set ignoredInterfaces = new HashSet();
050:
051: static {
052: ignoredInterfaces.add(Serializable.class);
053: ignoredInterfaces.add(Externalizable.class);
054: ignoredInterfaces.add(Cloneable.class);
055: ignoredInterfaces.add(Comparable.class);
056: }
057:
058: /**
059: * Determine the conventional variable name for the supplied
060: * <code>Object</code> based on its concrete type. The convention
061: * used is to return the uncapitalized short name of the <code>Class</code>,
062: * according to JavaBeans property naming rules: So,
063: * <code>com.myapp.Product</code> becomes <code>product</code>;
064: * <code>com.myapp.MyProduct</code> becomes <code>myProduct</code>;
065: * <code>com.myapp.UKProduct</code> becomes <code>UKProduct</code>.
066: * <p>For arrays, we use the pluralized version of the array component type.
067: * For <code>Collection</code>s we attempt to 'peek ahead' in the
068: * <code>Collection</code> to determine the component type and
069: * return the pluralized version of that component type.
070: */
071: public static String getVariableName(Object value) {
072: Assert.notNull(value, "Value must not be null");
073: Class valueClass = null;
074: boolean pluralize = false;
075:
076: if (value.getClass().isArray()) {
077: valueClass = value.getClass().getComponentType();
078: pluralize = true;
079: } else if (value instanceof Collection) {
080: Collection collection = (Collection) value;
081: if (collection.isEmpty()) {
082: throw new IllegalArgumentException(
083: "Cannot generate variable name for an empty Collection");
084: }
085: Object valueToCheck = peekAhead(collection);
086: valueClass = getClassForValue(valueToCheck);
087: pluralize = true;
088: } else {
089: valueClass = getClassForValue(value);
090: }
091:
092: String name = ClassUtils.getShortNameAsProperty(valueClass);
093: return (pluralize ? pluralize(name) : name);
094: }
095:
096: /**
097: * Convert <code>String</code>s in attribute name format (lowercase, hyphens separating words)
098: * into property name format (camel-cased). For example, <code>transaction-manager</code> is
099: * converted into <code>transactionManager</code>.
100: */
101: public static String attributeNameToPropertyName(
102: String attributeName) {
103: Assert.notNull(attributeName,
104: "'attributeName' must not be null");
105: if (attributeName.indexOf("-") == -1) {
106: return attributeName;
107: }
108: char[] chars = attributeName.toCharArray();
109: char[] result = new char[chars.length - 1]; // not completely accurate but good guess
110: int currPos = 0;
111: boolean upperCaseNext = false;
112: for (int i = 0; i < chars.length; i++) {
113: char c = chars[i];
114: if (c == '-') {
115: upperCaseNext = true;
116: } else if (upperCaseNext) {
117: result[currPos++] = Character.toUpperCase(c);
118: upperCaseNext = false;
119: } else {
120: result[currPos++] = c;
121: }
122: }
123: return new String(result, 0, currPos);
124: }
125:
126: /**
127: * Determines the class to use for naming a variable that contains
128: * the given value.
129: * <p>Will return the class of the given value, except when
130: * encountering a JDK proxy, in which case it will determine
131: * the 'primary' interface implemented by that proxy.
132: * @param value the value to check
133: * @return the class to use for naming a variable
134: */
135: private static Class getClassForValue(Object value) {
136: if (Proxy.isProxyClass(value.getClass())) {
137: Class[] ifcs = value.getClass().getInterfaces();
138: for (int i = 0; i < ifcs.length; i++) {
139: Class ifc = ifcs[i];
140: if (!ignoredInterfaces.contains(ifc)) {
141: return ifc;
142: }
143: }
144: }
145: return value.getClass();
146: }
147:
148: /**
149: * Pluralize the given name.
150: */
151: private static String pluralize(String name) {
152: return name + PLURAL_SUFFIX;
153: }
154:
155: /**
156: * Retrieves the <code>Class</code> of an element in the <code>Collection</code>.
157: * The exact element for which the <code>Class</code> is retreived will depend
158: * on the concrete <code>Collection</code> implementation.
159: */
160: private static Object peekAhead(Collection collection) {
161: Iterator it = collection.iterator();
162: if (!it.hasNext()) {
163: throw new IllegalStateException(
164: "Unable to peek ahead in non-empty collection - no element found");
165: }
166: Object value = it.next();
167: if (value == null) {
168: throw new IllegalStateException(
169: "Unable to peek ahead in non-empty collection - only null element found");
170: }
171: return value;
172: }
173:
174: /**
175: * Return an attribute name qualified by the supplied enclosing {@link Class}. For example,
176: * the attribute name '<code>foo</code>' qualified by {@link Class} '<code>com.myapp.SomeClass</code>'
177: * would be '<code>com.myapp.SomeClass.foo</code>'
178: */
179: public static String getQualifiedAttributeName(
180: Class enclosingClass, String attributeName) {
181: Assert.notNull(enclosingClass,
182: "'enclosingClass' must not be null");
183: Assert.notNull(attributeName,
184: "'attributeName' must not be null");
185: return enclosingClass.getName() + "." + attributeName;
186: }
187:
188: }
|