Java tutorial
/******************************************************************************* * Copyright (c) 2013 Oracle. All rights reserved. * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0, which accompanies this distribution * and is available at http://www.eclipse.org/legal/epl-v10.html. * * Contributors: * Oracle - initial API and implementation ******************************************************************************/ package org.eclipse.jpt.jpa.annotate.util; import java.math.BigDecimal; import java.util.Arrays; import java.util.Calendar; import java.util.Date; import java.util.Set; import org.eclipse.core.resources.IProject; import org.eclipse.jdt.core.Flags; import org.eclipse.jdt.core.IField; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.ITypeHierarchy; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.core.Signature; import org.eclipse.jpt.common.utility.internal.ClassNameTools; import org.eclipse.jpt.common.utility.internal.NameTools; import org.eclipse.jpt.common.utility.internal.StringTools; public class AnnotateMappingUtil { public static final String[] REPEATING_TYPES = new String[] { java.util.Collection.class.getName(), java.util.Enumeration.class.getName(), java.util.Iterator.class.getName(), java.util.Map.class.getName() }; /** java primitive types */ public static String[] PRIMITIVE_TYPES = new String[] { "int", //$NON-NLS-1$ "double", //$NON-NLS-1$ "long", //$NON-NLS-1$ "short", //$NON-NLS-1$ "byte", //$NON-NLS-1$ "float", //$NON-NLS-1$ "boolean", //$NON-NLS-1$ "char", //$NON-NLS-1$ "void" }; //$NON-NLS-1$ public static String[] PRIMITIVE_WRAPPERS = new String[] { Integer.class.getName(), Double.class.getName(), Long.class.getName(), Short.class.getName(), Byte.class.getName(), Float.class.getName(), Boolean.class.getName(), Character.class.getName() }; public final static String GET_PREFIX = "get"; //$NON-NLS-1$ public final static String IS_PREFIX = "is"; //$NON-NLS-1$ public final static String BOOLEAN = "boolean"; //$NON-NLS-1$ public static boolean isRepeatingType(String type, IProject project) throws JavaModelException { if (isArrayType(type)) return true; String strippedType = stripTypeParameters(type); for (String repeatingType : REPEATING_TYPES) { if (strippedType.equals(repeatingType) || isAssignableFrom(strippedType, repeatingType, project)) return true; } return false; } /** * Returns the properties in refType of the same type entityClass. * */ public static Set<String> getMappedByList(String entityClass, IType refType, boolean isManyToMany) throws JavaModelException { Set<String> propNames = new java.util.HashSet<String>(); IField[] fields = refType.getFields(); for (IField field : fields) { // filter out static/inherited fields if (Flags.isStatic(field.getFlags()) || Flags.isSuper(field.getFlags())) continue; String fieldType = getFieldType(field, refType); String dt = null; if (fieldType != null) { if (isManyToMany) { dt = getGenericsComponentTypeName(fieldType); if (dt != null) dt = resolveType(dt, refType); } else { dt = fieldType; } } if (dt != null && dt.equals(entityClass)) { propNames.add(field.getElementName()); } } return propNames; } public static String getGenericsComponentTypeName(String s) { //s is in the form T<x, y> int i1 = s.indexOf('<'); if (i1 < 0) { return null; } int i2 = s.indexOf('>', i1); if (i2 < 0) { return null; } String typeName = s.substring(i1 + 1, i2); //don't bother with the Map case for now if (typeName.indexOf(',') >= 0) { return null; } return typeName; } /** * returns the fully qualified type for an IField * @param field * @param containingType * @return * @throws JavaModelException */ public static String getFieldType(IField field, IType containingType) throws JavaModelException { String shortType = field.getTypeSignature(); shortType = Signature.toString(shortType); if (ClassNameTools.isPrimitive(shortType)) return shortType; String type = resolveType(shortType, containingType); return type; } public static boolean isNumeric(String type) { return (type.equals(Integer.class.getName()) || type.equals(Integer.TYPE.getName()) || type.equals(Float.class.getName()) || type.equals(Float.TYPE.getName()) || type.equals(Double.class.getName()) || type.equals(Double.TYPE.getName()) || type.equals(Short.class.getName()) || type.equals(Short.TYPE.getName()) || type.equals(Long.class.getName()) || type.equals(Long.TYPE.getName()) || type.equals(Byte.class.getName()) || type.equals(Byte.TYPE.getName()) || type.equals(Number.class.getName()) || type.equals(BigDecimal.class.getName())); } public static boolean isString(String type) { return (type.equals(String.class.getName())); } public static boolean isDate(String type, IProject proj) throws JavaModelException { return isAssignableFrom(type, Date.class.getName(), proj) || isAssignableFrom(type, Calendar.class.getName(), proj); } public static boolean isBoolean(String type) { return (type.equals(Boolean.class.getName()) || type.equals(Boolean.TYPE.getName())); } /** * Checks if this type is an array type ie type has [] at the end. */ public static boolean isArrayType(String type) { return type.endsWith("[]"); //$NON-NLS-1$ } public static boolean hasMatchingTypeParameterBraces(String type) { assert type != null : "Type was null."; //$NON-NLS-1$ char[] chars = type.toCharArray(); int openCount = 0; int closeCount = 0; for (int i = 0; i < chars.length; ++i) { if (chars[i] == '<') ++openCount; if (chars[i] == '>') ++closeCount; if (closeCount > openCount) return false; } return openCount == closeCount; } /** * returns true if the array brackets on the string type are properly matched * false otherwise */ public static boolean hasMatchingArrayBrackets(String type) { int firstBracket = type.indexOf("["); //$NON-NLS-1$ int nextBracket = type.indexOf("]"); //$NON-NLS-1$ // if no brackets at all, return true if (firstBracket == -1 && nextBracket == -1) { return true; } String tempType = type; while (tempType.length() > 0) { nextBracket = tempType.indexOf("]"); //$NON-NLS-1$ if (nextBracket < firstBracket || firstBracket == -1 || nextBracket != firstBracket + 1) { return false; } tempType = tempType.substring(nextBracket + 1); firstBracket = tempType.indexOf("["); //$NON-NLS-1$ } return true; } /** * Strips type parameter information from a type name, for example "java.util.List<java.lang.String>" * would become "java.util.List". Stripping "<>" would become "". If the "<" don't match, then * the provided type name is returned as-is, for example "List<<>" would be returned as "List<<>". * This will not return null. */ public static String stripTypeParameters(String typeName) { assert typeName != null : "Type name was null."; //$NON-NLS-1$ if (hasMatchingTypeParameterBraces(typeName)) { int index = typeName.indexOf('<'); if (index > -1) typeName = typeName.substring(0, index).trim(); } assert typeName != null : "Returning null type."; //$NON-NLS-1$ return typeName; } /** * Strips any array dimensions off of the provided type name. * For example, stripping "Foo[]" will return "Foo". If the brackets * are not matched, the type name will be returned unchanged. For example, * stripping "Foo]" will return "Foo]", and stripping "Foo[][" will * return "Foo[][". Stripping "[]" will return "". Null is not returned. */ public static String stripArrayDimensions(String typeName) { assert typeName != null : "Type name was null."; //$NON-NLS-1$ if (hasMatchingArrayBrackets(typeName)) { typeName = stripTypeParameters(typeName); // Deal with things like Collection<String[]> int index = typeName.indexOf('['); if (index > -1) typeName = typeName.substring(0, index).trim(); } assert typeName != null : "Returning null type."; //$NON-NLS-1$ return typeName; } /** * Retrieves an IType for a given Java project or any project it references. * Anonymous types cannot be retrieved with this method. * @param fullyQualifiedType The fully qualified name of the type - for example "java.lang.String". * This doesn't work for primitive or array types, such as "int" or "Foo[]". * @param project The project that is in scope. */ public static IType getType(String fullyQualifiedType, IProject project) throws JavaModelException { assert fullyQualifiedType != null : "Fully qualified type should not be null."; //$NON-NLS-1$ // the JDT returns a non-null anonymous class IType // for empty string and package names that end with a dot // if the type starts with a dot, the JDT helpfully removes it // and returns the type referenced without the dot // short circuit here for perf and so validation results make sense // e.g. if the valid type is "Thing", then ".Thing" and "Thing." should not be valid if (fullyQualifiedType.trim().length() == 0 || fullyQualifiedType.startsWith(".") //$NON-NLS-1$ || fullyQualifiedType.endsWith(".")) //$NON-NLS-1$ return null; int ltIndex = fullyQualifiedType.indexOf('<'); int gtIndex = fullyQualifiedType.lastIndexOf('>'); if (ltIndex != -1 && gtIndex != -1 && gtIndex > ltIndex) assert false : "Type should have type parameter info stripped before calling this method: " //$NON-NLS-1$ + fullyQualifiedType; assert !isArrayType(fullyQualifiedType) : "Type should have array brackets stripped before calling: " + //$NON-NLS-1$ fullyQualifiedType; assert project != null : "Project should not be null."; //$NON-NLS-1$ // if the project is not accessible, this will cause a JavaModelException below if (!project.isAccessible()) { return null; } IJavaProject javaProject = JavaCore.create(project); if ((javaProject == null) || (!javaProject.exists())) { return null; } IType type = null; type = javaProject.findType(fullyQualifiedType); if (type != null && (!type.exists() || type.isAnonymous())) { type = null; } return type; } /** * Determines if the class or interface represented by the subtype name is either the same as, or is a superclass * or superinterface of, the class or interface represented by the specified supertype name. It returns true if * so; otherwise it returns false. This method will not block and wait if the required compiler information is not * instantly available - in this case it will return false right away. Callers should be aware that false can * sometimes mean that "we could neither confirm nor deny that the type is assignable from the super type". Callers * should handle this by calling this method as often as needed and not caching the result. Type parameter * information should be removed before calling this method (see {@link #stripTypeParameters(String)} for * this purpose). * @param fullyQualifiedSubTypeName The subtype name. * @param fullyQualifiedSuperTypeName The supertype name. * @param project The relevant project. If null is passed for this parameter, then it attempts to * determine the active Java project based on the current editor. If no Java project can be * determined, then this method will definitley return false. */ public static boolean isAssignableFrom(String fullyQualifiedSubTypeName, String fullyQualifiedSuperTypeName, IProject project) throws JavaModelException { assert fullyQualifiedSubTypeName != null : "Sub type name was null."; //$NON-NLS-1$ assert fullyQualifiedSuperTypeName != null : "Super type name was null."; //$NON-NLS-1$ assert fullyQualifiedSubTypeName.indexOf( '<') == -1 : "Subtype should have type parameter info stripped before calling this method: " + //$NON-NLS-1$ fullyQualifiedSubTypeName; assert fullyQualifiedSuperTypeName.indexOf( '<') == -1 : "Supertype should have type parameter info stripped before calling this method: " + //$NON-NLS-1$ fullyQualifiedSuperTypeName; boolean assignableFrom = false; if (project != null) { IType subType = getType(fullyQualifiedSubTypeName, project); IType superType = getType(fullyQualifiedSuperTypeName, project); if (subType != null && superType != null) { ITypeHierarchy th = subType.newSupertypeHierarchy(null); assert th != null : "Type hierarchy was null."; //$NON-NLS-1$ assignableFrom = th.contains(superType); } } return assignableFrom; } /** * Resolves a type name against an IType to get a fully qualified type name. If * it cannot be resolved for some reason (eg. the type is ambiguous), this returns null. */ public static String resolveType(String typeName, IType type) throws JavaModelException { assert typeName != null : "Type name was null."; //$NON-NLS-1$ assert type != null : "Type was null."; //$NON-NLS-1$ int bracketIndex = typeName.indexOf('['); int typeParamIndex = typeName.indexOf('<'); int suffixIndex = -1; String typeSuffix = null; if (bracketIndex > -1 && typeParamIndex > -1) suffixIndex = bracketIndex <= typeParamIndex ? bracketIndex : typeParamIndex; else if (bracketIndex > -1) suffixIndex = bracketIndex; else if (typeParamIndex > -1) suffixIndex = typeParamIndex; if (suffixIndex > -1) { typeSuffix = typeName.substring(suffixIndex); typeName = typeName.substring(0, suffixIndex).trim(); } String value = null; String[][] typeMatches = type.resolveType(typeName); // If typeMatches.length > 1 then the type was ambiguous. We return null in that case. if (typeMatches != null && typeMatches.length == 1) { value = (typeMatches[0][0].length() == 0 ? typeMatches[0][1] : typeMatches[0][0] + "." + typeMatches[0][1]); //$NON-NLS-1$ } if (value == null && type.isBinary()) { // For some reason, resolving types against binary types doesn't always // (ever?) work. If the type we're trying to resolve it against is binary, // try to resolve against the project instead (and since there should only // be one reachable type with a given fully qualified name on the project // classpath anyway, this should be legit). IProject project = type.getJavaProject().getProject(); assert project != null : "Project was null."; //$NON-NLS-1$ IType resolvedType = getType(typeName, project); if (resolvedType != null) value = resolvedType.getFullyQualifiedName('.'); } if (value != null && typeSuffix != null) value += typeSuffix; return value; } /** * Strips out package information and returns the java class name * If the class uses <>, then keep that in. * Examples: <pre> * getClassName("MyClass") = "MyClass" * getClassName("java.util.ArrayList") = "ArrayList" * getClassName("java.util.String[]") = "String[]" * getClassName("java.util.ArrayList<java.lang.String>") = "ArrayList<String>" * getClassName("java.util.ArrayList<java.lang.ArrayList<String>>") = ArrayList<ArrayList<String>> * getClassName("java.util.ArrayList<java.lang.String[]>[]") = ArrayList<String[]>[] * </pre> * @param fullClassName the fully qualified class name * @return the name of the class */ public static String getClassName(String fullClassName) { if (fullClassName == null) { return null; } else { int endBracket = fullClassName.lastIndexOf('>'); //$NON-NLS-1$ if (endBracket >= 0) { int beginBracket = fullClassName.indexOf('<'); //$NON-NLS-1$ assert beginBracket >= 0 : "no matching '<' for '>' in " + fullClassName; //$NON-NLS-1$ assert beginBracket < endBracket : "bad class syntax in " + fullClassName; //$NON-NLS-1$ return (getClassName(fullClassName.substring(0, beginBracket)) + "<" + //$NON-NLS-1$ getClassName(fullClassName.substring(beginBracket + 1, endBracket)) + ">" + //$NON-NLS-1$ fullClassName.substring(endBracket + 1)); } else { int dotpos = fullClassName.lastIndexOf('.'); return (dotpos < 0) ? fullClassName : fullClassName.substring(dotpos + 1); } } } /****************************************************************************** * Determines is two objects are equal, accounting for one or both * objects being null or the two objects being array types. * @param o1 The first object to compare. * @param o2 The second object to compare. * @return True if the two objects are equal. ******************************************************************************/ public static final boolean areEqual(Object o1, Object o2) { boolean objectsAreEqual = false; if (o1 == o2) { objectsAreEqual = true; } else if (o1 != null && o2 != null) { if (o1.getClass().isArray() && o2.getClass().isArray()) { objectsAreEqual = Arrays.equals((Object[]) o1, (Object[]) o2); } else { objectsAreEqual = o1.equals(o2); } } return objectsAreEqual; } public static String dbNameToJavaName(String dbName) { String result = dbName; if (StringTools.isUppercase(result) || StringTools.isLowercase(result)) { // leave mixed case identifiers alone? result = StringTools.convertAllCapsToCamelCase(result, false); } result = NameTools.convertToJavaIdentifier(result); return result; } /** * Utility methods used to convert DB object names to * appropriate Java type and field name */ public static String pluralise(String name) { String result = name; if (name.length() == 1) { result += 's'; } else if (!seemsPluralised(name)) { String lower = name.toLowerCase(); if (!lower.endsWith("data")) { //orderData --> orderDatas is dumb char secondLast = lower.charAt(name.length() - 2); if (!isVowel(secondLast) && lower.endsWith("y")) { // city, body etc --> cities, bodies result = name.substring(0, name.length() - 1) + "ies"; } else if (lower.endsWith("ch") || lower.endsWith("s")) { // switch --> switches or bus --> buses result = name + "es"; } else { result = name + "s"; } } } return result; } public static String singularise(String name) { String result = name; if (seemsPluralised(name)) { String lower = name.toLowerCase(); if (lower.endsWith("ies")) { // cities --> city result = name.substring(0, name.length() - 3) + "y"; } else if (lower.endsWith("ches") || lower.endsWith("ses")) { // switches --> switch or buses --> bus result = name.substring(0, name.length() - 2); } else if (lower.endsWith("s")) { // customers --> customer result = name.substring(0, name.length() - 1); } } return result; } private final static boolean isVowel(char c) { boolean vowel = false; vowel |= c == 'a'; vowel |= c == 'e'; vowel |= c == 'i'; vowel |= c == 'o'; vowel |= c == 'u'; vowel |= c == 'y'; return vowel; } private static boolean seemsPluralised(String name) { name = name.toLowerCase(); boolean pluralised = false; pluralised |= name.endsWith("es"); pluralised |= name.endsWith("s"); pluralised &= !(name.endsWith("ss") || name.endsWith("us")); return pluralised; } }