package de.plushnikov.intellij.plugin.thirdparty; import com.intellij.openapi.util.text.StringUtil; import de.plushnikov.intellij.plugin.processor.field.AccessorsInfo; import java.util.Collection; import java.util.HashSet; import java.util.regex.Pattern; /** * @author ProjectLombok Team * @author Plushnikov Michail */ public class LombokUtils { public static final String LOMBOK_INTERN_FIELD_MARKER = "$"; /* NB: 'notnull' is not part of the pattern because there are lots of @NotNull annotations out there that are crappily named and actually mean something else, such as 'this field must not be null _when saved to the db_ but its perfectly okay to start out as such, and a no-args constructor and the implied starts-out-as-null state that goes with it is in fact mandatory' which happens with javax.validation.constraints.NotNull. Various problems with spring have also been reported. See issue #287, issue #271, and issue #43. */ public static final Pattern NON_NULL_PATTERN = Pattern.compile("^(?:nonnull)$", Pattern.CASE_INSENSITIVE); public static final Pattern NULLABLE_PATTERN = Pattern.compile("^(?:nullable|checkfornull)$", Pattern.CASE_INSENSITIVE); public static final Pattern DEPRECATED_PATTERN = Pattern.compile("^(?:deprecated)$", Pattern.CASE_INSENSITIVE); /** * Generates a getter name from a given field name. * <p/> * Strategy: * <ul> * <li>Reduce the field's name to its base name by stripping off any prefix (from {@code Accessors}). If the field name does not fit * the prefix list, this method immediately returns {@code null}.</li> * <li>If {@code Accessors} has {@code fluent=true}, then return the basename.</li> * <li>Pick a prefix. 'get' normally, but 'is' if {@code isBoolean} is true.</li> * <li>Only if {@code isBoolean} is true: Check if the field starts with {@code is} followed by a non-lowercase character. If so, return the field name verbatim.</li> * <li>Check if the first character of the field is lowercase. If so, check if the second character * exists and is title or upper case. If so, uppercase the first character. If not, titlecase the first character.</li> * <li>Return the prefix plus the possibly title/uppercased first character, and the rest of the field name.</li> * </ul> * * @param accessors Accessors configuration. * @param fieldName the name of the field. * @param isBoolean if the field is of type 'boolean'. For fields of type {@code java.lang.Boolean}, you should provide {@code false}. * @return The getter name for this field, or {@code null} if this field does not fit expected patterns and therefore cannot be turned into a getter name. */ public static String toGetterName(AccessorsInfo accessors, String fieldName, boolean isBoolean) { return toAccessorName(accessors, fieldName, isBoolean, "is", "get"); } /** * Generates a setter name from a given field name. * <p/> * Strategy: * <ul> * <li>Reduce the field's name to its base name by stripping off any prefix (from {@code Accessors}). If the field name does not fit * the prefix list, this method immediately returns {@code null}.</li> * <li>If {@code Accessors} has {@code fluent=true}, then return the basename.</li> * <li>Only if {@code isBoolean} is true: Check if the field starts with {@code is} followed by a non-lowercase character. * If so, replace {@code is} with {@code set} and return that.</li> * <li>Check if the first character of the field is lowercase. If so, check if the second character * exists and is title or upper case. If so, uppercase the first character. If not, titlecase the first character.</li> * <li>Return {@code "set"} plus the possibly title/uppercased first character, and the rest of the field name.</li> * </ul> * * @param accessors Accessors configuration. * @param fieldName the name of the field. * @param isBoolean if the field is of type 'boolean'. For fields of type {@code java.lang.Boolean}, you should provide {@code false}. * @return The setter name for this field, or {@code null} if this field does not fit expected patterns and therefore cannot be turned into a getter name. */ public static String toSetterName(AccessorsInfo accessors, String fieldName, boolean isBoolean) { return toAccessorName(accessors, fieldName, isBoolean, "set", "set"); } /** * Generates a wither name from a given field name. * <p/> * Strategy: * <ul> * <li>Reduce the field's name to its base name by stripping off any prefix (from {@code Accessors}). If the field name does not fit * the prefix list, this method immediately returns {@code null}.</li> * <li>Only if {@code isBoolean} is true: Check if the field starts with {@code is} followed by a non-lowercase character. * If so, replace {@code is} with {@code with} and return that.</li> * <li>Check if the first character of the field is lowercase. If so, check if the second character * exists and is title or upper case. If so, uppercase the first character. If not, titlecase the first character.</li> * <li>Return {@code "with"} plus the possibly title/uppercased first character, and the rest of the field name.</li> * </ul> * * @param accessors Accessors configuration. * @param fieldName the name of the field. * @param isBoolean if the field is of type 'boolean'. For fields of type {@code java.lang.Boolean}, you should provide {@code false}. * @return The wither name for this field, or {@code null} if this field does not fit expected patterns and therefore cannot be turned into a getter name. */ public static String toWitherName(AccessorsInfo accessors, String fieldName, boolean isBoolean) { return toAccessorName(accessors, fieldName, isBoolean, "with", "with"); } private static String toAccessorName(AccessorsInfo accessorsInfo, String fieldName, boolean isBoolean, String booleanPrefix, String normalPrefix) { final String result; fieldName = accessorsInfo.removePrefix(fieldName); if (accessorsInfo.isFluent()) { return fieldName; } final boolean useBooleanPrefix = isBoolean && !accessorsInfo.isDoNotUseIsPrefix(); if (useBooleanPrefix) { if (fieldName.startsWith("is") && fieldName.length() > 2 && !Character.isLowerCase(fieldName.charAt(2))) { final String baseName = fieldName.substring(2); result = buildName(booleanPrefix, baseName); } else { result = buildName(booleanPrefix, fieldName); } } else { result = buildName(normalPrefix, fieldName); } return result; } /** * Returns all names of methods that would represent the getter for a field with the provided name. * <p/> * For example if {@code isBoolean} is true, then a field named {@code isRunning} would produce:<br /> * {@code [isRunning, getRunning, isIsRunning, getIsRunning]} * * @param accessorsInfo * @param fieldName the name of the field. * @param isBoolean if the field is of type 'boolean'. For fields of type 'java.lang.Boolean', you should provide {@code false}. */ public static Collection<String> toAllGetterNames(AccessorsInfo accessorsInfo, String fieldName, boolean isBoolean) { return toAllAccessorNames(accessorsInfo, fieldName, isBoolean, "is", "get"); } /** * Returns all names of methods that would represent the setter for a field with the provided name. * <p/> * For example if {@code isBoolean} is true, then a field named {@code isRunning} would produce:<br /> * {@code [setRunning, setIsRunning]} * * @param accessorsInfo * @param fieldName the name of the field. * @param isBoolean if the field is of type 'boolean'. For fields of type 'java.lang.Boolean', you should provide {@code false}. */ public static Collection<String> toAllSetterNames(AccessorsInfo accessorsInfo, String fieldName, boolean isBoolean) { return toAllAccessorNames(accessorsInfo, fieldName, isBoolean, "set", "set"); } /** * Returns all names of methods that would represent the wither for a field with the provided name. * <p/> * For example if {@code isBoolean} is true, then a field named {@code isRunning} would produce:<br /> * {@code [withRunning, withIsRunning]} * * @param accessorsInfo * @param fieldName the name of the field. * @param isBoolean if the field is of type 'boolean'. For fields of type 'java.lang.Boolean', you should provide {@code false}. */ public static Collection<String> toAllWitherNames(AccessorsInfo accessorsInfo, String fieldName, boolean isBoolean) { return toAllAccessorNames(accessorsInfo, fieldName, isBoolean, "with", "with"); } private static Collection<String> toAllAccessorNames(AccessorsInfo accessorsInfo, String fieldName, boolean isBoolean, String booleanPrefix, String normalPrefix) { Collection<String> result = new HashSet<String>(); fieldName = accessorsInfo.removePrefix(fieldName); if (accessorsInfo.isFluent()) { result.add(StringUtil.decapitalize(fieldName)); return result; } if (isBoolean) { result.add(buildName(normalPrefix, fieldName)); result.add(buildName(booleanPrefix, fieldName)); if (fieldName.startsWith("is") && fieldName.length() > 2 && !Character.isLowerCase(fieldName.charAt(2))) { final String baseName = fieldName.substring(2); result.add(buildName(normalPrefix, baseName)); result.add(buildName(booleanPrefix, baseName)); } } else { result.add(buildName(normalPrefix, fieldName)); } return result; } private static String buildName(String prefix, String suffix) { return prefix + StringUtil.capitalize(suffix); } }