/* * Copyright 2009-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.codehaus.groovy.eclipse.codeassist.relevance; import java.util.Arrays; import org.codehaus.groovy.eclipse.codeassist.GroovyContentAssist; import org.codehaus.groovy.eclipse.codeassist.ProposalUtils; import org.eclipse.jdt.core.Flags; import org.eclipse.jdt.core.IAccessRule; import org.eclipse.jdt.core.IPackageFragment; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.core.compiler.CharOperation; import org.eclipse.jdt.core.compiler.IProblem; import org.eclipse.jdt.internal.compiler.env.AccessRestriction; import org.eclipse.jdt.internal.core.SourceType; /** * Computes the relevance of a type based on a set of relevance rules. The * caller has the option of specifying a subset of available rules to use, or if * none is provided, by default all the rules will be used. * <p> * New rules can be added and require a corresponding type definition */ public class RelevanceRules implements IRelevanceRule { public static int DEFAULT_STARTING_RELEVANCE_VALUE = 1; public static enum RelevanceRuleType { SOURCE_TYPE, ACCESSIBILITY, MODIFIERS, LIBRARY_TYPE, SIMILAR_PACKAGE } private RelevanceRuleType[] ruleTypes; public static RelevanceRules ALL_RULES = new RelevanceRules( RelevanceRuleType.LIBRARY_TYPE, RelevanceRuleType.SOURCE_TYPE, // FIXNS: Enable only after it has been tested // RelevanceRuleType.ACCESSIBILITY, RelevanceRuleType.MODIFIERS, RelevanceRuleType.SIMILAR_PACKAGE); public static IRelevanceRule getRule(RelevanceRuleType type) { if (type == null) { return null; } IRelevanceRule rule = null; switch (type) { case SOURCE_TYPE: rule = new SourceRule(); break; // FIXNS: Enable only after it has been tested // case ACCESSIBILITY: // rule = new AccessibilityRule(); // break; case MODIFIERS: rule = new ModifierRule(); break; case LIBRARY_TYPE: rule = new LibraryTypeRule(); break; case SIMILAR_PACKAGE: rule = new SimilarPackagesRule(); break; } return rule; } /** * Argument is a list of relevance rule types that should be used when * computing the relevance of a type. If null or empty is passed, all the * available rule types will be used. This constructor allows a caller to * use only a subset of rules. */ public RelevanceRules(RelevanceRuleType... ruleTypes) { this.ruleTypes = ruleTypes; } /** * Computes the integer relevance value of a given type based on registered * relevance rule types */ public int getRelevance(char[] fullyQualifiedName, IType[] contextTypes, int accessibility, int modifiers) { if (fullyQualifiedName == null) { return 0; } // use all the rule types if none were specified by the invoker. RelevanceRuleType[] rTypes = ruleTypes == null || ruleTypes.length == 0 ? RelevanceRuleType .values() : ruleTypes; int relevance = getStartingRelevanceValue(); for (RelevanceRuleType ruleType : rTypes) { IRelevanceRule rule = getRule(ruleType); if (rule != null) { relevance += rule.getRelevance(fullyQualifiedName, contextTypes, accessibility, modifiers); } } // Use lowest Relevance category as Types have lowest relevance // category return Relevance.LOWEST.getRelevance(relevance); } /** * Computes the integer relevance value of a given type based on registered * relevance rule types */ public int getRelevance(IType type, IType[] contextTypes) { if (type == null) { return 0; } // use all the rule types if none were specified by the invoker. RelevanceRuleType[] rTypes = ruleTypes == null || ruleTypes.length == 0 ? RelevanceRuleType .values() : ruleTypes; int relevance = getStartingRelevanceValue(); for (RelevanceRuleType ruleType : rTypes) { IRelevanceRule rule = getRule(ruleType); if (rule != null) { relevance += rule.getRelevance(type, contextTypes); } } // User lowest Relevance category as Types have lowest relevance // category return Relevance.LOWEST.getRelevance(relevance); } protected int getStartingRelevanceValue() { return DEFAULT_STARTING_RELEVANCE_VALUE; } /* * RULE DEFINITIONS */ /** * Simple rule that places higher priority on Source types. Binary types * have lower priority. */ public static class SourceRule extends AbstractRule { public int getRelevance(IType relevanceType, IType[] contextTypes) { // Source have higher relevance than Binary return relevanceType instanceof SourceType ? TypeRelevanceCategory.MEDIUM_TYPE .applyCategory(1) : TypeRelevanceCategory.LOW_TYPE .applyCategory(1); } public int getRelevance(char[] fullyQualifiedName, IType[] contextTypes, int accessibility, int modifiers) { // don't know return 0; } } /** * FIXNS: Not fully tested. Not recommended for use. * * Accessible types have higher priority than restricted types. * */ public static class AccessibilityRule extends AbstractRule { public int getRelevance(IType relevanceType, IType[] contextTypes) { if (relevanceType == null) { return 0; } // determine associated access restriction AccessRestriction accessRestriction = ProposalUtils .getTypeAccessibility(relevanceType); // If no access restriction found, assume accessible? int accessibility = IAccessRule.K_ACCESSIBLE; if (accessRestriction != null) { switch (accessRestriction.getProblemId()) { case IProblem.ForbiddenReference: accessibility = IAccessRule.K_NON_ACCESSIBLE; break; case IProblem.DiscouragedReference: // discouraged references have a lower priority accessibility = IAccessRule.K_DISCOURAGED; break; } } return getRelevance(null, null, accessibility, 0); } public int getRelevance(char[] fullyQualifiedName, IType[] contextTypes, int accessibility, int modifiers) { return accessibility == IAccessRule.K_ACCESSIBLE ? TypeRelevanceCategory.MEDIUM_TYPE .applyCategory(1) : 0; } } /** * Types in the same project as the context types (the context compilation * unit where a type is being imported or referenced) have higher priority * that types in other projects. Furthermore, private types have the highest * priority, followed by package private, followed by public */ public static class ModifierRule extends AbstractRule { protected TypeRelevanceCategory getTypeCategory(IType relevanceType, IType[] contextTypes) { TypeRelevanceCategory category = null; if (areTypesInSameCompilationUnit(relevanceType, contextTypes)) { category = TypeRelevanceCategory.HIGH_TYPE; } else if (areTypesInSamePackage(relevanceType, contextTypes)) { category = TypeRelevanceCategory.MEDIUM_HIGH_TYPE; } else { // ignore this rule if not in same package or unit category = null; } return category; } public int getRelevance(IType relevanceType, IType[] contextTypes) { int relevance = 0; TypeRelevanceCategory category = null; try { int modifiers = relevanceType.getFlags(); category = getTypeCategory(relevanceType, contextTypes); if (category != null) { relevance += (modifiers & Flags.AccDefault) != 0 ? 0 : 1; relevance += (modifiers & Flags.AccPrivate) != 0 ? 0 : 1; return category.applyCategory(relevance); } } catch (JavaModelException e) { GroovyContentAssist.logError("Exception calculating relevance", e); } return 0; } // don't do for relevance calculation involving content assist public int getRelevance(char[] fullyQualifiedName, IType[] contextTypes, int accessibility, int modifiers) { return 0; } } /** * * Types from certain libraries have higher priority. In particular, types * from java, groovy, groovyx, and javax have higher priority than types * from other packages. The order is shown in the following example <br> * Example: </br> <li> * java.lang.SomeType</li> <li> * groovy.lang.SomeType</li> <li> * groovyx.lang.SomeType</li> <li> * javax.lang.SomeType</li> <li> * com.lang.SomeType</li> * */ public static class LibraryTypeRule extends AbstractRule { enum LibraryType { JAVA("java"), JAVAX("javax"), GROOVY("groovy"), GROOVYX("groovyx"); private LibraryType(String value) { this.value = value.toCharArray(); } private final char[] value; public char[] getValue() { return value; } } /** * The library type is the first segment in the package name. * * @param relevanceType * @return first segment in the package name containing the type */ protected LibraryType getLibraryType(char[] qualifiedName) { char[][] segments = CharOperation.splitOn('.', qualifiedName); if (segments != null && segments.length > 0) { char[] firstPackSegment = segments[0]; for (LibraryType type : LibraryType.values()) { if (Arrays.equals(type.getValue(), firstPackSegment)) { return type; } } } return null; } public int getRelevance(IType relevanceType, IType[] contextTypes) { if (relevanceType == null) { return 0; } return getRelevance(relevanceType.getFullyQualifiedName() .toCharArray(), contextTypes, 0, 0); } public int getRelevance(char[] fullyQualifiedName, IType[] contextTypes, int accessibility, int modifiers) { // Default is zero, meaning relevance for types in any other library // is governed by other rules. Only types in the following libraries // get higher priority int relevance = 0; LibraryType packType = getLibraryType(fullyQualifiedName); if (packType != null) { switch (packType) { case JAVA: relevance += 4; break; case GROOVY: relevance += 3; break; case GROOVYX: relevance += 2; break; case JAVAX: relevance += 1; break; } } return TypeRelevanceCategory.LOW_TYPE.applyCategory(relevance); } } /** * * Types in packages with common segments have higher priority. As this rule * may clash with LibraryTypeRule, a higher relevance category is assigned * to this rule. This example shows the effect of the higher category. <br> * With just LibraryTypeRule, the following order would be expected:</br> * <li> * java.lang.SomeType</li> <li> * groovy.lang.SomeType</li> <li> * groovyx.lang.SomeType</li> <li> * javax.lang.SomeType</li> <li> * com.lang.SomeType</li> * <p> * However, if both LibraryTypeRule and SimilarPackagesRule are enabled, and * if SomeType is being imported into a compilation unit in * com.lang.AnotherType, the order would be: * </p> * <li> * com.lang.SomeType</li> <li> * java.lang.SomeType</li> <li> * groovy.lang.SomeType</li> <li> * groovyx.lang.SomeType</li> <li> * javax.lang.SomeType</li> * */ public static class SimilarPackagesRule extends AbstractRule { protected String convertToDot(String name) { return name != null ? name.replace('$', '.') : name; } public int getRelevance(IType relevanceType, IType[] contextTypes) { return getRelevance(relevanceType.getFullyQualifiedName('.') .toCharArray(), contextTypes, 0, 0); } public int getRelevance(char[] fullyQualifiedName, IType[] contextTypes, int accessibility, int modifiers) { int relevance = 0; IPackageFragment contextFragment = getContextPackageFragment(contextTypes); if (contextFragment != null && fullyQualifiedName != null) { String relQualified = String.valueOf(fullyQualifiedName); String contextQualified = convertToDot(contextFragment .getElementName()); String[] relSegments = relQualified.split("\\."); String[] contextSegments = contextQualified.split("\\."); for (int i = 0; i < relSegments.length && i < contextSegments.length; i++) { if (relSegments[i].equals(contextSegments[i])) { relevance++; } else { // Stop relevance counting once different segments are // encountered break; } } } return TypeRelevanceCategory.HIGH_TYPE.applyCategory(relevance); } } }