blob: 5a4d7effac1e716ff6e2afca98ffe305a60805ce [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2005, 2012 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
*******************************************************************************/
package org.eclipse.jdt.debug.tests.refactoring;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.debug.internal.core.IInternalDebugCoreConstants;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IField;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IMember;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.Signature;
import org.eclipse.jdt.internal.core.SourceMethod;
import org.eclipse.jdt.internal.core.SourceType;
/**
* Contains methods to find an IMember within a given path subdivided by the '$' character.
* Syntax:
* Type$InnerType$MethodNameAndSignature$AnonymousTypeDeclarationNumber$FieldName
* eg:<code>
* public class Foo{
* class Inner
* {
* public void aMethod()
* {
* Object anon = new Object(){
* int anIntField;
* String anonTypeMethod() {return "an Example";}
* }
* }
* }
* }</code>
* Syntax to get anIntField would be: Foo$Inner$aMethod()V$1$anIntField
* Syntax to get the anonymous toString would be: Foo$Inner$aMethod()V$1$anonTypeMethod()QString
* In the case of local types, the listed syntax should be Count and then Name, like: CountName
* eg:<code>1MyType</code>
*/
public class MemberParser{
/**
* @param typeQualifiedName
* @return
*/
private static ArrayList<String> createTypeList(String typeQualifiedName) {
String newname = typeQualifiedName;
newname = newname.replace('$','.');//ensure proper format was used.
String parsed[] = newname.split("\\."); //$NON-NLS-1$
//make list of types to find
ArrayList<String> typeList = new ArrayList<>();
for (int splitNum = 0; splitNum < parsed.length; splitNum++) {
typeList.add(parsed[splitNum]);
}
return typeList;
}
/**
* @param fragments the scope of which you wish to return compilation units
* @return a handle to all compilation units contained by the given fragments
* @throws JavaModelException
*/
private static ICompilationUnit[] getAllCompilationUnits(IPackageFragment[] fragments) throws JavaModelException {
if(fragments == null)
return null;
final Set<ICompilationUnit> results = new HashSet<>();
for (int fragmentNum = 0; fragmentNum < fragments.length; fragmentNum++) {
if(fragments[fragmentNum].containsJavaResources()){
ICompilationUnit cunits[] = fragments[fragmentNum].getCompilationUnits();
for (int cunitNum = 0; cunitNum < cunits.length; cunitNum++) {
results.add(cunits[cunitNum]);
}
}
}
if(results.isEmpty())
return null;
return results.toArray(new ICompilationUnit[results.size()]);
}
/**
* @param projects the scope of which you wish to return compilation units
* @return a handle to all compilation units contained by the given projects
* @throws JavaModelException
*/
private static ICompilationUnit[] getAllCompilationUnits(IProject[] projects) throws JavaModelException{
return getAllCompilationUnits(getAllPackageFragments(projects));
}
private static ICompilationUnit[] getAllCompilationUnits(String packageName, IProject[] projects)throws JavaModelException {
return getAllCompilationUnits(getAllPackageFragments(packageName, projects));
}
/**
* @param types
* @return an array of all declared methods for the given types
* @throws JavaModelException
*/
private static IMethod[] getAllMethods(IType[] types) throws JavaModelException{
if(types==null)
return null;
final Set<IMethod> results = new HashSet<>();
for (int typeNum = 0; typeNum < types.length; typeNum++) {
IMethod[] methods = types[typeNum].getMethods();
for (int methodNum = 0; methodNum < methods.length; methodNum++) {
results.add(methods[methodNum]);
}
}
if(results.isEmpty())
return null;
return results.toArray(new SourceMethod[results.size()]);
}
/**
* @param projects the scope of the return
* @return all package fragments in the scope
* @throws JavaModelException
*/
private static IPackageFragment[] getAllPackageFragments(IProject[] projects) throws JavaModelException {
final Set<IPackageFragment> results = new HashSet<>();
for (int projectNum = 0; projectNum < projects.length; projectNum++) {
IJavaProject javaProj = JavaCore.create(projects[projectNum]);
if(javaProj!= null && javaProj.exists() && javaProj.hasChildren()){
IPackageFragment fragments[] = javaProj.getPackageFragments();
for (int fragmentNum = 0; fragmentNum < fragments.length; fragmentNum++) {
results.add(fragments[fragmentNum]);
}
}
}
if(results.isEmpty())
return null;
return results.toArray(new IPackageFragment[results.size()]);
}
/**
* @return all projects in the workspace
*/
private static IProject[] getAllProjects(){
return ResourcesPlugin.getWorkspace().getRoot().getProjects();
}
/**
* @param cunits the scope of the search
* @return all types within the scope
* @throws JavaModelException
*/
private static IType[] getAllTypes(ICompilationUnit[] cunits) throws JavaModelException {
if(cunits == null)
return null;
final Set<IType> results = new HashSet<>();
for (int cunitNum = 0; cunitNum < cunits.length; cunitNum++) {
IType types[] = cunits[cunitNum].getTypes(); //get all topLevel types
for (int typeNum = 0; typeNum < types.length; typeNum++) {
results.add(types[typeNum]);
}
}
if(results.isEmpty())
return null;
return results.toArray(new IType[results.size()]);
}
/**
* @param methods the scope of the search
* @return an array of all types declared within the given methods.
* @throws JavaModelException
*/
private static IType[] getAllTypes(IMethod[] methods) throws JavaModelException {
if(methods==null)
return null;
final Set<IJavaElement> results = new HashSet<>();
for (int methodNum = 0; methodNum < methods.length; methodNum++) {
IJavaElement[] children = methods[methodNum].getChildren();
for (int childNum = 0; childNum < children.length; childNum++) {
if(children[childNum] instanceof IType)
results.add(children[childNum]);
}
}
if(results.isEmpty())
return null;
return results.toArray(new SourceType[results.size()]);
}
/**Will search within the given type and all of it's children - including methods
* and anonymous types for other types.
* @param types the scope of the search
* @return all types within the given scope
* @throws JavaModelException
*/
public static IType[] getAllTypes(IType[] types) throws JavaModelException{
if(types == null)
return null;
IType[] newtypes = types;
final Set<IType> results = new HashSet<>();
//get all the obvious type declarations
for (int mainTypeNum = 0; mainTypeNum < newtypes.length; mainTypeNum++) {
IType declaredTypes[] = newtypes[mainTypeNum].getTypes();
for (int declaredTypeNum = 0; declaredTypeNum < declaredTypes.length; declaredTypeNum++) {
results.add(declaredTypes[declaredTypeNum]);
}
//get all the type's method's type declarations
newtypes = getAllTypes(getAllMethods(newtypes));
for (int methodTypes = 0; methodTypes < newtypes.length; methodTypes++) {
results.add(newtypes[methodTypes]);
}
}
if(results.isEmpty())
return null;
//else
return results.toArray(new SourceType[results.size()]);//possibly change to new IType
}
/**
* Returns the Java project with the given name.
*
* @param name project name
* @return the Java project with the given name
*/
static protected IJavaProject getJavaProject() {
IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject();
return JavaCore.create(project);
}
/**
* @param packageName name of the package
* @param projects where to search
* @return the 1st instance of the given packageName
* @throws JavaModelException
*/
private static IPackageFragment[] getAllPackageFragments(String packageName, IProject[] projects) throws JavaModelException{
final Set<IPackageFragment> results = new HashSet<>();
for (int projectNum = 0; projectNum < projects.length; projectNum++) {
IJavaProject javaProj = JavaCore.create(projects[projectNum]);
if(javaProj!= null && javaProj.exists() && javaProj.hasChildren()){
IPackageFragment fragments[] = javaProj.getPackageFragments();
for (int fragmentNum = 0; fragmentNum < fragments.length; fragmentNum++) {
if(fragments[fragmentNum].getElementName().equalsIgnoreCase(packageName))
results.add(fragments[fragmentNum]);
}
}
}
if(results.isEmpty())
return null;
//else
return results.toArray(new IPackageFragment[results.size()]);
}
private static IProject getProject(String projectName){
return ResourcesPlugin.getWorkspace().getRoot().getProject(projectName);
}
private static IType getType(ArrayList<String> typeList, ICompilationUnit[] cunits) throws JavaModelException{
IType[] types = getAllTypes(cunits);
//if 1st letter is a number, it's anonymous
boolean targetIsAnonymous = Character.isDigit(typeList.get(0).toString().charAt(0));
boolean targetFound=false;
char separator = '.';
while (true) {//search all types for desired target - will return internally.
//check all the types we have
int typeNum=0;
while(typeNum < types.length) {//search current list of types
if(targetIsAnonymous){//must ensure format is same for both.
String nameOfCurrentType = types[typeNum].getTypeQualifiedName(separator);
nameOfCurrentType = nameOfCurrentType.substring(nameOfCurrentType.lastIndexOf(separator)+1);
targetFound = nameOfCurrentType.equalsIgnoreCase(typeList.get(0).toString());
}else{
targetFound = types[typeNum].getElementName().equalsIgnoreCase(typeList.get(0).toString());
}
if(targetFound){//yay!
typeList.remove(0);
if(typeList.isEmpty()){
return types[typeNum];//we're at our destination
}
//else, get all this type's subtypes
types = getAllTypes(new IType[]{types[typeNum]});//get next level
// check format of this new type
targetIsAnonymous = Character.isDigit(typeList.get(0).toString().charAt(0));
typeNum = 0;//start again.
}
else
typeNum++;//check the next type
}
//else, it is not in the top-level types - check in methods
types = getAllTypes(getAllMethods(types));
if(types==null)
return null;//couldn't find it.
}//end while
}
/**
* Will search the workspace and return the requested type. The more information given,
* the faster the search
* @param typeName the name of the type, with or without qualifiers - it cannot be null
* e.g. "aType.innerType.1.typeInAnonymousType" or even just "typeInAnonymousType"
* or "innerType.1.typeInAnonymousType".
* @param packageName the elemental name of the package containing the given type - may be null
* @param projectName the elemental name of the project containing the given type - may be null
* @return the IType handle to the requested type
* @throws JavaModelException
*/
public static IType getType(String typeName, String packageName, String projectName) throws JavaModelException{
if(typeName == null)
return null;
//make list of types to find, in order
ArrayList<String> typeList = createTypeList(typeName);
//get the proper project(s)
IProject[] projects=null;
if(projectName!=null && projectName.length()>0){
projects = new IProject[] {getProject(projectName)};
}
else{
projects = getAllProjects();
}
//get the Comp.units for those projects
ICompilationUnit cunits[] = null;
if(packageName!=null && packageName.length()>0){
cunits = getAllCompilationUnits(packageName, projects);
}
else{
cunits = getAllCompilationUnits(projects);
}
return getType(typeList, cunits);
}
/**
* @param cu the CompilationUnit containing the toplevel Type
* @param target - the IMember target, listed in full Syntax, as noted in MemberParser
* eg: EnclosingType$InnerType
* @return the Lowest level inner type specified in input
*/
public IMember getDeepest(ICompilationUnit cu, String target)
{
for(int i=0;i<target.length();i++)
{
if(target.charAt(i)=='$')
{//EnclosingType$InnerType$MoreInner
String tail = target.substring(i+1);
IType enclosure = cu.getType(target.substring(0, i));
if(enclosure.exists())
return getDeepest(enclosure,tail);
}
}
//has no inner type
return cu.getType(target);
}
/**
* Helper method for getLowestType (ICompilationUnit cu, String input)
* @param top name of enclosing Type
* @param tail the typename, possibly including inner type,
* separated by $.
* eg: EnclosingType$InnerType
* @return the designated type, or null if type not found.
*/
protected IMember getDeepest(IMember top, String tail) {
String newtail = tail;
if(newtail==null || newtail.length()==0 )
return top;
if(!top.exists())
return null;
//check if there are more nested elements
String head=null;
for(int i=0;i<newtail.length();i++)
{
if(newtail.charAt(i)=='$')//nested Item?
{//Enclosing$Inner$MoreInner
head = newtail.substring(0,i);
newtail = newtail.substring(i+1);
break;//found next item
}
}
if(head==null)//we are at last item to parse
{//swap Members
head = newtail;
newtail = null;
}
if(top instanceof IType)
return getNextFromType(top, head, newtail);
else
if(top instanceof IMethod)
return getNextFromMethod(top, head, newtail);
else
if(top instanceof IField)
return getNextFromField(top, head, newtail);
//else there is a problem!
return getDeepest(top,newtail);
}
/**
* @param head the string to parse for a name
* @return the name in the type, given in the format "Occurance#Type"
* e.g. head = "1Type";
*/
protected String getLocalTypeName(String head) {
for(int i=0;i<head.length();i++)
{
if(!Character.isDigit(head.charAt(i)))
{
return head.substring(i);
}
}
return IInternalDebugCoreConstants.EMPTY_STRING;//entire thing is a number
}
/**
* @param head the string to parse for an occurrence
* @return the name in the type, given in the format "Occurance#Type"
* e.g. head = "1Type";
*/
protected int getLocalTypeOccurrence(String head) {
for(int i=0;i<head.length();i++)
{
if(!Character.isDigit(head.charAt(i)))
return Integer.parseInt(head.substring(0, i));
}
return Integer.parseInt(head);//entire thing is a number
}
/**
* @param head name of method w/ signature at the end
* @return simply the name of the given method, using format:
* methodNameSignature.
* e.g. head = "someMethod()V"
*/
protected String getName(String head) {
for(int i=0;i<head.length();i++)
{
if(head.charAt(i)=='(')//nested Item?
return head.substring(0,i);
}
return null;
}
/**
* @param top the field in which to search
* @param head the next member to find
* @param tail the remaining members to find
* @return the next member down contained by the given Field
*/
protected IMember getNextFromField(IMember top, String head, String tail) {
IField current = (IField)top;
IType type = current.getType(getLocalTypeName(head),getLocalTypeOccurrence(head));
if(type.exists())
return getDeepest(type,tail);
//else
return null;//something failed.
}
/**
* @param top the member in which to search
* @param head the next member to find
* @param tail the remaining members to find
* @return the next member down contained by the given Method
*/
protected IMember getNextFromMethod(IMember top, String head, String tail) {
//must be a local or anonymous type
IMethod current = (IMethod)top;
//is next part a Type?
IType type = current.getType(getLocalTypeName(head), getLocalTypeOccurrence(head));
if(type.exists())
return getDeepest(type,tail);
//else
return null;
}
/**
* @param top the member in which to search
* @param head the next member to find
* @param tail the remaining members to find
* @return the next member down contained by the given Type
*/
protected IMember getNextFromType(IMember top, String head, String tail) {
IType current = (IType)top;
//is next part a Type?
IMember next = current.getType(head);
if(next.exists())
return getDeepest(next,tail);
//else, is next part a Field?
next = current.getField(head);
if(next.exists())
return getDeepest(next,tail);
//else, is next part a Method?
next = current.getMethod(getName(head),getSignature(head));
if(next.exists())
return getDeepest(next,tail);
//else
return null;//something failed.
}
/**
* @param head name of method w/ signature at the end
* @return simply the ParameterTypeSignature, using format:
* methodNameSignature.
* e.g. head = "someMethod()V"
*/
protected String[] getSignature(String head) {
for(int i=0;i<head.length();i++)
{
if(head.charAt(i)=='(')//nested Item?
return Signature.getParameterTypes(head.substring(i));
}
return null;
}
}