/*******************************************************************************
* Copyright (c) 2009,2015 Red Hat, Inc.
* Distributed under license by Red Hat, Inc. All rights reserved.
* This program is 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:
* Red Hat, Inc. - initial API and implementation
******************************************************************************/
package org.jboss.tools.common.java;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.jdt.core.IImportDeclaration;
import org.eclipse.jdt.core.ISourceRange;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.ITypeParameter;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.Signature;
import org.jboss.tools.common.core.CommonCorePlugin;
import org.jboss.tools.common.java.TypeDeclaration.Lazy;
/**
*
* @author Viacheslav Kabanovich
*
*/
//@SuppressWarnings("nls")
public class ParametedType implements IParametedType {
protected ParametedTypeFactory typeFactory = null;
protected IType type;
protected int arrayIndex = 0;
protected String signature;
protected List<ParametedType> parameterTypes = new ArrayList<ParametedType>(1);
protected boolean primitive;
protected boolean isUpper = false;
protected boolean isLower = false;
protected boolean isVariable = false;
boolean inheritanceIsBuilt = false;
protected ParametedType superType = null;
protected Collection<IParametedType> inheritedTypes = new ArrayList<IParametedType>(1);
Set<IParametedType> allInheritedTypes = null;
protected long inheritanceHashcode = -1;
public static interface PositionProvider {
ISourceRange getRange(String superTypeName);
boolean isLoaded();
}
PositionProvider provider = null;
public ParametedType() {}
/*
* (non-Javadoc)
* @see org.jboss.tools.common.java.IParametedType#isPrimitive()
*/
public boolean isPrimitive() {
return primitive;
}
public void setPrimitive(boolean primitive) {
this.primitive = primitive;
}
public boolean isUpper() {
return isUpper;
}
public void setUpper(boolean b) {
isUpper = b;
}
public boolean isLower() {
return isLower;
}
public void setLower(boolean b) {
isLower = b;
}
public boolean isVariable() {
return isVariable;
}
public void setVariable(boolean b) {
isVariable = b;
}
public ParametedTypeFactory getFactory() {
return typeFactory;
}
public void setFactory(ParametedTypeFactory typefactory) {
this.typeFactory = typefactory;
}
public IType getType() {
return type;
}
public int getArrayIndex() {
return arrayIndex;
}
public String getArrayPrefix() {
return toArrayPrefix(arrayIndex);
}
static String[] PREFIXES = new String[4];
static {
PREFIXES[0] = ""; //$NON-NLS-1$
for (int i = 1; i < PREFIXES.length; i++) PREFIXES[i] = PREFIXES[i - 1] + Signature.C_ARRAY;
}
private static String toArrayPrefix(int arrayIndex) {
return arrayIndex < PREFIXES.length ? PREFIXES[arrayIndex] : PREFIXES[3] + toArrayPrefix(arrayIndex - 3);
}
public String getSignature() {
return signature;
}
public void setType(IType type) {
this.type = type;
}
public void setSignature(String signature) {
this.signature = signature;
arrayIndex = 0;
if(signature != null) {
for (; arrayIndex < signature.length() && (signature.charAt(arrayIndex) == Signature.C_ARRAY); arrayIndex++) {}
}
}
public void addParameter(ParametedType p) {
parameterTypes.add(p);
}
public List<? extends IParametedType> getParameters() {
return parameterTypes;
}
public void setPositionProvider(PositionProvider p) {
provider = p;
}
@Override
public boolean equals(Object object) {
if(!(object instanceof ParametedType)) return false;
ParametedType other = (ParametedType)object;
return equals(other, null, iterationsLimit);
}
boolean equals(ParametedType other, Set<String> underConsideration,int depth) {
if(signature != null && signature.equals(other.signature)) {
return true;
}
if(type == null || other.type == null || !type.getFullyQualifiedName().equals(other.type.getFullyQualifiedName())) {
return false;
}
if(parameterTypes.size() != other.parameterTypes.size()) {
return false;
}
if(arrayIndex != other.arrayIndex) {
return false;
}
String code = null;
if(!parameterTypes.isEmpty()) {
if(depth <= 0) {
if(!equalsFailReported) {
reportFail("ParametedType.equals()", other, underConsideration);
equalsFailReported = true;
}
return false;
}
code = "" + getSignature() + "<-" + other.getSignature();
if(underConsideration == null) {
underConsideration = new HashSet<String>();
} else if(underConsideration.contains(code)) {
//Cycle is prevented, this case is already considered up stack.
return true;
}
underConsideration.add(code);
for (int i = 0; i < parameterTypes.size(); i++) {
ParametedType ithis = parameterTypes.get(i);
ParametedType iother = other.parameterTypes.get(i);
if(!ithis.equals(iother, underConsideration, depth - 1)) {
return false;
}
}
underConsideration.remove(code);
}
return true;
}
void buildInheritance() {
if(type == null) return;
inheritanceHashcode = 0;
Collection<IParametedType> inheritedTypes = new ArrayList<IParametedType>(2);
try {
if(!type.isInterface() && !type.isAnnotation()) {
String sc = type.getSuperclassTypeSignature();
boolean objectArray = false;
if(sc != null) {
sc = resolveParameters(sc);
} else if(!"java.lang.Object".equals(type.getFullyQualifiedName())) { //$NON-NLS-1$
sc = ParametedTypeFactory.OBJECT;
} else if("java.lang.Object".equals(type.getFullyQualifiedName()) && arrayIndex > 0) { //$NON-NLS-1$
objectArray = true;
sc = ParametedTypeFactory.OBJECT;
}
if(!objectArray && arrayIndex > 0) {
sc = getArrayPrefix() + sc;
}
superType = getFactory().getParametedType(type, this, sc);
if(superType != null) {
inheritanceHashcode = superType.getType().getFullyQualifiedName().hashCode();
if(provider != null) {
final String scn = type.getSuperclassName();
if(scn != null) {
if(provider.isLoaded() && provider.getRange(scn) != null) {
ISourceRange r = provider.getRange(scn);
superType = new TypeDeclaration(superType, type.getResource(), r.getOffset(), r.getLength());
} else if(!provider.isLoaded()) {
Lazy lazy = new Lazy() {
@Override
public void init(TypeDeclaration d) {
ISourceRange r = provider.getRange(scn);
if(r != null) {
d.init(r.getOffset(), r.getLength());
}
}
};
superType = new TypeDeclaration(superType, type.getResource(), lazy);
}
}
}
inheritedTypes.add(superType);
}
}
String[] is = type.getSuperInterfaceTypeSignatures();
if(is != null) for (int i = 0; i < is.length; i++) {
String p = resolveParameters(is[i]);
if(arrayIndex > 0) p = getArrayPrefix() + p;
ParametedType t = getFactory().getParametedType(type, this, p);
if(t != null) {
inheritanceHashcode = inheritanceHashcode * 773 + t.getType().getFullyQualifiedName().hashCode();
if(provider != null) {
final String scn = type.getSuperInterfaceNames()[i];
if(scn != null) {
if(provider.isLoaded() && provider.getRange(scn) != null) {
ISourceRange r = provider.getRange(scn);
t = new TypeDeclaration(t, type.getResource(), r.getOffset(), r.getLength());
} else if(!provider.isLoaded()) {
Lazy lazy = new Lazy() {
@Override
public void init(TypeDeclaration d) {
ISourceRange r = provider.getRange(scn);
if(r != null) {
d.init(r.getOffset(), r.getLength());
}
}
};
t = new TypeDeclaration(t, type.getResource(), lazy);
}
}
}
inheritedTypes.add(t);
}
}
if(type.getCompilationUnit() != null) {
for (IImportDeclaration d: type.getCompilationUnit().getImports()) {
String n = d.getElementName();
inheritanceHashcode = inheritanceHashcode * 773 + n.hashCode();
}
}
} catch (JavaModelException e) {
CommonCorePlugin.getDefault().logError(e);
}
this.inheritedTypes = inheritedTypes;
inheritanceIsBuilt = true;
}
public ParametedType getSuperType() {
if(!inheritanceIsBuilt) {
buildInheritance();
}
return superType;
}
public Collection<IParametedType> getInheritedTypes() {
if(!inheritanceIsBuilt) {
buildInheritance();
}
return inheritedTypes;
}
public String resolveParameters(String typeSignature) {
if(typeSignature == null) {
return typeSignature;
}
int i = typeSignature.indexOf(Signature.C_GENERIC_START);
if(i < 0) {
char c = typeSignature.length() == 0 ? '\0' : typeSignature.charAt(0);
char e = typeSignature.length() == 0 ? '\0' : typeSignature.charAt(typeSignature.length() - 1);
if((c == Signature.C_TYPE_VARIABLE || c == Signature.C_UNRESOLVED || c == Signature.C_RESOLVED) && e == Signature.C_SEMICOLON) {
String param = typeSignature.substring(1, typeSignature.length() - 1);
String s = findParameterSignature(param);
return s == null ? typeSignature : s;
}
return typeSignature;
}
int j = typeSignature.lastIndexOf(Signature.C_GENERIC_END);
if(j < i) {
return typeSignature;
}
boolean replaced = false;
StringBuffer newParams = new StringBuffer();
String[] ps = Signature.getTypeArguments(typeSignature);
for (String param: ps) {
String newParam = resolveParameters( param);
if(!param.equals(newParam)) replaced = true;
newParams.append(newParam);
}
if(replaced) {
typeSignature = typeSignature.substring(0, i)
+ Signature.C_GENERIC_START
+ newParams.toString()
+ Signature.C_GENERIC_END
+ Signature.C_SEMICOLON;
}
return typeSignature;
}
public String findParameterSignature(String paramName) {
buildParameters();
return signaturesByName.get(paramName);
}
Map<String, String> signaturesByName = null;
Map<String, ParametedType> parametersBySignature = null;
void buildParameters() {
if(signaturesByName == null && type != null) {
Map<String, String> sbn = new HashMap<String, String>();
Map<String, ParametedType> pbs = new HashMap<String, ParametedType>();
ITypeParameter[] ps = null;
try {
ps = type.getTypeParameters();
} catch (JavaModelException e) {
return;
}
if(ps != null) for (int i = 0; i < ps.length; i++) {
String paramName = ps[i].getElementName();
if(parameterTypes.size() > i) {
ParametedType p = parameterTypes.get(i);
sbn.put(paramName, p.getSignature());
pbs.put(p.getSignature(), p);
}
}
signaturesByName = sbn;
parametersBySignature = pbs;
}
}
public ParametedType findParameter(String signature) {
buildParameters();
return parametersBySignature.get(signature);
}
public Collection<IParametedType> getAllTypes() {
if(allInheritedTypes == null) {
allInheritedTypes = buildAllTypes(new HashSet<String>(), this, new HashSet<IParametedType>());
}
return allInheritedTypes;
}
Set<IParametedType> buildAllTypes(Set<String> processed, ParametedType p, Set<IParametedType> types) {
IType t = p.getType();
if(t != null) {
String key = p.getArrayPrefix() + t.getFullyQualifiedName();
if(!processed.contains(key)) {
processed.add(key);
types.add(p);
Collection<IParametedType> ts = p.getInheritedTypes();
if(ts != null) for (IParametedType pp: ts) {
buildAllTypes(processed, (ParametedType)pp, types);
}
}
}
return types;
}
@Override
public String toString() {
return signature + ":" + super.toString(); //$NON-NLS-1$
}
public boolean isAssignableTo(ParametedType other, boolean checkInheritance) {
return isAssignableTo(other, checkInheritance, new HashMap<String, IType>(), null, iterationsLimit);
}
private boolean isAssignableTo(ParametedType other, boolean checkInheritance, Map<String,IType> resolvedVars, Set<String> underConsideration, int depth) {
if(equals(other)) return true;
if("*".equals(other.getSignature())) { //$NON-NLS-1$
return true;
}
if(this.type == null) {
return (isVariable && other.isVariable && other.type == null);
}
if(other.isVariable && other.type == null) {
if(type != null) {
if(resolvedVars.get(other.getSignature()) != null) {
return resolvedVars.get(other.getSignature()) == type;
} else {
resolvedVars.put(other.getSignature(), type);
}
}
return true;
}
String code = (!parameterTypes.isEmpty() || !other.parameterTypes.isEmpty()) ? "" + getSignature() + "<-" + other.getSignature() : null;
if(underConsideration != null && underConsideration.contains(code)) {
//Cycle is prevented, this case is already considered up stack.
return true;
}
if(this.type.equals(other.type)) {
if(code != null) {
if(underConsideration == null) {
underConsideration = new HashSet<String>();
}
underConsideration.add(code);
}
if(areTypeParametersAssignableTo(other, resolvedVars, underConsideration, depth)) {
if(code != null) {
underConsideration.remove(code);
}
return true;
}
}
if(checkInheritance) {
Collection<IParametedType> ts = getAllTypes();
if(!ts.isEmpty() && (code != null)) {
if(code != null) {
if(underConsideration == null) {
underConsideration = new HashSet<String>();
}
underConsideration.add(code);
}
}
for (IParametedType t: ts) {
if(t == this) {
continue;
}
if(((ParametedType)t).isAssignableTo(other, false, resolvedVars, underConsideration, depth)) {
if(code != null) {
underConsideration.remove(code);
}
return true;
}
}
}
if(code != null && underConsideration != null) {
underConsideration.remove(code);
}
return false;
}
private boolean areTypeParametersAssignableTo(ParametedType other, Map<String,IType> resolvedVars, Set<String> underConsideration,int depth) {
if(other.parameterTypes.isEmpty()) return true;
if(this.parameterTypes.isEmpty()) {
for (ParametedType p2: other.parameterTypes) {
if("*".equals(p2.getSignature())) continue;
if("Ljava.lang.Object;".equals(p2.getSignature())) continue;
if("+Ljava.lang.Object;".equals(p2.getSignature())) continue;
if(p2.isVariable()) {
if(!p2.isLower() && !p2.isUpper()) continue;
}
return false;
}
return true;
}
if(this.parameterTypes.size() != other.parameterTypes.size()) return false;
if(depth <= 0) {
if(!isAssignableFailReported) {
reportFail("ParametedType.isAssignableTo()", other, underConsideration);
isAssignableFailReported = true;
}
return false;
}
for (int i = 0; i < parameterTypes.size(); i++) {
ParametedType p1 = parameterTypes.get(i);
ParametedType p2 = other.parameterTypes.get(i);
if(p1.isLower() || (p1.isUpper() && !p1.isVariable)) return false;
if(p1.isVariable()) {
if(p2.isVariable()) {
if(p2.isAssignableTo(p1, true, resolvedVars, underConsideration, depth - 1)) continue;
} else if(p2.isLower()) {
if(p2.isAssignableTo(p1, true, resolvedVars, underConsideration, depth - 1)) continue;
} else if(p2.isUpper()) {
if(p2.isAssignableTo(p1, true, resolvedVars, underConsideration, depth - 1)) continue;
if(p1.isAssignableTo(p2, true, resolvedVars, underConsideration, depth - 1)) continue;
} else {
if(p2.isAssignableTo(p1, true, resolvedVars, underConsideration, depth - 1)) continue;
}
} else {
if(p2.isLower()) {
if(p2.isAssignableTo(p1, true, resolvedVars, underConsideration, depth - 1)) continue;
} else {
if(p1.isAssignableTo(p2, true, resolvedVars, underConsideration, depth - 1)) continue;
}
}
return false;
}
return true;
}
static Map<String, String> primitives = new HashMap<String, String>();
static {
primitives.put("Integer", "int");
primitives.put("Short", "short");
primitives.put("Long", "long");
primitives.put("Character", "char");
primitives.put("Float", "float");
primitives.put("Double", "double");
primitives.put("Boolean", "boolean");
}
/*
* (non-Javadoc)
* @see org.jboss.tools.cdi.core.IParametedType#getSimpleName()
*/
public String getSimpleName() {
if(getSignature()!=null) {
if(isPrimitive()) {
int array = arrayIndex;
StringBuilder result = new StringBuilder(primitives.get(Signature.getSignatureSimpleName(getSignature().substring(array))));
if(array > 0) {
for (int i = 0; i < array; i++) {
result.append("[]"); //$NON-NLS-1$
}
}
return result.toString();
} else {
return Signature.getSignatureSimpleName(getSignature());
}
}
return ""; //$NON-NLS-1$
}
public long getInheritanceCode() {
if(!inheritanceIsBuilt) {
buildInheritance();
}
return inheritanceHashcode;
}
static final int iterationsLimit = 10;
static boolean equalsFailReported = false;
static boolean isAssignableFailReported = false;
private void reportFail(String name, ParametedType other, Set<String> underConsideration) {
String message = "Method " + name + " failed to complete reaching the limit of " + iterationsLimit + " iterations when processing types:";
StringBuilder sb = new StringBuilder(message);
sb.append("\n").append(getSignature()).append(" and ").append(other.getSignature()).append("\n");
if(underConsideration != null && !underConsideration.isEmpty()) {
sb.append("Previous iterations processed types:").append("\n");
for (String s: underConsideration) {
sb.append("\t").append(s).append("\n");
}
}
CommonCorePlugin.getPluginLog().logError(sb.toString());
}
}