// AspectJ
/*******************************************************************************
 * Copyright (c) 2000, 2010 IBM Corporation and others.
 *
 * 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/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *     Tim Hanson <thanson@bea.com> - fix for https://bugs.eclipse.org/bugs/show_bug.cgi?id=137634
 *******************************************************************************/
package org.aspectj.org.eclipse.jdt.internal.core.builder;

import java.util.Set;

import org.aspectj.org.eclipse.jdt.core.compiler.CharOperation;
import org.aspectj.org.eclipse.jdt.internal.compiler.lookup.TypeConstants;

// AspectJ Extension increased member visibilities for AspectJ
public class ReferenceCollection {

char[][][] qualifiedNameReferences; // contains no simple names as in just 'a' which is kept in simpleNameReferences instead
char[][] simpleNameReferences;
char[][] rootReferences;

//AspectJ Extension - raised visibility
public  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) {
	// if each qualified type name is already known then all of its subNames can be skipped
	// and its expected that very few qualified names in typeNameDependencies need to be added
	// but could always take 'p1.p2.p3.X' and make all qualified names 'p1' 'p1.p2' 'p1.p2.p3' 'p1.p2.p3.X', then intern
	char[][][] qNames = new char[typeNameDependencies.length][][];
	for (int i = typeNameDependencies.length; --i >= 0;)
		qNames[i] = CharOperation.splitOn('.', typeNameDependencies[i].toCharArray());
	qNames = internQualifiedNames(qNames, false);

	next : for (int i = qNames.length; --i >= 0;) {
		char[][] qualifiedTypeName = qNames[i];
		while (!includes(qualifiedTypeName)) {
			if (!includes(qualifiedTypeName[qualifiedTypeName.length - 1])) {
				int length = this.simpleNameReferences.length;
				System.arraycopy(this.simpleNameReferences, 0, this.simpleNameReferences = new char[length + 1][], 0, length);
				this.simpleNameReferences[length] = qualifiedTypeName[qualifiedTypeName.length - 1];
			}
			if (!insideRoot(qualifiedTypeName[0])) {
				int length = this.rootReferences.length;
				System.arraycopy(this.rootReferences, 0, this.rootReferences = new char[length + 1][], 0, length);
				this.rootReferences[length] = qualifiedTypeName[0];
			}
			int length = this.qualifiedNameReferences.length;
			System.arraycopy(this.qualifiedNameReferences, 0, this.qualifiedNameReferences = new char[length + 1][][], 0, length);
			this.qualifiedNameReferences[length] = qualifiedTypeName;

			qualifiedTypeName = CharOperation.subarray(qualifiedTypeName, 0, qualifiedTypeName.length - 1);
			char[][][] temp = internQualifiedNames(new char[][][] {qualifiedTypeName}, false);
			if (temp == EmptyQualifiedNames)
				continue next; // qualifiedTypeName is a well known name
			qualifiedTypeName = temp[0];
		}
	}
}

public boolean includes(char[] simpleName) {
	for (int i = 0, l = this.simpleNameReferences.length; i < l; i++)
		if (simpleName == this.simpleNameReferences[i]) return true;
	return false;
}

public boolean includes(char[][] qualifiedName) {
	for (int i = 0, l = this.qualifiedNameReferences.length; i < l; i++)
		if (qualifiedName == this.qualifiedNameReferences[i]) return true;
	return false;
}

/**
 * @deprecated
 */
public boolean includes(char[][][] qualifiedNames, char[][] simpleNames) {
	return includes(qualifiedNames, simpleNames, null);
}

public boolean includes(char[][][] qualifiedNames, char[][] simpleNames, char[][] rootNames) {
	// if either collection of names is null, it means it contained a well known name so we know it already has a match
	if (rootNames != null) {
		boolean foundRoot = false;
		for (int i = 0, l = rootNames.length; !foundRoot && i < l; i++)
			foundRoot = insideRoot(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"); //$NON-NLS-1$
			return true;
		} else if (qualifiedNames == null) {
			for (int i = 0, l = simpleNames.length; i < l; i++) {
				if (includes(simpleNames[i])) {
					if (JavaBuilder.DEBUG)
						System.out.println("Found match in well known package to " + new String(simpleNames[i])); //$NON-NLS-1$
					return true;
				}
			}
		} else {
			for (int i = 0, l = qualifiedNames.length; i < l; i++) {
				char[][] qualifiedName = qualifiedNames[i];
				if (qualifiedName.length == 1 ? includes(qualifiedName[0]) : includes(qualifiedName)) {
					if (JavaBuilder.DEBUG)
						System.out.println("Found well known match in " + CharOperation.toString(qualifiedName)); //$NON-NLS-1$
					return true;
				}
			}
		}
		return false;
	}

	int sLength = simpleNames.length;
	int qLength = qualifiedNames.length;
	if (sLength <= qLength) {
		for (int i = 0; i < sLength; i++) {
			if (includes(simpleNames[i])) {
				for (int j = 0; j < qLength; j++) {
					char[][] qualifiedName = qualifiedNames[j];
					if (qualifiedName.length == 1 ? includes(qualifiedName[0]) : includes(qualifiedName)) {
						if (JavaBuilder.DEBUG)
							System.out.println("Found match in " + CharOperation.toString(qualifiedName) //$NON-NLS-1$
								+ " to " + new String(simpleNames[i])); //$NON-NLS-1$
						return true;
					}
				}
				return false;
			}
		}
	} else {
		for (int i = 0; i < qLength; i++) {
			char[][] qualifiedName = qualifiedNames[i];
			if (qualifiedName.length == 1 ? includes(qualifiedName[0]) : includes(qualifiedName)) {
				for (int j = 0; j < sLength; j++) {
					if (includes(simpleNames[j])) {
						if (JavaBuilder.DEBUG)
							System.out.println("Found match in " + CharOperation.toString(qualifiedName) //$NON-NLS-1$
								+ " to " + new String(simpleNames[j])); //$NON-NLS-1$
						return true;
					}
				}
				return false;
			}
		}
	}
	return false;
}

public boolean insideRoot(char[] rootName) {
	for (int i = 0, l = this.rootReferences.length; i < l; i++)
		if (rootName == this.rootReferences[i]) return true;
	return false;
}


// When any type is compiled, its methods are verified for certain problems
// the MethodVerifier requests 3 well known types which end up in the reference collection
// having WellKnownQualifiedNames & WellKnownSimpleNames, saves every type 40 bytes
// NOTE: These collections are sorted by length
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}; // default package
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;

// each array contains qualified char[][], one for size 2, 3, 4, 5, 6, 7 & the rest
static final int MaxQualifiedNames = 7;
static QualifiedNameSet[] InternedQualifiedNames = new QualifiedNameSet[MaxQualifiedNames];
// each array contains simple char[], one for size 1 to 29 & the rest
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);
}

//TODO: remove once ReferenceCollection.internQualifiedNames(StringSet) is adapted to use java.util.Set, so that git history is preserved
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);
}

//TODO: remove once PDE API Tools has been adapted to also use java.util.Set, so that git history is preserved
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) {
	if (qualifiedNames == null) return EmptyQualifiedNames;
	int length = qualifiedNames.length;
	if (length == 0) return EmptyQualifiedNames;

	char[][][] keepers = new char[length][][];
	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; // all remaining well known names are shorter
			if (CharOperation.equals(qualifiedName, wellKnownName)) {
				if (keepWellKnown) {
					keepers[index++] = wellKnownName;
				}
				continue next;
			}
		}

		// InternedQualifiedNames[0] is for the rest (> 7 & 1)
		// InternedQualifiedNames[1] is for size 2...
		// InternedQualifiedNames[6] is for size 7
		QualifiedNameSet internedNames = InternedQualifiedNames[qLength <= MaxQualifiedNames ? qLength - 1 : 0];
		qualifiedName = internSimpleNames(qualifiedName, false);
		keepers[index++] = internedNames.add(qualifiedName);
	}
	if (length > index) {
		if (index == 0) return EmptyQualifiedNames;
		System.arraycopy(keepers, 0, keepers = new char[index][][], 0, index);
	}
	return keepers;
}

/**
 * @deprecated
 */
public static char[][] internSimpleNames(Set<String> simpleStrings) {
	return internSimpleNames(simpleStrings, true);
}

// TODO: remove once ReferenceCollection.internSimpleNames(StringSet, boolean) is adapted to use java.util.Set, so that git history is preserved
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);
}

//TODO: adjust to use java.util.Set once PDE API Tools have been adapted to use the set version, so that git history is preserved
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) {
	if (simpleNames == null) return EmptySimpleNames;
	int length = simpleNames.length;
	if (length == 0) return EmptySimpleNames;

	char[][] keepers = new char[length][];
	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; // all remaining well known names are shorter
			if (CharOperation.equals(name, wellKnownName)) {
				if (!removeWellKnown)
					keepers[index++] = WellKnownSimpleNames[j];
				continue next;
			}
		}

		// InternedSimpleNames[0] is for the rest (> 29)
		// InternedSimpleNames[1] is for size 1...
		// InternedSimpleNames[29] is for size 29
		NameSet internedNames = InternedSimpleNames[sLength < MaxSimpleNames ? sLength : 0];
		keepers[index++] = internedNames.add(name);
	}
	if (length > index) {
		if (index == 0) return EmptySimpleNames;
		System.arraycopy(keepers, 0, keepers = new char[index][], 0, index);
	}
	return keepers;
}
}
