package org.eclipse.jdt.internal.core.builder;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Set;
import java.util.stream.Collectors;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.internal.compiler.lookup.CompilationUnitScope;
import org.eclipse.jdt.internal.compiler.lookup.TypeConstants;
import org.eclipse.jdt.internal.compiler.util.SortedCharArrays;
public class ReferenceCollection {
char[][][] qualifiedNameReferences;
char[][] simpleNameReferences;
char[][] rootReferences;
protected ReferenceCollection(char[][][] qualifiedNameReferences, char[][] simpleNameReferences, char[][] rootReferences) {
this.qualifiedNameReferences = internQualifiedNames(qualifiedNameReferences, false);
this.simpleNameReferences = internSimpleNames(simpleNameReferences, true);
this.rootReferences = internSimpleNames(rootReferences, false);
}
public void addDependencies(String[] typeNameDependencies) {
next: for(String typeNameDependency: typeNameDependencies) {
char[][] qualifiedTypeName = CharOperation.splitOn('.', typeNameDependency.toCharArray());
if (!isWellKnownQualifiedName(qualifiedTypeName)) {
int qLength = qualifiedTypeName.length;
QualifiedNameSet internedNames = InternedQualifiedNames[qLength <= MaxQualifiedNames ? qLength - 1 : 0];
qualifiedTypeName = internSimpleNames(qualifiedTypeName, false, false);
qualifiedTypeName = internedNames.add(qualifiedTypeName);
int idx;
while ((idx = Arrays.binarySearch(this.qualifiedNameReferences, qualifiedTypeName, SortedCharArrays.CHAR_CHAR_ARR_COMPARATOR)) < 0) {
this.simpleNameReferences = ensureContainedInSortedOrder(this.simpleNameReferences, qualifiedTypeName[qualifiedTypeName.length - 1]);
this.rootReferences = ensureContainedInSortedOrder(this.rootReferences, qualifiedTypeName[0]);
int length = this.qualifiedNameReferences.length;
idx = -(idx+1);
this.qualifiedNameReferences = SortedCharArrays.insertIntoArray(this.qualifiedNameReferences, new char[length + 1][][], qualifiedTypeName, idx, this.qualifiedNameReferences.length);
qualifiedTypeName = CharOperation.subarray(qualifiedTypeName, 0, qualifiedTypeName.length - 1);
char[][][] temp = internQualifiedNames(new char[][][] {qualifiedTypeName}, false);
if (temp == EmptyQualifiedNames)
continue next;
qualifiedTypeName = temp[0];
}
}
}
}
public boolean includes(char[] simpleName) {
boolean result = sortedArrayContains(this.simpleNameReferences, simpleName, SortedCharArrays.CHAR_ARR_COMPARATOR);
if (REFERENCE_COLLECTION_DEBUG) {
assertIncludes(result, simpleName);
}
return result;
}
public boolean includes(char[][] qualifiedName) {
boolean result = sortedArrayContains(this.qualifiedNameReferences, qualifiedName, SortedCharArrays.CHAR_CHAR_ARR_COMPARATOR);
if (REFERENCE_COLLECTION_DEBUG) {
assertIncludes(result, qualifiedName);
}
return result;
}
private static String qualifiedNamesToString(char[][][] qualifiedNames) {
if (qualifiedNames == null)
return "null";
return Arrays.stream(qualifiedNames).map(CharOperation::toString).collect(Collectors.joining(","));
}
public boolean includes(char[][][] qualifiedNames, char[][] simpleNames) {
return includes(qualifiedNames, simpleNames, null);
}
public boolean includes(char[][][] qualifiedNames, char[][] simpleNames, char[][] rootNames) {
boolean result = doIncludes(qualifiedNames, simpleNames, rootNames);
if (REFERENCE_COLLECTION_DEBUG) {
assertIncludes(result, qualifiedNames, simpleNames, rootNames);
}
return result;
}
private boolean doIncludes(char[][][] qualifiedNames, char[][] simpleNames, char[][] rootNames) {
if (rootNames != null) {
if (!includesRootName(rootNames))
return false;
}
if (simpleNames == null || qualifiedNames == null) {
if (simpleNames == null && qualifiedNames == null) {
if (JavaBuilder.DEBUG)
System.out.println("Found well known match");
return true;
} else if (qualifiedNames == null) {
return includesSimpleName(simpleNames);
}
return includesQualifiedName(qualifiedNames);
}
if (simpleNames.length <= qualifiedNames.length) {
return includesSimpleName(simpleNames) && includesQualifiedName(qualifiedNames);
} else {
return includesQualifiedName(qualifiedNames) && includesSimpleName(simpleNames);
}
}
public boolean insideRoot(char[] rootName) {
boolean result = sortedArrayContains(this.rootReferences, rootName, SortedCharArrays.CHAR_ARR_COMPARATOR);
if (REFERENCE_COLLECTION_DEBUG) {
if (result != debugIncludes(rootName)) {
String message = "Mismatch: " + String.valueOf(rootName) + (result ? " should not " : " should ") + " be included in "
+ Arrays.asList(CharOperation.toStrings(this.rootReferences));
throw new IllegalStateException(message);
}
}
return result;
}
private static <T> boolean sortedArrayContains(T[] array, T element, Comparator<? super T> comparator) {
int l = array.length;
if (l < SortedCharArrays.BINARY_SEARCH_THRESHOLD) {
for (int i = 0; i < l; i++)
if (element == array[i]) return true;
return false;
}
return Arrays.binarySearch(array, element, comparator) >= 0;
}
private boolean includesSimpleName(char[][] simpleNames) {
return intersects(simpleNames, this.simpleNameReferences, SortedCharArrays.CHAR_ARR_COMPARATOR);
}
private boolean includesQualifiedName(char[][][] qualifiedNames) {
if (intersects(qualifiedNames, this.qualifiedNameReferences, SortedCharArrays.CHAR_CHAR_ARR_COMPARATOR)) {
return true;
}
char[][] maybeSimpleName;
for(int i = qualifiedNames.length - 1; i >= 0 && (maybeSimpleName = qualifiedNames[i]).length == 1; i--) {
if (includes(maybeSimpleName[0])) {
return true;
}
}
return false;
}
private boolean includesRootName(char[][] rootNames) {
return intersects(rootNames, this.rootReferences, SortedCharArrays.CHAR_ARR_COMPARATOR);
}
private static <T> boolean intersects(T[] firstSortedArr, T[] secondSortedArr, Comparator<? super T> comparator) {
for(int i = 0, l = firstSortedArr.length, j = 0, k = secondSortedArr.length; i < l && j < k;) {
T firstElement = firstSortedArr[i];
T secondElement = secondSortedArr[j];
int compare = comparator.compare(firstElement, secondElement);
if (compare == 0) {
return true;
} else if (compare < 0) {
i++;
if (l - i > SortedCharArrays.BINARY_SEARCH_THRESHOLD) {
i = Arrays.binarySearch(firstSortedArr, i, l, secondElement, comparator);
if (i >= 0) {
return true;
}
i = -(i + 1);
}
} else {
j++;
if (k - j > SortedCharArrays.BINARY_SEARCH_THRESHOLD) {
j = Arrays.binarySearch(secondSortedArr, j, k, firstElement, comparator);
if (j >= 0) {
return true;
}
j = -(j + 1);
}
}
}
return false;
}
private static char[][] ensureContainedInSortedOrder(char[][] sortedArray, char[] entry) {
int idx = Arrays.binarySearch(sortedArray, entry, SortedCharArrays.CHAR_ARR_COMPARATOR);
if (idx < 0) {
idx = -(idx + 1);
char[][] result = SortedCharArrays.insertIntoArray(sortedArray, new char[sortedArray.length + 1][], entry, idx, sortedArray.length);
return result;
}
return sortedArray;
}
private static boolean isWellKnownQualifiedName(char[][] qualifiedName) {
for (int i = 0, m = WellKnownQualifiedNames.length, qLength = qualifiedName.length; i < m; i++) {
char[][] wellKnownName = WellKnownQualifiedNames[i];
if (qLength > wellKnownName.length)
break;
if (CharOperation.equals(qualifiedName, wellKnownName)) {
return true;
}
}
return false;
}
static final char[][][] WellKnownQualifiedNames = new char[][][] {
TypeConstants.JAVA_LANG_RUNTIMEEXCEPTION,
TypeConstants.JAVA_LANG_THROWABLE,
TypeConstants.JAVA_LANG_OBJECT,
TypeConstants.JAVA_LANG,
new char[][] {TypeConstants.JAVA},
new char[][] {new char[] {'o', 'r', 'g'}},
new char[][] {new char[] {'c', 'o', 'm'}},
CharOperation.NO_CHAR_CHAR};
static final char[][] WellKnownSimpleNames = new char[][] {
TypeConstants.JAVA_LANG_RUNTIMEEXCEPTION[2],
TypeConstants.JAVA_LANG_THROWABLE[2],
TypeConstants.JAVA_LANG_OBJECT[2],
TypeConstants.JAVA,
TypeConstants.LANG,
new char[] {'o', 'r', 'g'},
new char[] {'c', 'o', 'm'}};
static final char[][][] EmptyQualifiedNames = new char[0][][];
static final char[][] EmptySimpleNames = CharOperation.NO_CHAR_CHAR;
static final int MaxQualifiedNames = 7;
static QualifiedNameSet[] InternedQualifiedNames = new QualifiedNameSet[MaxQualifiedNames];
static final int MaxSimpleNames = 30;
static NameSet[] InternedSimpleNames = new NameSet[MaxSimpleNames];
static {
for (int i = 0; i < MaxQualifiedNames; i++)
InternedQualifiedNames[i] = new QualifiedNameSet(37);
for (int i = 0; i < MaxSimpleNames; i++)
InternedSimpleNames[i] = new NameSet(37);
}
public static char[][][] internQualifiedNames(Set<String> qualifiedStrings) {
if (qualifiedStrings == null) return EmptyQualifiedNames;
int length = qualifiedStrings.size();
if (length == 0) return EmptyQualifiedNames;
char[][][] result = new char[length][][];
for (String qualifiedString : qualifiedStrings)
if (qualifiedString != null)
result[--length] = CharOperation.splitOn('/', qualifiedString.toCharArray());
return internQualifiedNames(result, false);
}
public static char[][][] internQualifiedNames(StringSet qualifiedStrings) {
if (qualifiedStrings == null) return EmptyQualifiedNames;
int length = qualifiedStrings.elementSize;
if (length == 0) return EmptyQualifiedNames;
char[][][] result = new char[length][][];
String[] strings = qualifiedStrings.values;
for (int i = 0, l = strings.length; i < l; i++)
if (strings[i] != null)
result[--length] = CharOperation.splitOn('/', strings[i].toCharArray());
return internQualifiedNames(result, false);
}
public static char[][][] internQualifiedNames(char[][][] qualifiedNames) {
return internQualifiedNames(qualifiedNames, false);
}
public static char[][][] internQualifiedNames(char[][][] qualifiedNames, boolean keepWellKnown) {
return internQualifiedNames(qualifiedNames, keepWellKnown, true);
}
static char[][][] internQualifiedNames(char[][][] qualifiedNames, boolean keepWellKnown, boolean doSort) {
if (qualifiedNames == null) return EmptyQualifiedNames;
int length = qualifiedNames.length;
if (length == 0) return EmptyQualifiedNames;
char[][][] keepers = new char[length][][];
char[][] prev = null;
boolean isSorted = true;
int index = 0;
next : for (int i = 0; i < length; i++) {
char[][] qualifiedName = qualifiedNames[i];
int qLength = qualifiedName.length;
for (int j = 0, m = WellKnownQualifiedNames.length; j < m; j++) {
char[][] wellKnownName = WellKnownQualifiedNames[j];
if (qLength > wellKnownName.length)
break;
if (CharOperation.equals(qualifiedName, wellKnownName)) {
if (keepWellKnown) {
if (doSort && isSorted) {
if (prev != null && SortedCharArrays.compareCharCharArray(prev, qualifiedName) > 0) {
isSorted = false;
}
prev = qualifiedName;
}
keepers[index++] = wellKnownName;
}
continue next;
}
}
QualifiedNameSet internedNames = InternedQualifiedNames[qLength <= MaxQualifiedNames ? qLength - 1 : 0];
qualifiedName = internSimpleNames(qualifiedName, false, false);
if (doSort && isSorted) {
if (prev != null && SortedCharArrays.compareCharCharArray(prev, qualifiedName) > 0) {
isSorted = false;
}
prev = qualifiedName;
}
keepers[index++] = internedNames.add(qualifiedName);
}
if (length > index) {
if (index == 0) return EmptyQualifiedNames;
System.arraycopy(keepers, 0, keepers = new char[index][][], 0, index);
}
if (doSort && !isSorted) {
Arrays.sort(keepers, SortedCharArrays.CHAR_CHAR_ARR_COMPARATOR);
}
return keepers;
}
public static char[][] internSimpleNames(Set<String> simpleStrings) {
return internSimpleNames(simpleStrings, true);
}
public static char[][] internSimpleNames(Set<String> simpleStrings, boolean removeWellKnown) {
if (simpleStrings == null) return EmptySimpleNames;
int length = simpleStrings.size();
if (length == 0) return EmptySimpleNames;
char[][] result = new char[length][];
for (String simpleString : simpleStrings)
if (simpleString != null)
result[--length] = simpleString.toCharArray();
return internSimpleNames(result, removeWellKnown);
}
public static char[][] internSimpleNames(StringSet simpleStrings, boolean removeWellKnown) {
if (simpleStrings == null) return EmptySimpleNames;
int length = simpleStrings.elementSize;
if (length == 0) return EmptySimpleNames;
char[][] result = new char[length][];
String[] strings = simpleStrings.values;
for (int i = 0, l = strings.length; i < l; i++)
if (strings[i] != null)
result[--length] = strings[i].toCharArray();
return internSimpleNames(result, removeWellKnown);
}
public static char[][] internSimpleNames(char[][] simpleNames, boolean removeWellKnown) {
return internSimpleNames(simpleNames, removeWellKnown, true);
}
static char[][] internSimpleNames(char[][] simpleNames, boolean removeWellKnown, boolean doSort) {
if (simpleNames == null) return EmptySimpleNames;
int length = simpleNames.length;
if (length == 0) return EmptySimpleNames;
char[][] keepers = new char[length][];
char[] prev = null;
boolean isSorted = true;
int index = 0;
next : for (int i = 0; i < length; i++) {
char[] name = simpleNames[i];
int sLength = name.length;
for (int j = 0, m = WellKnownSimpleNames.length; j < m; j++) {
char[] wellKnownName = WellKnownSimpleNames[j];
if (sLength > wellKnownName.length)
break;
if (CharOperation.equals(name, wellKnownName)) {
if (!removeWellKnown) {
keepers[index++] = wellKnownName;
if (doSort && isSorted) {
if (prev != null && SortedCharArrays.compareCharArray(prev, name) > 0) {
isSorted = false;
}
prev = name;
}
}
continue next;
}
}
NameSet internedNames = InternedSimpleNames[sLength < MaxSimpleNames ? sLength : 0];
keepers[index++] = internedNames.add(name);
if (doSort && isSorted) {
if (prev != null && SortedCharArrays.compareCharArray(prev, name) > 0) {
isSorted = false;
}
prev = name;
}
}
if (length > index) {
if (index == 0) return EmptySimpleNames;
System.arraycopy(keepers, 0, keepers = new char[index][], 0, index);
}
if (doSort && !isSorted) {
Arrays.sort(keepers, SortedCharArrays.CHAR_ARR_COMPARATOR);
}
return keepers;
}
public static boolean REFERENCE_COLLECTION_DEBUG = false;
private void assertIncludes(boolean expectation, char[] simpleName) {
if (expectation != debugIncludes(simpleName)) {
String message = "Mismatch: " + String.valueOf(simpleName) + (expectation ? " should not " : " should ") + " be included in "
+ Arrays.asList(CharOperation.toStrings(this.simpleNameReferences));
throw new IllegalStateException(message);
}
}
private void assertIncludes(boolean expectation, char[][] qualifiedName) {
if (expectation != debugIncludes(qualifiedName)) {
String message = "Mismatch: " + CharOperation.toString(qualifiedName) + (expectation ? " should not " : " should ") + " be included in "
+ qualifiedNamesToString(this.qualifiedNameReferences);
throw new IllegalStateException(message);
}
}
private void assertIncludes(boolean expectation, char[][][] qualifiedNames, char[][] simpleNames, char[][] rootNames) {
if (expectation != debugIncludes(qualifiedNames, simpleNames, rootNames)) {
String message = String.format("Mismatched includes(..): ReferenceCollection([%s], %s, %s).includes([%s], %s, %s)",
qualifiedNamesToString(this.qualifiedNameReferences),
Arrays.toString(CharOperation.toStrings(this.simpleNameReferences)),
Arrays.toString(CharOperation.toStrings(this.rootReferences)),
qualifiedNamesToString(qualifiedNames),
Arrays.toString(CharOperation.toStrings(simpleNames)),
Arrays.toString(CharOperation.toStrings(rootNames))
);
throw new IllegalStateException(message);
}
}
private boolean debugIncludes(char[][][] qualifiedNames, char[][] simpleNames, char[][] rootNames) {
if (rootNames != null) {
boolean foundRoot = false;
for (int i = 0, l = rootNames.length; !foundRoot && i < l; i++)
foundRoot = debugInsideRoot(rootNames[i]);
if (!foundRoot)
return false;
}
if (simpleNames == null || qualifiedNames == null) {
if (simpleNames == null && qualifiedNames == null) {
if (JavaBuilder.DEBUG)
System.out.println("Found well known match");
return true;
} else if (qualifiedNames == null) {
for (int i = 0, l = simpleNames.length; i < l; i++) {
if (debugIncludes(simpleNames[i])) {
if (JavaBuilder.DEBUG)
System.out.println("Found match in well known package to " + new String(simpleNames[i]));
return true;
}
}
} else {
for (int i = 0, l = qualifiedNames.length; i < l; i++) {
char[][] qualifiedName = qualifiedNames[i];
if (qualifiedName.length == 1 ? debugIncludes(qualifiedName[0]) : debugIncludes(qualifiedName)) {
if (JavaBuilder.DEBUG)
System.out.println("Found well known match in " + CharOperation.toString(qualifiedName));
return true;
}
}
}
return false;
}
int sLength = simpleNames.length;
int qLength = qualifiedNames.length;
if (sLength <= qLength) {
for (int i = 0; i < sLength; i++) {
if (debugIncludes(simpleNames[i])) {
for (int j = 0; j < qLength; j++) {
char[][] qualifiedName = qualifiedNames[j];
if (qualifiedName.length == 1 ? debugIncludes(qualifiedName[0]) : debugIncludes(qualifiedName)) {
if (JavaBuilder.DEBUG)
System.out.println("Found match in " + CharOperation.toString(qualifiedName)
+ " to " + new String(simpleNames[i]));
return true;
}
}
return false;
}
}
} else {
for (int i = 0; i < qLength; i++) {
char[][] qualifiedName = qualifiedNames[i];
if (qualifiedName.length == 1 ? debugIncludes(qualifiedName[0]) : debugIncludes(qualifiedName)) {
for (int j = 0; j < sLength; j++) {
if (debugIncludes(simpleNames[j])) {
if (JavaBuilder.DEBUG)
System.out.println("Found match in " + CharOperation.toString(qualifiedName)
+ " to " + new String(simpleNames[j]));
return true;
}
}
return false;
}
}
}
return false;
}
private boolean debugInsideRoot(char[] rootName) {
for (int i = 0, l = this.rootReferences.length; i < l; i++)
if (rootName == this.rootReferences[i]) return true;
return false;
}
private boolean debugIncludes(char[] simpleName) {
for (int i = 0, l = this.simpleNameReferences.length; i < l; i++)
if (simpleName == this.simpleNameReferences[i]) return true;
return false;
}
private boolean debugIncludes(char[][] qualifiedName) {
for (int i = 0, l = this.qualifiedNameReferences.length; i < l; i++)
if (qualifiedName == this.qualifiedNameReferences[i]) return true;
return false;
}
}