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("<"); - } 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; + } }
