All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.isis.applib.Identifier Maven / Gradle / Ivy

/*
 *  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 {

    private static final List EMPTY_LIST_OF_STRINGS = Collections.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 parameterClassNames) {
        return new Identifier(declaringClassName, actionName, parameterClassNames, Type.ACTION);
    }

    /**
     * Helper, used within contructor chaining
     */
    private static List classNamesOf(final Class[] parameterClasses) {
        if (parameterClasses == null) {
            return EMPTY_LIST_OF_STRINGS;
        }
        final List parameterClassNames = Lists.newArrayList();
        for (Class parameterClass : parameterClasses) {
            parameterClassNames.add(parameterClass.getName());
        }
        return parameterClassNames;
    }

    // ///////////////////////////////////////////////////////////////////////////
    // Instance variables
    // ///////////////////////////////////////////////////////////////////////////

    private final String className;
    private final String memberName;
    private final List 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 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() {
        String className = getClassName();
        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 getMemberParameterNames() {
        return parameterNames;
    }

    public List 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) {
        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 a, final List 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);
        }
        List parmList = new ArrayList();
        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);
            for (int i = 0; tokens.hasMoreTokens(); i++) {
                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 naturalNames(final List names) {
        List naturalNames = Lists.newArrayList();
        for (final String name : names) {
            naturalNames.add(NameUtils.naturalName(name));
        }
        return Collections.unmodifiableList(naturalNames);
    }

}





© 2015 - 2025 Weber Informatics LLC | Privacy Policy