This is an automated email from the ASF dual-hosted git repository.

asf-gitbox-commits pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cayenne.git

commit 17433f78a63807cc72a02f62dcb1e02efb00ce3a
Author: Andrus Adamchik <[email protected]>
AuthorDate: Sat May 23 18:27:06 2026 -0400

    Cleanup: Util.hashCode is deprecated in favor of plain Java alternatives
---
 .../main/java/org/apache/cayenne/util/Util.java    | 1422 ++++++++++----------
 1 file changed, 712 insertions(+), 710 deletions(-)

diff --git a/cayenne/src/main/java/org/apache/cayenne/util/Util.java 
b/cayenne/src/main/java/org/apache/cayenne/util/Util.java
index 7abb0c4c6..7492a90f8 100644
--- a/cayenne/src/main/java/org/apache/cayenne/util/Util.java
+++ b/cayenne/src/main/java/org/apache/cayenne/util/Util.java
@@ -74,714 +74,716 @@ import java.util.regex.Pattern;
  */
 public class Util {
 
-       private static final Map<String, String> SPECIAL_CHAR_TO_JAVA_MAPPING = 
new HashMap<>();
-       static {
-               SPECIAL_CHAR_TO_JAVA_MAPPING.put("#", "pound");
-       }
-
-       @Deprecated
-       private static DefaultAdhocObjectFactory objectFactory;
-
-       static {
-               objectFactory = new DefaultAdhocObjectFactory(null, new 
DefaultClassLoaderManager());
-       }
-
-       /**
-        * Converts URL to file. Throws {@link IllegalArgumentException} if the 
URL
-        * is not a "file://" URL.
-        */
-       public static File toFile(URL url) throws IllegalArgumentException {
-               // must convert spaces to %20, or URL->URI conversion may fail
-               String urlString = url.toExternalForm();
-
-               URI uri;
-               try {
-                       uri = new URI(urlString.replace(" ", "%20"));
-               } catch (URISyntaxException e) {
-                       throw new IllegalArgumentException("URL " + urlString + 
" can't be converted to URI", e);
-               }
-               return new File(uri);
-       }
-
-       /**
-        * Reads file contents, returning it as a String, using System default 
line
-        * separator.
-        */
-       public static String stringFromFile(File file) throws IOException {
-               return stringFromFile(file, 
System.getProperty("line.separator"));
-       }
-
-       /**
-        * Reads file contents, returning it as a String, joining lines with
-        * provided separator.
-        */
-       public static String stringFromFile(File file, String joinWith) throws 
IOException {
-               StringBuilder buf = new StringBuilder();
-
-               try (BufferedReader in = new BufferedReader(new 
FileReader(file));) {
-                       String line = null;
-                       while ((line = in.readLine()) != null) {
-                               buf.append(line).append(joinWith);
-                       }
-               }
-               return buf.toString();
-       }
-
-       /**
-        * @param objects
-        *            An Iterable of objects that will be converted to Strings 
and joined together.
-        * @param separator
-        *            The separator between the strings.
-        * @return A single string of all the input strings separated by the
-        *         separator.
-        * @deprecated use {@link String#join(CharSequence, CharSequence...)} 
or {@link java.util.stream.Collectors#joining}
-        */
-       @Deprecated(since = "5.0", forRemoval = true)
-       public static String join(Iterable<?> objects, String separator) {
-               if (objects == null) {
-                       return "";
-               }
-
-               if (separator == null) {
-                       separator = "";
-               }
-
-               StringBuilder builder = new StringBuilder();
-
-               for (Object o : objects) {
-                       if (builder.length() > 0) {
-                               builder.append(separator);
-                       }
-
-                       String string = o != null ? o.toString() : "";
-                       builder.append(string);
-               }
-
-               return builder.toString();
-       }
-
-       /**
-        * Replaces all backslashes "\" with forward slashes "/". Convenience 
method
-        * to convert path Strings to URI format.
-        */
-       public static String substBackslashes(String string) {
-               return RegexUtil.substBackslashes(string);
-       }
-
-       /**
-        * Looks up and returns the root cause of an exception. If none is 
found,
-        * returns supplied Throwable object unchanged. If root is found,
-        * recursively "unwraps" it, and returns the result to the user.
-        */
-       public static Throwable unwindException(Throwable th) {
-               if (th instanceof SAXException) {
-                       SAXException sax = (SAXException) th;
-                       if (sax.getException() != null) {
-                               return unwindException(sax.getException());
-                       }
-               } else if (th instanceof SQLException) {
-                       SQLException sql = (SQLException) th;
-                       if (sql.getNextException() != null) {
-                               return unwindException(sql.getNextException());
-                       }
-               } else if (th.getCause() != null) {
-                       return unwindException(th.getCause());
-               }
-
-               return th;
-       }
-
-       /**
-        * Compares two objects similar to "Object.equals(Object)". Unlike
-        * Object.equals(..), this method doesn't throw an exception if any of 
the
-        * two objects is null.
-        *
-        * @deprecated Use {@link Objects#equals(Object, Object)} for non-array 
types,
-        *             or {@link Objects#deepEquals(Object, Object)} when the 
values may be arrays.
-        */
-       @Deprecated(since = "5.0", forRemoval = true)
-       public static boolean nullSafeEquals(Object o1, Object o2) {
-               return Objects.deepEquals(o1, o2);
-       }
-
-       /**
-        * Compares two objects similar to "Comparable.compareTo(Object)". 
Unlike
-        * Comparable.compareTo(..), this method doesn't throw an exception if 
any
-        * of the two objects is null.
-        *
-        * @since 1.1
-        */
-       public static <T> int nullSafeCompare(boolean nullsFirst, Comparable<T> 
o1, T o2) {
-               if (o1 == null && o2 == null) {
-                       return 0;
-               } else if (o1 == null) {
-                       return nullsFirst ? -1 : 1;
-               } else if (o2 == null) {
-                       return nullsFirst ? 1 : -1;
-               } else {
-                       return o1.compareTo(o2);
-               }
-       }
-
-       /**
-        * Returns true, if the String is null or an empty string.
-        */
-       public static boolean isEmptyString(CharSequence string) {
-               return string == null || string.length() == 0;
-       }
-
-       /**
-        * Returns true, if string not empty and contains non-whitespace 
characters.
-        * @since 4.1
-        */
-       public static boolean isBlank(CharSequence cs) {
-               int strLen;
-               if (cs != null && (strLen = cs.length()) != 0) {
-                       for(int i = 0; i < strLen; ++i) {
-                               if (!Character.isWhitespace(cs.charAt(i))) {
-                                       return false;
-                               }
-                       }
-
-                       return true;
-               } else {
-                       return true;
-               }
-       }
-
-       /**
-        * @since 4.1
-        * @deprecated use {@code cs.chars().allMatch(Character::isDigit)} or 
equivalent
-        */
-       @Deprecated(since = "5.0", forRemoval = true)
-       public static boolean isNumeric(CharSequence cs) {
-               if (isEmptyString(cs)) {
-                       return false;
-               } else {
-                       for(int i = 0; i < cs.length(); ++i) {
-                               if (!Character.isDigit(cs.charAt(i))) {
-                                       return false;
-                               }
-                       }
-
-                       return true;
-               }
-       }
-
-       /**
-        * Returns how many times the sub appears in the str.
-        * @since 4.1
-        */
-       public static int countMatches(final String str, final String sub) {
-               if (isEmptyString(str) || isEmptyString(sub)) {
-                       return 0;
-               }
-               int count = 0, idx = 0;
-               while ((idx = str.indexOf(sub, idx)) != -1) {
-                       count++;
-                       idx += sub.length();
-               }
-               return count;
-       }
-
-       /**
-        * Capitalizes the first letter of the property name.
-        *
-        * @since 4.1
-        */
-       public static String capitalized(String name) {
-               if (name == null || name.length() == 0) {
-                       return name;
-               }
-
-               char c = Character.toUpperCase(name.charAt(0));
-               return (name.length() == 1) ? Character.toString(c) : c + 
name.substring(1);
-       }
-
-       /**
-        * Returns string with lowercased first letter
-        *
-        * @since 4.2
-        */
-       public static String uncapitalized(String aString) {
-               if (aString == null || aString.length() == 0) {
-                       return aString;
-               }
-
-               char c = Character.toLowerCase(aString.charAt(0));
-               return (aString.length() == 1) ? Character.toString(c) : c + 
aString.substring(1);
-       }
-
-       /**
-        * Creates Serializable object copy using serialization/deserialization.
-        */
-       @SuppressWarnings("unchecked")
-       public static <T extends Serializable> T cloneViaSerialization(T 
object) throws Exception {
-               ByteArrayOutputStream bytes = new ByteArrayOutputStream() {
-
-                       @Override
-                       public byte[] toByteArray() {
-                               return buf;
-                       }
-               };
-
-               try (ObjectOutputStream out = new ObjectOutputStream(bytes)) {
-                       out.writeObject(object);
-               }
-
-               try (ObjectInputStream in = new ObjectInputStream(new 
ByteArrayInputStream(bytes.toByteArray()))) {
-                       return (T) in.readObject();
-               }
-       }
-
-       /**
-        * Creates an XMLReader with default feature set. Note that all Cayenne
-        * internal XML parsers should probably use XMLReader obtained via this
-        * method for consistency sake, and can customize feature sets as 
needed.
-        */
-       public static XMLReader createXmlReader() throws SAXException, 
ParserConfigurationException {
-               SAXParserFactory spf = SAXParserFactory.newInstance();
-               
spf.setFeature("http://apache.org/xml/features/disallow-doctype-decl";, true);
-               
spf.setFeature("http://xml.org/sax/features/external-general-entities";, false);
-               
spf.setFeature("http://xml.org/sax/features/external-parameter-entities";, 
false);
-               spf.setFeature("http://xml.org/sax/features/namespaces";, true);
-
-               // Create a JAXP SAXParser
-               SAXParser saxParser = spf.newSAXParser();
-
-               // Get the encapsulated SAX XMLReader
-               return saxParser.getXMLReader();
-       }
-
-       /**
-        * @since 4.1
-        * @param url to read
-        * @return org.w3c.dom.Document from the given URL
-        */
-       public static Document readDocument(URL url) {
-               DocumentBuilderFactory documentBuilderFactory = 
DocumentBuilderFactory.newInstance();
-               documentBuilderFactory.setNamespaceAware(false);
-               documentBuilderFactory.setXIncludeAware(false);
-               documentBuilderFactory.setExpandEntityReferences(false);
-
-               try {
-                       
documentBuilderFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl";,
 true);
-                       
documentBuilderFactory.setFeature("http://xml.org/sax/features/external-general-entities";,
 false);
-                       
documentBuilderFactory.setFeature("http://xml.org/sax/features/external-parameter-entities";,
 false);
-                       
documentBuilderFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd";,
 false);
-               } catch (ParserConfigurationException ex) {
-                       throw new ConfigurationException("Unable to configure 
DocumentBuilderFactory", ex);
-               }
-
-               try {
-                       DocumentBuilder domBuilder = 
documentBuilderFactory.newDocumentBuilder();
-                       try (InputStream inputStream = url.openStream()) {
-                               return domBuilder.parse(inputStream);
-                       } catch (IOException | SAXException e) {
-                               throw new ConfigurationException("Error loading 
configuration from %s", e, url);
-                       }
-               } catch (ParserConfigurationException e) {
-                       throw new ConfigurationException(e);
-               }
-       }
-
-       /**
-        * Returns package name for the Java class as a path separated with 
forward
-        * slash ("/"). Method is used to lookup resources that are located in
-        * package subdirectories. For example, a String "a/b/c" will be 
returned
-        * for class name "a.b.c.ClassName".
-        */
-       public static String getPackagePath(String className) {
-               return RegexUtil.getPackagePath(className);
-       }
-
-       /**
-        * Returns an unqualified class name for the fully qualified name.
-        *
-        * @since 3.0
-        */
-       public static String stripPackageName(String className) {
-               if (className == null || className.length() == 0) {
-                       return className;
-               }
-
-               int lastDot = className.lastIndexOf('.');
-
-               if ((-1 == lastDot) || ((className.length() - 1) == lastDot)) {
-                       return className;
-               }
-
-               return className.substring(lastDot + 1);
-       }
-
-       /**
-        * Creates a mutable map out of two arrays with keys and values.
-        *
-        * @since 1.2
-        */
-       public static <K, V> Map<K, V> toMap(K[] keys, V[] values) {
-               int keysSize = (keys != null) ? keys.length : 0;
-               int valuesSize = (values != null) ? values.length : 0;
-
-               if (keysSize == 0 && valuesSize == 0) {
-                       // return mutable map
-                       return new HashMap<>();
-               }
-
-               if (keysSize != valuesSize) {
-                       throw new IllegalArgumentException("The number of keys 
doesn't match the number of values.");
-               }
-
-               Map<K, V> map = new HashMap<>();
-               for (int i = 0; i < keysSize; i++) {
-                       map.put(keys[i], values[i]);
-               }
-
-               return map;
-       }
-
-       /**
-        * Extracts extension from the file name. Dot is not included in the
-        * returned string.
-        */
-       public static String extractFileExtension(String fileName) {
-               int dotInd = fileName.lastIndexOf('.');
-
-               // if dot is in the first position,
-               // we are dealing with a hidden file rather than an extension
-               return (dotInd > 0 && dotInd < fileName.length()) ? 
fileName.substring(dotInd + 1) : null;
-       }
-
-       /**
-        * Strips extension from the file name.
-        */
-       public static String stripFileExtension(String fileName) {
-               int dotInd = fileName.lastIndexOf('.');
-
-               // if dot is in the first position,
-               // we are dealing with a hidden file rather than an extension
-               return (dotInd > 0) ? fileName.substring(0, dotInd) : fileName;
-       }
-
-       /**
-        * Strips "\n", "\r\n", "\r" from the argument string, replacing them 
with a
-        * provided character.
-        *
-        * @since 3.1
-        */
-       public static String stripLineBreaks(String string, char replaceWith) {
-
-               if (string == null) {
-                       return null;
-               }
-
-               int len = string.length();
-               char[] buffer = new char[len];
-               boolean matched = false;
-
-               int j = 0;
-               for (int i = 0; i < len; i++, j++) {
-                       char c = string.charAt(i);
-
-                       // skip \n, \r, \r\n
-                       if (c == '\n' || c == '\r') {
-
-                               matched = true;
-
-                               // do lookahead
-                               if (i + 1 < len && string.charAt(i + 1) == 
'\n') {
-                                       i++;
-                               }
-
-                               buffer[j] = replaceWith;
-                       } else {
-                               buffer[j] = c;
-                       }
-               }
-
-               return matched ? new String(buffer, 0, j) : string;
-       }
-
-       /**
-        * Encodes a string so that it can be used as an attribute value in an 
XML
-        * document. Will do conversion of the greater/less signs, quotes and
-        * ampersands.
-        */
-       public static String encodeXmlAttribute(String string) {
-               if (string == null) {
-                       return null;
-               }
-
-               int len = string.length();
-               if (len == 0) {
-                       return string;
-               }
-
-               StringBuilder encoded = new StringBuilder();
-               for (int i = 0; i < len; i++) {
-                       char c = string.charAt(i);
-                       if (c == '<') {
-                               encoded.append("&lt;");
-                       } else if (c == '\"') {
-                               encoded.append("&quot;");
-                       } else if (c == '>') {
-                               encoded.append("&gt;");
-                       } else if (c == '\'') {
-                               encoded.append("&apos;");
-                       } else if (c == '&') {
-                               encoded.append("&amp;");
-                       } else {
-                               encoded.append(c);
-                       }
-               }
-
-               return encoded.toString();
-       }
-
-       /**
-        * Trims long strings substituting middle part with "...".
-        *
-        * @param str
-        *            String to trim.
-        * @param maxLength
-        *            maximum allowable length. Must be at least 5, or an
-        *            IllegalArgumentException is thrown.
-        * @return String
-        */
-       public static String prettyTrim(String str, int maxLength) {
-               if (maxLength < 5) {
-                       throw new IllegalArgumentException("Algorithm for 
'prettyTrim' works only with length >= 5. "
-                                       + "Supplied length is " + maxLength);
-               }
-
-               if (str == null || str.length() <= maxLength) {
-                       return str;
-               }
-
-               // find a section to cut off
-               int len = maxLength - 3;
-               int startLen = len / 2;
-               int endLen = len - startLen;
-
-               return str.substring(0, startLen) + "..." + 
str.substring(str.length() - endLen);
-       }
-
-       /**
-        * Returns a sorted iterator from an unsorted one. Use this method as a 
last
-        * resort, since it is much less efficient then just sorting a 
collection
-        * that backs the original iterator.
-        */
-       public static <T> Iterator<T> sortedIterator(Iterator<T> it, 
Comparator<T> comparator) {
-               List<T> list = new ArrayList<>();
-               while (it.hasNext()) {
-                       list.add(it.next());
-               }
-
-               Collections.sort(list, comparator);
-               return list.iterator();
-       }
-
-       /**
-        * Builds a hashCode of Collection.
-        */
-       public static int hashCode(Collection<?> c) {
-               HashCodeBuilder builder = new HashCodeBuilder();
-               for (Object o : c) {
-                       builder.append(o);
-               }
-               return builder.toHashCode();
-       }
-
-       /**
-        * @since 1.2
-        */
-       public static Pattern sqlPatternToPattern(String pattern, boolean 
ignoreCase) {
-               String preprocessed = RegexUtil.sqlPatternToRegex(pattern);
-
-               int flag = (ignoreCase) ? Pattern.CASE_INSENSITIVE | 
Pattern.UNICODE_CASE : 0;
-               return Pattern.compile(preprocessed, flag);
-       }
-
-       /**
-        * Returns true if a Member is accessible via reflection under normal 
Java
-        * access controls.
-        *
-        * @since 1.2
-        */
-       public static boolean isAccessible(Member member) {
-               return Modifier.isPublic(member.getModifiers()) && 
Modifier.isPublic(member.getDeclaringClass().getModifiers());
-       }
-
-       /**
-        * Creates a Java class, handling regular class names as well as
-        * single-dimensional arrays and primitive types.
-        *
-        * @since 1.2
-        * @deprecated since 4.0 this method based on statically defined class
-        *             loading algorithm is not going to work in environments 
like
-        *             OSGi. {@link AdhocObjectFactory} should be used as it can
-        *             provide the environment-specific class loading policy.
-        */
-       @Deprecated
-       public static Class<?> getJavaClass(String className) throws 
ClassNotFoundException {
-               return objectFactory.getJavaClass(className);
-       }
-
-       /**
-        * Converts names like "ABCD_EFG_123" to Java-style names like 
"abcdEfg123". If
-        * <code>capitalize</code> is true, returned name is capitalized (for 
instance if
-        * this is a class name).
-        *
-        * @since 4.0
-        */
-       // TODO: trace direct users and switch over to ObjectNameGenerator
-       public static String underscoredToJava(String name, boolean capitalize) 
{
-               StringTokenizer st = new StringTokenizer(name, "_");
-               StringBuilder buf = new StringBuilder();
-
-               boolean first = true;
-               while (st.hasMoreTokens()) {
-                       String token = st.nextToken();
-
-                       // clear of non-java chars
-                       token = specialCharsToJava(token);
-
-                       int len = token.length();
-                       if (len == 0) {
-                               continue;
-                       }
-
-                       // sniff mixed case vs. single case styles
-                       boolean hasLowerCase = false;
-                       boolean hasUpperCase = false;
-                       for (int i = 0; i < len && !(hasUpperCase && 
hasLowerCase); i++) {
-                               if (Character.isUpperCase(token.charAt(i))) {
-                                       hasUpperCase = true;
-                               } else if 
(Character.isLowerCase(token.charAt(i))) {
-                                       hasLowerCase = true;
-                               }
-                       }
-
-                       // if mixed case, preserve it, if all upper, convert to 
lower
-                       if (hasUpperCase && !hasLowerCase) {
-                               token = token.toLowerCase();
-                       }
-
-                       if (first) {
-                               // apply explicit capitalization rules, if this 
is the first token
-                               first = false;
-                               if (capitalize) {
-                                       
buf.append(Character.toUpperCase(token.charAt(0)));
-                               } else {
-                                       
buf.append(Character.toLowerCase(token.charAt(0)));
-                               }
-                       } else {
-                               
buf.append(Character.toUpperCase(token.charAt(0)));
-                       }
-
-                       if (len > 1) {
-                               buf.append(token.substring(1, len));
-                       }
-               }
-               return buf.toString();
-       }
-
-       /**
-        * Replaces special chars with human-readable and Java-id-compatible 
symbols.
-        *
-        * @since 4.0
-        */
-       public static String specialCharsToJava(String string) {
-               int len = string.length();
-               if (len == 0) {
-                       return string;
-               }
-
-               StringBuilder buffer = new StringBuilder(len);
-               for (int i = 0; i < len; i++) {
-
-                       char c = string.charAt(i);
-                       if (Character.isJavaIdentifierPart(c)) {
-                               buffer.append(c);
-                       } else {
-                               Object word = 
SPECIAL_CHAR_TO_JAVA_MAPPING.get(String.valueOf(c));
-                               buffer.append(word != null ? word : "_");
-                       }
-               }
-
-               return buffer.toString();
-       }
-
-       static void setReverse(final Persistent sourceObject, String 
propertyName, final Persistent targetObject) {
-
-               ArcProperty property = (ArcProperty) 
Cayenne.getClassDescriptor(sourceObject).getProperty(propertyName);
-               ArcProperty reverseArc = property.getComplimentaryReverseArc();
-               if (reverseArc != null) {
-                       reverseArc.visit(new PropertyVisitor() {
-
-                               public boolean visitToMany(ToManyProperty 
property) {
-                                       
property.addTargetDirectly(targetObject, sourceObject);
-                                       return false;
-                               }
-
-                               public boolean visitToOne(ToOneProperty 
property) {
-                                       property.setTarget(targetObject, 
sourceObject, false);
-                                       return false;
-                               }
-
-                               public boolean visitAttribute(AttributeProperty 
property) {
-                                       return false;
-                               }
-
-                       });
-
-                       sourceObject.getObjectContext().getGraphManager()
-                                       .arcCreated(targetObject.getObjectId(), 
sourceObject.getObjectId(), new ArcId(reverseArc));
-
-                       markAsDirty(targetObject);
-               }
-       }
-
-       static void unsetReverse(final Persistent sourceObject, String 
propertyName, final Persistent targetObject) {
-
-               ArcProperty property = (ArcProperty) 
Cayenne.getClassDescriptor(sourceObject).getProperty(propertyName);
-               ArcProperty reverseArc = property.getComplimentaryReverseArc();
-               if (reverseArc != null) {
-                       reverseArc.visit(new PropertyVisitor() {
-
-                               public boolean visitToMany(ToManyProperty 
property) {
-                                       
property.removeTargetDirectly(targetObject, sourceObject);
-                                       return false;
-                               }
-
-                               public boolean visitToOne(ToOneProperty 
property) {
-                                       property.setTarget(targetObject, null, 
false);
-                                       return false;
-                               }
-
-                               public boolean visitAttribute(AttributeProperty 
property) {
-                                       return false;
-                               }
-
-                       });
-
-                       sourceObject.getObjectContext().getGraphManager()
-                                       .arcDeleted(targetObject.getObjectId(), 
sourceObject.getObjectId(), new ArcId(reverseArc));
-
-                       markAsDirty(targetObject);
-               }
-       }
-
-       /**
-        * Changes object state to MODIFIED if needed, returning true if the 
change
-        * has occurred, false if not.
-        */
-       static boolean markAsDirty(Persistent object) {
-               if (object.getPersistenceState() == PersistenceState.COMMITTED) 
{
-                       object.setPersistenceState(PersistenceState.MODIFIED);
-                       return true;
-               }
-
-               return false;
-       }
+    private static final Map<String, String> SPECIAL_CHAR_TO_JAVA_MAPPING = 
new HashMap<>();
+
+    static {
+        SPECIAL_CHAR_TO_JAVA_MAPPING.put("#", "pound");
+    }
+
+    @Deprecated
+    private static DefaultAdhocObjectFactory objectFactory;
+
+    static {
+        objectFactory = new DefaultAdhocObjectFactory(null, new 
DefaultClassLoaderManager());
+    }
+
+    /**
+     * Converts URL to file. Throws {@link IllegalArgumentException} if the URL
+     * is not a "file://" URL.
+     */
+    public static File toFile(URL url) throws IllegalArgumentException {
+        // must convert spaces to %20, or URL->URI conversion may fail
+        String urlString = url.toExternalForm();
+
+        URI uri;
+        try {
+            uri = new URI(urlString.replace(" ", "%20"));
+        } catch (URISyntaxException e) {
+            throw new IllegalArgumentException("URL " + urlString + " can't be 
converted to URI", e);
+        }
+        return new File(uri);
+    }
+
+    /**
+     * Reads file contents, returning it as a String, using System default line
+     * separator.
+     */
+    public static String stringFromFile(File file) throws IOException {
+        return stringFromFile(file, System.getProperty("line.separator"));
+    }
+
+    /**
+     * Reads file contents, returning it as a String, joining lines with
+     * provided separator.
+     */
+    public static String stringFromFile(File file, String joinWith) throws 
IOException {
+        StringBuilder buf = new StringBuilder();
+
+        try (BufferedReader in = new BufferedReader(new FileReader(file));) {
+            String line = null;
+            while ((line = in.readLine()) != null) {
+                buf.append(line).append(joinWith);
+            }
+        }
+        return buf.toString();
+    }
+
+    /**
+     * @param objects   An Iterable of objects that will be converted to 
Strings and joined together.
+     * @param separator The separator between the strings.
+     * @return A single string of all the input strings separated by the
+     * separator.
+     * @deprecated use {@link String#join(CharSequence, CharSequence...)} or 
{@link java.util.stream.Collectors#joining}
+     */
+    @Deprecated(since = "5.0", forRemoval = true)
+    public static String join(Iterable<?> objects, String separator) {
+        if (objects == null) {
+            return "";
+        }
+
+        if (separator == null) {
+            separator = "";
+        }
+
+        StringBuilder builder = new StringBuilder();
+
+        for (Object o : objects) {
+            if (builder.length() > 0) {
+                builder.append(separator);
+            }
+
+            String string = o != null ? o.toString() : "";
+            builder.append(string);
+        }
+
+        return builder.toString();
+    }
+
+    /**
+     * Replaces all backslashes "\" with forward slashes "/". Convenience 
method
+     * to convert path Strings to URI format.
+     */
+    public static String substBackslashes(String string) {
+        return RegexUtil.substBackslashes(string);
+    }
+
+    /**
+     * Looks up and returns the root cause of an exception. If none is found,
+     * returns supplied Throwable object unchanged. If root is found,
+     * recursively "unwraps" it, and returns the result to the user.
+     */
+    public static Throwable unwindException(Throwable th) {
+        if (th instanceof SAXException) {
+            SAXException sax = (SAXException) th;
+            if (sax.getException() != null) {
+                return unwindException(sax.getException());
+            }
+        } else if (th instanceof SQLException) {
+            SQLException sql = (SQLException) th;
+            if (sql.getNextException() != null) {
+                return unwindException(sql.getNextException());
+            }
+        } else if (th.getCause() != null) {
+            return unwindException(th.getCause());
+        }
+
+        return th;
+    }
+
+    /**
+     * Compares two objects similar to "Object.equals(Object)". Unlike
+     * Object.equals(..), this method doesn't throw an exception if any of the
+     * two objects is null.
+     *
+     * @deprecated Use {@link Objects#equals(Object, Object)} for non-array 
types,
+     * or {@link Objects#deepEquals(Object, Object)} when the values may be 
arrays.
+     */
+    @Deprecated(since = "5.0", forRemoval = true)
+    public static boolean nullSafeEquals(Object o1, Object o2) {
+        return Objects.deepEquals(o1, o2);
+    }
+
+    /**
+     * Compares two objects similar to "Comparable.compareTo(Object)". Unlike
+     * Comparable.compareTo(..), this method doesn't throw an exception if any
+     * of the two objects is null.
+     *
+     * @since 1.1
+     */
+    public static <T> int nullSafeCompare(boolean nullsFirst, Comparable<T> 
o1, T o2) {
+        if (o1 == null && o2 == null) {
+            return 0;
+        } else if (o1 == null) {
+            return nullsFirst ? -1 : 1;
+        } else if (o2 == null) {
+            return nullsFirst ? 1 : -1;
+        } else {
+            return o1.compareTo(o2);
+        }
+    }
+
+    /**
+     * Returns true, if the String is null or an empty string.
+     */
+    public static boolean isEmptyString(CharSequence string) {
+        return string == null || string.length() == 0;
+    }
+
+    /**
+     * Returns true, if string not empty and contains non-whitespace 
characters.
+     *
+     * @since 4.1
+     */
+    public static boolean isBlank(CharSequence cs) {
+        int strLen;
+        if (cs != null && (strLen = cs.length()) != 0) {
+            for (int i = 0; i < strLen; ++i) {
+                if (!Character.isWhitespace(cs.charAt(i))) {
+                    return false;
+                }
+            }
+
+            return true;
+        } else {
+            return true;
+        }
+    }
+
+    /**
+     * @since 4.1
+     * @deprecated use {@code cs.chars().allMatch(Character::isDigit)} or 
equivalent
+     */
+    @Deprecated(since = "5.0", forRemoval = true)
+    public static boolean isNumeric(CharSequence cs) {
+        if (isEmptyString(cs)) {
+            return false;
+        } else {
+            for (int i = 0; i < cs.length(); ++i) {
+                if (!Character.isDigit(cs.charAt(i))) {
+                    return false;
+                }
+            }
+
+            return true;
+        }
+    }
+
+    /**
+     * Returns how many times the sub appears in the str.
+     *
+     * @since 4.1
+     */
+    public static int countMatches(final String str, final String sub) {
+        if (isEmptyString(str) || isEmptyString(sub)) {
+            return 0;
+        }
+        int count = 0, idx = 0;
+        while ((idx = str.indexOf(sub, idx)) != -1) {
+            count++;
+            idx += sub.length();
+        }
+        return count;
+    }
+
+    /**
+     * Capitalizes the first letter of the property name.
+     *
+     * @since 4.1
+     */
+    public static String capitalized(String name) {
+        if (name == null || name.length() == 0) {
+            return name;
+        }
+
+        char c = Character.toUpperCase(name.charAt(0));
+        return (name.length() == 1) ? Character.toString(c) : c + 
name.substring(1);
+    }
+
+    /**
+     * Returns string with lowercased first letter
+     *
+     * @since 4.2
+     */
+    public static String uncapitalized(String aString) {
+        if (aString == null || aString.length() == 0) {
+            return aString;
+        }
+
+        char c = Character.toLowerCase(aString.charAt(0));
+        return (aString.length() == 1) ? Character.toString(c) : c + 
aString.substring(1);
+    }
+
+    /**
+     * Creates Serializable object copy using serialization/deserialization.
+     */
+    @SuppressWarnings("unchecked")
+    public static <T extends Serializable> T cloneViaSerialization(T object) 
throws Exception {
+        ByteArrayOutputStream bytes = new ByteArrayOutputStream() {
+
+            @Override
+            public byte[] toByteArray() {
+                return buf;
+            }
+        };
+
+        try (ObjectOutputStream out = new ObjectOutputStream(bytes)) {
+            out.writeObject(object);
+        }
+
+        try (ObjectInputStream in = new ObjectInputStream(new 
ByteArrayInputStream(bytes.toByteArray()))) {
+            return (T) in.readObject();
+        }
+    }
+
+    /**
+     * Creates an XMLReader with default feature set. Note that all Cayenne
+     * internal XML parsers should probably use XMLReader obtained via this
+     * method for consistency sake, and can customize feature sets as needed.
+     */
+    public static XMLReader createXmlReader() throws SAXException, 
ParserConfigurationException {
+        SAXParserFactory spf = SAXParserFactory.newInstance();
+        spf.setFeature("http://apache.org/xml/features/disallow-doctype-decl";, 
true);
+        
spf.setFeature("http://xml.org/sax/features/external-general-entities";, false);
+        
spf.setFeature("http://xml.org/sax/features/external-parameter-entities";, 
false);
+        spf.setFeature("http://xml.org/sax/features/namespaces";, true);
+
+        // Create a JAXP SAXParser
+        SAXParser saxParser = spf.newSAXParser();
+
+        // Get the encapsulated SAX XMLReader
+        return saxParser.getXMLReader();
+    }
+
+    /**
+     * @param url to read
+     * @return org.w3c.dom.Document from the given URL
+     * @since 4.1
+     */
+    public static Document readDocument(URL url) {
+        DocumentBuilderFactory documentBuilderFactory = 
DocumentBuilderFactory.newInstance();
+        documentBuilderFactory.setNamespaceAware(false);
+        documentBuilderFactory.setXIncludeAware(false);
+        documentBuilderFactory.setExpandEntityReferences(false);
+
+        try {
+            
documentBuilderFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl";,
 true);
+            
documentBuilderFactory.setFeature("http://xml.org/sax/features/external-general-entities";,
 false);
+            
documentBuilderFactory.setFeature("http://xml.org/sax/features/external-parameter-entities";,
 false);
+            
documentBuilderFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd";,
 false);
+        } catch (ParserConfigurationException ex) {
+            throw new ConfigurationException("Unable to configure 
DocumentBuilderFactory", ex);
+        }
+
+        try {
+            DocumentBuilder domBuilder = 
documentBuilderFactory.newDocumentBuilder();
+            try (InputStream inputStream = url.openStream()) {
+                return domBuilder.parse(inputStream);
+            } catch (IOException | SAXException e) {
+                throw new ConfigurationException("Error loading configuration 
from %s", e, url);
+            }
+        } catch (ParserConfigurationException e) {
+            throw new ConfigurationException(e);
+        }
+    }
+
+    /**
+     * Returns package name for the Java class as a path separated with forward
+     * slash ("/"). Method is used to lookup resources that are located in
+     * package subdirectories. For example, a String "a/b/c" will be returned
+     * for class name "a.b.c.ClassName".
+     */
+    public static String getPackagePath(String className) {
+        return RegexUtil.getPackagePath(className);
+    }
+
+    /**
+     * Returns an unqualified class name for the fully qualified name.
+     *
+     * @since 3.0
+     */
+    public static String stripPackageName(String className) {
+        if (className == null || className.length() == 0) {
+            return className;
+        }
+
+        int lastDot = className.lastIndexOf('.');
+
+        if ((-1 == lastDot) || ((className.length() - 1) == lastDot)) {
+            return className;
+        }
+
+        return className.substring(lastDot + 1);
+    }
+
+    /**
+     * Creates a mutable map out of two arrays with keys and values.
+     *
+     * @since 1.2
+     */
+    public static <K, V> Map<K, V> toMap(K[] keys, V[] values) {
+        int keysSize = (keys != null) ? keys.length : 0;
+        int valuesSize = (values != null) ? values.length : 0;
+
+        if (keysSize == 0 && valuesSize == 0) {
+            // return mutable map
+            return new HashMap<>();
+        }
+
+        if (keysSize != valuesSize) {
+            throw new IllegalArgumentException("The number of keys doesn't 
match the number of values.");
+        }
+
+        Map<K, V> map = new HashMap<>();
+        for (int i = 0; i < keysSize; i++) {
+            map.put(keys[i], values[i]);
+        }
+
+        return map;
+    }
+
+    /**
+     * Extracts extension from the file name. Dot is not included in the
+     * returned string.
+     */
+    public static String extractFileExtension(String fileName) {
+        int dotInd = fileName.lastIndexOf('.');
+
+        // if dot is in the first position,
+        // we are dealing with a hidden file rather than an extension
+        return (dotInd > 0 && dotInd < fileName.length()) ? 
fileName.substring(dotInd + 1) : null;
+    }
+
+    /**
+     * Strips extension from the file name.
+     */
+    public static String stripFileExtension(String fileName) {
+        int dotInd = fileName.lastIndexOf('.');
+
+        // if dot is in the first position,
+        // we are dealing with a hidden file rather than an extension
+        return (dotInd > 0) ? fileName.substring(0, dotInd) : fileName;
+    }
+
+    /**
+     * Strips "\n", "\r\n", "\r" from the argument string, replacing them with 
a
+     * provided character.
+     *
+     * @since 3.1
+     */
+    public static String stripLineBreaks(String string, char replaceWith) {
+
+        if (string == null) {
+            return null;
+        }
+
+        int len = string.length();
+        char[] buffer = new char[len];
+        boolean matched = false;
+
+        int j = 0;
+        for (int i = 0; i < len; i++, j++) {
+            char c = string.charAt(i);
+
+            // skip \n, \r, \r\n
+            if (c == '\n' || c == '\r') {
+
+                matched = true;
+
+                // do lookahead
+                if (i + 1 < len && string.charAt(i + 1) == '\n') {
+                    i++;
+                }
+
+                buffer[j] = replaceWith;
+            } else {
+                buffer[j] = c;
+            }
+        }
+
+        return matched ? new String(buffer, 0, j) : string;
+    }
+
+    /**
+     * Encodes a string so that it can be used as an attribute value in an XML
+     * document. Will do conversion of the greater/less signs, quotes and
+     * ampersands.
+     */
+    public static String encodeXmlAttribute(String string) {
+        if (string == null) {
+            return null;
+        }
+
+        int len = string.length();
+        if (len == 0) {
+            return string;
+        }
+
+        StringBuilder encoded = new StringBuilder();
+        for (int i = 0; i < len; i++) {
+            char c = string.charAt(i);
+            if (c == '<') {
+                encoded.append("&lt;");
+            } else if (c == '\"') {
+                encoded.append("&quot;");
+            } else if (c == '>') {
+                encoded.append("&gt;");
+            } else if (c == '\'') {
+                encoded.append("&apos;");
+            } else if (c == '&') {
+                encoded.append("&amp;");
+            } else {
+                encoded.append(c);
+            }
+        }
+
+        return encoded.toString();
+    }
+
+    /**
+     * Trims long strings substituting middle part with "...".
+     *
+     * @param str       String to trim.
+     * @param maxLength maximum allowable length. Must be at least 5, or an
+     *                  IllegalArgumentException is thrown.
+     * @return String
+     */
+    public static String prettyTrim(String str, int maxLength) {
+        if (maxLength < 5) {
+            throw new IllegalArgumentException("Algorithm for 'prettyTrim' 
works only with length >= 5. "
+                    + "Supplied length is " + maxLength);
+        }
+
+        if (str == null || str.length() <= maxLength) {
+            return str;
+        }
+
+        // find a section to cut off
+        int len = maxLength - 3;
+        int startLen = len / 2;
+        int endLen = len - startLen;
+
+        return str.substring(0, startLen) + "..." + str.substring(str.length() 
- endLen);
+    }
+
+    /**
+     * Returns a sorted iterator from an unsorted one. Use this method as a 
last
+     * resort, since it is much less efficient then just sorting a collection
+     * that backs the original iterator.
+     */
+    public static <T> Iterator<T> sortedIterator(Iterator<T> it, Comparator<T> 
comparator) {
+        List<T> list = new ArrayList<>();
+        while (it.hasNext()) {
+            list.add(it.next());
+        }
+
+        Collections.sort(list, comparator);
+        return list.iterator();
+    }
+
+    /**
+     * Builds a hashCode of Collection.
+     *
+     * @deprecated unused
+     */
+    @Deprecated(since = "5.0", forRemoval = true)
+    public static int hashCode(Collection<?> c) {
+        HashCodeBuilder builder = new HashCodeBuilder();
+        for (Object o : c) {
+            builder.append(o);
+        }
+        return builder.toHashCode();
+    }
+
+    /**
+     * @since 1.2
+     */
+    public static Pattern sqlPatternToPattern(String pattern, boolean 
ignoreCase) {
+        String preprocessed = RegexUtil.sqlPatternToRegex(pattern);
+
+        int flag = (ignoreCase) ? Pattern.CASE_INSENSITIVE | 
Pattern.UNICODE_CASE : 0;
+        return Pattern.compile(preprocessed, flag);
+    }
+
+    /**
+     * Returns true if a Member is accessible via reflection under normal Java
+     * access controls.
+     *
+     * @since 1.2
+     */
+    public static boolean isAccessible(Member member) {
+        return Modifier.isPublic(member.getModifiers()) && 
Modifier.isPublic(member.getDeclaringClass().getModifiers());
+    }
+
+    /**
+     * Creates a Java class, handling regular class names as well as
+     * single-dimensional arrays and primitive types.
+     *
+     * @since 1.2
+     * @deprecated since 4.0 this method based on statically defined class
+     * loading algorithm is not going to work in environments like
+     * OSGi. {@link AdhocObjectFactory} should be used as it can
+     * provide the environment-specific class loading policy.
+     */
+    @Deprecated
+    public static Class<?> getJavaClass(String className) throws 
ClassNotFoundException {
+        return objectFactory.getJavaClass(className);
+    }
+
+    /**
+     * Converts names like "ABCD_EFG_123" to Java-style names like 
"abcdEfg123". If
+     * <code>capitalize</code> is true, returned name is capitalized (for 
instance if
+     * this is a class name).
+     *
+     * @since 4.0
+     */
+    // TODO: trace direct users and switch over to ObjectNameGenerator
+    public static String underscoredToJava(String name, boolean capitalize) {
+        StringTokenizer st = new StringTokenizer(name, "_");
+        StringBuilder buf = new StringBuilder();
+
+        boolean first = true;
+        while (st.hasMoreTokens()) {
+            String token = st.nextToken();
+
+            // clear of non-java chars
+            token = specialCharsToJava(token);
+
+            int len = token.length();
+            if (len == 0) {
+                continue;
+            }
+
+            // sniff mixed case vs. single case styles
+            boolean hasLowerCase = false;
+            boolean hasUpperCase = false;
+            for (int i = 0; i < len && !(hasUpperCase && hasLowerCase); i++) {
+                if (Character.isUpperCase(token.charAt(i))) {
+                    hasUpperCase = true;
+                } else if (Character.isLowerCase(token.charAt(i))) {
+                    hasLowerCase = true;
+                }
+            }
+
+            // if mixed case, preserve it, if all upper, convert to lower
+            if (hasUpperCase && !hasLowerCase) {
+                token = token.toLowerCase();
+            }
+
+            if (first) {
+                // apply explicit capitalization rules, if this is the first 
token
+                first = false;
+                if (capitalize) {
+                    buf.append(Character.toUpperCase(token.charAt(0)));
+                } else {
+                    buf.append(Character.toLowerCase(token.charAt(0)));
+                }
+            } else {
+                buf.append(Character.toUpperCase(token.charAt(0)));
+            }
+
+            if (len > 1) {
+                buf.append(token.substring(1, len));
+            }
+        }
+        return buf.toString();
+    }
+
+    /**
+     * Replaces special chars with human-readable and Java-id-compatible 
symbols.
+     *
+     * @since 4.0
+     */
+    public static String specialCharsToJava(String string) {
+        int len = string.length();
+        if (len == 0) {
+            return string;
+        }
+
+        StringBuilder buffer = new StringBuilder(len);
+        for (int i = 0; i < len; i++) {
+
+            char c = string.charAt(i);
+            if (Character.isJavaIdentifierPart(c)) {
+                buffer.append(c);
+            } else {
+                Object word = 
SPECIAL_CHAR_TO_JAVA_MAPPING.get(String.valueOf(c));
+                buffer.append(word != null ? word : "_");
+            }
+        }
+
+        return buffer.toString();
+    }
+
+    static void setReverse(final Persistent sourceObject, String propertyName, 
final Persistent targetObject) {
+
+        ArcProperty property = (ArcProperty) 
Cayenne.getClassDescriptor(sourceObject).getProperty(propertyName);
+        ArcProperty reverseArc = property.getComplimentaryReverseArc();
+        if (reverseArc != null) {
+            reverseArc.visit(new PropertyVisitor() {
+
+                public boolean visitToMany(ToManyProperty property) {
+                    property.addTargetDirectly(targetObject, sourceObject);
+                    return false;
+                }
+
+                public boolean visitToOne(ToOneProperty property) {
+                    property.setTarget(targetObject, sourceObject, false);
+                    return false;
+                }
+
+                public boolean visitAttribute(AttributeProperty property) {
+                    return false;
+                }
+
+            });
+
+            sourceObject.getObjectContext().getGraphManager()
+                    .arcCreated(targetObject.getObjectId(), 
sourceObject.getObjectId(), new ArcId(reverseArc));
+
+            markAsDirty(targetObject);
+        }
+    }
+
+    static void unsetReverse(final Persistent sourceObject, String 
propertyName, final Persistent targetObject) {
+
+        ArcProperty property = (ArcProperty) 
Cayenne.getClassDescriptor(sourceObject).getProperty(propertyName);
+        ArcProperty reverseArc = property.getComplimentaryReverseArc();
+        if (reverseArc != null) {
+            reverseArc.visit(new PropertyVisitor() {
+
+                public boolean visitToMany(ToManyProperty property) {
+                    property.removeTargetDirectly(targetObject, sourceObject);
+                    return false;
+                }
+
+                public boolean visitToOne(ToOneProperty property) {
+                    property.setTarget(targetObject, null, false);
+                    return false;
+                }
+
+                public boolean visitAttribute(AttributeProperty property) {
+                    return false;
+                }
+
+            });
+
+            sourceObject.getObjectContext().getGraphManager()
+                    .arcDeleted(targetObject.getObjectId(), 
sourceObject.getObjectId(), new ArcId(reverseArc));
+
+            markAsDirty(targetObject);
+        }
+    }
+
+    /**
+     * Changes object state to MODIFIED if needed, returning true if the change
+     * has occurred, false if not.
+     */
+    static boolean markAsDirty(Persistent object) {
+        if (object.getPersistenceState() == PersistenceState.COMMITTED) {
+            object.setPersistenceState(PersistenceState.MODIFIED);
+            return true;
+        }
+
+        return false;
+    }
 }


Reply via email to