001    package org.nakedobjects.applib;
002    
003    import org.nakedobjects.applib.util.NameUtils;
004    
005    
006    public class Identifier implements Comparable<Identifier> {
007    
008        /**
009         * What type of feature this identifies.
010         */
011        public static enum Type {
012            CLASS, PROPERTY_OR_COLLECTION, ACTION
013        }
014    
015        public static enum Depth {
016            CLASS {
017                            @Override
018                            public String toIdentityString(Identifier identifier) {
019                                    return identifier.toClassIdentityString();
020                            }
021                    },
022            CLASS_MEMBERNAME {
023                            @Override
024                            public String toIdentityString(Identifier identifier) {
025                                    return identifier.toClassAndNameIdentityString();
026                            }
027                    },
028            CLASS_MEMBERNAME_PARMS {
029                            @Override
030                            public String toIdentityString(Identifier identifier) {
031                                    return identifier.toFullIdentityString();
032                            }
033                    },
034            MEMBERNAME_ONLY {
035                            @Override
036                            public String toIdentityString(Identifier identifier) {
037                                    return identifier.toNameIdentityString();
038                            }
039                    },
040            PARMS_ONLY {
041                            @Override
042                            public String toIdentityString(Identifier identifier) {
043                                    return identifier.toParmsIdentityString();
044                            }
045                    };
046                    public abstract String toIdentityString(Identifier identifier);
047        }
048        
049        public static Depth CLASS = Depth.CLASS;
050        public static Depth CLASS_MEMBERNAME = Depth.CLASS_MEMBERNAME;
051        public static Depth CLASS_MEMBERNAME_PARMS = Depth.CLASS_MEMBERNAME_PARMS;
052        public static Depth MEMBERNAME_ONLY = Depth.MEMBERNAME_ONLY;
053        public static Depth PARMS_ONLY = Depth.PARMS_ONLY;
054    
055        
056        // ///////////////////////////////////////////////////////////////////////////
057        // Factory methods
058        // ///////////////////////////////////////////////////////////////////////////
059    
060        public static Identifier classIdentifier(final Class<?> cls) {
061            return classIdentifier(cls.getName());
062        }
063    
064        public static Identifier classIdentifier(final String className) {
065            return new Identifier(className, "", new String[] {}, Type.CLASS);
066        }
067    
068        public static Identifier propertyOrCollectionIdentifier(final Class<?> declaringClass, final String propertyOrCollectionName) {
069            return propertyOrCollectionIdentifier(declaringClass.getCanonicalName(), propertyOrCollectionName);
070        }
071    
072        public static Identifier propertyOrCollectionIdentifier(final String declaringClassName, final String propertyOrCollectionName) {
073            return new Identifier(declaringClassName, propertyOrCollectionName, new String[] {}, Type.PROPERTY_OR_COLLECTION);
074        }
075    
076        public static Identifier actionIdentifier(
077                final Class<?> declaringClass,
078                final String actionName,
079                final Class<?>[] parameterClasses) {
080            return actionIdentifier(declaringClass.getCanonicalName(), actionName, toParameterStringArray(parameterClasses));
081        }
082    
083        public static Identifier actionIdentifier(
084                final String declaringClassName,
085                final String actionName,
086                final Class<?>[] parameterClasses) {
087            return actionIdentifier(declaringClassName, actionName, toParameterStringArray(parameterClasses));
088        }
089    
090        public static Identifier actionIdentifier(
091                final String declaringClassName,
092                final String actionName,
093                final String[] parameterClassNames) {
094            return new Identifier(declaringClassName, actionName, parameterClassNames, Type.ACTION);
095        }
096    
097        /**
098         * Helper, used within contructor chaining
099         */
100        private static String[] toParameterStringArray(final Class<?>[] parameterClasses) {
101            final String[] parameters = new String[parameterClasses == null ? 0 : parameterClasses.length];
102            for (int i = 0; i < parameters.length; i++) {
103                parameters[i] = parameterClasses[i].getName();
104            }
105            return parameters;
106        }
107    
108        // ///////////////////////////////////////////////////////////////////////////
109        // Instance variables
110        // ///////////////////////////////////////////////////////////////////////////
111    
112        private final String className;
113        private final String memberName;
114        private final String[] parameterNames;
115        private final Type type;
116        private String identityString;
117    
118        /**
119         * Caching of {@link #toString()}, for performance.
120         */
121        private String asString = null;
122    
123        // ///////////////////////////////////////////////////////////////////////////
124        // Constructor
125        // ///////////////////////////////////////////////////////////////////////////
126    
127        private Identifier(final String className, final String memberName, final String[] parameterNames, final Type type) {
128            this.className = className;
129            this.memberName = memberName;
130            this.parameterNames = parameterNames;
131            this.type = type;
132        }
133    
134        public String getClassName() {
135            return className;
136        }
137    
138        public String getMemberName() {
139            return memberName;
140        }
141    
142        public String getMemberNaturalName() {
143            return NameUtils.naturalName(memberName);
144        }
145    
146        public String[] getMemberParameterNames() {
147            return parameterNames;
148        }
149    
150        public String[] getMemberParameterNaturalNames() {
151            return NameUtils.naturalNames(parameterNames);
152        }
153    
154        public Type getType() {
155            return type;
156        }
157    
158        /**
159         * Convenience method.
160         * 
161         * @return
162         */
163        public boolean isPropertyOrCollection() {
164            return type == Type.PROPERTY_OR_COLLECTION;
165        }
166    
167        // ///////////////////////////////////////////////////////////////////////////
168        // toXxxString
169        // ///////////////////////////////////////////////////////////////////////////
170    
171        public String toIdentityString(final Depth depth) {
172            return depth.toIdentityString(this);
173        }
174    
175        public String toClassIdentityString() {
176            return toClassIdentityString(new StringBuilder()).toString();
177        }
178    
179        public StringBuilder toClassIdentityString(StringBuilder buf) {
180            return buf.append(className);
181        }
182    
183        
184        public String toNameIdentityString() {
185            return toNameIdentityString(new StringBuilder()).toString();
186        }
187    
188        public StringBuilder toNameIdentityString(StringBuilder buf) {
189            return buf.append(memberName);
190        }
191    
192        
193        public String toClassAndNameIdentityString() {
194            return toClassAndNameIdentityString(new StringBuilder()).toString();
195        }
196    
197        public StringBuilder toClassAndNameIdentityString(StringBuilder buf) {
198            return toClassIdentityString(buf).append("#").append(memberName);
199        }
200    
201        
202        public String toParmsIdentityString() {
203            return toParmsIdentityString(new StringBuilder()).toString();
204        }
205    
206        public StringBuilder toParmsIdentityString(StringBuilder buf) {
207            if (type == Type.ACTION) {
208                buf.append('(');
209                for (int i = 0; i < parameterNames.length; i++) {
210                    if (i > 0) {
211                            buf.append(",");
212                    }
213                    buf.append(parameterNames[i]);
214                }
215                buf.append(')');
216            }
217            return buf;
218        }
219    
220        public String toNameParmsIdentityString() {
221            return getMemberName() + toParmsIdentityString();
222        }
223    
224        public StringBuilder toNameParmsIdentityString(StringBuilder buf) {
225            buf.append(getMemberName());
226            toParmsIdentityString(buf);
227            return buf;
228        }
229    
230        public String toFullIdentityString() {
231            if (identityString == null) {
232                if (memberName.length() == 0) {
233                    identityString = toClassIdentityString();
234                } else {
235                    final StringBuilder buf = new StringBuilder();
236                    toClassAndNameIdentityString(buf);
237                    toParmsIdentityString(buf);
238                    identityString = buf.toString();
239                }
240            }
241            return identityString;
242        }
243    
244        // ///////////////////////////////////////////////////////////////////////////
245        // compareTo
246        // ///////////////////////////////////////////////////////////////////////////
247    
248        public int compareTo(final Identifier o2) {
249            return toString().compareTo(o2.toString());
250        }
251    
252        // ///////////////////////////////////////////////////////////////////////////
253        // equals, hashCode
254        // ///////////////////////////////////////////////////////////////////////////
255    
256        /**
257         * REVIEW: why not just compare the {@link #toString()} representations?
258         */
259        @Override
260        public boolean equals(final Object obj) {
261            if (this == obj) {
262                return true;
263            } 
264            if (!(obj instanceof Identifier)) {
265                    return false;
266            } 
267            final Identifier other = (Identifier) obj;
268                    return equals(other);
269        }
270    
271            public boolean equals(final Identifier other) {
272                    return equals(other.className, className) && 
273                           equals(other.memberName, other.memberName) && 
274                           equals(other.parameterNames, parameterNames);
275            }
276    
277        private boolean equals(final String a, final String b) {
278            if (a == b) {
279                return true;
280            }
281    
282            if (a != null) {
283                return a.equals(b);
284            }
285    
286            return false;
287        }
288    
289        private boolean equals(final String[] a, final String[] b) {
290            if (a == null && b == null) {
291                return true;
292            }
293            if (a == null && b != null) {
294                return false;
295            }
296            if (a != null && b == null) {
297                return false;
298            }
299            if (a.length != b.length) {
300                return false;
301            }
302            for (int i = 0; i < b.length; i++) {
303                if (!a[i].equals(b[i])) {
304                    return false;
305                }
306            }
307            return true;
308        }
309    
310        @Override
311        public int hashCode() {
312            return toString().hashCode();
313        }
314    
315        // ///////////////////////////////////////////////////////////////////////////
316        // toString
317        // ///////////////////////////////////////////////////////////////////////////
318    
319        @Override
320        public String toString() {
321            if (asString == null) {
322                final StringBuilder buf = new StringBuilder();
323                buf.append(className);
324                buf.append('#');
325                buf.append(memberName);
326                buf.append('(');
327                for (int i = 0; i < parameterNames.length; i++) {
328                    if (i > 0) {
329                        buf.append(", ");
330                    }
331                    buf.append(parameterNames[i]);
332                }
333                buf.append(')');
334                asString = buf.toString();
335            }
336            return asString;
337        }
338    
339    }
340    // Copyright (c) Naked Objects Group Ltd.