/** * */ package org.rubypeople.rdt.internal.corext.util; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.rubypeople.rdt.core.Flags; import org.rubypeople.rdt.core.IMember; import org.rubypeople.rdt.core.IMethod; import org.rubypeople.rdt.core.IRubyElement; import org.rubypeople.rdt.core.IRubyProject; import org.rubypeople.rdt.core.IRubyScript; import org.rubypeople.rdt.core.ISourceFolder; import org.rubypeople.rdt.core.ISourceFolderRoot; import org.rubypeople.rdt.core.IType; import org.rubypeople.rdt.core.ITypeHierarchy; import org.rubypeople.rdt.core.RubyModelException; import org.rubypeople.rdt.internal.core.util.CharOperation; import org.rubypeople.rdt.internal.ui.RubyPlugin; /** * @author Chris * */ public final class RubyModelUtil { public static final String DEFAULT_SCRIPT_SUFFIX = ".rb"; private static boolean PRIMARY_ONLY = false; /** * Returns the original cu if the given cu is a working copy. If the cu is * already an original the input cu is returned. The returned cu might not * exist */ public static IRubyScript toOriginal(IRubyScript cu) { if (PRIMARY_ONLY) { testRubyScriptOwner("toOriginal", cu); //$NON-NLS-1$ } // To stay compatible with old version returned null // if cu is null if (cu == null) return cu; return cu.getPrimary(); } private static void testRubyScriptOwner(String methodName, IRubyScript cu) { if (cu == null) { return; } if (!isPrimary(cu)) { RubyPlugin.logErrorMessage(methodName + ": operating with non-primary cu"); //$NON-NLS-1$ } } /** * Returns true if a cu is a primary cu (original or shared working copy) */ public static boolean isPrimary(IRubyScript cu) { return cu.getOwner() == null; } public static void reconcile(IRubyScript unit) throws RubyModelException { unit.reconcile( false /* don't force problem detection */, null /* use primary owner */, null /* no progress monitor */); } /** * Returns the original element if the given element is a working copy. If the cu is already * an original the input element is returned. The returned element might not exist */ public static IRubyElement toOriginal(IRubyElement element) { return element.getPrimaryElement(); } /** * Returns the package fragment root of <code>IRubyElement</code>. If the given * element is already a package fragment root, the element itself is returned. */ public static ISourceFolderRoot getSourceFolderRoot(IRubyElement element) { return (ISourceFolderRoot) element.getAncestor(IRubyElement.SOURCE_FOLDER_ROOT); } public static ISourceFolder getSourceFolder(IRubyElement element) { ISourceFolder srcFolder = (ISourceFolder) element.getAncestor(IRubyElement.SOURCE_FOLDER); if (srcFolder == null) { IRubyProject proj = element.getRubyProject(); ISourceFolderRoot root = proj.getSourceFolderRoot(proj.getResource()); return root.getSourceFolder(""); } return srcFolder; } public static boolean isExcludedPath(IPath resourcePath, IPath[] exclusionPatterns) { char[] path = resourcePath.toString().toCharArray(); for (int i = 0, length = exclusionPatterns.length; i < length; i++) { char[] pattern= exclusionPatterns[i].toString().toCharArray(); if (CharOperation.pathMatch(pattern, path, true, '/')) { return true; } } return false; } /** * Concatenates two names. Uses a '/' for separation. * Both strings can be empty or <code>null</code>. */ public static String concatenateName(char[] name1, char[] name2) { StringBuffer buf= new StringBuffer(); if (name1 != null && name1.length > 0) { buf.append(name1); } if (name2 != null && name2.length > 0) { if (buf.length() > 0) { buf.append("/"); } buf.append(name2); } return buf.toString(); } /** * Returns the fully qualified name of the given type using '::' as separators. * This is a replace for IType.getFullyQualifiedTypeName * which uses '$' as separators. As '$' is also a valid character in an id * this is ambiguous. */ public static String getFullyQualifiedName(IType type) { return type.getFullyQualifiedName(); } /** * Finds a type in a ruby script. Typical usage is to find the corresponding * type in a working copy. * @param script the compilation unit to search in * @param typeQualifiedName the type qualified name (type name with enclosing type names (separated by dots)) * @return the type found, or null if not existing */ public static IType findTypeInRubyScript(IRubyScript script, String typeQualifiedName) throws RubyModelException { IType[] types= script.getAllTypes(); for (int i= 0; i < types.length; i++) { String currName= getTypeQualifiedName(types[i]); if (typeQualifiedName.equals(currName)) { return types[i]; } } return null; } /** * Returns the qualified type name of the given type using '.' as separators. * This is a replace for IType.getTypeQualifiedName() * which uses '$' as separators. As '$' is also a valid character in an id * this is ambiguous. JavaCore PR: 1GCFUNT */ public static String getTypeQualifiedName(IType type) { return type.getTypeQualifiedName("::"); } /* * http://bugs.eclipse.org/bugs/show_bug.cgi?id=19253 * * Reconciling happens in a separate thread. This can cause a situation where the * Java element gets disposed after an exists test has been done. So we should not * log not present exceptions when they happen in working copies. */ public static boolean isExceptionToBeLogged(CoreException exception) { if (!(exception instanceof RubyModelException)) return true; RubyModelException je= (RubyModelException)exception; if (!je.isDoesNotExist()) return true; IRubyElement[] elements= je.getRubyModelStatus().getElements(); for (int i= 0; i < elements.length; i++) { IRubyElement element= elements[i]; // if the element is already a compilation unit don't log // does not exist exceptions. See bug // https://bugs.eclipse.org/bugs/show_bug.cgi?id=75894 // for more details if (element.getElementType() == IRubyElement.SCRIPT) continue; IRubyScript unit= (IRubyScript)element.getAncestor(IRubyElement.SCRIPT); if (unit == null) return true; if (!unit.isWorkingCopy()) return true; } return false; } public static boolean isSuperType(ITypeHierarchy hierarchy, IType possibleSuperType, IType type) { // filed bug 112635 to add this method to ITypeHierarchy IType superClass= hierarchy.getSuperclass(type); if (superClass != null && (possibleSuperType.equals(superClass) || isSuperType(hierarchy, possibleSuperType, superClass))) { return true; } if (Flags.isModule(hierarchy.getCachedFlags(possibleSuperType))) { IType[] superInterfaces= hierarchy.getSuperModules(type); for (int i= 0; i < superInterfaces.length; i++) { IType curr= superInterfaces[i]; if (possibleSuperType.equals(curr) || isSuperType(hierarchy, possibleSuperType, curr)) { return true; } } } return false; } /** * Evaluates if a member in the focus' element hierarchy is visible from * elements in a package. * @param member The member to test the visibility for * @param pack The package of the focus element focus */ public static boolean isVisibleInHierarchy(IMember member, ISourceFolder pack) throws RubyModelException { if (member.isType(IRubyElement.GLOBAL)) return true; if (!member.isType(IRubyElement.METHOD)) return false; IMethod method = (IMethod) member; IType declaringType= member.getDeclaringType(); if (method.getVisibility() == IMethod.PUBLIC || method.getVisibility() == IMethod.PROTECTED || (declaringType != null && declaringType.isModule())) { return true; } else if (method.getVisibility() == IMethod.PRIVATE) { return false; } ISourceFolder otherpack= (ISourceFolder) member.getAncestor(IRubyElement.SOURCE_FOLDER); return (pack != null && pack.equals(otherpack)); } /** * Finds a method in a type and all its super types. The super class hierarchy is searched first, then the super interfaces. * This searches for a method with the same name and signature. Parameter types are only * compared by the simple name, no resolving for the fully qualified type name is done. * Constructors are only compared by parameters, not the name. * NOTE: For finding overridden methods or for finding the declaring method, use {@link MethodOverrideTester} * @param hierarchy The hierarchy containing the type * @param type The type to start the search from * @param name The name of the method to find * @param paramTypes The type signatures of the parameters e.g. <code>{"QString;","I"}</code> * @param isConstructor If the method is a constructor * @return The first found method or <code>null</code>, if nothing found */ public static IMethod findMethodInHierarchy(ITypeHierarchy hierarchy, IType type, String name, String[] paramTypes, boolean isConstructor) throws RubyModelException { // FIXME We shouldn't be taking iin parameter types at all. All we care about in Ruby is the method name. (we might care a little bit about arity of method, but not for overriding). IMethod method= findMethod(name, paramTypes, isConstructor, type); if (method != null) { return method; } IType superClass= hierarchy.getSuperclass(type); if (superClass != null) { IMethod res= findMethodInHierarchy(hierarchy, superClass, name, paramTypes, isConstructor); if (res != null) { return res; } } if (!isConstructor) { IType[] superInterfaces= hierarchy.getSuperModules(type); for (int i= 0; i < superInterfaces.length; i++) { IMethod res= findMethodInHierarchy(hierarchy, superInterfaces[i], name, paramTypes, false); if (res != null) { return res; } } } return method; } /** * Finds a method in a type. * This searches for a method with the same name and signature. Parameter types are only * compared by the simple name, no resolving for the fully qualified type name is done. * Constructors are only compared by parameters, not the name. * @param name The name of the method to find * @param paramTypes The type signatures of the parameters e.g. <code>{"QString;","I"}</code> * @param isConstructor If the method is a constructor * @return The first found method or <code>null</code>, if nothing found */ public static IMethod findMethod(String name, String[] paramTypes, boolean isConstructor, IType type) throws RubyModelException { IMethod[] methods= type.getMethods(); for (int i= 0; i < methods.length; i++) { if (isSameMethodSignature(name, paramTypes, isConstructor, methods[i])) { return methods[i]; } } return null; } /** * Tests if a method equals to the given signature. * Parameter types are only compared by the simple name, no resolving for * the fully qualified type name is done. Constructors are only compared by * parameters, not the name. * @param name Name of the method * @param paramTypes The type signatures of the parameters e.g. <code>{"QString;","I"}</code> * @param isConstructor Specifies if the method is a constructor * @return Returns <code>true</code> if the method has the given name and parameter types and constructor state. */ public static boolean isSameMethodSignature(String name, String[] paramTypes, boolean isConstructor, IMethod curr) throws RubyModelException { // XXX Remove paramTypes arg! if (isConstructor || name.equals(curr.getElementName())) { if (isConstructor == curr.isConstructor()) { // if (paramTypes.length == curr.getNumberOfParameters()) { return true; // } } } return false; } }