You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cayenne.apache.org by aa...@apache.org on 2026/05/23 23:06:33 UTC
(cayenne) 05/08: Cleanup: Util.hashCode is deprecated in favor of plain Java alternatives
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 <an...@objectstyle.com>
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("<");
- } else if (c == '\"') {
- encoded.append(""");
- } else if (c == '>') {
- encoded.append(">");
- } else if (c == '\'') {
- encoded.append("'");
- } else if (c == '&') {
- encoded.append("&");
- } 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("<");
+ } else if (c == '\"') {
+ encoded.append(""");
+ } else if (c == '>') {
+ encoded.append(">");
+ } else if (c == '\'') {
+ encoded.append("'");
+ } else if (c == '&') {
+ encoded.append("&");
+ } 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;
+ }
}