/*
 * Copyright (c) 1998, 2018 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0,
 * or the Eclipse Distribution License v. 1.0 which is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
 */

// Contributors:
//     Oracle - initial API and implementation from Oracle TopLink
package org.eclipse.persistence.sdo.helper.extension;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.StringTokenizer;
import java.util.regex.Pattern;

import org.eclipse.persistence.sdo.SDOConstants;
import org.eclipse.persistence.sdo.SDOProperty;
import org.eclipse.persistence.sdo.SDOType;
import org.eclipse.persistence.internal.helper.ClassConstants;
import org.eclipse.persistence.logging.AbstractSessionLog;

/**
 * <p><b>Purpose</b>: Common functions in support of SDO.
 * <p><b>Responsibilities</b>:
 * <ul>
 * <li> JAXB 1.0 Name Mangling algorithm functions are provided to support generation of valid class/method names..
 * <li> JSR-222 JAXB 2.0 Java Package Name generation algorithm function follows https://jaxb.dev.java.net/spec-download.html
 * in section D.5.1 "Mapping from a Namespace URI"
 * </ul>
 */
public class SDOUtil {

    /** Valid hexadecimal digits */
    private static final String HEXADECIMAL_DIGITS = "0123456789abcdefABCDEF";
    /** Warning string to signify that the input to the package generator may not be a valid URI */
    private static final String INVALID_URI_WARNING = "SDOUtil: The URI [{0}] used for java package name generation is invalid - generating [{1}].";

    /** NCName start character class content - used to compose {@link #XML_NCNAME_VALIDATION_PATTERN}. */
    private static final String XML_NCNAME_START_CHAR_GROUP =
            "A-Z_a-z\\x{C0}-\\x{D6}\\x{D8}-\\x{F6}\\x{F8}-\\x{2FF}\\x{370}-\\x{37D}\\x{37F}-\\x{1FFF}\\x{200C}-\\x{200D}\\x{2070}-\\x{218F}\\x{2C00}-\\x{2FEF}\\x{3001}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFFD}\\x{10000}-\\x{EFFFF}";
    /** NCName character class content - used to compose {@link #XML_NCNAME_VALIDATION_PATTERN}. */
    private static final String XML_NCNAME_CHAR_GROUP =
            "\\-.0-9\\x{B7}\\x{300}-\\x{36F}\\x{203F}-\\x{2040}";

    /**
     * Pattern for validation of XML non-colonial name (NCName).
     * @see <a href="http://www.w3.org/TR/REC-xml-names/#NT-NCName">http://www.w3.org/TR/REC-xml-names/#NT-NCName</a>
     */
    private static final Pattern XML_NCNAME_VALIDATION_PATTERN = Pattern.compile(
            "^[ " + XML_NCNAME_START_CHAR_GROUP + "][" + XML_NCNAME_START_CHAR_GROUP + XML_NCNAME_CHAR_GROUP + "]*$");

    private static final String IS = "is";
    private static final String GET = "get";
    private static final String SET = "set";

    /**
     * INTERNAL:
     * Search Java reserved name arrays and report (but don't fix) any naming collisions
     */
    private static void preProcessJavaReservedNames(String name) {
        preProcessReservedNames(name, SDOConstants.javaReservedWordsList, "sdo_type_generation_warning_class_name_violates_java_spec");
    }

    /**
     * INTERNAL:
     * Search SDO reserved name arrays and report (but don't fix) any naming collisions
     */
    private static void preProcessSDOReservedNames(String name) {
        preProcessReservedNames(name, SDOConstants.sdoInterfaceReservedWordsList, "sdo_type_generation_warning_class_name_violates_sdo_spec");
    }

    /**
     * INTERNAL:
     * Search reserved name arrays and report (but don't fix) any naming collisions
     * @param wordArray
     * @param warningLogKey
     */
    private static void preProcessReservedNames(String name, String[] wordArray, String warningLogKey) {
        // search the reserved words list and recommend alternatives
        for(int i=0; i < wordArray.length; i++) {
            if(wordArray[i].equalsIgnoreCase(name)) {
                AbstractSessionLog.getLog().log(AbstractSessionLog.WARNING, warningLogKey, //
                    new Object[] { "SDOUtil", name, wordArray[i]});
            }
        }
    }


    /** allow _ underscores in class/function names */
    private static boolean asWordSeparator = false;

    /**
     * INTERNAL:
     * Get default package name when no targetNamespace URI exists.<br>
     * This function follows the JSR-222 JAXB 2.0 algorithm from https://jaxb.dev.java.net/spec-download.html<br>
     * @return default Java package name String
     */
    public static String getDefaultPackageName() {
        return getPackageNameFromURI(SDOConstants.EMPTY_STRING);
    }

    /**
     * INTERNAL:
     * Get default package name from a namespace URI.<br>
     * This function follows the JSR-222 JAXB 2.0 algorithm from https://jaxb.dev.java.net/spec-download.html.<br>
     * @param uriString - a namespace URL or URN
     * @return Java package name String
     */
    public static String getPackageNameFromURI(String uriString) {
        String strToken;
        String prefix;
        int position = 0;
        StringBuffer pkgName = new StringBuffer();
        if (null == uriString || uriString.equals(SDOConstants.EMPTY_STRING)) {
            AbstractSessionLog.getLog().log(AbstractSessionLog.FINEST, INVALID_URI_WARNING,//
                    new Object[] { uriString, SDOConstants.JAVA_TYPEGENERATION_DEFAULT_PACKAGE_NAME }, false);
            return SDOConstants.JAVA_TYPEGENERATION_DEFAULT_PACKAGE_NAME;
        }

        /**
         * Step 1: (Remove the schema and ":" part)
         * An XML namespace is represented by a URI. Since XML Namespace will be
         *     mapped to a Java package, it is necessary to specify a default mapping from a
         *     URI to a Java package name. The URI format is described in [RFC2396].
         *     The following steps describe how to map a URI to a Java package name. The
         *     example URI, http://example.org/go/file.xsd, is used to
         *     illustrate each step.
         *     1. Remove the scheme and ":" part from the beginning of the URI, if present.
         * Since there is no formal syntax to identify the optional URI scheme, restrict
         *     the schemes to be removed to case insensitive checks for schemes
         *     "http" and "urn".
         *     //example.org/go/file.xsd
         */
        // Remove only urn: and http: schemes - retain ftp, file, gopher, mail, news, telnet
        URI uri;
        String originalUriString = uriString;
        String schemePrefix;
        // Save error state so that we can emit a warning after the URI has been processed
        boolean invalidOriginalFormat = false;
        // Save whether we are a supported urn or http scheme
    boolean invalidScheme = false;
        try {
            // Creating a URI object and catching a syntax exception may be a performance hit
            uri = new URI(uriString);
            schemePrefix = uri.getScheme();
            // Remove http or urn schemes for valid URI's
      if(null != schemePrefix){
        if((schemePrefix.equalsIgnoreCase("http") || schemePrefix.equalsIgnoreCase("urn"))) {
          uriString = uri.getSchemeSpecificPart();
        }else{
          invalidScheme = true;
        }
            }
        } catch (NullPointerException npe) {
            AbstractSessionLog.getLog().log(AbstractSessionLog.FINEST, INVALID_URI_WARNING,//
                    new Object[] { "null", SDOConstants.JAVA_TYPEGENERATION_DEFAULT_PACKAGE_NAME }, false);
            return SDOConstants.JAVA_TYPEGENERATION_DEFAULT_PACKAGE_NAME;
        } catch (URISyntaxException use) {
            // Warn that the URI is invalid, but process the string into a valid package anyway
            invalidOriginalFormat = true;
            // Remove http or urn schemes for invalid URI's
            if (uriString.length() > 4) {
                prefix = uriString.substring(0, 4);
                if (prefix.equalsIgnoreCase("urn:")) {
                    uriString = uriString.substring(4);
                } else {
                    prefix = uriString.substring(0, 5);
                    if (prefix.equalsIgnoreCase("http:")) {
                        uriString = uriString.substring(5);
                    }
                }
            }
        } finally {
            /**
             * Step 2: remove trailing file type, one of .?? or .??? or .html.
             * //example.org/go/file
             * Note: The trailing host fragment will be removed for non http|urn schemes such as file:.
             */
            int potentialPathSepIndex = uriString.lastIndexOf('/'); // Don't handle ? param separator on purpose
            int potentialHostSepIndex = uriString.indexOf('/');
            int potentialFileExtIndex = uriString.lastIndexOf('.');
            /**
             * When to remove the last .ext or trailing host fragment.
             * Valid scheme    |  has file ext    = remove/keep last {.[^.]+} fragment
             * 0 | 0 Remove host prefix            ie: file://site.com -> file.site
             * 0 | 1 Remove file ext                ie: file://site.com/file.xsd -> file.com.site
             * 1 | 0 Don't remove host prefix    ie: urn://site.com -> com.site
             * 1 | 1 Remove file ext                ie: urn://site.com/file.xsd -> com.site
             */
            // Don't Remove trailing host fragment for http|urn schemes
            if((invalidScheme && potentialFileExtIndex != -1) || //
                ((potentialFileExtIndex != -1 && potentialPathSepIndex != -1 && //
                potentialHostSepIndex != -1 && (potentialPathSepIndex - potentialHostSepIndex) > 1))) { // -1's are handled
                String extension = uriString.substring(potentialFileExtIndex);
        if (extension.length() == 3 || extension.length() == 4    || extension.equalsIgnoreCase(".html")) {
                    uriString = uriString.substring(0, potentialFileExtIndex);
                }
            }

            /**
             * Step 3: (split string into word list) 3. Parse the remaining
             * String into a list of strings using / and : as separators
             * Treat consecutive separators as a single separator.
             * {"example.org", "go", "file" }
             */
            StringTokenizer aTokenizer = new StringTokenizer(uriString, "/:");
            int length = aTokenizer.countTokens();
            if (length == 0) {
                return SDOConstants.JAVA_TYPEGENERATION_DEFAULT_PACKAGE_NAME;
            }

            /**
             * Step 4: (unescape each escape sequence octet) 4. For each string
             * in the list produced by previous step, unescape each escape
             * sequence octet. {"example.org", "go", "file" } Generating a
             * Java package name 4/19/06 JAXB 2.0 - Final Release 341
             */
            ArrayList<String> strings = new ArrayList<String>(length);
            while (aTokenizer.hasMoreTokens()) {
                strToken = aTokenizer.nextToken();
                strings.add(decodeUriHexadecimalEscapeSequence(strToken));
            }

            /**
             * Step 5: replace [-] with [.] if the scheme is a URN 5. If the
             * scheme is a urn, replace all dashes, -, occurring in the
             * first component with [.].2
             */

            /**
             * Step 6: Apply algorithm described in Section 7.7 Unique Package
             * Names in [JLS] to derive a unique package name from the
             * potential internet domain name contained within the first
             * component. The internet domain name is reversed, component by
             * component. Note that a leading www. is not considered part of
             * an internet domain name and must be dropped. If the first
             * component does not contain either one of the top-level domain
             * names, for example, com, gov, net, org, edu, or one of the
             * English two-letter codes identifying countries as specified in
             * ISO Standard 3166, 1981, this step must be skipped. {org,
             * example, go, file}
             */
            strToken = strings.remove(0).toLowerCase();
            // Reuse the Tokenizer - tokenize on package separator
            aTokenizer = new StringTokenizer(strToken, ".");
            // Check for URI's that are composed only of metacharacter package separators
            if(aTokenizer.countTokens() < 1) {
                AbstractSessionLog.getLog().log(AbstractSessionLog.FINEST, INVALID_URI_WARNING,//
                        new Object[] { uriString, SDOConstants.JAVA_TYPEGENERATION_DEFAULT_PACKAGE_NAME }, false);
                return SDOConstants.JAVA_TYPEGENERATION_DEFAULT_PACKAGE_NAME;
            } else {
                while (aTokenizer.hasMoreTokens()) {
                    strToken = aTokenizer.nextToken();
                    if (!strToken.equals("www")) {
                        strings.add(0, strToken);
                    }
                }
            }

            /**
             * Step 7: (convert each string to be all lower case) 7. For each
             * string in the list, convert each string to be all lower case.
             * {org, example, go, file }
             */
            position = 0;
            for (String aString : strings) {
                strings.set(position++, aString.toLowerCase());
            }

            /**
             * Step 8: (convert each string to a valid identifier) 8. For each
             * string remaining, the following conventions are adopted from
             * [JLS] Section 7.7, Unique Package Names. Follow step 8a-c
             * below.
             */
            position = 0;
            for (String aString : strings) {
                StringBuffer buffer = new StringBuffer();

                /**
                 * Step 8a: If the string component contains a hyphen, or any other
                 * special character not allowed in an identifier, convert it
                 * into an underscore.
                 */
                for (int j = 0; j < aString.length(); j++) {
                    char charToken = aString.charAt(j);
                    if (Character.isJavaIdentifierPart(charToken)) {
                        buffer.append(charToken);
                    } else {
                        buffer.append('_');
                    }
                }

                /**
                 * Step 8b:
                 * From the Java Language Specification section 7.7 b. If any of
                 * the resulting package name components are keywords then
                 * append underscore to them.
                 * We are not performing this step here - and are allowing all java reserved keywords to pass
                 * See the enum com.sun.tools.javac.parser.Token for a list of keywords
                 */

                /**
                 * Step 8c: If any of the resulting package name components start with
                 * a digit, or any other character that is not allowed as an
                 * initial character of an identifier, have an underscore
                 * prefixed to the component. {org, example, go, file }
                 */
                if (!Character.isJavaIdentifierStart(buffer.charAt(0))) {
                    buffer.insert(0, '_');
                }
                if (position++ != 0) {
                    buffer.insert(0, '.');
                }

                pkgName.append(buffer.toString());
            }
            if(invalidOriginalFormat) {
                AbstractSessionLog.getLog().log(AbstractSessionLog.FINEST,//
                        INVALID_URI_WARNING, new Object[] { originalUriString, pkgName }, false);
            }
        }
        return pkgName.toString();
    }

    /**
     * INTERNAL:
     * Decode Hexadecimal "%hh" escape sequences in a URI.
     * All escape codes must be valid 2 digit sequences.
     *
     * @param uri - URI component
     * @return URI component with escape sequence decoded into a
     */
    private static String decodeUriHexadecimalEscapeSequence(String uri) {
        // This function is used by the Java Package Name generation algorithm that implements JAXB 2.0 D.5.1
        StringBuffer sb = new StringBuffer(uri.length());
        for (int index = 0; index < uri.length(); index++) {
            char c = uri.charAt(index);
            // Escape sequence found - get the hex value and convert
            if (c == '%') {
                if (((index + 2) < uri.length()) && //
                    HEXADECIMAL_DIGITS.indexOf(uri.charAt(index + 1)) >= 0 &&//
                    HEXADECIMAL_DIGITS.indexOf(uri.charAt(index + 2)) >= 0) {
                    // Look ahead 2 digits
                    String g = uri.substring(index + 1, index + 3);
                    // Convert base 16 to base 10 to char and append
                    sb.append((char)Integer.parseInt(g, 16));
                    /**
                     * Increase the index by 2 - so we skip the 2 digit hex code after the %
                     * See JAXB 2.0 spec p.348 section D.5.1.4
                     * "For each string in the list produced by step 3.  Unescape each escape sequence octet.
                     * IE: North%20America should be "North America" and later in step 8
                     * "north_america" not "north_20america"
                     */
                    index+=2;
                } else {
                    sb.append(c);
                }
            } else {
                sb.append(c);
            }
        }
        return (sb.toString());
    }


    /**
     * INTERNAL:
     * Return a valid Java class name or method name for a given string
     * @param s
     * @param isClass (flag whether called from a method context)
     * @return
     */
    public static String className(String s, boolean isClass) {
        return className(s, true, isClass, true);
    }

    /**
     * INTERNAL:
     * Return a valid Java class name or method name for a given string
     * @param s
     * @param isClass (flag whether called from a method context)
     * @param flag
     * @return
     */
    public static String className(String s, boolean flag, boolean isClass, boolean logOn) {
        // 1) warn if the class conflicts with any Java reserved words
        preProcessJavaReservedNames(s);
        // 2) warn if the class conflicts with any SDO reserved words
        preProcessSDOReservedNames(s);

        String[] as = getWordList(s);
        StringBuffer stringbuffer = new StringBuffer();
        StringBuffer stringbuffer1 = new StringBuffer();
        if (as.length == 0) {
            return stringbuffer.toString();
        }

        for (int i = 0; i < as.length; i++) {
            char[] ac = as[i].toCharArray();
            if (Character.isLowerCase(ac[0])) {
                ac[0] = Character.toUpperCase(ac[0]);
            }
            for (int j = 0; j < ac.length; j++) {
                if ((ac[j] >= ' ') && (ac[j] < '\177')) {
                    if ((ac[j] != '_') || !asWordSeparator) {
                        stringbuffer.append(ac[j]);
                    }
                    continue;
                }
                if (flag) {
                    stringbuffer.append(escapeUnicode(stringbuffer1, ac[j]));
                } else {
                    stringbuffer.append(ac[j]);
                }
            }
        }

        String normalizedName = stringbuffer.toString();
        // report a warning whether we were required to normalize the name (beyond initial capitalization)
        if(!s.equals(normalizedName) && logOn) {
            // log changes
            int logLevel;
            // log capitalization-only changes at a lower level
            if(!s.equalsIgnoreCase(normalizedName)) {
                logLevel = AbstractSessionLog.INFO;
            } else {
                logLevel = AbstractSessionLog.FINER;
            }

            // report differently depending on whether the input was a class or function name
            if(isClass) {
                AbstractSessionLog.getLog().log(logLevel, //
                    "sdo_type_generation_modified_class_naming_format_to",
                    new Object[] { "SDOUtil", s, normalizedName});
            } else {
                AbstractSessionLog.getLog().log(logLevel, //
                        "sdo_type_generation_modified_function_naming_format_to",
                        new Object[] { "SDOUtil", s, normalizedName});
            }
        }

        return normalizedName;
    }

    /**
     * INTERNAL:
     * Return a valid Java method name for a given string
     * @param s
     * @return
     */
    public static String methodName(String s) {
        return methodName(s, true);
    }

    /**
     * INTERNAL:
     * Return a valid Java method name for a given string
     * @param s
     * @param flag
     * @return
     */
    public static String methodName(String s, boolean flag) {
        return className(s, flag, false, true);
    }

    /**
     * INTERNAL:
     * Return a valid Java set method name for a given string
     * @param s
     * @return
     */
    public static String setMethodName(String s) {
        StringBuffer stringbuffer = new StringBuffer();
        stringbuffer.append(SET).append(methodName(s));
        return stringbuffer.toString();
    }

    /**
     * INTERNAL:
     * Return a valid Java get method name for a given string. This method will check
     * the returnType to see if it is a boolean/Boolean:  if so, 'is' will be used in
     * the method name instead of 'get'.
     *
     * @param s
     * @param returnType
     * @return
     */
    public static String getMethodName(String s, String returnType) {
        StringBuffer stringBuffer = new StringBuffer();
        if (returnType.equals(ClassConstants.PBOOLEAN.getName()) || returnType.equals(ClassConstants.BOOLEAN.getName())) {
            stringBuffer.append(IS);
        } else {
            stringBuffer.append(GET);
        }
        stringBuffer.append(SDOUtil.className(s, true, false, false));
        return stringBuffer.toString();
    }

    /**
     * INTERNAL:
     * Return a valid Java get method name for a given string. This method will NOT check
     * the returnType to see if it is a boolean/Boolean and all method names will start with
     * "GET"
     *
     * @param s
     * @param returnType
     * @return
     */
    public static String getBooleanGetMethodName(String s, String returnType){
        StringBuffer stringBuffer = new StringBuffer();
        stringBuffer.append(SDOUtil.GET);
        stringBuffer.append(SDOUtil.className(s, true, false, false));
        return stringBuffer.toString();
    }

    /**
     * INTERNAL:
     * @param s
     * @return
     */
    public static String constantName(String s) {
        StringBuffer stringbuffer = new StringBuffer();
        String[] as = getWordList(s);
        if (as.length > 0) {
            stringbuffer.append(as[0].toUpperCase());
            for (int i = 1; i < as.length; i++) {
                stringbuffer.append('_');
                stringbuffer.append(as[i].toUpperCase());
            }
        }
        return stringbuffer.toString();
    }

    /**
     *
     * @param s
     * @return
     */
    private static String[] getWordList(String s) {
        java.util.List arraylist = new ArrayList();
        int i = s.length();
        int j = 0;
        do {
            if (j >= i) {
                break;
            }
            for (; (j < i) && isPunct(s.charAt(j)); j++) {
            }
            if (j >= i) {
                break;
            }
            int k = nextBreak(s, j);
            String s1 = (k != -1) ? s.substring(j, k) : s.substring(j);
            arraylist.add(escape(s1));
            if (k == -1) {
                break;
            }
            j = k;
        } while (true);
        return (String[])arraylist.toArray(new String[0]);
    }

    private static boolean isPunct(char c) {
        if ((c == '-') || (c == '.') || (c == ':') || (c == '\267') || (c == '\u0387') || (c == '\u06DD') || (c == '\u06DE')) {
            return true;
        }
        return (c == '_') && asWordSeparator;
    }

    private static boolean isUncased(char c) {
        return Character.isLetter(c) && !Character.isUpperCase(c) && !Character.isLowerCase(c);
    }

    private static int nextBreak(String s, int i) {
        int j = s.length();
        for (int k = i; k < j; k++) {
            char c = s.charAt(k);
            if (((c == '_') && !asWordSeparator) || (k >= (j - 1))) {
                continue;
            }
            char c1 = s.charAt(k + 1);
            if ((c1 == '_') && !asWordSeparator) {
                continue;
            }
            if (isPunct(c1)) {
                return k + 1;
            }
            if (Character.isDigit(c) && !Character.isDigit(c1)) {
                return k + 1;
            }
            if (!Character.isDigit(c) && Character.isDigit(c1)) {
                return k + 1;
            }
            if (Character.isLowerCase(c) && !Character.isLowerCase(c1)) {
                return k + 1;
            }
            if (k < (j - 2)) {
                char c2 = s.charAt(k + 2);
                if ((c2 == '_') && !asWordSeparator) {
                    continue;
                }
                if (Character.isUpperCase(c) && Character.isUpperCase(c1) && Character.isLowerCase(c2)) {
                    return k + 1;
                }
            }
            if (Character.isLetter(c) && !Character.isLetter(c1)) {
                return k + 1;
            }
            if (!Character.isLetter(c) && Character.isLetter(c1)) {
                return k + 1;
            }
            if (isUncased(c) && !isUncased(c1)) {
                return k + 1;
            }
            if (!isUncased(c) && isUncased(c1)) {
                return k + 1;
            }
        }
        return -1;
    }

    private static String escape(String s) {
        int i = s.length();
        for (int j = 0; j < i; j++) {
            if (!Character.isJavaIdentifierPart(s.charAt(j))) {
                StringBuffer stringbuffer = new StringBuffer(s.substring(0, j));
                escape(stringbuffer, s, j);
                return stringbuffer.toString();
            }
        }
        return s;
    }

    private static void escape(StringBuffer stringbuffer, String s, int i) {
        int j = s.length();
        for (int k = i; k < j; k++) {
            char c = s.charAt(k);
            if (Character.isJavaIdentifierPart(c)) {
                stringbuffer.append(c);
                continue;
            }
            stringbuffer.append("_");
            if (c <= '\017') {
                stringbuffer.append("000");
            } else if (c <= '\377') {
                stringbuffer.append("00");
            } else if (c <= '\u0FFF') {
                stringbuffer.append("0");
            }
            stringbuffer.append(Integer.toString(c, 16));
        }
    }

    private static String escapeUnicode(StringBuffer stringbuffer, char c) {
        String s = Integer.toString(c, 16);
        int i = s.length();
        stringbuffer.setLength(6);
        stringbuffer.setCharAt(0, '\\');
        stringbuffer.setCharAt(1, 'u');
        int j = 2;
        for (int k = i; k < 4;) {
            stringbuffer.setCharAt(j, '0');
            k++;
            j++;
        }

        stringbuffer.replace(j, 6, s);
        return stringbuffer.toString();
    }

    public static String getJavaTypeForProperty(SDOProperty property) {
        if (property.isMany() || property.getType().isXsdList()) {
            return "java.util.List";
        } else {
            SDOType propertyType = property.getType();
            if(propertyType.isDataType()) {
                Class instanceClass = propertyType.getInstanceClass();
                if (ClassConstants.ABYTE.equals(instanceClass)) {
                    return "Byte[]";
                } else if (ClassConstants.APBYTE.equals(instanceClass)) {
                    return "byte[]";
                }
            }
            return propertyType.getInstanceClassName();
        }
    }

    public static String getBuiltInType(String typeName) {
        if ((typeName.equals(ClassConstants.PBOOLEAN.getName())) || (typeName.equals(ClassConstants.BOOLEAN.getName()))) {
            return "Boolean";
        } else if ((typeName.equals(ClassConstants.PBYTE.getName())) || (typeName.equals(ClassConstants.BYTE.getName()))) {
            return "Byte";
        } else if (typeName.equals("byte[]") || typeName.equals("Byte[]") ||  (typeName.equals(ClassConstants.APBYTE.getName())) || (typeName.equals(ClassConstants.ABYTE.getName()))) {
            return "Bytes";
        } else if ((typeName.equals(ClassConstants.PCHAR.getName())) || (typeName.equals(ClassConstants.CHAR.getName()))) {
            return "Char";
        } else if ((typeName.equals(ClassConstants.PDOUBLE.getName())) || (typeName.equals(ClassConstants.DOUBLE.getName()))) {
            return "Double";
        } else if ((typeName.equals(ClassConstants.PFLOAT.getName())) || (typeName.equals(ClassConstants.FLOAT.getName()))) {
            return "Float";
        } else if ((typeName.equals(ClassConstants.PLONG.getName())) || (typeName.equals(ClassConstants.LONG.getName()))) {
            return "Long";
        } else if ((typeName.equals(ClassConstants.PSHORT.getName())) || (typeName.equals(ClassConstants.SHORT.getName()))) {
            return "Short";
        } else if ((typeName.equals(ClassConstants.PINT.getName())) || (typeName.equals(ClassConstants.INTEGER.getName()))) {
            return "Int";
        } else if (typeName.equals(ClassConstants.STRING.getName())) {
            return "String";
        } else if (typeName.equals(ClassConstants.BIGINTEGER.getName())) {
            return "BigInteger";
        } else if (typeName.equals(ClassConstants.BIGDECIMAL.getName())) {
            return "BigDecimal";
        } else if (typeName.equals(ClassConstants.UTILDATE.getName())) {
            return "Date";
        } else if (typeName.equals("java.util.List")) {
            return "List";
        }
        return null;
    }

    /**
     * INTERNAL:
     * Validates whether given string is a valid XML non-colonial name (NCName).
     * @param name string
     * @return {@code true} given string is a valid NCName
     * @see <a href="http://www.w3.org/TR/REC-xml-names/#NT-NCName">http://www.w3.org/TR/REC-xml-names/#NT-NCName</a>
     */
    public static boolean isValidXmlNCName(String name) {
        return XML_NCNAME_VALIDATION_PATTERN.matcher(name).matches();
    }
}
