blob: bb6014c0a2b9bb5a5db56d4efc279626bbfaba06 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2013 Oracle. All rights reserved.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0, which accompanies this distribution
* and is available at https://www.eclipse.org/legal/epl-2.0/.
*
* Contributors:
* Oracle - initial API and implementation
******************************************************************************/
package org.eclipse.jpt.jpa.annotate.util;
import java.math.BigDecimal;
import java.sql.Time;
import java.sql.Timestamp;
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 isTime(String type)
{
return (type.equals(Time.class.getName()) || type.equals(Timestamp.class.getName()));
}
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: " + fullyQualifiedType; //$NON-NLS-1$
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 &lt;&gt;, then keep that in.
* Examples: <pre>
* getClassName("MyClass") = "MyClass"
* getClassName("java.util.ArrayList") = "ArrayList"
* getClassName("java.util.String[]") = "String[]"
* getClassName("java.util.ArrayList&lt;java.lang.String&gt;") = "ArrayList&lt;String&gt;"
* getClassName("java.util.ArrayList&lt;java.lang.ArrayList&lt;String&gt;&gt;") = ArrayList&lt;ArrayList&lt;String&gt;&gt;
* getClassName("java.util.ArrayList&lt;java.lang.String[]&gt;[]") = ArrayList&lt;String[]&gt;[]
* </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;
}
}