/******************************************************************************* * Copyright (c) 2000, 2005 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are 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: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.rubypeople.rdt.ui; import java.io.File; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.IAdaptable; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.ui.model.IWorkbenchAdapter; import org.rubypeople.rdt.core.IField; import org.rubypeople.rdt.core.IMethod; import org.rubypeople.rdt.core.IRubyElement; 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.RubyModelException; import org.rubypeople.rdt.internal.corext.util.Messages; import org.rubypeople.rdt.internal.ui.RubyPlugin; import org.rubypeople.rdt.internal.ui.RubyUIMessages; /** * <code>RubyElementLabels</code> provides helper methods to render names of * Ruby elements. * * @since 0.8.0 */ public class RubyElementLabels { /** * Method names contain parameter names. e.g. <code>foo(index)</code> */ public final static long M_PARAMETER_NAMES = 1L << 1; /** * Method names are fully qualified. e.g. <code>java.util.Vector.size</code> */ public final static long M_FULLY_QUALIFIED = 1L << 7; /** * Method names are post qualified. e.g. * <code>size - java.util.Vector</code> */ public final static long M_POST_QUALIFIED = 1L << 8; /** * Initializer names are fully qualified. e.g. * <code>java.util.Vector.{ ... }</code> */ public final static long I_FULLY_QUALIFIED = 1L << 10; /** * Type names are post qualified. e.g. <code>{ ... } - java.util.Map</code> */ public final static long I_POST_QUALIFIED = 1L << 11; /** * Fields names are fully qualified. e.g. <code>java.lang.System.out</code> */ public final static long F_FULLY_QUALIFIED = 1L << 16; /** * Fields names are post qualified. e.g. <code>out - java.lang.System</code> */ public final static long F_POST_QUALIFIED = 1L << 17; /** * Type names are filename qualified. e.g. <code>src/folder/ClassName</code> */ public final static long T_FILENAME_QUALIFIED = 1L << 18; /** * Type names are fully qualified. e.g. <code>ActiveRecord::Base</code> */ public final static long T_NAME_FULLY_QUALIFIED = 1L << 19; /** * Type names are post qualified. e.g. <code>Base - ActiveRecord</code> */ public final static long T_POST_QUALIFIED = 1L << 20; /** * Declarations (import container / declaration, package declaration) are * qualified. e.g. <code>java.util.Vector.class/import container</code> */ public final static long D_QUALIFIED = 1L << 24; /** * Declarations (import container / declaration, package declaration) are * post qualified. e.g. * <code>import container - java.util.Vector.class</code> */ public final static long D_POST_QUALIFIED = 1L << 25; /** * Class file names are fully qualified. e.g. * <code>java.util.Vector.class</code> */ public final static long CF_QUALIFIED = 1L << 27; /** * Class file names are post qualified. e.g. * <code>Vector.class - java.util</code> */ public final static long CF_POST_QUALIFIED = 1L << 28; /** * Compilation unit names are fully qualified. e.g. * <code>java.util.Vector.java</code> */ public final static long CU_QUALIFIED = 1L << 31; /** * Compilation unit names are post qualified. e.g. * <code>Vector.java - java.util</code> */ public final static long CU_POST_QUALIFIED = 1L << 32; /** * Package names are qualified. e.g. <code>MyProject/src/java.util</code> */ public final static long P_QUALIFIED = 1L << 35; /** * Package names are post qualified. e.g. * <code>java.util - MyProject/src</code> */ public final static long P_POST_QUALIFIED = 1L << 36; /** * Package names are compressed. e.g. <code>o*.e*.search</code> */ public final static long P_COMPRESSED = 1L << 37; /** * Package Fragment Roots contain variable name if from a variable. e.g. * <code>JRE_LIB - c:\java\lib\rt.jar</code> */ public final static long ROOT_VARIABLE = 1L << 40; /** * Package Fragment Roots contain the project name if not an archive * (prepended). e.g. <code>MyProject/src</code> */ public final static long ROOT_QUALIFIED = 1L << 41; /** * Package Fragment Roots contain the project name if not an archive * (appended). e.g. <code>src - MyProject</code> */ public final static long ROOT_POST_QUALIFIED = 1L << 42; /** * Add root path to all elements except Package Fragment Roots and Ruby * projects. e.g. <code>java.lang.Vector - c:\java\lib\rt.jar</code> * Option only applies to getElementLabel */ public final static long APPEND_ROOT_PATH = 1L << 43; /** * Add root path to all elements except Package Fragment Roots and Ruby * projects. e.g. <code>java.lang.Vector - c:\java\lib\rt.jar</code> * Option only applies to getElementLabel */ public final static long PREPEND_ROOT_PATH = 1L << 44; /** * Post qualify referenced package fragment roots. For example * <code>jdt.jar - org.eclipse.jdt.ui</code> if the jar is referenced from * another project. */ public final static long REFERENCED_ROOT_POST_QUALIFIED = 1L << 45; /** * Specified to use the resolved information of a IType, IMethod or IField. * See {@link IType#isResolved()}. If resolved information is available, * types will be rendered with type parameters of the instantiated type. * Resolved method render with the parameter types of the method instance. * <code>Vector<String>.get(String)</code> */ public final static long USE_RESOLVED = 1L << 48; /** * Qualify all elements */ public final static long ALL_FULLY_QUALIFIED = new Long(F_FULLY_QUALIFIED | M_FULLY_QUALIFIED | I_FULLY_QUALIFIED | T_FILENAME_QUALIFIED | D_QUALIFIED | CF_QUALIFIED | CU_QUALIFIED | P_QUALIFIED | ROOT_QUALIFIED).longValue(); /** * Post qualify all elements */ public final static long ALL_POST_QUALIFIED = new Long(F_POST_QUALIFIED | M_POST_QUALIFIED | I_POST_QUALIFIED | T_POST_QUALIFIED | D_POST_QUALIFIED | CF_POST_QUALIFIED | CU_POST_QUALIFIED | P_POST_QUALIFIED | ROOT_POST_QUALIFIED).longValue(); /** * Default options (M_PARAMETER_NAMES enabled) */ public final static long ALL_DEFAULT = new Long(M_PARAMETER_NAMES).longValue(); /** * Default qualify options (All except Root and Package) */ public final static long DEFAULT_QUALIFIED = new Long(F_FULLY_QUALIFIED | M_FULLY_QUALIFIED | I_FULLY_QUALIFIED | T_FILENAME_QUALIFIED | D_QUALIFIED | CF_QUALIFIED | CU_QUALIFIED) .longValue(); /** * Default post qualify options (All except Root and Package) */ public final static long DEFAULT_POST_QUALIFIED = new Long(F_POST_QUALIFIED | M_POST_QUALIFIED | I_POST_QUALIFIED | T_POST_QUALIFIED | D_POST_QUALIFIED | CF_POST_QUALIFIED | CU_POST_QUALIFIED).longValue(); /** * Prepend first category (if any) to field. * @since 3.2 */ public final static long F_CATEGORY= 1L << 49; /** * Prepend first category (if any) to method. * @since 3.2 */ public final static long M_CATEGORY= 1L << 50; /** * Prepend first category (if any) to type. * @since 3.2 */ public final static long T_CATEGORY= 1L << 51; /** * Show category for all elements. * @since 3.2 */ public final static long ALL_CATEGORY= new Long(RubyElementLabels.F_CATEGORY | RubyElementLabels.M_CATEGORY | RubyElementLabels.T_CATEGORY).longValue(); /** * User-readable string for separating post qualified names (e.g. " - "). */ public final static String CONCAT_STRING = RubyUIMessages.RubyElementLabels_concat_string; /** * User-readable string for separating list items (e.g. ", "). */ public final static String COMMA_STRING = RubyUIMessages.RubyElementLabels_comma_string; /** * User-readable string for separating the return type (e.g. " : "). */ public final static String DECL_STRING = RubyUIMessages.RubyElementLabels_declseparator_string; /** * User-readable string for ellipsis ("..."). */ public final static String ELLIPSIS_STRING = "..."; //$NON-NLS-1$ private final static long QUALIFIER_FLAGS = P_COMPRESSED | USE_RESOLVED; /** * User-readable string for the default package name (e.g. "(root source folder)"). */ public final static String DEFAULT_PACKAGE= RubyUIMessages.RubyElementLabels_default_package; /* * Package name compression */ private static String fgPkgNamePattern= ""; //$NON-NLS-1$ private static String fgPkgNamePrefix; private static String fgPkgNamePostfix; private static int fgPkgNameChars; private static int fgPkgNameLength= -1; private RubyElementLabels() { } private static final boolean getFlag(long flags, long flag) { return (flags & flag) != 0; } /** * Returns the label of the given object. The object must be of type * {@link IRubyElement} or adapt to {@link IWorkbenchAdapter}. The empty * string is returned if the element type is not known. * * @param obj * Object to get the label from. * @param flags * The rendering flags * @return Returns the label or the empty string if the object type is not * supported. */ public static String getTextLabel(Object obj, long flags) { if (obj instanceof IRubyElement) { return getElementLabel((IRubyElement) obj, flags); } else if (obj instanceof IAdaptable) { IWorkbenchAdapter wbadapter = (IWorkbenchAdapter) ((IAdaptable) obj) .getAdapter(IWorkbenchAdapter.class); if (wbadapter != null) { return wbadapter.getLabel(obj); } } return ""; //$NON-NLS-1$ } /** * Returns the label for a Ruby element with the flags as defined by this * class. * * @param element * The element to render. * @param flags * The rendering flags. * @return the label of the Ruby element */ public static String getElementLabel(IRubyElement element, long flags) { StringBuffer buf = new StringBuffer(60); getElementLabel(element, flags, buf); return buf.toString(); } /** * Returns the label for a Ruby element with the flags as defined by this * class. * * @param element * The element to render. * @param flags * The rendering flags. * @param buf * The buffer to append the resulting label to. */ public static void getElementLabel(IRubyElement element, long flags, StringBuffer buf) { int type = element.getElementType(); switch (type) { case IRubyElement.METHOD: getMethodLabel((IMethod) element, flags, buf); break; case IRubyElement.FIELD: getFieldLabel((IField) element, flags, buf); break; case IRubyElement.LOCAL_VARIABLE: getLocalVariableLabel((IField) element, flags, buf); break; case IRubyElement.TYPE: getTypeLabel((IType) element, flags, buf); break; case IRubyElement.SCRIPT: getRubyScriptLabel((IRubyScript) element, flags, buf); break; case IRubyElement.IMPORT_CONTAINER: case IRubyElement.IMPORT_DECLARATION: getDeclarationLabel(element, flags, buf); break; case IRubyElement.SOURCE_FOLDER: getSourceFolderLabel((ISourceFolder) element, flags, buf); break; case IRubyElement.SOURCE_FOLDER_ROOT: getSourceFolderRootLabel((ISourceFolderRoot) element, flags, buf); break; case IRubyElement.RUBY_PROJECT: case IRubyElement.RUBY_MODEL: default: buf.append(element.getElementName()); } } /** * Appends the label for a method to a {@link StringBuffer}. Considers the * M_* flags. * * @param method * The element to render. * @param flags * The rendering flags. Flags with names starting with 'M_' are * considered. * @param buf * The buffer to append the resulting label to. */ public static void getMethodLabel(IMethod method, long flags, StringBuffer buf) { try { // qualification if (getFlag(flags, M_FULLY_QUALIFIED)) { if (method.getDeclaringType() != null) { getTypeLabel(method.getDeclaringType(), T_NAME_FULLY_QUALIFIED | (flags & QUALIFIER_FLAGS), buf); buf.append('.'); } } buf.append(method.getElementName()); // parameters buf.append('('); if (getFlag(flags, M_PARAMETER_NAMES)) { String[] types = null; int nParams = 0; boolean renderVarargs = false; String[] names = null; if (getFlag(flags, M_PARAMETER_NAMES) && method.exists()) { names = method.getParameterNames(); if (types == null) { nParams = names.length; } else { if (nParams != names.length) { // see // https://bugs.eclipse.org/bugs/show_bug.cgi?id=99137 // and // https://bugs.eclipse.org/bugs/show_bug.cgi?id=101029 // RubyPlugin.logErrorMessage("RubyElementLabels: // Number of param types(" + nParams + ") != number // of names(" + names.length + "): " + // method.getElementName()); // //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$ names = null; // no names rendered } } } for (int i = 0; i < nParams; i++) { if (i > 0) { buf.append(COMMA_STRING); } if (names != null) { buf.append(names[i]); } } } buf.append(')'); // post qualification if (getFlag(flags, M_POST_QUALIFIED)) { if (method.getDeclaringType() != null) { buf.append(CONCAT_STRING); getTypeLabel(method.getDeclaringType(), T_FILENAME_QUALIFIED | (flags & QUALIFIER_FLAGS), buf); } } } catch (RubyModelException e) { RubyPlugin.log(e); // NotExistsException will not reach this point } } /** * Appends the label for a field to a {@link StringBuffer}. Considers the * F_* flags. * * @param field * The element to render. * @param flags * The rendering flags. Flags with names starting with 'F_' are * considered. * @param buf * The buffer to append the resulting label to. */ public static void getFieldLabel(IField field, long flags, StringBuffer buf) { // qualification if (getFlag(flags, F_FULLY_QUALIFIED)) { getTypeLabel(field.getDeclaringType(), T_FILENAME_QUALIFIED | (flags & QUALIFIER_FLAGS), buf); buf.append('.'); } buf.append(field.getElementName()); // post qualification if (getFlag(flags, F_POST_QUALIFIED)) { buf.append(CONCAT_STRING); getTypeLabel(field.getDeclaringType(), T_FILENAME_QUALIFIED | (flags & QUALIFIER_FLAGS), buf); } } /** * Appends the label for a local variable to a {@link StringBuffer}. * * @param localVariable * The element to render. * @param flags * The rendering flags. Flags with names starting with 'F_' are * considered. * @param buf * The buffer to append the resulting label to. */ public static void getLocalVariableLabel(IField localVariable, long flags, StringBuffer buf) { if (getFlag(flags, F_FULLY_QUALIFIED)) { getElementLabel(localVariable.getParent(), M_FULLY_QUALIFIED | T_FILENAME_QUALIFIED | (flags & QUALIFIER_FLAGS), buf); buf.append('.'); } buf.append(localVariable.getElementName()); // post qualification if (getFlag(flags, F_POST_QUALIFIED)) { buf.append(CONCAT_STRING); getElementLabel(localVariable.getParent(), M_FULLY_QUALIFIED | T_FILENAME_QUALIFIED | (flags & QUALIFIER_FLAGS), buf); } } /** * Appends the label for a type to a {@link StringBuffer}. Considers the * T_* flags. * * @param type * The element to render. * @param flags * The rendering flags. Flags with names starting with 'T_' are * considered. * @param buf * The buffer to append the resulting label to. */ public static void getTypeLabel(IType type, long flags, StringBuffer buf) { if (getFlag(flags, T_FILENAME_QUALIFIED)) { ISourceFolder folder = type.getSourceFolder(); if (!folder.isDefaultPackage()) { getSourceFolderLabel(folder, (flags & QUALIFIER_FLAGS), buf); buf.append('/'); } getRubyScriptLabel(type.getRubyScript(), (flags & QUALIFIER_FLAGS), buf); buf.append(':'); } if (getFlag(flags, T_FILENAME_QUALIFIED | T_NAME_FULLY_QUALIFIED)) { IType declaringType = type.getDeclaringType(); if (declaringType != null) { getTypeLabel(declaringType, T_NAME_FULLY_QUALIFIED | (flags & QUALIFIER_FLAGS), buf); buf.append("::"); } int parentType = type.getParent().getElementType(); if (parentType == IRubyElement.METHOD || parentType == IRubyElement.FIELD) { // anonymous // or local getElementLabel(type.getParent(), 0, buf); buf.append('.'); } } String typeName = type.getElementName(); if (typeName.length() == 0) { // anonymous try { String supertypeName = type.getSuperclassName(); // String[] superInterfaceNames = type.getSuperInterfaceNames(); // if (superInterfaceNames.length > 0) { // supertypeName = Signature.getSimpleName(superInterfaceNames[0]); // } else { // supertypeName = Signature.getSimpleName(type.getSuperclassName()); // } typeName = Messages.format(RubyUIMessages.RubyElementLabels_anonym_type, supertypeName); } catch (RubyModelException e) { // ignore typeName = RubyUIMessages.RubyElementLabels_anonym; } } buf.append(typeName); // post qualification if (getFlag(flags, T_POST_QUALIFIED)) { buf.append(CONCAT_STRING); IType declaringType = type.getDeclaringType(); if (declaringType != null) { getTypeLabel(declaringType, T_NAME_FULLY_QUALIFIED | (flags & QUALIFIER_FLAGS), buf); int parentType = type.getParent().getElementType(); if (parentType == IRubyElement.METHOD || parentType == IRubyElement.FIELD) { // anonymous // or // local buf.append('.'); getElementLabel(type.getParent(), 0, buf); } buf.append(CONCAT_STRING); } ISourceFolder folder = type.getSourceFolder(); if (!folder.isDefaultPackage()) { getSourceFolderLabel(folder, flags & QUALIFIER_FLAGS, buf); buf.append('/'); } getRubyScriptLabel(type.getRubyScript(), (flags & QUALIFIER_FLAGS), buf); try { int offset = type.getNameRange().getOffset(); buf.append(", offset: "); buf.append(offset); } catch (RubyModelException e) { RubyPlugin.log(e); } } } /** * Appends the label for a import container, import or package declaration * to a {@link StringBuffer}. Considers the D_* flags. * * @param declaration * The element to render. * @param flags * The rendering flags. Flags with names starting with 'D_' are * considered. * @param buf * The buffer to append the resulting label to. */ public static void getDeclarationLabel(IRubyElement declaration, long flags, StringBuffer buf) { if (getFlag(flags, D_QUALIFIED)) { IRubyElement openable = (IRubyElement) declaration.getOpenable(); if (openable != null) { buf.append(getElementLabel(openable, CF_QUALIFIED | CU_QUALIFIED | (flags & QUALIFIER_FLAGS))); buf.append('/'); } } if (declaration.getElementType() == IRubyElement.IMPORT_CONTAINER) { buf.append(RubyUIMessages.RubyElementLabels_import_container); } else { buf.append(declaration.getElementName()); } // post qualification if (getFlag(flags, D_POST_QUALIFIED)) { IRubyElement openable = (IRubyElement) declaration.getOpenable(); if (openable != null) { buf.append(CONCAT_STRING); buf.append(getElementLabel(openable, CF_QUALIFIED | CU_QUALIFIED | (flags & QUALIFIER_FLAGS))); } } } /** * Appends the label for a ruby script to a {@link StringBuffer}. * Considers the CU_* flags. * * @param script * The element to render. * @param flags * The rendering flags. Flags with names starting with 'CU_' are * considered. * @param buf * The buffer to append the resulting label to. */ public static void getRubyScriptLabel(IRubyScript script, long flags, StringBuffer buf) { if (getFlag(flags, CU_QUALIFIED)) { ISourceFolder pack = (ISourceFolder) script.getParent(); if (!pack.isDefaultPackage()) { getSourceFolderLabel(pack, (flags & QUALIFIER_FLAGS), buf); buf.append('.'); } } buf.append(script.getElementName()); if (getFlag(flags, CU_POST_QUALIFIED)) { buf.append(CONCAT_STRING); getSourceFolderLabel((ISourceFolder) script.getParent(), flags & QUALIFIER_FLAGS, buf); } } /** * Appends the label for a package fragment to a {@link StringBuffer}. Considers the P_* flags. * @param pack The element to render. * @param flags The rendering flags. Flags with names starting with P_' are considered. * @param buf The buffer to append the resulting label to. */ public static void getSourceFolderLabel(ISourceFolder pack, long flags, StringBuffer buf) { if (getFlag(flags, P_QUALIFIED)) { getSourceFolderRootLabel((ISourceFolderRoot) pack.getParent(), ROOT_QUALIFIED, buf); buf.append('/'); } refreshPackageNamePattern(); if (pack.isDefaultPackage()) { buf.append(DEFAULT_PACKAGE); } else if (getFlag(flags, P_COMPRESSED) && fgPkgNameLength >= 0) { String name= pack.getElementName(); int start= 0; int dot= name.indexOf('/', start); while (dot > 0) { if (dot - start > fgPkgNameLength-1) { buf.append(fgPkgNamePrefix); if (fgPkgNameChars > 0) buf.append(name.substring(start, Math.min(start+ fgPkgNameChars, dot))); buf.append(fgPkgNamePostfix); } else buf.append(name.substring(start, dot + 1)); start= dot + 1; dot= name.indexOf('/', start); } buf.append(name.substring(start)); } else { String name = pack.getElementName(); buf.append(name.replace(File.separatorChar, '/')); } if (getFlag(flags, P_POST_QUALIFIED)) { buf.append(CONCAT_STRING); getSourceFolderRootLabel((ISourceFolderRoot) pack.getParent(), ROOT_QUALIFIED, buf); } } private static void refreshPackageNamePattern() { String pattern= getPkgNamePatternForPackagesView(); final String EMPTY_STRING= ""; //$NON-NLS-1$ if (pattern.equals(fgPkgNamePattern)) return; else if (pattern.length() == 0) { fgPkgNamePattern= EMPTY_STRING; fgPkgNameLength= -1; return; } fgPkgNamePattern= pattern; int i= 0; fgPkgNameChars= 0; fgPkgNamePrefix= EMPTY_STRING; fgPkgNamePostfix= EMPTY_STRING; while (i < pattern.length()) { char ch= pattern.charAt(i); if (Character.isDigit(ch)) { fgPkgNameChars= ch-48; if (i > 0) fgPkgNamePrefix= pattern.substring(0, i); if (i >= 0) fgPkgNamePostfix= pattern.substring(i+1); fgPkgNameLength= fgPkgNamePrefix.length() + fgPkgNameChars + fgPkgNamePostfix.length(); return; } i++; } fgPkgNamePrefix= pattern; fgPkgNameLength= pattern.length(); } private static String getPkgNamePatternForPackagesView() { IPreferenceStore store= PreferenceConstants.getPreferenceStore(); if (!store.getBoolean(PreferenceConstants.APPEARANCE_COMPRESS_PACKAGE_NAMES)) return ""; //$NON-NLS-1$ return store.getString(PreferenceConstants.APPEARANCE_PKG_NAME_PATTERN_FOR_PKG_VIEW); } /** * Appends the label for a package fragment root to a {@link StringBuffer}. Considers the ROOT_* flags. * @param root The element to render. * @param flags The rendering flags. Flags with names starting with ROOT_' are considered. * @param buf The buffer to append the resulting label to. */ public static void getSourceFolderRootLabel(ISourceFolderRoot root, long flags, StringBuffer buf) { // TODO Uncomment to handle archives // if (root.isArchive()) // getArchiveLabel(root, flags, buf); // else getFolderLabel(root, flags, buf); } private static void getFolderLabel(ISourceFolderRoot root, long flags, StringBuffer buf) { IResource resource= root.getResource(); boolean rootQualified= getFlag(flags, ROOT_QUALIFIED); boolean referencedQualified= getFlag(flags, REFERENCED_ROOT_POST_QUALIFIED) && isReferenced(root); if (rootQualified) { buf.append(root.getPath().makeRelative().toString()); } else { if (resource != null) buf.append(resource.getProjectRelativePath().toString()); else buf.append(root.getElementName()); if (referencedQualified) { buf.append(CONCAT_STRING); buf.append(resource.getProject().getName()); } else if (getFlag(flags, ROOT_POST_QUALIFIED)) { buf.append(CONCAT_STRING); buf.append(root.getParent().getElementName()); } } } /** * Returns <code>true</code> if the given package fragment root is * referenced. This means it is own by a different project but is referenced * by the root's parent. Returns <code>false</code> if the given root * doesn't have an underlying resource. * * @since 3.2 */ private static boolean isReferenced(ISourceFolderRoot root) { IResource resource= root.getResource(); if (resource != null) { IProject jarProject= resource.getProject(); IProject container= root.getRubyProject().getProject(); return !container.equals(jarProject); } return false; } }