/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.isis.applib; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.StringTokenizer; import com.google.common.base.Joiner; import com.google.common.collect.Lists; public class Identifier implements Comparable<Identifier> { private static final List<String> EMPTY_LIST_OF_STRINGS = Collections.<String> emptyList(); /** * What type of feature this identifies. */ public static enum Type { CLASS, PROPERTY_OR_COLLECTION, ACTION } public static enum Depth { CLASS { @Override public String toIdentityString(final Identifier identifier) { return identifier.toClassIdentityString(); } }, CLASS_MEMBERNAME { @Override public String toIdentityString(final Identifier identifier) { return identifier.toClassAndNameIdentityString(); } }, CLASS_MEMBERNAME_PARAMETERS { @Override public String toIdentityString(final Identifier identifier) { return identifier.toFullIdentityString(); } }, MEMBERNAME_ONLY { @Override public String toIdentityString(final Identifier identifier) { return identifier.toNameIdentityString(); } }, PARAMETERS_ONLY { @Override public String toIdentityString(final Identifier identifier) { return identifier.toParmsIdentityString(); } }; public abstract String toIdentityString(Identifier identifier); } public static Depth CLASS = Depth.CLASS; public static Depth CLASS_MEMBERNAME = Depth.CLASS_MEMBERNAME; public static Depth CLASS_MEMBERNAME_PARAMETERS = Depth.CLASS_MEMBERNAME_PARAMETERS; public static Depth MEMBERNAME_ONLY = Depth.MEMBERNAME_ONLY; public static Depth PARAMETERS_ONLY = Depth.PARAMETERS_ONLY; // /////////////////////////////////////////////////////////////////////////// // Factory methods // /////////////////////////////////////////////////////////////////////////// public static Identifier classIdentifier(final Class<?> cls) { return classIdentifier(cls.getName()); } public static Identifier classIdentifier(final String className) { return new Identifier(className, "", EMPTY_LIST_OF_STRINGS, Type.CLASS); } public static Identifier propertyOrCollectionIdentifier(final Class<?> declaringClass, final String propertyOrCollectionName) { return propertyOrCollectionIdentifier(declaringClass.getCanonicalName(), propertyOrCollectionName); } public static Identifier propertyOrCollectionIdentifier(final String declaringClassName, final String propertyOrCollectionName) { return new Identifier(declaringClassName, propertyOrCollectionName, EMPTY_LIST_OF_STRINGS, Type.PROPERTY_OR_COLLECTION); } public static Identifier actionIdentifier(final Class<?> declaringClass, final String actionName, final Class<?>... parameterClasses) { return actionIdentifier(declaringClass.getCanonicalName(), actionName, classNamesOf(parameterClasses)); } public static Identifier actionIdentifier(final String declaringClassName, final String actionName, final Class<?>... parameterClasses) { return actionIdentifier(declaringClassName, actionName, classNamesOf(parameterClasses)); } public static Identifier actionIdentifier(final String declaringClassName, final String actionName, final List<String> parameterClassNames) { return new Identifier(declaringClassName, actionName, parameterClassNames, Type.ACTION); } /** * Helper, used within contructor chaining */ private static List<String> classNamesOf(final Class<?>[] parameterClasses) { if (parameterClasses == null) { return EMPTY_LIST_OF_STRINGS; } final List<String> parameterClassNames = Lists.newArrayList(); for (final Class<?> parameterClass : parameterClasses) { parameterClassNames.add(parameterClass.getName()); } return parameterClassNames; } // /////////////////////////////////////////////////////////////////////////// // Instance variables // /////////////////////////////////////////////////////////////////////////// private final String className; private final String memberName; private final List<String> parameterNames; private final Type type; private String identityString; /** * Caching of {@link #toString()}, for performance. */ private String asString = null; // /////////////////////////////////////////////////////////////////////////// // Constructor // /////////////////////////////////////////////////////////////////////////// private Identifier(final String className, final String memberName, final List<String> parameterNames, final Type type) { this.className = className; this.memberName = memberName; this.parameterNames = Collections.unmodifiableList(parameterNames); this.type = type; } public String getClassName() { return className; } public String getClassNaturalName() { final String className = getClassName(); final String isolatedName = className.substring(className.lastIndexOf('.') + 1); return NameUtils.naturalName(isolatedName); } public String getMemberName() { return memberName; } public String getMemberNaturalName() { return NameUtils.naturalName(memberName); } public List<String> getMemberParameterNames() { return parameterNames; } public List<String> getMemberParameterNaturalNames() { return NameUtils.naturalNames(parameterNames); } public Type getType() { return type; } /** * Convenience method. * * @return */ public boolean isPropertyOrCollection() { return type == Type.PROPERTY_OR_COLLECTION; } // /////////////////////////////////////////////////////////////////////////// // toXxxString // /////////////////////////////////////////////////////////////////////////// public String toIdentityString(final Depth depth) { return depth.toIdentityString(this); } public String toClassIdentityString() { return toClassIdentityString(new StringBuilder()).toString(); } public StringBuilder toClassIdentityString(final StringBuilder buf) { return buf.append(className); } public String toNameIdentityString() { return toNameIdentityString(new StringBuilder()).toString(); } public StringBuilder toNameIdentityString(final StringBuilder buf) { return buf.append(memberName); } public String toClassAndNameIdentityString() { return toClassAndNameIdentityString(new StringBuilder()).toString(); } public StringBuilder toClassAndNameIdentityString(final StringBuilder buf) { final StringBuilder builder = toClassIdentityString(buf).append("#").append(memberName); if (type == Type.ACTION) { builder.append("()"); } return builder; } public String toParmsIdentityString() { return toParmsIdentityString(new StringBuilder()).toString(); } public StringBuilder toParmsIdentityString(final StringBuilder buf) { if (type == Type.ACTION) { appendParameterNamesTo(buf); } return buf; } private void appendParameterNamesTo(final StringBuilder buf) { buf.append('('); Joiner.on(',').appendTo(buf, parameterNames); buf.append(')'); } public String toNameParmsIdentityString() { return getMemberName() + toParmsIdentityString(); } public StringBuilder toNameParmsIdentityString(final StringBuilder buf) { buf.append(getMemberName()); toParmsIdentityString(buf); return buf; } public String toFullIdentityString() { if (identityString == null) { if (memberName.length() == 0) { identityString = toClassIdentityString(); } else { final StringBuilder buf = new StringBuilder(); toClassIdentityString(buf).append("#").append(memberName); toParmsIdentityString(buf); identityString = buf.toString(); } } return identityString; } // /////////////////////////////////////////////////////////////////////////// // compareTo // /////////////////////////////////////////////////////////////////////////// @Override public int compareTo(final Identifier o2) { return toString().compareTo(o2.toString()); } // /////////////////////////////////////////////////////////////////////////// // equals, hashCode // /////////////////////////////////////////////////////////////////////////// /** * REVIEW: why not just compare the {@link #toString()} representations? */ @Override public boolean equals(final Object obj) { if (this == obj) { return true; } if (!(obj instanceof Identifier)) { return false; } final Identifier other = (Identifier) obj; return equals(other); } public boolean equals(final Identifier other) { return equals(other.className, className) && equals(other.memberName, other.memberName) && equals(other.parameterNames, parameterNames); } private boolean equals(final String a, final String b) { if (a == b) { return true; } return a != null && a.equals(b); } private boolean equals(final List<String> a, final List<String> b) { if (a == null && b == null) { return true; } else if (a == null && b != null) { return false; } else if (a != null && b == null) { return false; } else if (a != null && b != null) { return a.equals(b); } return true; } @Override public int hashCode() { return toString().hashCode(); } // /////////////////////////////////////////////////////////////////////////// // toString // /////////////////////////////////////////////////////////////////////////// @Override public String toString() { if (asString == null) { final StringBuilder buf = new StringBuilder(); buf.append(className); buf.append('#'); buf.append(memberName); appendParameterNamesTo(buf); asString = buf.toString(); } return asString; } /** * Factory method. * * @see #toIdentityString(int) */ public static Identifier fromIdentityString(final String asString) { if (asString == null) { throw new IllegalArgumentException("expected: non-null identity string"); } final int indexOfHash = asString.indexOf("#"); final int indexOfOpenBracket = asString.indexOf("("); final int indexOfCloseBracket = asString.indexOf(")"); final String className = asString.substring(0, indexOfHash == -1 ? asString.length() : indexOfHash); if (indexOfHash == -1 || indexOfHash == (asString.length() - 1)) { return classIdentifier(className); } String name = null; if (indexOfOpenBracket == -1) { name = asString.substring(indexOfHash + 1); return propertyOrCollectionIdentifier(className, name); } final List<String> parmList = new ArrayList<String>(); name = asString.substring(indexOfHash + 1, indexOfOpenBracket); final String allParms = asString.substring(indexOfOpenBracket + 1, indexOfCloseBracket).trim(); if (allParms.length() > 0) { // use StringTokenizer for .NET compatibility final StringTokenizer tokens = new StringTokenizer(allParms, ",", false); while (tokens.hasMoreTokens()) { final String nextParam = tokens.nextToken(); parmList.add(nextParam); } } return actionIdentifier(className, name, parmList); } } /** * Not public API, provides a number of utilities to represent formal * {@link Identifier} names more naturally. */ class NameUtils { private static final char SPACE = ' '; /** * Returns a word spaced version of the specified name, so there are spaces * between the words, where each word starts with a capital letter. E.g., * "NextAvailableDate" is returned as "Next Available Date". */ public static String naturalName(final String name) { final int length = name.length(); if (length <= 1) { return name.toUpperCase();// ensure first character is upper case } final StringBuffer naturalName = new StringBuffer(length); char previousCharacter; char character = Character.toUpperCase(name.charAt(0));// ensure first // character is // upper case naturalName.append(character); char nextCharacter = name.charAt(1); for (int pos = 2; pos < length; pos++) { previousCharacter = character; character = nextCharacter; nextCharacter = name.charAt(pos); if (previousCharacter != SPACE) { if (Character.isUpperCase(character) && !Character.isUpperCase(previousCharacter)) { naturalName.append(SPACE); } if (Character.isUpperCase(character) && Character.isLowerCase(nextCharacter) && Character.isUpperCase(previousCharacter)) { naturalName.append(SPACE); } if (Character.isDigit(character) && !Character.isDigit(previousCharacter)) { naturalName.append(SPACE); } } naturalName.append(character); } naturalName.append(nextCharacter); return naturalName.toString(); } public static List<String> naturalNames(final List<String> names) { final List<String> naturalNames = Lists.newArrayList(); for (final String name : names) { naturalNames.add(NameUtils.naturalName(name)); } return Collections.unmodifiableList(naturalNames); } }