From bfa41bb6cd708b5670f112312e68fb073a4ea18c Mon Sep 17 00:00:00 2001 From: Brian Remedios Date: Sun, 24 Aug 2008 23:05:01 +0000 Subject: [PATCH] Completed Method & Type property descriptors and refactored them under a common class that can filter by optional package prefixes. Matching test cases for same. Updated TypeMap to catch errors, new test cases. Updated ClassUtil with new functionality for the new descriptors and restored lost indentation formatting. git-svn-id: https://pmd.svn.sourceforge.net/svnroot/pmd/trunk@6388 51baf565-9d33-0410-a72c-fc3788e3496d --- .../AbstractPropertyDescriptorTester.java | 9 +- .../pmd/properties/DoublePropertyTest.java | 3 + .../pmd/properties/FloatPropertyTest.java | 3 + .../pmd/properties/IntegerPropertyTest.java | 24 +- .../pmd/properties/MethodPropertyTest.java | 135 +++++++++++ .../NonRuleWithAllPropertyTypes.java | 4 +- .../pmd/properties/StringPropertyTest.java | 3 + .../pmd/properties/TypePropertyTest.java | 38 +-- .../net/sourceforge/pmd/util/TypeMapTest.java | 83 +++++++ .../properties/AbstractPackagedProperty.java | 114 +++++++++ .../rule/properties/AbstractProperty.java | 49 +++- .../lang/rule/properties/MethodProperty.java | 227 +++++++++++++++--- .../lang/rule/properties/StringProperty.java | 6 + .../lang/rule/properties/TypeProperty.java | 38 +-- .../net/sourceforge/pmd/util/ClassUtil.java | 135 +++++++---- pmd/src/net/sourceforge/pmd/util/TypeMap.java | 214 +++++++++++------ 16 files changed, 870 insertions(+), 215 deletions(-) create mode 100644 pmd/regress/test/net/sourceforge/pmd/properties/MethodPropertyTest.java create mode 100644 pmd/regress/test/net/sourceforge/pmd/util/TypeMapTest.java create mode 100644 pmd/src/net/sourceforge/pmd/lang/rule/properties/AbstractPackagedProperty.java diff --git a/pmd/regress/test/net/sourceforge/pmd/properties/AbstractPropertyDescriptorTester.java b/pmd/regress/test/net/sourceforge/pmd/properties/AbstractPropertyDescriptorTester.java index 103dd19866..b729b56026 100644 --- a/pmd/regress/test/net/sourceforge/pmd/properties/AbstractPropertyDescriptorTester.java +++ b/pmd/regress/test/net/sourceforge/pmd/properties/AbstractPropertyDescriptorTester.java @@ -6,8 +6,11 @@ import net.sourceforge.pmd.PropertyDescriptor; import net.sourceforge.pmd.util.CollectionUtil; import org.junit.Test; + /** - * + * Base functionality for all concrete subclasses that evaluate type-specific property descriptors. + * Checks for error conditions during construction, error value detection, serialization, etc. + * * @author Brian Remedios */ public abstract class AbstractPropertyDescriptorTester { @@ -225,8 +228,4 @@ public abstract class AbstractPropertyDescriptorTester { } return results; } - -// public static junit.framework.Test suite() { -// return new junit.framework.JUnit4TestAdapter(AbstractPropertyDescriptorTester.class); -// } } diff --git a/pmd/regress/test/net/sourceforge/pmd/properties/DoublePropertyTest.java b/pmd/regress/test/net/sourceforge/pmd/properties/DoublePropertyTest.java index 445037b96c..7e79c18f82 100644 --- a/pmd/regress/test/net/sourceforge/pmd/properties/DoublePropertyTest.java +++ b/pmd/regress/test/net/sourceforge/pmd/properties/DoublePropertyTest.java @@ -4,6 +4,9 @@ import net.sourceforge.pmd.PropertyDescriptor; import net.sourceforge.pmd.lang.rule.properties.DoubleProperty; /** + * Evaluates the functionality of the DoubleProperty descriptor by testing its ability to catch creation + * errors (illegal args), flag out-of-range test values, and serialize/deserialize groups of double values + * onto/from a string buffer. * * @author Brian Remedios */ diff --git a/pmd/regress/test/net/sourceforge/pmd/properties/FloatPropertyTest.java b/pmd/regress/test/net/sourceforge/pmd/properties/FloatPropertyTest.java index e3c179df57..c8650c6292 100644 --- a/pmd/regress/test/net/sourceforge/pmd/properties/FloatPropertyTest.java +++ b/pmd/regress/test/net/sourceforge/pmd/properties/FloatPropertyTest.java @@ -4,6 +4,9 @@ import net.sourceforge.pmd.PropertyDescriptor; import net.sourceforge.pmd.lang.rule.properties.FloatProperty; /** + * Evaluates the functionality of the FloatProperty descriptor by testing its ability to catch creation + * errors (illegal args), flag out-of-range test values, and serialize/deserialize groups of float values + * onto/from a string buffer. * * @author Brian Remedios */ diff --git a/pmd/regress/test/net/sourceforge/pmd/properties/IntegerPropertyTest.java b/pmd/regress/test/net/sourceforge/pmd/properties/IntegerPropertyTest.java index a02d405a81..9fdbd5b0b3 100644 --- a/pmd/regress/test/net/sourceforge/pmd/properties/IntegerPropertyTest.java +++ b/pmd/regress/test/net/sourceforge/pmd/properties/IntegerPropertyTest.java @@ -6,6 +6,9 @@ import net.sourceforge.pmd.PropertyDescriptor; import net.sourceforge.pmd.lang.rule.properties.IntegerProperty; /** + * Evaluates the functionality of the IntegerProperty descriptor by testing its ability to catch creation + * errors (illegal args), flag out-of-range test values, and serialize/deserialize groups of integers + * onto/from a string buffer. * * @author Brian Remedios */ @@ -14,16 +17,16 @@ public class IntegerPropertyTest extends AbstractPropertyDescriptorTester { private static final int MIN = 1; private static final int MAX = 12; private static final int SHIFT = 3; - + /** * Method createValue. * @param count int * @return Object */ protected Object createValue(int count) { - + if (count == 1) return Integer.valueOf((int)(System.currentTimeMillis() % 100)); - + Integer[] values = new Integer[count]; for (int i=0; i("enumType", "Property with a enumerated choices", new String[] {"String", "Object"}, new Class[] {String.class, Object.class}, 1, 5.0f); public static final PropertyDescriptor multiEnumType = new EnumeratedProperty("enumType", "Property with a enumerated choices", new String[] {"String", "Object"}, new Class[] {String.class, Object.class}, new int[] {0,1}, 5.0f); diff --git a/pmd/regress/test/net/sourceforge/pmd/properties/StringPropertyTest.java b/pmd/regress/test/net/sourceforge/pmd/properties/StringPropertyTest.java index 68342e3e03..7a03bc4914 100644 --- a/pmd/regress/test/net/sourceforge/pmd/properties/StringPropertyTest.java +++ b/pmd/regress/test/net/sourceforge/pmd/properties/StringPropertyTest.java @@ -4,6 +4,9 @@ import net.sourceforge.pmd.PropertyDescriptor; import net.sourceforge.pmd.lang.rule.properties.StringProperty; /** + * Evaluates the functionality of the StringProperty descriptor by testing its ability to catch creation + * errors (illegal args), flag invalid strings per any specified expressions, and serialize/deserialize + * groups of strings onto/from a string buffer. * * @author Brian Remedios */ diff --git a/pmd/regress/test/net/sourceforge/pmd/properties/TypePropertyTest.java b/pmd/regress/test/net/sourceforge/pmd/properties/TypePropertyTest.java index 91406a3533..b72ddde051 100644 --- a/pmd/regress/test/net/sourceforge/pmd/properties/TypePropertyTest.java +++ b/pmd/regress/test/net/sourceforge/pmd/properties/TypePropertyTest.java @@ -1,20 +1,28 @@ package test.net.sourceforge.pmd.properties; +import java.util.Comparator; import java.util.HashMap; import java.util.Map; - -import org.junit.Test; +import java.util.Observer; +import java.util.Set; import net.sourceforge.pmd.PropertyDescriptor; import net.sourceforge.pmd.lang.rule.properties.TypeProperty; /** + * Evaluates the functionality of the TypeProperty descriptor by testing its ability to catch creation + * errors (illegal args), flag invalid Type values per the allowable packages, and serialize/deserialize + * groups of types onto/from a string buffer. + * + * We're using java.lang classes for 'normal' constructors and applying java.util types as ones we expect + * to fail. * * @author Brian Remedios */ public class TypePropertyTest extends AbstractPropertyDescriptorTester { - public static final Class[] classes = new Class[] { String.class, Integer.class, int.class, HashMap.class, Map.class }; + private static final Class[] javaLangClasses = new Class[] { String.class, Integer.class, Thread.class, Object.class, Runtime.class }; + private static final Class[] javaUtilTypes = new Class[] { HashMap.class, Map.class, Comparator.class, Set.class, Observer.class }; public TypePropertyTest() { super(); @@ -27,7 +35,7 @@ public class TypePropertyTest extends AbstractPropertyDescriptorTester { */ protected Object createValue(int count) { - if (count == 1) return randomChoice(classes); + if (count == 1) return randomChoice(javaLangClasses); Object[] values = new Object[count]; for (int i=0; i nameSet = new HashSet(items.length); + String name = null; + + for (int i=0; i TYPE_SHORTCUTS = ClassUtil.getClassShortNames(); + + private static String shortestNameFor(Class cls) { + String compactName = TYPE_SHORTCUTS.get(cls); + return compactName == null ? cls.getName() : compactName; + } + + /** + * @param method + * @return + */ + public static String asStringFor(Method method) { + StringBuilder sb = new StringBuilder(); + asStringOn(method, sb); + return sb.toString(); + } + + /** + * Return the value as a string that can be easily recognized and parsed + * when we see it again. + * + * @param value Object + * @return String + */ + protected String asString(Object value) { + return value == null ? "" : asStringFor((Method)value); + } + + private static void serializedTypeIdOn(Class type, StringBuilder sb) { + + Class arrayType = type.getComponentType(); + if (arrayType == null) { + sb.append(shortestNameFor(type)); + return; + } + sb.append(shortestNameFor(arrayType)).append("[]"); + } + + /** + * Serializes the method signature onto the specified buffer. + * + * @param method Method + * @param sb StringBuilder + */ + public static void asStringOn(Method method, StringBuilder sb) { + + Class clazz = method.getDeclaringClass(); + + sb.append(shortestNameFor(clazz) ); + sb.append(CLASS_METHOD_DELIMITER); + sb.append(method.getName()); + + sb.append(METHOD_GROUP_DELIMITERS[0]); + + Class[] argTypes = method.getParameterTypes(); + if (argTypes.length == 0) { + sb.append(METHOD_GROUP_DELIMITERS[1]); + return; + } + + serializedTypeIdOn(argTypes[0], sb); + for (int i=1; i cls = classIn(propertyString); - String methodName = methodNameIn(propertyString); - Class[] parameterTypes = parameterTypesIn(propertyString); - - try { - return cls.getMethod(methodName, parameterTypes); - } catch (Exception e) { - throw new IllegalArgumentException("invalid method: " + propertyString); + if (!isMultiValue()) { + return methodFrom( + valueString, CLASS_METHOD_DELIMITER, METHOD_ARG_DELIMITER + ); } - } + + String[] values = StringUtil.substringsOf(valueString, multiValueDelimiter()); - private Class classIn(String propertyString) throws IllegalArgumentException { - - int dotPos = propertyString.lastIndexOf('.'); - String className = propertyString.substring(0, dotPos); - - try { - return Class.forName(className); - } catch (Exception ex) { - throw new IllegalArgumentException("class not found: " + className); + Method[] methods = new Method[values.length]; + for (int i=0; i[] parameterTypesIn(String propertyString) { - return null; + return methods; } } diff --git a/pmd/src/net/sourceforge/pmd/lang/rule/properties/StringProperty.java b/pmd/src/net/sourceforge/pmd/lang/rule/properties/StringProperty.java index ea1c7975b8..e1bd91f309 100644 --- a/pmd/src/net/sourceforge/pmd/lang/rule/properties/StringProperty.java +++ b/pmd/src/net/sourceforge/pmd/lang/rule/properties/StringProperty.java @@ -64,6 +64,12 @@ public class StringProperty extends AbstractProperty { checkDefaults(theDefaultValue, aMultiValueDelimiter); } + /** + * + * @param defaultValue + * @param delim + * @throws IllegalArgumentException + */ private static void checkDefaults(Object defaultValue, char delim) { if (defaultValue == null) { return; } diff --git a/pmd/src/net/sourceforge/pmd/lang/rule/properties/TypeProperty.java b/pmd/src/net/sourceforge/pmd/lang/rule/properties/TypeProperty.java index 5ed7878ccb..6aaa952232 100644 --- a/pmd/src/net/sourceforge/pmd/lang/rule/properties/TypeProperty.java +++ b/pmd/src/net/sourceforge/pmd/lang/rule/properties/TypeProperty.java @@ -4,13 +4,14 @@ package net.sourceforge.pmd.lang.rule.properties; import net.sourceforge.pmd.util.ClassUtil; +import net.sourceforge.pmd.util.StringUtil; /** * Defines a property that supports class types, even for primitive values! * * @author Brian Remedios */ -public class TypeProperty extends StringProperty { +public class TypeProperty extends AbstractPackagedProperty { private static final char DELIMITER = '|'; @@ -19,10 +20,11 @@ public class TypeProperty extends StringProperty { * @param theName String * @param theDescription String * @param theDefault Class + * @param legalPackageNames String[] * @param theUIOrder float */ - public TypeProperty(String theName, String theDescription, Class theDefault, float theUIOrder) { - super(theName, theDescription, theDefault, theUIOrder, DELIMITER); + public TypeProperty(String theName, String theDescription, Class theDefault, String[] legalPackageNames, float theUIOrder) { + super(theName, theDescription, theDefault, legalPackageNames, theUIOrder); isMultiValue(false); } @@ -32,14 +34,19 @@ public class TypeProperty extends StringProperty { * @param theName String * @param theDescription String * @param theDefaults Class[] + * @param legalPackageNames String[] * @param theUIOrder float */ - public TypeProperty(String theName, String theDescription, Class[] theDefaults, float theUIOrder) { - super(theName, theDescription, theDefaults, theUIOrder, DELIMITER); + public TypeProperty(String theName, String theDescription, Class[] theDefaults, String[] legalPackageNames, float theUIOrder) { + super(theName, theDescription, theDefaults, legalPackageNames, theUIOrder); isMultiValue(true); } + protected String packageNameOf(Object item) { + return ((Class)item).getName(); + } + /** * Method type. * @return Class @@ -49,6 +56,10 @@ public class TypeProperty extends StringProperty { public Class type() { return Class.class; } + + protected String itemTypeName() { + return "type"; + } /** * Method asString. @@ -64,6 +75,7 @@ public class TypeProperty extends StringProperty { * Method classFrom. * @param className String * @return Class + * @throws IllegalArgumentException */ private Class classFrom(String className) { @@ -92,24 +104,12 @@ public class TypeProperty extends StringProperty { return classFrom(valueString); } - String[] values = (String[]) super.valueFrom(valueString); + String[] values = StringUtil.substringsOf(valueString, DELIMITER); - Class[] classes = new Class[values.length]; + Class[] classes = new Class[values.length]; for (int i = 0; i < values.length; i++) { classes[i] = classFrom(values[i]); } return classes; } - - /** - * Neutralize unwanted superclass functionality that will result - * in a class cast exception. - * - * @param value Object - * @return String - */ - @Override - protected String valueErrorFor(Object value) { - return null; - } } diff --git a/pmd/src/net/sourceforge/pmd/util/ClassUtil.java b/pmd/src/net/sourceforge/pmd/util/ClassUtil.java index c67f145cdf..0709e02194 100644 --- a/pmd/src/net/sourceforge/pmd/util/ClassUtil.java +++ b/pmd/src/net/sourceforge/pmd/util/ClassUtil.java @@ -1,69 +1,112 @@ package net.sourceforge.pmd.util; +import java.lang.reflect.Method; import java.math.BigDecimal; +import java.util.HashMap; +import java.util.Map; /** - * Various class-related utility methods + * Various class-related utility methods. * * @author Brian Remedios */ public final class ClassUtil { - private ClassUtil() { - }; + public static final Class[] EMPTY_CLASS_ARRAY = new Class[0]; - @SuppressWarnings("PMD.AvoidUsingShortType") - private static final TypeMap PRIMITIVE_TYPE_NAMES = new TypeMap(new Class[] { int.class, byte.class, long.class, - short.class, float.class, double.class, char.class, boolean.class, }); + private ClassUtil() { + }; - private static final TypeMap TYPES_BY_NAME = new TypeMap(new Class[] { Integer.class, Byte.class, Long.class, - Short.class, Float.class, Double.class, Character.class, Boolean.class, BigDecimal.class, String.class, - Object.class, }); + @SuppressWarnings("PMD.AvoidUsingShortType") + private static final TypeMap PRIMITIVE_TYPE_NAMES = new TypeMap( + new Class[] { int.class, byte.class, long.class, short.class, + float.class, double.class, char.class, boolean.class, }); - /** - * Returns the type(class) for the name specified - * or null if not found. - * - * @param name String - * @return Class - */ - public static Class getPrimitiveTypeFor(String name) { - return PRIMITIVE_TYPE_NAMES.typeFor(name); - } + private static final TypeMap TYPES_BY_NAME = new TypeMap(new Class[] { + Integer.class, Byte.class, Long.class, Short.class, Float.class, + Double.class, Character.class, Boolean.class, BigDecimal.class, + String.class, Object.class, }); - /** - * Attempt to determine the actual class given the short name. - * - * @param shortName String - * @return Class - */ - public static Class getTypeFor(String shortName) { - - Class type = TYPES_BY_NAME.typeFor(shortName); - if (type != null) { - return type; + /** + * Returns the type(class) for the name specified or null if not found. + * + * @param name String + * @return Class + */ + public static Class getPrimitiveTypeFor(String name) { + return PRIMITIVE_TYPE_NAMES.typeFor(name); } - type = PRIMITIVE_TYPE_NAMES.typeFor(shortName); - if (type != null) { - return type; + /** + * Return a map of all the short names of classes we maintain mappings for. + * The names are keyed by the classes themselves. + * + * @return + */ + public static Map getClassShortNames() { + + Map map = new HashMap(); + map.putAll(PRIMITIVE_TYPE_NAMES.asInverseWithShortName()); + map.putAll(TYPES_BY_NAME.asInverseWithShortName()); + return map; } - return CollectionUtil.getCollectionTypeFor(shortName); - } + /** + * Attempt to determine the actual class given the short name. + * + * @param shortName String + * @return Class + */ + public static Class getTypeFor(String shortName) { - /** - * Returns the abbreviated name of the type, - * without the package name - * - * @param fullTypeName - * @return String - */ + Class type = TYPES_BY_NAME.typeFor(shortName); + if (type != null) { + return type; + } - public static String withoutPackageName(String fullTypeName) { + type = PRIMITIVE_TYPE_NAMES.typeFor(shortName); + if (type != null) { + return type; + } - int dotPos = fullTypeName.lastIndexOf('.'); + return CollectionUtil.getCollectionTypeFor(shortName); + } - return dotPos > 0 ? fullTypeName.substring(dotPos + 1) : fullTypeName; - } + /** + * Returns the abbreviated name of the type, without the package name + * + * @param fullTypeName + * @return String + */ + + public static String withoutPackageName(String fullTypeName) { + + int dotPos = fullTypeName.lastIndexOf('.'); + return dotPos > 0 ? fullTypeName.substring(dotPos + 1) : fullTypeName; + } + + /** + * Attempts to return the specified method from the class provided but will + * walk up its superclasses until it finds a match. Returns null if it + * doesn't. + * + * @param clasz Class + * @param methodName String + * @param paramTypes Class[] + * @return Method + */ + public static Method methodFor(Class clasz, String methodName, Class[] paramTypes) { + + Method method = null; + Class current = clasz; + while (current != Object.class) { + try { + method = current.getDeclaredMethod(methodName, paramTypes); + } catch (NoSuchMethodException ex) { + current = current.getSuperclass(); + } + if (method != null) { return method; } + } + return null; + } } diff --git a/pmd/src/net/sourceforge/pmd/util/TypeMap.java b/pmd/src/net/sourceforge/pmd/util/TypeMap.java index 7191d7a4bb..11f9344548 100644 --- a/pmd/src/net/sourceforge/pmd/util/TypeMap.java +++ b/pmd/src/net/sourceforge/pmd/util/TypeMap.java @@ -1,84 +1,158 @@ package net.sourceforge.pmd.util; import java.util.HashMap; +import java.util.Iterator; import java.util.Map; /** - * A specialized map that stores classes by both their full and short names. + * A specialized map that stores types by both their full and short (without package prefixes) names. + * If an incoming type shares the same name (but different package/prefix) with a type already in the + * map then an IllegalArgumentException will be thrown since any subsequent retrievals by said short + * name could be in error. * * @author Brian Remedios */ public class TypeMap { - private Map> typesByName; + private Map> typesByName; - /** - * Constructor for TypeMap. - * @param initialSize int - */ - public TypeMap(int initialSize) { - typesByName = new HashMap>(initialSize); - } - - /** - * Constructor for TypeMap that takes in an initial set of types. - * - * @param types Class[] - */ - public TypeMap(Class... types) { - this(types.length); - add(types); - } - - /** - * Adds a type to the receiver and stores it keyed by both its full - * and short names. - * - * @param type Class - */ - public void add(Class type) { - typesByName.put(type.getName(), type); - typesByName.put(ClassUtil.withoutPackageName(type.getName()), type); - } - - /** - * Returns whether the type is known to the receiver. - * - * @param type Class - * @return boolean - */ - public boolean contains(Class type) { - return typesByName.containsValue(type); - } - - /** - * Returns whether the typeName is known to the receiver. - * - * @param typeName String - * @return boolean - */ - public boolean contains(String typeName) { - return typesByName.containsKey(typeName); - } - - /** - * Returns the type for the typeName specified. - * - * @param typeName String - * @return Class - */ - public Class typeFor(String typeName) { - return typesByName.get(typeName); - } - - /** - * Adds an array of types to the receiver at once. - * - * @param types Class[] - */ - public void add(Class... types) { - for (Class element : types) { - add(element); + /** + * Constructor for TypeMap. + * + * @param initialSize int + */ + public TypeMap(int initialSize) { + typesByName = new HashMap>(initialSize); + } + + /** + * Constructor for TypeMap that takes in an initial set of types. + * + * @param types Class[] + */ + public TypeMap(Class... types) { + this(types.length); + add(types); + } + + /** + * Adds a type to the receiver and stores it keyed by both its full and + * short names. Throws an exception if the short name of the argument + * matches an existing one already in the map for a different class. + * + * @param type Class + * @throws IllegalArgumentException + */ + @SuppressWarnings("PMD.CompareObjectsWithEquals") + public void add(Class type) { + + final String shortName = ClassUtil.withoutPackageName(type.getName()); + Class existingType = typesByName.get(shortName); + if (existingType == null) { + typesByName.put(type.getName(), type); + typesByName.put(shortName, type); + return; + } + + if (existingType != type) { + throw new IllegalArgumentException( + "Short name collision between existing " + + existingType + " and new " + type + ); + } + } + + /** + * Returns whether the type is known to the receiver. + * + * @param type Class + * @return boolean + */ + public boolean contains(Class type) { + return typesByName.containsValue(type); + } + + /** + * Returns whether the typeName is known to the receiver. + * + * @param typeName String + * @return boolean + */ + public boolean contains(String typeName) { + return typesByName.containsKey(typeName); + } + + /** + * Returns the type for the typeName specified. + * + * @param typeName String + * @return Class + */ + public Class typeFor(String typeName) { + return typesByName.get(typeName); + } + + /** + * Adds an array of types to the receiver at once. + * + * @param types Class[] + */ + public void add(Class... types) { + for (Class element : types) { + add(element); + } + } + + /** + * Creates and returns a map of short type names (without the package + * prefixes) keyed by the classes themselves. + * + * @return Map + */ + public Map, String> asInverseWithShortName() { + + Map, String> inverseMap = new HashMap, String>(typesByName.size() / 2); + + Iterator iter = typesByName.entrySet().iterator(); + while (iter.hasNext()) { + Map.Entry entry = (Map.Entry) iter.next(); + storeShortest(inverseMap, entry.getValue(), (String) entry.getKey()); + } + + return inverseMap; + } + + /** + * Returns the total number of entries in the receiver. This will be exactly + * twice the number of types added. + * + * @return + */ + public int size() { + return typesByName.size(); + } + + /** + * Store the shorter of the incoming value or the existing value in the map + * at the key specified. + * + * @param map + * @param key + * @param value + */ + private void storeShortest(Map map, Object key, String value) { + + String existingValue = (String) map.get(key); + + if (existingValue == null) { + map.put(key, value); + return; + } + + if (existingValue.length() < value.length()) { + return; + } + + map.put(key, value); } - } }